Skip to content

feat: landing page, unified navbar, and interactive product mocks - #60

Merged
NoelHuibers merged 1 commit into
mainfrom
feat/landing-page
May 21, 2026
Merged

feat: landing page, unified navbar, and interactive product mocks#60
NoelHuibers merged 1 commit into
mainfrom
feat/landing-page

Conversation

@noluyorAbi

Copy link
Copy Markdown
Collaborator
Inklings mascot

Landing page, unified navbar, and interactive product mocks

Summary

Previously / was a one-line redirect("/inkwell"). This PR ships an actual landing surface that explains the project, previews all four product surfaces with interactive mocks, and routes visitors into them. It also consolidates the per-tab headers into one site-wide SiteNav, swaps the Next 16-deprecated <Image priority /> for loading="eager" fetchPriority="high", and fixes a hydration mismatch caused by SSR-vs-browser float drift.

User flow

flowchart LR
  V((Visitor)) --> H["/ Landing"]

  H -->|Hero CTA| SI["/inkwell"]
  H -->|Hero CTA| SG["/game"]
  H -->|Hero CTA| SQ["/quill"]
  H -->|Section CTA| SB["/blots"]
  H -->|Nav GitHub icon| GH[(GitHub repo)]
  H -->|References tile| Deck[/piv-pitch.pdf/]

  SI -->|SiteNav brand| H
  SB -->|SiteNav brand| H
  SG -->|SiteNav brand| H
  SQ -->|SiteNav brand| H

  SI <-->|SiteNav tabs| SB
  SB <-->|SiteNav tabs| SG
  SG <-->|SiteNav tabs| SQ
Loading

Landing page anatomy

flowchart TB
  Body["app/layout.tsx<br/>body: flex min-h-dvh flex-col"]
  Body --> Nav["SiteNav<br/>sticky · usePathname"]
  Body --> Main["main: flex flex-1 flex-col"]
  Main --> Page["app/page.tsx · LandingPage RSC"]
  Main --> Tabs["app/(tabs)/* routes"]

  Page --> Sections
  subgraph Sections [Landing sections]
    direction TB
    Hero["Hero + Mascot3D"]
    Abs["Abstract"]
    Prob["Problem"]
    Core["Core idea"]
    Meth["Method pipeline"]
    Surf["4 Surface previews"]
    Profile["Stylometric fingerprint"]
    Corp["Corpus stats"]
    Refs["References + BibTeX"]
    Team["Team"]
    Footer["Footer"]
    Hero --> Abs --> Prob --> Core --> Meth --> Surf --> Profile --> Corp --> Refs --> Team --> Footer
  end

  Surf --> Mocks
  subgraph Mocks [Interactive mocks · client components]
    IM["InkwellMock"]
    GM["GameMock"]
    QM["QuillMock"]
    BK["BookCard grid"]
  end
Loading

Highlights

Landing surface (app/page.tsx)

  • Hero: 3D cursor-tilt mascot, parallax drop-shadow, conic-gradient halo, satellite blots.
  • Paper-style Abstract, 5-stage clickable Method pipeline, Corpus stat cards, References with one-click BibTeX-style snippet copy.
  • 5-axis stylometric fingerprint (lexical richness, sentence length, abstraction, formality, narrative pace).
  • Team grid with hue-coded blots.

Interactive mocks (components/landing/*-mock.tsx)

Mock What it does
InkwellMock Toggle Classical / Modern / Colour layout and algo / llm / crowd / blend hue source; 110 procedurally placed blots reposition + recolor live.
GameMock Three modes: Smudge → Swatch (6 swatches), Smudge → Wheel (36-slice clickable wheel + handle), Twin Smudges (same / different judgement). Streak, score, reveal with Δ° + points.
QuillMock Editable textarea, live heuristic hue readout, 5 target colours (meadow green, orwell blue, kafka indigo, borges amber, morrison rose), per-target rewritten paragraph.
Mascot3D rotateX / rotateY cursor tracking, scale(1.05) on hover, parallax drop-shadow, radial sheen via mix-blend-mode: screen.

Unified SiteNav (components/site-nav.tsx)

  • Mounted in root layout → appears on every route.
  • Brand + tab list (The Inkwell, The Blots, The Blotting Game, The Quill) + GitHub icon + contextual Enter the Inkwell CTA on / only.
  • Active route highlighted via usePathname with aria-current="page" and an ink-bleed underline.
  • Exports GITHUB_URL, GithubIcon, ArrowIcon so consumers share one source of truth.

Layout flex chain

  • body: flex min-h-dvh flex-colSiteNav (sticky) + main: flex flex-1 flex-col.
  • CanvasShell previously used h-[calc(100dvh-57px)] (coupled to assumed header height). Now flex flex-1 — no magic numbers.

Hydration safety

Math.cos / Math.sin chains in inkwell-mock (blot positions) and game-mock (color-wheel SVG d attributes) produced subtly different floats under Node-V8 vs browser-V8. React fired hydration warnings on the trailing digits. Fixed by quantizing all DOM-bound floats with .toFixed(2-3) at generation time.

Next.js 16 compliance

  • <Image priority /> is deprecated in v16 (see installed node_modules/next/dist/docs/01-app/03-api-reference/02-components/image.md).
  • Migrated to loading="eager" fetchPriority="high" on LCP candidates (SiteNav brand, Mascot3D hero).
  • Mascot3D prop renamed priorityeager to mirror the new semantics.

Security

pnpm-workspace.yaml overrides resolve two moderate transitive CVEs:

CVE Package Path
GHSA-67mh-4wv8-2f99 esbuild (<= 0.24.2) → forced >= 0.25.0 drizzle-kit > @esbuild-kit/esm-loader > @esbuild-kit/core-utils > esbuild
GHSA-qx2v-qp2m-jg93 postcss (< 8.5.10) → forced >= 8.5.10 next > postcss

pnpm audit is clean.

Cleanup applied in this PR

  • Deleted orphan public/inkling-mascot.png (418 KB, zero refs after the -no-background migration).
  • Removed unused @keyframes inklings-bleed-in and .inklings-bleed-in utility.
  • Collapsed redundant useMemo wrappers in game-mock.tsx (4 wrappers around primitive expressions) into a single guess* pair.
  • Replaced duplicated local Arrow SVG in page.tsx with the shared ArrowIcon exported from site-nav.tsx.

File-by-file

New

  • app/page.tsx (was 6 lines of redirect; now full landing)
  • app/{favicon.ico,icon.png,apple-icon.png,opengraph-image.png,twitter-image.png} — Next 16 metadata-based asset routes
  • public/inkling-mascot-no-background.png — transparent mascot
  • brand/*.png — GitHub avatar + social preview source assets
  • components/site-nav.tsx
  • components/landing/blot.tsx
  • components/landing/book-card.tsx
  • components/landing/inkwell-mock.tsx
  • components/landing/game-mock.tsx
  • components/landing/quill-mock.tsx
  • components/landing/mascot-3d.tsx
  • components/landing/profile-bars.tsx
  • components/landing/research.tsx

Modified

  • app/layout.tsx — mounts SiteNav, body flex column, full metadata + viewport exports
  • app/globals.css — landing keyframes (inklings-float, inklings-drift, inklings-bar-fill, inklings-spin-slow, inklings-pulse-soft)
  • components/canvas/canvas-shell.tsxh-[calc(100dvh-57px)]flex flex-1
  • README.md — project overview, problem, core idea, features matrix

Deleted

  • app/(tabs)/layout.tsx — header moved to root layout
  • components/tabs-nav.tsx — replaced by SiteNav
  • public/inkling-mascot.png — orphan

Test plan

  • pnpm typecheck clean
  • pnpm check (biome) clean
  • pnpm audit clean
  • /, /inkwell, /blots, /game, /quill return 200
  • No hydration warnings in browser console on /
  • Visual QA in Safari (flex chain — see follow-ups)
  • Dark mode pass on landing surfaces
  • Interactive QA: Inkwell mode/source toggles, Game streak/score, Quill target nudges, Mascot tilt

Known follow-ups (not blocking)

  • Mascot3D runs setState on every mousemove. Migrate to refs + CSS custom properties for compositor-only updates.
  • Below-fold mocks (InkwellMock, GameMock, QuillMock, MethodPipeline, References) ship in the initial JS bundle. Wrap in next/dynamic.
  • Split research.tsx into a server-only file (Abstract, CorpusStats) and a client-only file (MethodPipeline, References).
  • Add min-h-0 to <main> defensively for Safari flex-collapse safety.
  • Consolidate BookCard (landing) with BlotCard (app/(tabs)/blots/page.tsx) once /blots wires real 4-channel data.

Generated for LMU Praktikum Informationsvisualisierung · SoSe 2026 · Inklings team.

Replaces the `/` -> `/inkwell` redirect with a full marketing landing
surface. Adds a site-wide unified `SiteNav`, four interactive mock
surfaces previewing each product tab, and paper-style research
sections (Abstract, Method pipeline, Corpus stats, References).

Why
- The homepage was a one-line redirect; first impression was the
  empty Inkwell shell. We needed an actual entry point that explains
  the project, shows the four surfaces, and links into the deck and
  repo.
- The Inkwell / Blots / Game / Quill tabs had their own ad-hoc
  header. Consolidating into a single sticky nav at the root layout
  removes duplication and gives consistent navigation everywhere.

Highlights
- Landing page: hero with 3D cursor-tilt mascot, paper-style Abstract,
  5-stage clickable Method pipeline, Corpus stat cards, References
  + one-click BibTeX copy, 5-axis stylometric fingerprint, team grid.
- Four interactive mocks pulled from the live tab routes: Inkwell
  scatter with Classical / Modern / Colour + algo / llm / crowd /
  blend toggle (110 procedural blots), Blotting Game in all three
  modes (Smudge -> Swatch, Smudge -> Wheel with 36-slice clickable
  wheel, Twin Smudges) with streak + score, Quill diff with live
  heuristic hue readout + 5-target rewrite picker.
- Mascot3D: rotateX / rotateY cursor tracking, parallax drop-shadow,
  screen-blend radial sheen, scale on hover.
- Unified SiteNav mounted in `app/layout.tsx`. Brand + tab list +
  GitHub link + contextual CTA. Active route via `usePathname` with
  `aria-current="page"` and an ink-bleed underline. Deletes
  `app/(tabs)/layout.tsx` and `components/tabs-nav.tsx`.
- Layout flex chain: body `flex min-h-dvh flex-col` -> SiteNav (sticky)
  + main `flex flex-1 flex-col`. `CanvasShell` drops the magic
  `h-[calc(100dvh-57px)]` for `flex flex-1`.

Hydration fix
- Math.cos / sin chains in `inkwell-mock` and `game-mock` produced
  subtly different floats under Node-V8 (SSR) vs browser-V8 -- React
  warned on mismatching `left` / `top` / SVG `d` attributes. Quantize
  every DOM-bound float at generation time via `toFixed(2-3)`.

Next.js 16 compliance
- `<Image priority />` is deprecated in v16. Migrated to
  `loading="eager" fetchPriority="high"` per the installed docs.

Security
- `pnpm audit` flagged two moderate transitive CVEs (already fixed on
  main): GHSA-67mh-4wv8-2f99 (esbuild via drizzle-kit) and
  GHSA-qx2v-qp2m-jg93 (postcss via Next). Branch keeps the workspace
  overrides clean; `pnpm audit` is clean.

Cleanup applied in this change
- Deleted orphan `public/inkling-mascot.png` (418KB, zero refs).
- Removed unused `@keyframes inklings-bleed-in` and `.inklings-bleed-in`
  utility.
- Stripped redundant `useMemo` wrappers in `game-mock.tsx`; collapsed
  `wheel*` and `swatch*` into a single `guess*` pair.
- Exported `ArrowIcon` from `site-nav` instead of duplicating it in
  `page.tsx`.

Verified: pnpm typecheck + biome check clean, /, /inkwell, /blots,
/game, /quill all return 200, no hydration warnings.
Copilot AI review requested due to automatic review settings May 20, 2026 13:32
@vercel

vercel Bot commented May 20, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
inklings Ready Ready Preview, Comment May 20, 2026 1:32pm

@noluyorAbi

Copy link
Copy Markdown
Collaborator Author

Code review

Found 1 issue:

  1. estimateHueFromText directional comment contradicts the implementation. Comment claims "long sentences = indigo, warm words = amber", but the code uses base hue 220 with avg (sentence length) subtracted and warmHits * 20 added. Long sentences therefore push the hue down from 220 toward ~160 (green/teal), away from indigo; warm-word hits push the hue up from 220 toward ~280 (indigo/purple), nowhere near amber (~30–60). Only the "short clauses = cold/blue" claim holds. Either the comment or the math needs to flip — most likely flip the sign on warmHits and add avg instead of subtracting, or rewrite the comment to match the current direction.

function estimateHueFromText(text: string): number {
// toy heuristic: short clauses = cold/blue, long sentences = indigo, warm words = amber
if (text.trim().length === 0) return 0;
const words = text.split(/\s+/);
const avg = text.length / Math.max(1, text.split(/[.!?]/).filter(Boolean).length);
const warmHits = (
text.toLowerCase().match(/\b(warm|gold|sun|fire|amber|rose|honey|burn)\b/g) ?? []
).length;
const calmHits = (
text.toLowerCase().match(/\b(quiet|soft|gentle|cloud|grass|meadow|drift|hush)\b/g) ?? []
).length;
let hue = 220 - Math.min(60, avg) + warmHits * 20 - calmHits * 18;
hue += Math.min(40, words.length / 4);
return ((hue % 360) + 360) % 360;
}

🤖 Generated with Claude Code

If this code review was useful, please react with 👍. Otherwise, react with 👎.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR replaces the / redirect with a full landing page that introduces the project and previews the four product “surfaces”, while also unifying navigation into a single sticky SiteNav mounted at the root layout.

Changes:

  • Add a new landing page at / with multiple sections and interactive mock previews (Inkwell/Game/Quill) to route users into the app.
  • Replace the per-tab header (TabsNav + (tabs)/layout) with a site-wide SiteNav in app/layout.tsx.
  • Update layout/CSS to use a flex-based height chain and add landing-specific animation utilities.

Reviewed changes

Copilot reviewed 16 out of 27 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
README.md Rewrites project overview, quickstart, and structure docs for the new landing + features.
components/tabs-nav.tsx Deletes old tabs navigation component (replaced by SiteNav).
components/site-nav.tsx Adds unified sticky navbar, GitHub link, CTA, and shared icons/constants.
components/landing/research.tsx Adds landing sections (Abstract/Method/Corpus/References).
components/landing/quill-mock.tsx Adds interactive Quill mock (live hue + target nudges).
components/landing/profile-bars.tsx Adds stylometric “fingerprint” bar component.
components/landing/mascot-3d.tsx Adds interactive 3D tilt mascot component.
components/landing/inkwell-mock.tsx Adds interactive Inkwell mock (layout + hue source toggles).
components/landing/game-mock.tsx Adds interactive Blotting Game mock (swatch/wheel/twin modes).
components/landing/book-card.tsx Adds landing book preview cards.
components/landing/blot.tsx Adds reusable blot SVG component for landing visuals.
components/canvas/canvas-shell.tsx Switches to flex-1 height strategy to remove header-height coupling.
app/page.tsx Replaces redirect with the full landing page implementation.
app/layout.tsx Mounts SiteNav, updates metadata/viewport, and establishes flex layout chain.
app/globals.css Adds landing animation keyframes/utilities.
app/(tabs)/layout.tsx Deletes old tab layout wrapper/header (nav moved to root layout).
Comments suppressed due to low confidence (1)

components/landing/research.tsx:174

  • There’s a duplicated arrow SVG here while ArrowIcon is already exported from components/site-nav.tsx. Reusing the shared icon would reduce duplication and keep the icon style consistent across the site.
function Arrow() {
  return (
    <svg
      width="16"
      height="16"
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth="2"
      strokeLinecap="round"
      strokeLinejoin="round"
      aria-hidden="true"
    >
      <path d="M5 12h14" />
      <path d="m12 5 7 7-7 7" />
    </svg>
  );
}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread README.md
canvas/ — deck.gl canvas shell
landing/ — blot, profile-bars, book-card, inkwell-mock
ui/ — shadcn primitives
tabs-nav.tsx · providers.tsx
Comment thread README.md
cp .env.example .env.local # fill DATABASE_URL, OPENAI_API_KEY, ANTHROPIC_API_KEY
pnpm db:push # apply Drizzle schema to Neon
pnpm seed:tracer # seed a starter corpus
pnpm dev # http://localhost:3000 → /inkwell
Comment thread components/site-nav.tsx
Comment on lines +25 to +35
<nav className="flex items-center gap-1">
<ul className="hidden items-center gap-1 md:flex">
{TABS.map((t) => (
<li key={t.href}>
<TabLink href={t.href} label={t.label} active={isActive(pathname, t.href)} />
</li>
))}
</ul>
<GithubLink />
{onHome && <CtaButton href="/inkwell" label="Enter the Inkwell" />}
</nav>
Comment on lines +1 to +5
"use client";

import { useState } from "react";
import { GITHUB_URL } from "@/components/site-nav";

Comment thread app/layout.tsx
Comment on lines +7 to +12
const siteUrl = process.env.NEXT_PUBLIC_APP_URL ?? "http://localhost:3000";

export const metadata: Metadata = {
title: "Inklings",
description: "A canvas of authors, in shape and in hue.",
metadataBase: new URL(siteUrl),
title: {
default: "Inklings — What color is Shakespeare?",
Comment thread app/page.tsx
Comment on lines +30 to +36
export default function LandingPage() {
return (
<div className="relative overflow-x-hidden">
<Hero />
<Abstract />
<Problem />
<CoreIdea />
@NoelHuibers
NoelHuibers merged commit 4a17edb into main May 21, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants