From 599fa7dbbaa813b18c458a53ebfdf06b6aa3140c Mon Sep 17 00:00:00 2001 From: Alex-Zughaid <117576511+Alex-Zughaid@users.noreply.github.com> Date: Wed, 12 Aug 2026 16:12:51 +0100 Subject: [PATCH 1/4] reworked layout also added carousel on home page --- web2/src/app/globals.css | 14 ++ web2/src/app/page.tsx | 15 ++ web2/src/components/conditional-sidebar.tsx | 4 +- web2/src/components/get-involved-carousel.tsx | 177 ++++++++++++++++++ web2/src/components/navbar.tsx | 136 +++++++++++--- web2/src/components/table-of-contents.tsx | 108 +++++++++++ 6 files changed, 426 insertions(+), 28 deletions(-) create mode 100644 web2/src/components/get-involved-carousel.tsx create mode 100644 web2/src/components/table-of-contents.tsx diff --git a/web2/src/app/globals.css b/web2/src/app/globals.css index dc17816..9f58d37 100644 --- a/web2/src/app/globals.css +++ b/web2/src/app/globals.css @@ -96,6 +96,15 @@ body { .delay-100 { animation-delay: 100ms; } .delay-200 { animation-delay: 200ms; } +@keyframes carousel-in-right { + from { opacity: 0; transform: translateX(24px); } + to { opacity: 1; transform: translateX(0); } +} +@keyframes carousel-in-left { + from { opacity: 0; transform: translateX(-24px); } + to { opacity: 1; transform: translateX(0); } +} + /* ─── Prose ─────────────────────────────────────────────────── */ @layer components { .prose-physlib { @apply text-foreground leading-relaxed; } @@ -124,6 +133,11 @@ body { .prose-physlib img { @apply rounded my-4 max-w-full h-auto; } } +/* ─── TOC anchor offset (clears fixed navbar) ───────────────── */ +h2[id], h3[id] { + scroll-margin-top: 5rem; +} + @keyframes shimmer { 0% { background-position: -200% 0; } 100% { background-position: 200% 0; } diff --git a/web2/src/app/page.tsx b/web2/src/app/page.tsx index 21e4c50..d6c1ebe 100644 --- a/web2/src/app/page.tsx +++ b/web2/src/app/page.tsx @@ -1,5 +1,6 @@ import Link from "next/link"; import { site } from "@/lib/site"; +import { GetInvolvedCarousel } from "@/components/get-involved-carousel"; export default function HomePage() { return ( @@ -66,6 +67,20 @@ export default function HomePage() { + {/* ═══ GET INVOLVED ════════════════════════════════════════ */} +
+
+

Get Involved

+

+ How you can contribute: +

+ +
+
+ {/* ═══ WHAT IS LEAN ═══════════════════════════════════════ */}
diff --git a/web2/src/components/conditional-sidebar.tsx b/web2/src/components/conditional-sidebar.tsx index 8ea90db..49dfaf0 100644 --- a/web2/src/components/conditional-sidebar.tsx +++ b/web2/src/components/conditional-sidebar.tsx @@ -1,10 +1,10 @@ "use client"; import { usePathname } from "next/navigation"; -import { SidebarNav } from "./sidebar-nav"; +import { TableOfContents } from "./table-of-contents"; export function ConditionalSidebar() { const pathname = usePathname(); if (pathname === "/") return null; - return ; + return ; } diff --git a/web2/src/components/get-involved-carousel.tsx b/web2/src/components/get-involved-carousel.tsx new file mode 100644 index 0000000..e8f0bc8 --- /dev/null +++ b/web2/src/components/get-involved-carousel.tsx @@ -0,0 +1,177 @@ +"use client"; + +import { useState } from "react"; +import Link from "next/link"; + +const cards = [ + { + title: "Promote the project", + items: [ + "Share on social media (LinkedIn, X, Reddit)", + "Mention Physlib to colleagues, students, or supervisors", + "Reference the project in talks, papers, or course materials", + "Star the repository on GitHub to boost visibility", + ], + cta: { label: "View on GitHub", href: "https://github.com/leanprover-community/Physlib", external: true }, + }, + { + title: "Contribute Lean code", + items: [ + "Pick up open issues labeled 'good first issue' on GitHub", + "Formalize theorems, definitions, or calculations from physics", + "Golf existing proofs", + "Work on the APIs", + ], + cta: { label: "Browse issues", href: "https://github.com/leanprover-community/Physlib/issues", external: true }, + }, + { + title: "Create informal results", + items: [ + "Write up clear mathematical statements of physics results", + "Provide references to formal proofs", + "Describe what a result means physically and why it matters", + "Open a GitHub issue or post in Zulip with your informal write-up", + ], + cta: { label: "Join Zulip", href: "https://leanprover.zulipchat.com/", external: true }, + }, + { + title: "Review the documentation", + items: [ + "Read through existing docs and note anything unclear or incorrect", + "Check that examples and code snippets still work", + "Suggest better explanations or missing context", + "Report issues or open a PR with fixes directly", + ], + cta: { label: "Read the docs", href: "/getting-started", external: false }, + }, +]; + +export function GetInvolvedCarousel() { + const [index, setIndex] = useState(0); + const [dir, setDir] = useState<"left" | "right" | null>(null); + const [animKey, setAnimKey] = useState(0); + + function go(next: number) { + const newIndex = (next + cards.length) % cards.length; + setDir(next > index || (index === cards.length - 1 && next === 0) ? "right" : "left"); + setIndex(newIndex); + setAnimKey((k) => k + 1); + } + + const card = cards[index]; + + return ( +
+ {/* carousel row */} +
+ + {/* left arrow */} + + + {/* card */} +
+

+ {card.title} +

+ +
    + {card.items.map((item) => ( +
  • + + {item} +
  • + ))} +
+ + {card.cta.external ? ( + + {card.cta.label} + + + ) : ( + + {card.cta.label} + + )} +
+ + {/* right arrow */} + +
+ + {/* dot indicators */} +
+ {cards.map((_, i) => ( +
+
+ ); +} + +function ChevronLeft() { + return ( + + + + ); +} + +function ChevronRight() { + return ( + + + + ); +} + +function ExternalIcon() { + return ( + + + + ); +} diff --git a/web2/src/components/navbar.tsx b/web2/src/components/navbar.tsx index d7b62cc..f73bdbe 100644 --- a/web2/src/components/navbar.tsx +++ b/web2/src/components/navbar.tsx @@ -2,17 +2,95 @@ import Link from "next/link"; import { usePathname } from "next/navigation"; -import { useState } from "react"; -import { site, navSections } from "@/lib/site"; +import { useState, useRef } from "react"; +import { site, navSections, type NavSection } from "@/lib/site"; import { ThemeToggle } from "./theme-toggle"; -const links = [ - { label: "Docs", href: site.docs }, - { label: "Todo list", href: "/todo" }, - { label: "Get Involved", href: "/get-involved" }, - { label: "Trackers", href: "/api-tracker" }, - { label: "Sponsor", href: "/sponsor" }, -]; +function NavDropdown({ section, pathname }: { section: NavSection; pathname: string }) { + const [open, setOpen] = useState(false); + const timerRef = useRef | null>(null); + + const isActive = section.items.some( + (item) => + !item.external && + (item.href === "/" ? pathname === "/" : pathname.startsWith(item.href)) + ); + + function handleMouseEnter() { + if (timerRef.current) clearTimeout(timerRef.current); + setOpen(true); + } + + function handleMouseLeave() { + timerRef.current = setTimeout(() => setOpen(false), 100); + } + + return ( +
+ + + {open && ( +
+ {section.items.map((item) => { + const active = + !item.external && + (item.href === "/" ? pathname === "/" : pathname.startsWith(item.href)); + return item.external ? ( + + {item.label} + + + ) : ( + + {active && ( + + )} + {item.label} + + ); + })} +
+ )} +
+ ); +} export function Navbar() { const pathname = usePathname(); @@ -37,24 +115,11 @@ export function Navbar() { {site.name} - {/* Desktop nav — centered */} + {/* Desktop nav — centered, section dropdowns */} {/* Right */} @@ -192,3 +257,22 @@ function CloseIcon() { ); } + +function ExternalIcon() { + return ( + + + + ); +} diff --git a/web2/src/components/table-of-contents.tsx b/web2/src/components/table-of-contents.tsx new file mode 100644 index 0000000..40600f2 --- /dev/null +++ b/web2/src/components/table-of-contents.tsx @@ -0,0 +1,108 @@ +"use client"; + +import { useEffect, useRef, useState } from "react"; +import { usePathname } from "next/navigation"; + +interface Heading { + id: string; + text: string; + level: number; +} + +export function TableOfContents() { + const pathname = usePathname(); + const [headings, setHeadings] = useState([]); + const [activeId, setActiveId] = useState(""); + const observerRef = useRef(null); + + // Re-scan headings whenever the page changes + useEffect(() => { + const timer = setTimeout(() => { + // Matches both h2/h3[id] and PageHeader-style

...

+ const elements = document.querySelectorAll( + "main h2[id], main h3[id], main header[id]" + ); + const headingList = Array.from(elements).reduce((acc, el) => { + if (el.tagName === "HEADER") { + const inner = el.querySelector("h2, h3"); + if (inner) { + acc.push({ + id: el.id, + text: inner.textContent?.trim() ?? "", + level: inner.tagName === "H2" ? 2 : 3, + }); + } + } else { + acc.push({ + id: el.id, + text: el.textContent?.trim() ?? "", + level: el.tagName === "H2" ? 2 : 3, + }); + } + return acc; + }, []); + setHeadings(headingList); + setActiveId(headingList[0]?.id ?? ""); + }, 150); + return () => clearTimeout(timer); + }, [pathname]); + + // Highlight the active section with IntersectionObserver + useEffect(() => { + if (headings.length === 0) return; + + if (observerRef.current) observerRef.current.disconnect(); + + observerRef.current = new IntersectionObserver( + (entries) => { + // Pick the topmost visible heading + const visible = entries + .filter((e) => e.isIntersecting) + .sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top); + if (visible.length > 0) setActiveId(visible[0].target.id); + }, + { rootMargin: "-72px 0px -60% 0px", threshold: 0 } + ); + + headings.forEach(({ id }) => { + const el = document.getElementById(id); + if (el) observerRef.current!.observe(el); + }); + + return () => observerRef.current?.disconnect(); + }, [headings]); + + if (headings.length === 0) return null; + + return ( + + ); +} From 1890b53178591bc0ae2e516458440cdcaf21870d Mon Sep 17 00:00:00 2001 From: Alex-Zughaid <117576511+Alex-Zughaid@users.noreply.github.com> Date: Wed, 12 Aug 2026 17:01:05 +0100 Subject: [PATCH 2/4] added sidebar for other pages --- web2/src/app/documentation-tracker/page.tsx | 10 +++- web2/src/app/gh-guide/page.tsx | 58 ++++++--------------- 2 files changed, 24 insertions(+), 44 deletions(-) diff --git a/web2/src/app/documentation-tracker/page.tsx b/web2/src/app/documentation-tracker/page.tsx index eb70526..ecfd434 100644 --- a/web2/src/app/documentation-tracker/page.tsx +++ b/web2/src/app/documentation-tracker/page.tsx @@ -28,7 +28,7 @@ export default function DocumentationTrackerPage() {
-

+

Steps to Help with Documentation

@@ -38,7 +38,9 @@ export default function DocumentationTrackerPage() { {docSteps.map((step) => (

-

{step.title}

+

+ {step.title} +

    {step.items.map((item, i) => (
  1. @@ -52,6 +54,10 @@ export default function DocumentationTrackerPage() { ); } +function slugify(title: string) { + return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, ""); +} + const docSteps = [ { title: "Phase 1: Get the Code", diff --git a/web2/src/app/gh-guide/page.tsx b/web2/src/app/gh-guide/page.tsx index 048ecb4..fe8645e 100644 --- a/web2/src/app/gh-guide/page.tsx +++ b/web2/src/app/gh-guide/page.tsx @@ -1,5 +1,6 @@ import { Card } from "@heroui/react"; import type { Metadata } from "next"; +import { PageHeader } from "@/components/page-header"; import { site } from "@/lib/site"; export const metadata: Metadata = { @@ -28,12 +29,7 @@ export default function GhGuidePage() { {/* Step 1 — Deciding on a Problem to Work On */}
    -

    - 1. Deciding on a Problem to Work On -

    +
    @@ -44,7 +40,7 @@ export default function GhGuidePage() { Before starting any work, you can open an issue on the Physlib repository to describe the problem you are encountering or the feature you want to add. This makes maintainers aware of the work you plan on doing and helps with - tracking the project. + tracking the project.

    {/* Step 3 — Pull Request */}
    -

    - 3. Make a Pull Request -

    +

    Small pull-requests are better than large ones — even if it's just a single result. Follow the PR template provided by GitHub when opening your PR. @@ -169,7 +155,8 @@ export default function GhGuidePage() {

    Adding Labels @@ -190,7 +177,8 @@ export default function GhGuidePage() {

    Lifecycle of a PR @@ -207,7 +195,8 @@ export default function GhGuidePage() {

    Other Common Labels @@ -253,12 +242,7 @@ export default function GhGuidePage() {

    -

    - 4. Managing the PR -

    +

    After opening a PR, the maintainers will review the changes and provide feedback. Feel free to begin work on a separate PR in the meantime but be prepared to make changes to this one if required.

    @@ -282,12 +266,7 @@ export default function GhGuidePage() {
    -

    - 5. Merging the PR -

    +

    Once the reviewer is happy with the changes, they will merge the PR into the main branch. Physlib uses a{" "} -

    - Additional Guidance -

    +

    Please use your real name in your GitHub account settings and commit author. Physlib uses GitHub account names in its automated documentation generation, so using your real name From a32347db27d3031cc52ef264ba8395de8e69ae50 Mon Sep 17 00:00:00 2001 From: Alex-Zughaid <117576511+Alex-Zughaid@users.noreply.github.com> Date: Wed, 12 Aug 2026 17:18:54 +0100 Subject: [PATCH 3/4] fix(graphviz): dedupe script loads by real completion, not DOM presence loadScript() treated an existing