From ed07bb56813d61e1803cc3b36a0195e5db203b7e Mon Sep 17 00:00:00 2001 From: jwinterm Date: Fri, 5 Jun 2026 19:24:37 -0400 Subject: [PATCH] Add splash intro, nuclear games + consulting, Cherenkov hero - Splash: full-viewport animated SUCH logo cover with four corner nav buttons; reveals the site once "entered" (scroll-through or Explore) and does not resurrect on scroll-up. Original hero restored beneath it (title-anchored monitor pulse + Cherenkov-blue background). - Logo: traveling Cherenkov glow ring + gentle letter breath (SVG/Framer, reduced-motion safe); beveled glass corner buttons. - Services: new "Nuclear & Radiation Modeling" section + "Consulting" umbrella (payments, crypto, nuclear), MCNP Google Scholar link, and a not-a-licensed-PE clarification. - About/founder bio: LLNL credibility + accessibility, art101.io link. - Products: add Barns & Neutrons, Alpha Doc, Decay Theory (detail pages, OG cards, SVG icons); client-side sortable grid with coming-soon at the bottom. New Neroswap + Custom Websites icons (de-duplicated). - SEO: game pages added to sitemap. - Copy: dial back em-dashes to colons/commas/parens on home + services. --- .gitignore | 4 + apps/web/public/images/products/alpha-doc.svg | 29 ++++ .../images/products/barns-and-neutrons.svg | 27 +++ .../images/products/custom-websites.svg | 10 ++ .../public/images/products/decay-theory.svg | 37 +++++ apps/web/public/images/products/neroswap.svg | 6 + apps/web/src/app/about/page.tsx | 2 +- apps/web/src/app/page.tsx | 115 ++++++++----- .../products/alpha-doc/opengraph-image.tsx | 13 ++ apps/web/src/app/products/alpha-doc/page.tsx | 140 ++++++++++++++++ .../barns-and-neutrons/opengraph-image.tsx | 13 ++ .../app/products/barns-and-neutrons/page.tsx | 148 +++++++++++++++++ .../products/decay-theory/opengraph-image.tsx | 13 ++ .../src/app/products/decay-theory/page.tsx | 140 ++++++++++++++++ apps/web/src/app/products/page.tsx | 106 ++++++------ apps/web/src/app/services/page.tsx | 116 +++++++++++-- apps/web/src/app/sitemap.ts | 3 + .../components/about/founder-signature.tsx | 4 +- .../components/landing/dynamic-background.tsx | 20 ++- .../src/components/landing/hero-splash.tsx | 124 ++++++++++++++ .../components/landing/services-preview.tsx | 3 +- apps/web/src/components/layout/header.tsx | 15 +- apps/web/src/components/layout/mobile-nav.tsx | 12 +- .../components/portfolio/products-grid.tsx | 156 ++++++++++++++++++ apps/web/src/lib/og.tsx | 68 ++++++++ packages/ui/src/procedural/nebula-field.tsx | 50 ++++-- 26 files changed, 1234 insertions(+), 140 deletions(-) create mode 100644 apps/web/public/images/products/alpha-doc.svg create mode 100644 apps/web/public/images/products/barns-and-neutrons.svg create mode 100644 apps/web/public/images/products/custom-websites.svg create mode 100644 apps/web/public/images/products/decay-theory.svg create mode 100644 apps/web/public/images/products/neroswap.svg create mode 100644 apps/web/src/app/products/alpha-doc/opengraph-image.tsx create mode 100644 apps/web/src/app/products/alpha-doc/page.tsx create mode 100644 apps/web/src/app/products/barns-and-neutrons/opengraph-image.tsx create mode 100644 apps/web/src/app/products/barns-and-neutrons/page.tsx create mode 100644 apps/web/src/app/products/decay-theory/opengraph-image.tsx create mode 100644 apps/web/src/app/products/decay-theory/page.tsx create mode 100644 apps/web/src/components/landing/hero-splash.tsx create mode 100644 apps/web/src/components/portfolio/products-grid.tsx create mode 100644 apps/web/src/lib/og.tsx diff --git a/.gitignore b/.gitignore index 24abf7f..ce437e4 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,7 @@ test-results/ # AI / Assistant .claude/ + +# Private strategy / internal notes (never publish) +STRATEGY.md +docs/internal/ diff --git a/apps/web/public/images/products/alpha-doc.svg b/apps/web/public/images/products/alpha-doc.svg new file mode 100644 index 0000000..0cc2447 --- /dev/null +++ b/apps/web/public/images/products/alpha-doc.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/images/products/barns-and-neutrons.svg b/apps/web/public/images/products/barns-and-neutrons.svg new file mode 100644 index 0000000..c123042 --- /dev/null +++ b/apps/web/public/images/products/barns-and-neutrons.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/images/products/custom-websites.svg b/apps/web/public/images/products/custom-websites.svg new file mode 100644 index 0000000..9322bf9 --- /dev/null +++ b/apps/web/public/images/products/custom-websites.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/apps/web/public/images/products/decay-theory.svg b/apps/web/public/images/products/decay-theory.svg new file mode 100644 index 0000000..9fa623d --- /dev/null +++ b/apps/web/public/images/products/decay-theory.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/web/public/images/products/neroswap.svg b/apps/web/public/images/products/neroswap.svg new file mode 100644 index 0000000..120cfee --- /dev/null +++ b/apps/web/public/images/products/neroswap.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/apps/web/src/app/about/page.tsx b/apps/web/src/app/about/page.tsx index cf4af54..417de9d 100644 --- a/apps/web/src/app/about/page.tsx +++ b/apps/web/src/app/about/page.tsx @@ -32,7 +32,7 @@ export default function AboutPage() {

- Such Software is a software studio in Kennett Square, PA. Founded by John Winter Murphy, we build accessible e-commerce websites, crypto infrastructure, and original mobile apps and games. + Such Software is a software studio in Kennett Square, PA, founded by John Winter Murphy, whose background is in nuclear and radiation science: a radiation-detector PhD and six years at Lawrence Livermore National Laboratory. We build accessible e-commerce websites, crypto infrastructure, and original mobile apps and games (including a series of nuclear-themed games), and offer nuclear and radiation modeling consulting.

diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx index a6c86af..d71d51f 100644 --- a/apps/web/src/app/page.tsx +++ b/apps/web/src/app/page.tsx @@ -7,60 +7,89 @@ import { ServicesPreview } from "@/components/landing/services-preview"; import { Header } from "@/components/layout/header"; import { MobileNav } from "@/components/layout/mobile-nav"; import { DynamicBackground } from "@/components/landing/dynamic-background"; +import { HeroSplash } from "@/components/landing/hero-splash"; import { ScrollReveal } from "@/components/scroll-reveal"; -import Link from "next/link"; import { Button } from "@repo/ui/components/button"; -import { useRef, useState, useEffect } from "react"; +import Link from "next/link"; +import { useEffect, useLayoutEffect, useRef, useState } from "react"; + +// Avoid the "useLayoutEffect does nothing on the server" warning during SSR. +const useIsoLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect; export default function Home() { + // The splash owns the first screen. Once the visitor scrolls through it (or hits + // "Explore"), we "enter" the site: the splash unmounts for good and the real hero, + // header, bottom nav, and background take over. Scrolling back up lands on the hero, + // never the splash again. + const [entered, setEntered] = useState(false); + const splashRef = useRef(null); + const restoreScroll = useRef(0); + + // Deliberately place the nebula/monitor just to the left of the hero title, at any + // screen width (restored from the original hero). const titleRef = useRef(null); const [nebulaPosition, setNebulaPosition] = useState<{ x: number; y: number } | null>(null); + // Dismiss the splash once it has fully scrolled out of view. + useEffect(() => { + if (entered) return; + const el = splashRef.current; + if (!el) return; + const io = new IntersectionObserver( + ([entry]) => { + if (!entry.isIntersecting) { + restoreScroll.current = Math.max(0, window.scrollY - el.offsetHeight); + setEntered(true); + } + }, + { threshold: 0 }, + ); + io.observe(el); + return () => io.disconnect(); + }, [entered]); + + // After the splash unmounts, keep the visitor's position in the content stable + // (no jump). Runs before paint. + useIsoLayoutEffect(() => { + if (entered) window.scrollTo(0, restoreScroll.current); + }, [entered]); + + // Compute the nebula position from the title. Recompute once entered (the title is + // now in its final on-screen position) and on resize / font load. useEffect(() => { const updatePosition = () => { - if (titleRef.current) { - const rect = titleRef.current.getBoundingClientRect(); - const viewportWidth = window.innerWidth; - const viewportHeight = window.innerHeight; - - // Position nebula to the left of the title with a comfortable margin - // Convert pixel position to percentage of viewport - const titleLeftPercent = (rect.left / viewportWidth) * 100; - const titleTopPercent = (rect.top / viewportHeight) * 100; - - // Place the nebula 8% to the left of where the title starts - // and vertically aligned with the title's top area - const nebulaX = Math.max(5, titleLeftPercent - 8); - const nebulaY = Math.max(10, titleTopPercent - 2); - - setNebulaPosition({ x: nebulaX, y: nebulaY }); - } + if (!titleRef.current) return; + const rect = titleRef.current.getBoundingClientRect(); + const titleLeftPercent = (rect.left / window.innerWidth) * 100; + const titleTopPercent = (rect.top / window.innerHeight) * 100; + setNebulaPosition({ + x: Math.max(5, titleLeftPercent - 8), + y: Math.max(10, titleTopPercent - 2), + }); }; - updatePosition(); - - // Update on resize - window.addEventListener('resize', updatePosition); - - // Also update after fonts load (can affect title width) + window.addEventListener("resize", updatePosition); document.fonts?.ready.then(updatePosition); - - return () => window.removeEventListener('resize', updatePosition); - }, []); + return () => window.removeEventListener("resize", updatePosition); + }, [entered]); + + const enterFromButton = () => { + restoreScroll.current = 0; + setEntered(true); + }; return ( -
- -
+
+ +
-
+ {!entered && } + +

Precision Engineering for Everyone @@ -71,14 +100,10 @@ export default function Home() {
- + - +
@@ -115,7 +140,7 @@ export default function Home() {

- +
); -} \ No newline at end of file +} diff --git a/apps/web/src/app/products/alpha-doc/opengraph-image.tsx b/apps/web/src/app/products/alpha-doc/opengraph-image.tsx new file mode 100644 index 0000000..725a6c4 --- /dev/null +++ b/apps/web/src/app/products/alpha-doc/opengraph-image.tsx @@ -0,0 +1,13 @@ +import { gameOgImage, OG_SIZE, OG_CONTENT_TYPE } from "@/lib/og"; + +export const alt = "Alpha Doc — an academic-horror roguelike"; +export const size = OG_SIZE; +export const contentType = OG_CONTENT_TYPE; + +export default function Image() { + return gameOgImage({ + title: "Alpha Doc", + tagline: "A top-down roguelike where you're the disaster. Master four kinds of radiation.", + accent: "#fb923c", + }); +} diff --git a/apps/web/src/app/products/alpha-doc/page.tsx b/apps/web/src/app/products/alpha-doc/page.tsx new file mode 100644 index 0000000..8d3092f --- /dev/null +++ b/apps/web/src/app/products/alpha-doc/page.tsx @@ -0,0 +1,140 @@ +import { Header } from "@/components/layout/header"; +import { MobileNav } from "@/components/layout/mobile-nav"; +import { Card } from "@repo/ui/components/card"; +import Link from "next/link"; +import Image from "next/image"; +import { JsonLd, breadcrumbLd, faqLd } from "@/components/seo/json-ld"; +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Alpha Doc: An Academic-Horror Roguelike | Such Software", + description: "Alpha Doc is a top-down roguelike where you're the irradiated PhD student. Master four kinds of radiation and descend to your thesis defense. In development.", + alternates: { canonical: "/products/alpha-doc" }, + keywords: [ + "roguelike", + "academic horror game", + "radiation game", + "indie roguelike", + "physics game", + "alpha doc", + ], + openGraph: { + title: "Alpha Doc: An Academic-Horror Roguelike", + description: "A top-down roguelike where you're the disaster. Master four kinds of radiation and decide if the truth matters more than your PhD. In development.", + type: "website", + url: "/products/alpha-doc", + }, +}; + +const faqs = [ + { q: "What is Alpha Doc?", a: "A top-down roguelike set in a physics research facility warped by a containment breach. You play a doctoral candidate who caused the disaster the same night as their thesis defense — now irradiated, you descend through five department wings to face the Committee." }, + { q: "What are the four radiation types?", a: "Alpha, Beta, Gamma, and Neutron, each a distinct playstyle. Neutron is the standout: it's not a weapon but an environmental transmutation tool — you reshape the map itself, destroying cover and creating hazards before a fight even starts." }, + { q: "Is there a morality system?", a: "Yes, but it's never an explicit choice. The aggressive play that wins fights also destroys the evidence of your culpability. Your standing is inferred from the world and reflected in four different endings — you can't quite feel good about winning." }, + { q: "How will it be sold?", a: "Modern shareware: a free, complete first sector (the Grant Office), then a single one-time purchase to unlock the rest. No ads, no microtransactions, no energy timers." }, + { q: "When is it out?", a: "It's in active development — a playable vertical slice exists. Follow Such Software for release news on Steam, itch.io, iOS, and Android." }, +]; + +const jsonLd = { + "@context": "https://schema.org", + "@type": "VideoGame", + name: "Alpha Doc", + url: "https://such.software/products/alpha-doc", + description: + "A top-down academic-horror roguelike. Descend through a warped physics facility, master four types of radiation, and decide if the truth matters more than your PhD.", + applicationCategory: "Game", + genre: ["Roguelike", "Action", "Indie"], + gamePlatform: ["PC", "iOS", "Android"], + author: { "@type": "Organization", name: "Such Software" }, + publisher: { "@type": "Organization", name: "Such Software" }, +}; + +const features = [ + { title: "Four-element combat", body: "Alpha, Beta, Gamma, and Neutron are four real playstyles, not reskins. Each rewards a different read of the room." }, + { title: "Neutron reshapes the map", body: "The Neutron element transmutes the environment — destroy cover, open paths, arm dormant hazards. You change the battlefield before the battle." }, + { title: "Instability as a dial", body: "Push instability for harder-hitting shots at a slower fire rate, or dial it back and let it decay. Tension, not a punishment cliff." }, + { title: "Emergent morality", body: "No morality meter. Burning evidence for power quietly shifts dialogue, companion reactions, and which of four endings you reach." }, + { title: "Academic horror", body: "Your enemies are colleagues with real grievances — the reviewer you ignored, the safety inspector you overruled, the adjuncts caught in the blast." }, + { title: "Respectful premium design", body: "Free first sector, one-time unlock, no ads or timers. The systems are built for a complete experience, not a monetization funnel." }, +]; + +export default function AlphaDocPage() { + return ( +
+ +