From 074c53e6fe7580e53b88e2b0c217b859aee11d5f Mon Sep 17 00:00:00 2001 From: abrcrmb Date: Thu, 8 Oct 2026 12:02:31 +0100 Subject: [PATCH 1/2] feat: favicon, site header with docs/GitHub links, mobile nav, hero motion - app/icon.svg, app/apple-icon.png: favicon and iOS home-screen icon from the org's new lock mark (kinlock-icon/). - components/nav/SiteHeader.tsx: persistent header across every page. Desktop: brand, primary nav (Send/Request/Verify), and two separately-styled, separately-destined buttons (outline Documentation -> kinlock-org.github.io, filled-neutral GitHub -> github.com/Kinlock-Org). Collapses to a hamburger disclosure menu below md, closes on route change and Escape. - Fixed the landing page footer's stale "How it works, in detail" link, which pointed at a raw GitHub markdown file instead of the real docs site. - globals.css: a strong --ease-out token (animate skill) used by the mobile menu's entrance and a staggered hero fade/slide-up on the landing page, both gated under prefers-reduced-motion. No new dependency: pure CSS, following the animate skill's "cheapest tool that works" rule. --- app/(marketing)/page.tsx | 13 ++- app/apple-icon.png | Bin 0 -> 4756 bytes app/globals.css | 55 ++++++++++++ app/icon.svg | 13 +++ app/layout.tsx | 2 + components/nav/SiteHeader.tsx | 153 ++++++++++++++++++++++++++++++++++ messages/en.json | 9 ++ 7 files changed, 238 insertions(+), 7 deletions(-) create mode 100644 app/apple-icon.png create mode 100644 app/icon.svg create mode 100644 components/nav/SiteHeader.tsx diff --git a/app/(marketing)/page.tsx b/app/(marketing)/page.tsx index 6b4c5ab..de1093a 100644 --- a/app/(marketing)/page.tsx +++ b/app/(marketing)/page.tsx @@ -19,13 +19,15 @@ function Hero() {
-

+

{t("pages.home.heroHeadline")}

-

{t("pages.home.heroSubtext")}

+

+ {t("pages.home.heroSubtext")} +

{t("pages.home.heroCta")} @@ -120,10 +122,7 @@ function Footer() { {t("pages.home.footerSource")} - + {t("pages.home.footerDocs")} diff --git a/app/apple-icon.png b/app/apple-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a8b54175c061954ed7fbdde0dab3de2a0e9e15d9 GIT binary patch literal 4756 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 + Kinlock + + + + + + + + + + + diff --git a/app/layout.tsx b/app/layout.tsx index 503ee83..435612a 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,5 +1,6 @@ import { Outfit } from "next/font/google"; import type { ReactNode } from "react"; +import { SiteHeader } from "@/components/nav/SiteHeader"; import { t } from "@/lib/i18n/messages"; import "./globals.css"; @@ -17,6 +18,7 @@ export default function RootLayout({ children }: { children: ReactNode }) {
{t("common.testnetBanner")}
+ {children} diff --git a/components/nav/SiteHeader.tsx b/components/nav/SiteHeader.tsx new file mode 100644 index 0000000..1f6f524 --- /dev/null +++ b/components/nav/SiteHeader.tsx @@ -0,0 +1,153 @@ +"use client"; + +import { usePathname } from "next/navigation"; +import { useEffect, useId, useState } from "react"; +import { t } from "@/lib/i18n/messages"; + +const LINKS = [ + { href: "/send", key: "nav.send" as const }, + { href: "/request", key: "nav.request" as const }, + { href: "/verify", key: "nav.verify" as const }, +]; + +const navLinkClass = (active: boolean) => + `text-sm font-medium transition-colors motion-reduce:transition-none ${ + active ? "text-accent" : "text-ink/70 hover:text-accent" + }`; + +/** + * Persistent site header: brand, primary nav, and two separately-styled, separately-destined + * buttons for the docs site and the GitHub repos (roadmap: docs/site-polish). Collapses to a + * disclosure menu below `md`. A client leaf per the animate skill's RSC isolation rule; the rest + * of the tree (layout, pages) stays server-rendered. + */ +export function SiteHeader() { + const pathname = usePathname(); + const [open, setOpen] = useState(false); + const menuId = useId(); + + // biome-ignore lint/correctness/useExhaustiveDependencies: closes the menu on route change + useEffect(() => setOpen(false), [pathname]); + + useEffect(() => { + if (!open) return; + const onKeyDown = (e: KeyboardEvent) => { + if (e.key === "Escape") setOpen(false); + }; + document.addEventListener("keydown", onKeyDown); + return () => document.removeEventListener("keydown", onKeyDown); + }, [open]); + + return ( +
+
+ + + {t("app.name")} + + + + + +
+ + {open ? ( + + ) : null} +
+ ); +} diff --git a/messages/en.json b/messages/en.json index d67e945..fe11fa3 100644 --- a/messages/en.json +++ b/messages/en.json @@ -10,6 +10,15 @@ "usdOnlyNote": "No reliable exchange rate is available for this currency, so only the USD amount is shown.", "rateRiskNote": "Exchange rates change. The local-currency amount is an estimate and is not guaranteed." }, + "nav": { + "send": "Send", + "request": "Request", + "verify": "Verify", + "docs": "Documentation", + "github": "GitHub", + "openMenu": "Open menu", + "closeMenu": "Close menu" + }, "receipt": { "headline": "Payment to verified payee", "valid": "Valid", From fabc27c469650b21422a19312e15b641f0a4476a Mon Sep 17 00:00:00 2001 From: abrcrmb Date: Thu, 8 Oct 2026 12:03:25 +0100 Subject: [PATCH 2/2] docs(roadmap): add M3-25 for the site-polish PR --- ROADMAP.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 9fd18e8..264ff9f 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -4,7 +4,7 @@ | | | |---|---| -| **Last updated** | 2026-10-08 (wording: "verified payee" replaces "school or landlord") | +| **Last updated** | 2026-10-08 (M3-25 favicon, site header, mobile nav, motion) | | **Docs baseline** | v0.3, worldwide scope (`PRD.md`, `ARCHITECTURE.md`, `ARCHITECTURE_ESSENTIALS.md`, `AGENTS.md`, `CLAUDE.md`, `project_structure.md`) | | **Current phase** | Phase 0 (Foundations) → starting Phase 1 (M0 Validate) | | **Readiness** | See [§3](#3-progress-snapshot) | @@ -131,7 +131,7 @@ Readiness % = `DONE ÷ (all rows − DEFERRED − DROPPED)`. Conditional rows (P | 1 M0 Validate | 17 | 0 | 1 | 16 | 0 | 0 | 0 | 0% | | 2 Contract + registry | 36 | 16 | 5 | 15 | 0 | 0 | 0 | 44% | | 3 SDK + indexer | 19 | 1 | 1 | 17 | 0 | 0 | 0 | 5% | -| 4 App | 24 | 9 | 6 | 9 | 0 | 0 | 0 | 38% | +| 4 App | 25 | 10 | 6 | 9 | 0 | 0 | 0 | 40% | | 5 Testnet pilot | 7 | 0 | 0 | 7 | 0 | 0 | 0 | 0% | | 6 Pre-mainnet features | 10 | 0 | 0 | 10 | 0 | 0 | 0 | 0% | | 7 Hardening | 26 | 0 | 0 | 26 | 0 | 0 | 0 | 0% | @@ -139,7 +139,7 @@ Readiness % = `DONE ÷ (all rows − DEFERRED − DROPPED)`. Conditional rows (P | 9 Conditional ramp | 9 | 0 | 0 | 0 | 0 | 9 | 0 | n/a | | 10 Wave + community | 8 | 1 | 0 | 7 | 0 | 0 | 0 | 13% | | 11 Deferred parking lot | 16 | 0 | 0 | 0 | 0 | 16 | 0 | n/a | -| **All** | **199** | **39** | **15** | **120** | **0** | **25** | **0** | **22%** | +| **All** | **200** | **40** | **15** | **120** | **0** | **25** | **0** | **23%** | --- @@ -314,6 +314,7 @@ Readiness % = `DONE ÷ (all rows − DEFERRED − DROPPED)`. Conditional rows (P | M3-22 | Testnet hosting and environment config | app | P0 | IN PROGRESS | M3-19 | Public testnet URL live | | M3-23 | Externalize all UI strings (`messages/en.json`) and add `Intl`-based locale-aware formatting; handle zero- and three-decimal currencies, non-Latin text, and RTL-safe layout | app | P0 | DONE | M3-01 | Lint or test fails on inline strings; formatting tests across at least 3 locales and currencies | | M3-24 | CI guard: fail on hard-coded country, currency, anchor, or locale literals outside registry data, tests, and fixtures | all | P1 | TODO | M3-01, M2-01 | Guard runs in app and sdk CI; passing and failing examples tested | +| M3-25 | Favicon/app icon, persistent site header (nav, docs and GitHub links), mobile hamburger menu, reduced-motion-safe entrance motion | app | P2 | DONE | M3-01, M3-04 | Favicon set in every browser; nav usable one-handed on a narrow viewport; `prefers-reduced-motion` honored | --- @@ -523,6 +524,7 @@ Newest first. One entry per PR. Required for every contribution (see §2). | Date | PR / ref | Repo | Rows touched | Summary | |---|---|---|---|---| +| 2026-10-08 | `feat/site-polish` | app | DONE: M3-25 (new row) | Added `app/icon.svg`/`app/apple-icon.png` (favicon and iOS icon, from the org's new lock mark in `kinlock-icon/`). Built `components/nav/SiteHeader.tsx`: a persistent header on every page, desktop nav (Send/Request/Verify) plus two separately-styled, separately-destined buttons (outline "Documentation" to `kinlock-org.github.io`, filled-neutral "GitHub" to `github.com/Kinlock-Org`), collapsing to a hamburger disclosure menu below `md` (closes on route change and Escape). Fixed the landing footer's stale docs link, which pointed at a raw GitHub markdown file instead of the real docs site. Installed the `emilkowalski/skills` `animate` skill and used it for the menu's entrance and a staggered hero fade/slide-up on the landing page (CSS-only, no new dependency, both gated under `prefers-reduced-motion`). Verified: `pnpm lint`, `pnpm typecheck`, `pnpm test` (93/93), `pnpm build` all green; visually checked desktop, mobile closed/open, and dark mode with Playwright screenshots. Not done here: the GitHub org avatar itself (no API/CLI command sets it; recommended `kinlock-icon/icon-light-1024.png`, needs a human to upload via org settings) | | 2026-10-08 | `docs/verified-payee-wording` | app | no row changes | Synced `AGENTS.md`/`docs/PRD.md`/`docs/ARCHITECTURE_ESSENTIALS.md` from `Kinlock-Org/.github` (`scripts/sync-docs.sh`), and updated `messages/en.json` (`app.tagline`, `pages.home.heroHeadline`/`heroSubtext`/`lockBody`/`sendersBody`, `pages.send.intro`): "a verified school or landlord" replaced with "a verified payee" everywhere it was used as a generic stand-in for the product's reach, rather than naming the two current MVP categories factually. Verified: `pnpm lint`, `pnpm typecheck`, `pnpm test` (92/92, including the inline-strings guard) all green. Canonical doc source edited in `.github` PR #23 | | 2026-10-08 | `feat/m3-13-receipt-verify` | app | DONE: M3-13 | Built `/r/[txHash]/[eventIndex]` and `/verify`, replacing their `PageStub`s. Both call the SDK's `verifyReceipt` (chain-authoritative tiered verification) and show "Valid"/"Not valid"; the "Payment to verified payee" headline (hard rule 7) renders only when `result.valid` is actually `true`. A separate `getLock` read is used purely to label the asset (code and issuer) since `VerifyReceiptResult.receipt` doesn't carry the token; falls back to an unlabeled amount rather than guessing when that lock is unavailable. New shared pieces: `lib/receipts/parse.ts` (pure link/hash parsing, unit-tested), `lib/receipts/receipt-text.ts` (message-key mapping), `components/receipts/ReceiptResult.tsx` (shared result view for both pages). Verified: `pnpm lint`, `pnpm typecheck`, `pnpm test` (92/92 passed, including new `parse.test.ts`), `pnpm build` all green | | 2026-10-08 | `docs/link-doc-hub` | app | no row changes (org row W-10 tracked in `.github`) | Linked `Kinlock-Org.github.io` (the org's documentation-issue hub) from README, with `area:app` |