Compare commits
10
Commits
64ae7d0bb7
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0812dae8c5 | ||
|
|
57b26f1803 | ||
|
|
73c0c28f8a | ||
|
|
97761b5df4 | ||
|
|
1446b46190 | ||
|
|
5e68b9868c | ||
|
|
12ddf4db4b | ||
|
|
8fb7391d60 | ||
|
|
73ee207912 | ||
|
|
5c9b9ccb17 |
@@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
{
|
||||||
|
"title": "A critical vulnerability with Next.js 16: Code execution",
|
||||||
|
"description": "A critical vulnerability has been identified in the React Server Components (RSC) protocol",
|
||||||
|
"author": "Klesti Selimaj",
|
||||||
|
"date": "2025-12-03",
|
||||||
|
"readingTime": 1,
|
||||||
|
"tags": ["next.js", "vulnerability", "critical", "0day", "ssr"],
|
||||||
|
"image": "/blog-img/nextjs-critical.png",
|
||||||
|
"category": "news"
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
A critical vulnerability has been identified in the React Server Components (RSC) protocol. The issue is rated CVSS 10.0 and can allow remote code execution when processing attacker-controlled requests in unpatched environments.
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
The exploit is running code on the server from the client without admin rights, somewhere in the SSR (Server Side Renderer) there is a exploit that allows a client to get unauthorized access to the server running Next.js.
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
Next.js is a react framework for making full-stack application with node.js and React.js, it has a complex SSR (Server Side Renderer) which makes it easy to miss things sometimes, however this is a very critical error, i believe it wasn't the staff's fault, things like these are very likely to happen, but Next.js is used by 75% of modern websites.
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
## Affected versions
|
||||||
|
|
||||||
|
- Next.js 15.x
|
||||||
|
- Next.js 16.x
|
||||||
|
- Next.js 14.3.0-canary.77 and later canary releases
|
||||||
|
|
||||||
|
Next.js 13.x, Next.js 14.x stable, Pages Router applications, and the Edge Runtime are not affected.
|
||||||
|
|
||||||
|
## Fixed Versions
|
||||||
|
|
||||||
|
- 15.0.5
|
||||||
|
- 15.1.9
|
||||||
|
- 15.2.6
|
||||||
|
- 15.3.6
|
||||||
|
- 15.4.8
|
||||||
|
- 15.5.7
|
||||||
|
- 16.0.7
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
They have fixed it as seen on the original blog post: https://nextjs.org/blog/CVE-2025-66478
|
||||||
|
|
||||||
|
But this shows just how important it is to watch out when working on such complex structures, one small mistake can cost a big price, remote code execution is one of them.
|
||||||
Generated
+40
-40
@@ -12,7 +12,7 @@
|
|||||||
"highlight.js": "^11.11.1",
|
"highlight.js": "^11.11.1",
|
||||||
"katex": "^0.16.25",
|
"katex": "^0.16.25",
|
||||||
"lucide-react": "^0.555.0",
|
"lucide-react": "^0.555.0",
|
||||||
"next": "16.0.6",
|
"next": "^16.0.7",
|
||||||
"react": "19.2.0",
|
"react": "19.2.0",
|
||||||
"react-dom": "19.2.0",
|
"react-dom": "19.2.0",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
@@ -574,15 +574,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/env": {
|
"node_modules/@next/env": {
|
||||||
"version": "16.0.6",
|
"version": "16.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.0.7.tgz",
|
||||||
"integrity": "sha512-PFTK/G/vM3UJwK5XDYMFOqt8QW42mmhSgdKDapOlCqBUAOfJN2dyOnASR/xUR/JRrro0pLohh/zOJ77xUQWQAg==",
|
"integrity": "sha512-gpaNgUh5nftFKRkRQGnVi5dpcYSKGcZZkQffZ172OrG/XkrnS7UBTQ648YY+8ME92cC4IojpI2LqTC8sTDhAaw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-darwin-arm64": {
|
"node_modules/@next/swc-darwin-arm64": {
|
||||||
"version": "16.0.6",
|
"version": "16.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.0.7.tgz",
|
||||||
"integrity": "sha512-AGzKiPlDiui+9JcPRHLI4V9WFTTcKukhJTfK9qu3e0tz+Y/88B7vo5yZoO7UaikplJEHORzG3QaBFQfkjhnL0Q==",
|
"integrity": "sha512-LlDtCYOEj/rfSnEn/Idi+j1QKHxY9BJFmxx7108A6D8K0SB+bNgfYQATPk/4LqOl4C0Wo3LACg2ie6s7xqMpJg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -596,9 +596,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-darwin-x64": {
|
"node_modules/@next/swc-darwin-x64": {
|
||||||
"version": "16.0.6",
|
"version": "16.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.0.7.tgz",
|
||||||
"integrity": "sha512-LlLLNrK9WCIUkq2GciWDcquXYIf7vLxX8XE49gz7EncssZGL1vlHwgmURiJsUZAvk0HM1a8qb1ABDezsjAE/jw==",
|
"integrity": "sha512-rtZ7BhnVvO1ICf3QzfW9H3aPz7GhBrnSIMZyr4Qy6boXF0b5E3QLs+cvJmg3PsTCG2M1PBoC+DANUi4wCOKXpA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -612,9 +612,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-arm64-gnu": {
|
"node_modules/@next/swc-linux-arm64-gnu": {
|
||||||
"version": "16.0.6",
|
"version": "16.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.0.7.tgz",
|
||||||
"integrity": "sha512-r04NzmLSGGfG8EPXKVK72N5zDNnq9pa9el78LhdtqIC3zqKh74QfKHnk24DoK4PEs6eY7sIK/CnNpt30oc59kg==",
|
"integrity": "sha512-mloD5WcPIeIeeZqAIP5c2kdaTa6StwP4/2EGy1mUw8HiexSHGK/jcM7lFuS3u3i2zn+xH9+wXJs6njO7VrAqww==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -628,9 +628,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-arm64-musl": {
|
"node_modules/@next/swc-linux-arm64-musl": {
|
||||||
"version": "16.0.6",
|
"version": "16.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.0.7.tgz",
|
||||||
"integrity": "sha512-hfB/QV0hA7lbD1OJxp52wVDlpffUMfyxUB5ysZbb/pBC5iuhyLcEKSVQo56PFUUmUQzbMsAtUu6k2Gh9bBtWXA==",
|
"integrity": "sha512-+ksWNrZrthisXuo9gd1XnjHRowCbMtl/YgMpbRvFeDEqEBd523YHPWpBuDjomod88U8Xliw5DHhekBC3EOOd9g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -644,9 +644,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-x64-gnu": {
|
"node_modules/@next/swc-linux-x64-gnu": {
|
||||||
"version": "16.0.6",
|
"version": "16.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.0.7.tgz",
|
||||||
"integrity": "sha512-PZJushBgfvKhJBy01yXMdgL+l5XKr7uSn5jhOQXQXiH3iPT2M9iG64yHpPNGIKitKrHJInwmhPVGogZBAJOCPw==",
|
"integrity": "sha512-4WtJU5cRDxpEE44Ana2Xro1284hnyVpBb62lIpU5k85D8xXxatT+rXxBgPkc7C1XwkZMWpK5rXLXTh9PFipWsA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -660,9 +660,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-x64-musl": {
|
"node_modules/@next/swc-linux-x64-musl": {
|
||||||
"version": "16.0.6",
|
"version": "16.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.0.7.tgz",
|
||||||
"integrity": "sha512-LqY76IojrH9yS5fyATjLzlOIOgwyzBuNRqXwVxcGfZ58DWNQSyfnLGlfF6shAEqjwlDNLh4Z+P0rnOI87Y9jEw==",
|
"integrity": "sha512-HYlhqIP6kBPXalW2dbMTSuB4+8fe+j9juyxwfMwCe9kQPPeiyFn7NMjNfoFOfJ2eXkeQsoUGXg+O2SE3m4Qg2w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -676,9 +676,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-win32-arm64-msvc": {
|
"node_modules/@next/swc-win32-arm64-msvc": {
|
||||||
"version": "16.0.6",
|
"version": "16.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.0.7.tgz",
|
||||||
"integrity": "sha512-eIfSNNqAkj0tqKRf0u7BVjqylJCuabSrxnpSENY3YKApqwDMeAqYPmnOwmVe6DDl3Lvkbe7cJAyP6i9hQ5PmmQ==",
|
"integrity": "sha512-EviG+43iOoBRZg9deGauXExjRphhuYmIOJ12b9sAPy0eQ6iwcPxfED2asb/s2/yiLYOdm37kPaiZu8uXSYPs0Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -692,9 +692,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-win32-x64-msvc": {
|
"node_modules/@next/swc-win32-x64-msvc": {
|
||||||
"version": "16.0.6",
|
"version": "16.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.0.7.tgz",
|
||||||
"integrity": "sha512-QGs18P4OKdK9y2F3Th42+KGnwsc2iaThOe6jxQgP62kslUU4W+g6AzI6bdIn/pslhSfxjAMU5SjakfT5Fyo/xA==",
|
"integrity": "sha512-gniPjy55zp5Eg0896qSrf3yB1dw4F/3s8VK1ephdsZZ129j2n6e1WqCbE2YgcKhW9hPB9TVZENugquWJD5x0ug==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -2954,12 +2954,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/next": {
|
"node_modules/next": {
|
||||||
"version": "16.0.6",
|
"version": "16.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/next/-/next-16.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/next/-/next-16.0.7.tgz",
|
||||||
"integrity": "sha512-2zOZ/4FdaAp5hfCU/RnzARlZzBsjaTZ/XjNQmuyYLluAPM7kcrbIkdeO2SL0Ysd1vnrSgU+GwugfeWX1cUCgCg==",
|
"integrity": "sha512-3mBRJyPxT4LOxAJI6IsXeFtKfiJUbjCLgvXO02fV8Wy/lIhPvP94Fe7dGhUgHXcQy4sSuYwQNcOLhIfOm0rL0A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@next/env": "16.0.6",
|
"@next/env": "16.0.7",
|
||||||
"@swc/helpers": "0.5.15",
|
"@swc/helpers": "0.5.15",
|
||||||
"caniuse-lite": "^1.0.30001579",
|
"caniuse-lite": "^1.0.30001579",
|
||||||
"postcss": "8.4.31",
|
"postcss": "8.4.31",
|
||||||
@@ -2972,14 +2972,14 @@
|
|||||||
"node": ">=20.9.0"
|
"node": ">=20.9.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@next/swc-darwin-arm64": "16.0.6",
|
"@next/swc-darwin-arm64": "16.0.7",
|
||||||
"@next/swc-darwin-x64": "16.0.6",
|
"@next/swc-darwin-x64": "16.0.7",
|
||||||
"@next/swc-linux-arm64-gnu": "16.0.6",
|
"@next/swc-linux-arm64-gnu": "16.0.7",
|
||||||
"@next/swc-linux-arm64-musl": "16.0.6",
|
"@next/swc-linux-arm64-musl": "16.0.7",
|
||||||
"@next/swc-linux-x64-gnu": "16.0.6",
|
"@next/swc-linux-x64-gnu": "16.0.7",
|
||||||
"@next/swc-linux-x64-musl": "16.0.6",
|
"@next/swc-linux-x64-musl": "16.0.7",
|
||||||
"@next/swc-win32-arm64-msvc": "16.0.6",
|
"@next/swc-win32-arm64-msvc": "16.0.7",
|
||||||
"@next/swc-win32-x64-msvc": "16.0.6",
|
"@next/swc-win32-x64-msvc": "16.0.7",
|
||||||
"sharp": "^0.34.4"
|
"sharp": "^0.34.4"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,7 @@
|
|||||||
"highlight.js": "^11.11.1",
|
"highlight.js": "^11.11.1",
|
||||||
"katex": "^0.16.25",
|
"katex": "^0.16.25",
|
||||||
"lucide-react": "^0.555.0",
|
"lucide-react": "^0.555.0",
|
||||||
"next": "16.0.6",
|
"next": "^16.0.7",
|
||||||
"react": "19.2.0",
|
"react": "19.2.0",
|
||||||
"react-dom": "19.2.0",
|
"react-dom": "19.2.0",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 285 KiB |
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { Sparkles } from "lucide-react";
|
import { Paperclip, Sparkles } from "lucide-react";
|
||||||
import WebStuff from "@/components/Web";
|
import WebStuff from "@/components/Web";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
import { PostPreview } from "@/lib/posts";
|
import { PostPreview } from "@/lib/posts";
|
||||||
@@ -59,11 +59,25 @@ export default function BlogPost() {
|
|||||||
initial="hidden"
|
initial="hidden"
|
||||||
animate="visible"
|
animate="visible"
|
||||||
>
|
>
|
||||||
<motion.div variants={itemVariants} className="mb-4">
|
<motion.div
|
||||||
|
variants={itemVariants}
|
||||||
|
className="mb-4 w-full flex justify-between items-center"
|
||||||
|
>
|
||||||
<span className="inline-flex items-center gap-2 px-4 py-2 bg-purple-500/10 border border-purple-500/20 rounded-full text-purple-400 text-sm">
|
<span className="inline-flex items-center gap-2 px-4 py-2 bg-purple-500/10 border border-purple-500/20 rounded-full text-purple-400 text-sm">
|
||||||
<Sparkles className="w-4 h-4" />
|
<Sparkles className="w-4 h-4" />
|
||||||
{post.meta.category}
|
{post.meta.category}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
<span
|
||||||
|
className="px-3 py-2 bg-slate-500/10 border border-slate-500/20 rounded-full text-slate-400 transition-colors hover:bg-purple-500/10 hover:border-purple-500/20 hover:text-purple-400 text-sm"
|
||||||
|
onClick={() => {
|
||||||
|
navigator.clipboard.writeText(
|
||||||
|
`https://orus-dev.netlify.app${post.meta.url}`
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Paperclip className="w-4 h-4" />
|
||||||
|
</span>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<motion.h1
|
<motion.h1
|
||||||
|
|||||||
+15
-25
@@ -39,9 +39,8 @@ export default function Blog() {
|
|||||||
: projects.filter((p) => p.category === activeFilter);
|
: projects.filter((p) => p.category === activeFilter);
|
||||||
|
|
||||||
const containerVariants = {
|
const containerVariants = {
|
||||||
hidden: { opacity: 0 },
|
hidden: {},
|
||||||
visible: {
|
visible: {
|
||||||
opacity: 1,
|
|
||||||
transition: {
|
transition: {
|
||||||
staggerChildren: 0.1,
|
staggerChildren: 0.1,
|
||||||
},
|
},
|
||||||
@@ -49,13 +48,11 @@ export default function Blog() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const itemVariants = {
|
const itemVariants = {
|
||||||
hidden: { y: 20, opacity: 0 },
|
hidden: { opacity: 0, y: 20 },
|
||||||
visible: {
|
visible: {
|
||||||
y: 0,
|
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
transition: {
|
y: 0,
|
||||||
duration: 0.5,
|
transition: { duration: 0.9 },
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -115,22 +112,18 @@ export default function Blog() {
|
|||||||
</motion.section>
|
</motion.section>
|
||||||
|
|
||||||
{/* Blog Posts List */}
|
{/* Blog Posts List */}
|
||||||
<motion.section
|
<section className="relative z-10 px-6 py-12 max-w-5xl mx-auto">
|
||||||
className="relative z-10 px-6 py-12 max-w-5xl mx-auto"
|
|
||||||
initial="hidden"
|
|
||||||
animate="visible"
|
|
||||||
variants={containerVariants}
|
|
||||||
>
|
|
||||||
<div className="flex flex-col gap-8">
|
<div className="flex flex-col gap-8">
|
||||||
{filteredPosts.map((post, i) => (
|
{filteredPosts.map((post, i) => (
|
||||||
<motion.article
|
<motion.div
|
||||||
key={i}
|
key={i}
|
||||||
layout
|
className="relative group bg-slate-900/40 backdrop-blur border border-slate-700/50 rounded-2xl p-6 hover:border-purple-500/50 transition-colors"
|
||||||
className="relative group bg-slate-900/80 backdrop-blur border border-slate-700 rounded-2xl p-6 hover:border-purple-500/50 transition-all"
|
whileHover={{ y: -3, transition: { duration: 0.5 } }}
|
||||||
whileHover={{ y: -3 }}
|
variants={itemVariants}
|
||||||
|
initial="hidden"
|
||||||
|
animate="visible"
|
||||||
>
|
>
|
||||||
<div className="flex flex-col md:flex-row gap-4">
|
<div className="flex flex-col md:flex-row gap-4">
|
||||||
{/* Optional Thumbnail */}
|
|
||||||
{post.image && (
|
{post.image && (
|
||||||
<div className="shrink-0 w-full md:w-40 h-32 md:h-28 overflow-hidden rounded-xl">
|
<div className="shrink-0 w-full md:w-40 h-32 md:h-28 overflow-hidden rounded-xl">
|
||||||
<img
|
<img
|
||||||
@@ -141,17 +134,15 @@ export default function Blog() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Content */}
|
|
||||||
<div className="flex-1 flex flex-col justify-between">
|
<div className="flex-1 flex flex-col justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-2xl font-bold mb-2 group-hover:text-purple-400 transition-colors">
|
<h3 className="text-2xl font-bold mb-2 group-hover:text-purple-400 transition-colors cursor-pointer">
|
||||||
{post.title}
|
<a href={post.url}>{post.title}</a>
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-slate-300 mb-4 line-clamp-3">
|
<p className="text-slate-300 mb-4 line-clamp-3">
|
||||||
{post.description}
|
{post.description}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* Metadata */}
|
|
||||||
<div className="flex flex-wrap items-center gap-3 text-sm text-slate-400 mb-4">
|
<div className="flex flex-wrap items-center gap-3 text-sm text-slate-400 mb-4">
|
||||||
<span>{post.author}</span>
|
<span>{post.author}</span>
|
||||||
<span>•</span>
|
<span>•</span>
|
||||||
@@ -173,7 +164,6 @@ export default function Blog() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Read More */}
|
|
||||||
<a
|
<a
|
||||||
href={post.url}
|
href={post.url}
|
||||||
className="self-start mt-auto inline-flex items-center gap-2 px-4 py-2 bg-purple-500/10 hover:bg-purple-500/20 border border-purple-500/20 rounded-lg text-sm font-medium text-purple-400 transition-colors"
|
className="self-start mt-auto inline-flex items-center gap-2 px-4 py-2 bg-purple-500/10 hover:bg-purple-500/20 border border-purple-500/20 rounded-lg text-sm font-medium text-purple-400 transition-colors"
|
||||||
@@ -182,10 +172,10 @@ export default function Blog() {
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</motion.article>
|
</motion.div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</motion.section>
|
</section>
|
||||||
</WebStuff>
|
</WebStuff>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 3.5 KiB |
+2
-2
@@ -13,8 +13,8 @@ const geistMono = Geist_Mono({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Create Next App",
|
title: "ORUS | The community cybersecurity organization",
|
||||||
description: "Generated by create next app",
|
description: "Hello World! 👋 Welcome to ORUS, a cybersecurity & dev organization,",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
|
|||||||
@@ -140,8 +140,11 @@ export default function Projects() {
|
|||||||
key={i}
|
key={i}
|
||||||
layout
|
layout
|
||||||
className="relative group"
|
className="relative group"
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.6, ease: "easeOut" }}
|
||||||
|
viewport={{ once: true, amount: 0.3 }}
|
||||||
whileHover={{ y: -8 }}
|
whileHover={{ y: -8 }}
|
||||||
viewport={{ once: false }}
|
|
||||||
>
|
>
|
||||||
<div className="absolute inset-0 bg-linear-to-r from-purple-500/20 to-blue-500/20 rounded-2xl blur-xl group-hover:blur-2xl transition-all" />
|
<div className="absolute inset-0 bg-linear-to-r from-purple-500/20 to-blue-500/20 rounded-2xl blur-xl group-hover:blur-2xl transition-all" />
|
||||||
<div className="relative bg-slate-900/80 backdrop-blur border border-slate-700 hover:border-purple-500/50 rounded-2xl p-6 transition-all h-full flex flex-col">
|
<div className="relative bg-slate-900/80 backdrop-blur border border-slate-700 hover:border-purple-500/50 rounded-2xl p-6 transition-all h-full flex flex-col">
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
import { AnimatePresence } from "framer-motion";
|
||||||
|
import { Menu, MessageSquare, X } from "lucide-react";
|
||||||
|
import { useState, useEffect, useRef } from "react";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import { createPortal } from "react-dom";
|
||||||
|
|
||||||
|
export default function MobileMenu() {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [mounted, setMounted] = useState(false);
|
||||||
|
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||||
|
const [buttonPosition, setButtonPosition] = useState({ top: 0, right: 0 });
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open && buttonRef.current) {
|
||||||
|
const rect = buttonRef.current.getBoundingClientRect();
|
||||||
|
setButtonPosition({
|
||||||
|
top: rect.bottom + 8,
|
||||||
|
right: window.innerWidth - rect.right
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
|
const menuContent = (
|
||||||
|
<AnimatePresence>
|
||||||
|
{open && (
|
||||||
|
<>
|
||||||
|
{/* Backdrop */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
exit={{ opacity: 0 }}
|
||||||
|
style={{ zIndex: 999999 }}
|
||||||
|
className="fixed inset-0 bg-black/20 md:hidden"
|
||||||
|
onClick={() => setOpen(false)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Menu */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: -10 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
exit={{ opacity: 0, y: -10 }}
|
||||||
|
transition={{ duration: 0.25 }}
|
||||||
|
style={{
|
||||||
|
position: 'fixed',
|
||||||
|
top: `${buttonPosition.top}px`,
|
||||||
|
right: `${buttonPosition.right}px`,
|
||||||
|
zIndex: 1000000
|
||||||
|
}}
|
||||||
|
className="w-44 bg-slate-900 border border-slate-700 rounded-xl shadow-2xl p-4 flex flex-col gap-4 md:hidden"
|
||||||
|
>
|
||||||
|
{["Blog", "Projects", "About"].map((item) => (
|
||||||
|
<a
|
||||||
|
key={item}
|
||||||
|
href={`/${item.toLowerCase()}`}
|
||||||
|
className="text-slate-200 hover:text-white transition-colors"
|
||||||
|
onClick={() => setOpen(false)}
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<a
|
||||||
|
href="https://discord.gg/HFRsNxfCqT"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="inline-flex items-center gap-2 px-3 py-2 bg-purple-500 hover:bg-purple-600 rounded-lg font-semibold text-sm text-white"
|
||||||
|
onClick={() => setOpen(false)}
|
||||||
|
>
|
||||||
|
<MessageSquare className="w-4 h-4" />
|
||||||
|
Join Discord
|
||||||
|
</a>
|
||||||
|
</motion.div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="md:hidden">
|
||||||
|
<button
|
||||||
|
ref={buttonRef}
|
||||||
|
onClick={() => setOpen((prev) => !prev)}
|
||||||
|
className="p-2 text-slate-200"
|
||||||
|
>
|
||||||
|
{open ? <X className="w-6 h-6" /> : <Menu className="w-6 h-6" />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{mounted && createPortal(menuContent, document.body)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import { motion } from "framer-motion";
|
||||||
|
import MobileMenu from "./MobileMenu";
|
||||||
|
import { MessageSquare } from "lucide-react";
|
||||||
|
|
||||||
|
export default function NavMenu() {
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
className="flex gap-6 items-center"
|
||||||
|
variants={{
|
||||||
|
hidden: {},
|
||||||
|
visible: {
|
||||||
|
transition: {
|
||||||
|
staggerChildren: 0.08,
|
||||||
|
delayChildren: 0.15,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{["Blog", "Projects", "About"].map((item) => (
|
||||||
|
<motion.a
|
||||||
|
key={item}
|
||||||
|
href={`/${item.toLowerCase()}`}
|
||||||
|
className="text-slate-300 hover:text-white transition-colors hidden md:block"
|
||||||
|
variants={{
|
||||||
|
hidden: { opacity: 0, y: -12 },
|
||||||
|
visible: { opacity: 1, y: 0 },
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 0.8,
|
||||||
|
}}
|
||||||
|
whileHover={{ y: -2 }}
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</motion.a>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<motion.a
|
||||||
|
href="https://discord.gg/HFRsNxfCqT"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="inline-flex items-center gap-2 px-4 py-2 bg-purple-500 hover:bg-purple-600 rounded-lg font-semibold transition-colors text-sm"
|
||||||
|
variants={{
|
||||||
|
hidden: { opacity: 0, y: -12 },
|
||||||
|
visible: { opacity: 1, y: 0 },
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 0.45,
|
||||||
|
ease: [0.25, 0.1, 0.25, 1],
|
||||||
|
}}
|
||||||
|
whileTap={{ scale: 0.95 }}
|
||||||
|
>
|
||||||
|
<MessageSquare className="w-4 h-4" />
|
||||||
|
Join Discord
|
||||||
|
</motion.a>
|
||||||
|
|
||||||
|
<MobileMenu />
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
+5
-52
@@ -1,5 +1,7 @@
|
|||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { Github, MessageSquare } from "lucide-react";
|
import { Github, MessageSquare } from "lucide-react";
|
||||||
|
import MobileMenu from "./MobileMenu";
|
||||||
|
import NavMenu from "./NavMenu";
|
||||||
|
|
||||||
export default function WebStuff({
|
export default function WebStuff({
|
||||||
children,
|
children,
|
||||||
@@ -68,59 +70,10 @@ export default function WebStuff({
|
|||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1 }}
|
||||||
transition={{ duration: 0.6, delay: 0.1 }}
|
transition={{ duration: 0.6, delay: 0.1 }}
|
||||||
>
|
>
|
||||||
<img src="orus2.svg" alt="Logo" className="h-4" />
|
<img src="/orus2.svg" alt="Logo" className="h-4" />
|
||||||
</motion.a>
|
</motion.a>
|
||||||
|
|
||||||
{/* Menu */}
|
<NavMenu />
|
||||||
<motion.div
|
|
||||||
className="flex gap-6 items-center"
|
|
||||||
variants={{
|
|
||||||
hidden: {},
|
|
||||||
visible: {
|
|
||||||
transition: {
|
|
||||||
staggerChildren: 0.08,
|
|
||||||
delayChildren: 0.15,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{["Blog", "Projects", "About"].map((item) => (
|
|
||||||
<motion.a
|
|
||||||
key={item}
|
|
||||||
href={`/${item.toLowerCase()}`}
|
|
||||||
className="text-slate-300 hover:text-white transition-colors hidden md:block"
|
|
||||||
variants={{
|
|
||||||
hidden: { opacity: 0, y: -12 },
|
|
||||||
visible: { opacity: 1, y: 0 },
|
|
||||||
}}
|
|
||||||
transition={{
|
|
||||||
duration: 0.8,
|
|
||||||
}}
|
|
||||||
whileHover={{ y: -2 }}
|
|
||||||
>
|
|
||||||
{item}
|
|
||||||
</motion.a>
|
|
||||||
))}
|
|
||||||
|
|
||||||
<motion.a
|
|
||||||
href="https://discord.gg/HFRsNxfCqT"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="inline-flex items-center gap-2 px-4 py-2 bg-purple-500 hover:bg-purple-600 rounded-lg font-semibold transition-colors text-sm"
|
|
||||||
variants={{
|
|
||||||
hidden: { opacity: 0, y: -12 },
|
|
||||||
visible: { opacity: 1, y: 0 },
|
|
||||||
}}
|
|
||||||
transition={{
|
|
||||||
duration: 0.45,
|
|
||||||
ease: [0.25, 0.1, 0.25, 1],
|
|
||||||
}}
|
|
||||||
whileTap={{ scale: 0.95 }}
|
|
||||||
>
|
|
||||||
<MessageSquare className="w-4 h-4" />
|
|
||||||
Join Discord
|
|
||||||
</motion.a>
|
|
||||||
</motion.div>
|
|
||||||
</motion.nav>
|
</motion.nav>
|
||||||
|
|
||||||
{children}
|
{children}
|
||||||
@@ -128,7 +81,7 @@ export default function WebStuff({
|
|||||||
<footer className="relative z-10 px-6 py-12 border-t border-slate-800/50 mt-20">
|
<footer className="relative z-10 px-6 py-12 border-t border-slate-800/50 mt-20">
|
||||||
<div className="max-w-7xl mx-auto">
|
<div className="max-w-7xl mx-auto">
|
||||||
<div className="flex flex-col md:flex-row justify-between items-center gap-6">
|
<div className="flex flex-col md:flex-row justify-between items-center gap-6">
|
||||||
<img src="orus.svg" alt="Logo" className="h-4" />
|
<img src="/orus.svg" alt="Logo" className="h-4" />
|
||||||
|
|
||||||
<div className="text-slate-400 text-center">
|
<div className="text-slate-400 text-center">
|
||||||
© 2025 ORUS. Open source for everyone. Built with 💜 for
|
© 2025 ORUS. Open source for everyone. Built with 💜 for
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export default function formatNumber(num: number): string {
|
export default function formatNumber(num: number): string {
|
||||||
|
if (num === null) return num;
|
||||||
if (num < 1000) return num.toString();
|
if (num < 1000) return num.toString();
|
||||||
if (num < 1_000_000)
|
if (num < 1_000_000)
|
||||||
return (num / 1000).toFixed(num % 1000 === 0 ? 0 : 1) + "k";
|
return (num / 1000).toFixed(num % 1000 === 0 ? 0 : 1) + "k";
|
||||||
|
|||||||
Reference in New Issue
Block a user