diff --git a/package-lock.json b/package-lock.json index 2266ebb..ded952c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "react-dom": "19.1.0", "react-icons": "^5.5.0", "react-markdown": "9.0.3", + "rehype-slug": "^6.0.0", "remark-gfm": "4.0.0", "rom-patcher-js": "github:marcrobledo/RomPatcher.js#91e522e247f709e894761157ccba3189004d0859" }, @@ -2115,6 +2116,12 @@ "node": ">= 0.4" } }, + "node_modules/github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", + "license": "ISC" + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -2194,6 +2201,19 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-heading-rank": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-heading-rank/-/hast-util-heading-rank-3.0.0.tgz", + "integrity": "sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-to-jsx-runtime": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", @@ -2221,6 +2241,19 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-to-string": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz", + "integrity": "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-whitespace": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", @@ -4092,6 +4125,23 @@ "node": ">= 6" } }, + "node_modules/rehype-slug": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/rehype-slug/-/rehype-slug-6.0.0.tgz", + "integrity": "sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "github-slugger": "^2.0.0", + "hast-util-heading-rank": "^3.0.0", + "hast-util-to-string": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/remark-gfm": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.0.tgz", diff --git a/package.json b/package.json index e5cb23f..f0a4121 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "react-dom": "19.1.0", "react-icons": "^5.5.0", "react-markdown": "9.0.3", + "rehype-slug": "^6.0.0", "remark-gfm": "4.0.0", "rom-patcher-js": "github:marcrobledo/RomPatcher.js#91e522e247f709e894761157ccba3189004d0859" }, diff --git a/src/app/globals.css b/src/app/globals.css index da34570..aa2c0ee 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -75,6 +75,22 @@ body { font-family: Arial, Helvetica, sans-serif; } +/* Smooth scrolling and anchor offset for sticky header */ +html { scroll-behavior: smooth; } +:root { --anchor-offset: 38px; } +@media (min-width: 768px) { :root { --anchor-offset: 72px; } } +/* Modern browsers honor scroll-padding-top on the scroll container */ +html { scroll-padding-top: var(--anchor-offset); } +/* Fallback for elements targeted directly (especially headings) */ +.prose h1[id], +.prose h2[id], +.prose h3[id], +.prose h4[id], +.prose h5[id], +.prose h6[id] { + scroll-margin-top: var(--anchor-offset); +} + .bg-grid { background-image: radial-gradient(var(--grid-dot-color) 1px, transparent 1px); background-size: 22px 22px; } .card { diff --git a/src/app/hack/[slug]/page.tsx b/src/app/hack/[slug]/page.tsx index f064022..3030bf5 100644 --- a/src/app/hack/[slug]/page.tsx +++ b/src/app/hack/[slug]/page.tsx @@ -4,6 +4,7 @@ import Gallery from "@/components/Hack/Gallery"; import HackActions from "@/components/Hack/HackActions"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; +import rehypeSlug from "rehype-slug"; import Image from "next/image"; import { FaDiscord, FaTwitter } from "react-icons/fa6"; import PokeCommunityIcon from "@/components/Icons/PokeCommunityIcon"; @@ -127,7 +128,7 @@ export default async function HackDetail({ params }: HackDetailProps) {