diff --git a/README.md b/README.md index 9fb98ff..00837cf 100644 --- a/README.md +++ b/README.md @@ -159,7 +159,7 @@ Sign-in posts the fields the wallet actually signed — `{message, recipient, no **Visual identity.** The screens follow one deliberate system rather than a framework default: an achromatic ledger palette where the chrome (borders, buttons, nav) carries no colour and a single brand green is reserved for the one number that matters — money that has actually arrived. Type is Geist (Geist Mono for amounts and addresses, weight 700 for money only), surfaces are flat with a 1px rule instead of shadows, labels are sentence case, controls have a crisp 4px radius (cards 8px), and the dashboard sits in an open-plane sidebar shell that folds to a top bar on a phone. The mark (an arrow descending onto a rule) is `components/ui/mark.tsx`. Tokens live in `app/globals.css` as Tailwind `@theme` roles, so every screen references the role, never a raw colour. The project pfp and favicon are that same mark on the brand green, rendered deterministically from its geometry by `pnpm brand` (`scripts/render-brand.ts` → `public/pfp.png` 1024×1024 and `app/icon.png` 256×256); the hosted page serves the pfp as its Open Graph / Twitter preview image. -The landing page opens _inside the settled state_: the one brand green, enlarged from a single arrived number to a whole hero field, with the receipt — Settle's actual output — as the picture on it and a three-fact proof strip beneath (non-custodial, fee up front, every step on the record). The device is the curated fintech landing (a full brand-colour hero, a tight grotesque statement, a closing field that bookends the page); the colour is Settle's own. The rest of the page is the paper ledger: a numbered how-it-works bar whose last stage is the green "Settled", a working token picker (pick a token, watch the deposit amount follow for a fixed $1,500 invoice), one large statement — "Getting paid shouldn't require a crypto education." — the real pay page and deposit panel with a live QR, and the questions a real invoice raises. The page's pictures are the product's own screens built in HTML, not stock imagery. +The landing page is set against the dark, crypto-exchange language the human picked as a reference: a near-black ground with one magenta shaft of light behind a centred hero ("Invoice in dollars. Paid in any token."), floating token chips (USDC, USDT, wNEAR, BTC) behind a glass stage that holds the real client-view screen, a chain bar that says where money settles — NEAR, Ethereum, Base, Arbitrum, Solana — and that Bitcoin is refund-only, then the steps, a working token picker (pick a token, watch the deposit amount follow for a fixed $1,500 invoice), three feature cards with mini product visuals, the questions a real invoice raises, and a closing call to action. The magenta is **chrome only**; the one green still means money that has arrived, and the receipt section keeps the light paper ledger — green ink on paper — so the colour continues to carry its meaning. That is done with two class-scoped token sets in `app/globals.css`: `.night` re-declares the colour and radius roles for the marketing page, `.paper` restores the ledger. The page's pictures are the product's own screens built in HTML, not stock imagery. **Written during the hackathon window.** All of this code was written between 2026-11-03 and the submission deadline (2026-11-14 23:59 UTC), in this repository's public commit history, phase by phase against the tasks in `ROADMAP.md`. No pre-existing project was renamed or re-submitted. The team's other entry, **Stack**, is a separate repository with its own history (rule Q-015). diff --git a/ROADMAP.md b/ROADMAP.md index e891885..6df44f7 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -18,7 +18,7 @@ | 1. Core logic (`lib/core`) | Nov 5–6 | 100% | ✅ done (S1.1–S1.7; S1.7 validates Bitcoin refund addresses, so a BTC payer is checked here rather than only by the vendor) | | 2. Integrations (1Click, NEAR, DB) | Nov 5–7 | 90% | in progress (S2.1–S2.9 done; S2.10 spends payer funds, blocked) | | 3. API routes | Nov 6–7 | 100% | ✅ done (S3.1–S3.8; `storage_deposit` spend stays blocked on S2.10) | -| 4. UI | Nov 7–9 | 100% | ✅ done (S4.1–S4.14; the E2E demo journey shipped with Phase 6, S4.9 is the app-wide identity, S4.10 designs the landing page, S4.11 keeps the wallet stack out of the first bundle, S4.12/13 are the Landbook-inspired redesign and the brand assets, S4.14 keeps a 24-decimal amount inside its quote column, and one live wallet sign-in is still 👤) | +| 4. UI | Nov 7–9 | 100% | ✅ done (S4.1–S4.15; the E2E demo journey shipped with Phase 6, S4.9 is the app-wide identity, S4.10 designs the landing page, S4.11 keeps the wallet stack out of the first bundle, S4.12/13 are the Landbook-inspired redesign and the brand assets, S4.14 keeps a 24-decimal amount inside its quote column, S4.15 restyles the landing as a dark crypto-exchange hero, and one live wallet sign-in is still 👤) | | 5. Worker | Nov 8 | 100% | ✅ done (S5.1–S5.3; both loops run on the app's own deps and were run as a real process) | | 6. Demo mode & dry-run | Nov 9–10 | 100% | ✅ done (S6.1–S6.7; `pnpm demo` runs with no `.env`, and the Playwright journey runs in CI against a **production build** — `pnpm demo:prod`, S6.7 — so CI drives the artifact Phase 8 deploys) | | 7. Hardening & edge cases | Nov 10–11 | 100% | ✅ done (S7.1–S7.13; four were already built and are now pinned by tests, #108 closed the bare-receipt-link dead-end, and S7.13/#140 — found on the hosted URL, not by CI — stopped the landing page's first render depending on `window`, with a fifth e2e test that opens `/` and refuses a hydration mismatch. 🔍 the memo-chain list and the region-block mapping are still ours — S0.12/S0.13 came back from the vendor in 2026-11-09 and published neither) | @@ -137,6 +137,7 @@ Format: `- [task id] what blocks it · who can unblock · since date` - [x] **S4.12** [#143](https://github.com/MFrancis-dev/settle/issues/143) Landbook-inspired landing redesign · 2026-11-09: the human asked for a frontend redesign with "land-page.com" inspiration. That host is a **parked GoDaddy domain** (HTTP 200 → `/lander`; HTTPS 525), so the honest substitute is **Landbook** (land-book.com) — its HTML 403s but `llms.txt`/`rss.xml` answer, and the two money-adjacent featured sites were measured live in a browser: **Kuda** (full deep-purple brand field hero, cream ground, 4px buttons, proof strip) and **Remote** (tight grotesque statement, 4px buttons, white ground). The device is theirs, the colour is Settle's: the page now opens _inside the settled state_ — the one brand green enlarged to a whole hero field with the receipt as the picture and a three-fact proof strip (non-custodial / fee up front / on the record), a numbered how-it-works bar, the working token picker, one large statement ("Getting paid shouldn't require a crypto education."), the client-view fragments, the FAQ, and a closing green field that bookends the page. Controls tighten 6px → 4px (both references); new `invert`/`outline-invert` button variants and `Mark`/`SignInButton` `onField` props carry the chrome onto the field. Verified in Chromium at 1440 px and 390 px: **0 pageerror, 0 console error, 0 HTTP ≥ 400, 0 horizontal overflow**; all 5 e2e tests pass against a fresh production build, 664 unit + 1 skip, typecheck/lint/format clean. No app-screen behaviour change. - [x] **S4.13** [#143](https://github.com/MFrancis-dev/settle/issues/143) Minimalist brand assets (pfp / favicon / OG) · 2026-11-09: `pnpm brand` (`scripts/render-brand.ts`) rasterises the settle mark — an arrow descending onto a rule — on the brand green, deterministically from its geometry (reproducible for the AI screener, Q-017). Measured output: `public/pfp.png` **1024×1024** (10,800 bytes) and `app/icon.png` **256×256** (2,515 bytes), read back from the PNG IHDR. The pfp is the project avatar; `app/layout.tsx` serves it as the Open Graph / Twitter preview image (`metadataBase` from `PUBLIC_APP_URL`, live-URL fallback), and `app/icon.png` is the favicon. `LAUNCH.md` §3 now points the X post at the real hosted demo URL and the pfp. - [x] **S4.14** [#146](https://github.com/MFrancis-dev/settle/issues/146) Long amounts stay inside their column · 2026-11-09: found while recording the demo video, not by a test. The pay page prints the exact deposit amount, and a wNEAR quote is 24 decimals — one unbreakable token that overflowed its half of the quote grid and printed on top of "Seller receives". `Money` now sets `min-w-0` and `overflow-wrap:anywhere` on the value, the quote grid gives each cell `min-w-0`, and the happy-path e2e test asserts `scrollWidth <= clientWidth` for both quote cells. The exact digits are still shown in full (a truncated amount would be a wrong amount to send); the exact base-unit line is unchanged. +- [x] **S4.15** [#143](https://github.com/MFrancis-dev/settle/issues/143) Dark exchange-style landing · 2026-11-10: the human asked for the site redesigned against a Dribbble reference ("Crypto Exchange Web Platform" by Conceptzilla). Dribbble blocks curl/WebFetch, so the shot was read from the page's own `og:image` in a browser and measured: near-black ground, a magenta light shaft behind a centred hero, glass cards on hairline borders, pill buttons (one filled, one outlined), floating token chips around the product screen. That language is now `/`: a `.night` token scope in `app/globals.css` (new `--color-accent*` magenta is **chrome only**, never data; brand green stays the meaning of arrived money), a centred two-line hero, floating USDC/USDT/wNEAR/BTC chips behind a glass stage holding the real `ClientViewDemo`, a chain bar (NEAR/Ethereum/Base/Arbitrum/Solana settle; Bitcoin refunds only), three step cards, the live `TokenPickerDemo`, three feature cards with mini product visuals, the FAQ and a closing CTA. The receipt stays on a `.paper` scope, so the one green amount is still green ink on light paper and the existing e2e colour assertion (< 600 RGB sum) still guards it. New `accent`/`outline` button variants and a `pill` option; `Mark` gained `tone` and `SignInButton` a `variant` prop. Verified in Chromium at 1440 px and 390 px: the hero card fits with **0 horizontal overflow** (`scrollWidth` = viewport), the landing e2e contract (HTML offers sign-in, hydrates with no `pageerror`, receipt amount not white-on-white) passes, plus 671 unit + 1 skip, typecheck/lint/format clean. No app-screen behaviour change. ## Phase 5 — Worker (Nov 8) @@ -224,6 +225,7 @@ Format: `- [task id] what blocks it · who can unblock · since date` One line per contribution, newest first: `YYYY-MM-DD · who/agent · what changed · tasks` +- 2026-11-10 · agent · Restyled the submission-facing landing on the human's "use this as a template" with a Dribbble crypto-exchange shot. Dribbble serves curl and WebFetch nothing, so the reference was read from its `og:image` in the browser: near-black ground, one magenta light shaft behind a centred hero, glass panels on hairline borders, pill CTAs, floating coin chips. **S4.15** ([#143](https://github.com/MFrancis-dev/settle/issues/143)): a `.night` scope re-declares the colour and radius tokens under a single class, so the page flips to that palette without a second stylesheet, and `.paper` restores the light ledger for the receipt; the new magenta is chrome only, while brand green keeps meaning money that arrived. Hero, glass stage around the real client-view demo, chain bar (Bitcoin is refund-only), steps, live token picker, three feature cards with mini product visuals, FAQ, closing CTA. Measured in Chromium: **0 horizontal overflow at 1440 px and 390 px** (`documentElement.scrollWidth` equals the viewport), the landing e2e contract still passes (server HTML offers sign-in, hydration throws no `pageerror`, the receipt amount's RGB sum stays under 600), **671 unit + 1 skip**, typecheck/lint/format clean. No phase percentage moves — the redesign lands inside an already-complete phase · S4.15 (new, [#143](https://github.com/MFrancis-dev/settle/issues/143)) - 2026-11-10 · agent · Closed the leaked-credential item on the human's "revoke the leaked PAT": **the token is already dead.** `gh auth status` now fails every authenticated call for `TemiMustapha` ("The token in default is invalid"), where on 2026-11-09 the same entry answered as that account, so it was revoked or expired server-side in between. Removed the dead entry from this machine's keyring and corrected `LAUNCH.md`, which still listed the revocation as outstanding. No secret was quoted, printed or copied anywhere. · S10.6 - 2026-11-10 · agent · Shipped S1.7 as [#149](https://github.com/MFrancis-dev/settle/pull/149) (`f9ce1c8`) and re-deployed the site on the human's "pre-judging freshness" instruction. The live build's challenge probe answers 200, `/api/health` is green, the landing page opens in Chromium with no console message, and the live `/api/tokens` carries the new `refundChain` field — BTC appears twice as **refund-only** (`settleChain: null`) and in neither `payoutAssets` entry. Then, preparing S8.4 on the human's approval, the vendor gate moved: **every unauthenticated `POST /v0/quote` returned `400 Internal server error`** — dry and non-dry, three routes including the same USDC→USDC dry call that answered 200 the day before — while the same body with a bogus bearer returned `401 Invalid token` and `/v0/tokens` still answered 200. That is recorded in `ARCHITECTURE.md` §3.2, `README.md` and `LAUNCH.md` §1, and it makes the partner JWT a requirement for any quote rather than a fee discount, which is now the first thing S8.4 waits on. · S1.7, S8.4, S8.5 diff --git a/app/globals.css b/app/globals.css index 7bac571..8805c8e 100644 --- a/app/globals.css +++ b/app/globals.css @@ -55,6 +55,12 @@ --radius-card: 0.5rem; --radius-control: 0.25rem; + /* The accent is the landing's chrome hue — the one colour that is not a state. It never appears + on data: an amount that arrived stays brand green, a refusal stays danger. */ + --color-accent: #e94ecd; + --color-accent-strong: #ff6fd8; + --color-accent-soft: #2a0f26; + /* Flat by default; a quiet lift is reserved for real overlays (the mobile nav sheet, modals). */ --shadow-card: none; --shadow-raised: 0 2px 4px rgb(22 24 29 / 0.06), 0 16px 40px -16px rgb(22 24 29 / 0.18); @@ -85,6 +91,59 @@ letter-spacing: -0.015em; } + /* + * Two token scopes, so a surface can invert without every component learning a theme. + * + * `.night` is the marketing ground (S4.13): near-black, a magenta accent and roomier cards. It + * re-maps the neutral roles only — `brand` still means the money arrived. `.paper` restores the + * ledger palette for the receipt, which is Settle's real output and stays a white document. + */ + .night { + --color-canvas: #07060b; + --color-surface: #0e0c15; + --color-surface-sunk: #151220; + --color-line: #241f30; + --color-line-strong: #3a3349; + + --color-ink: #f5f2f9; + --color-ink-muted: #aaa3b8; + --color-ink-faint: #827a92; + --color-ink-invert: #0b0910; + + --color-control: #1b1726; + --color-control-strong: #262033; + + --color-brand: #35d399; + --color-brand-strong: #7af0c0; + --color-brand-soft: #10352a; + + --radius-card: 1.25rem; + --radius-control: 0.75rem; + } + + .paper { + --color-canvas: #f4f5f7; + --color-surface: #ffffff; + --color-surface-sunk: #eceef1; + --color-line: #e3e5e9; + --color-line-strong: #c6cad1; + + --color-ink: #16181d; + --color-ink-muted: #4b515c; + --color-ink-faint: #646b78; + --color-ink-invert: #ffffff; + + --color-control: #e6e7ea; + --color-control-strong: #d9dbe0; + + --color-brand: #0b6e4f; + --color-brand-strong: #07543b; + --color-brand-soft: #e3efe9; + + --radius-card: 0.5rem; + --radius-control: 0.25rem; + } + @media (prefers-reduced-motion: reduce) { *, *::before, diff --git a/app/page.tsx b/app/page.tsx index b20ba83..7f6b883 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,20 +1,21 @@ /** * Landing: what Settle is, and the two doors in — sign in, or try the demo. - * Roadmap: S4.1, S4.10, S4.12 · Spec: PRD §7 (plain money language, no crypto jargon), §8 (a judge + * Roadmap: S4.1, S4.10, S4.13 · Spec: PRD §7 (plain money language, no crypto jargon), §8 (a judge * understands the pitch in under a minute) * - * Design (S4.12, Landbook pass): the page opens *inside the settled state* — the one brand green, - * enlarged from a single arrived number to a whole field, with the receipt (Settle's actual output) - * as the picture on it. The device is the Landbook fintech hero (Kuda's full brand field, Remote's - * tight grotesque statement), the colour is Settle's own. The rest of the page is the paper ledger; - * a closing green field bookends it. The settle mark — an arrow descending onto a rule — runs the - * header, the proof strip, the green "Settled" stage and the closing construction. + * Design (S4.13): the reference is a night-ground exchange platform — near-black, one magenta shaft + * of light behind a centred statement, glass cards on a hairline border, pill actions. Settle keeps + * its own subject: the hero's picture is the real pay page (a client choosing a token), the receipt + * is left as a white paper document on the dark, and every number on the page adds up. The magenta + * never touches data — money that arrived stays brand green, which is why the receipt reads as paper + * and not as another dark panel. * * Server-rendered on purpose: the first screen must be readable with the wallet extension still * loading, and a judge's first visit is this HTML. */ import Link from "next/link"; +import type { ReactNode } from "react"; import { SignInButton } from "@/components/wallet/SignInButton"; import { buttonClass } from "@/components/ui/button"; @@ -25,6 +26,15 @@ import { TokenPickerDemo } from "@/components/landing/TokenPickerDemo"; import { ClientViewDemo } from "@/components/landing/ClientViewDemo"; import { Reveal } from "@/components/landing/Reveal"; +const CHAINS = ["NEAR", "Ethereum", "Base", "Arbitrum", "Solana"]; + +const TOKENS = [ + { symbol: "USDC", chain: "Arbitrum", className: "left-1 top-14" }, + { symbol: "USDT", chain: "Ethereum", className: "right-1 top-32" }, + { symbol: "wNEAR", chain: "NEAR", className: "left-3 bottom-24" }, + { symbol: "BTC", chain: "Bitcoin", className: "right-2 bottom-10" }, +]; + export default async function Page({ searchParams, }: { @@ -35,118 +45,195 @@ export default async function Page({ const requested = returnPath((await searchParams).next); return ( -
- {/* The settled state: header + hero + proof, one full brand field. */} -
-
-
- - - Settle - - -
+
+
-
-
-

- Invoicing for freelancers paid in digital assets -

-

- Invoice in dollars. - Paid in any token. -

-

- Your client pays whatever they already hold — USDC, USDT, BTC, ETH, NEAR. Settle - converts it and sends the exact amount to the address you choose. You see one - number: the invoice, settled. -

-
- - - Try the demo - + {/* The stage: Settle's real pay screen, its client-side panel, and the tokens it feeds. */} +
+
+ {TOKENS.map((token) => ( + + ))} + +
+
+
+

Invoice #INV-0001 · $1,500.00

+ + pay.settle + +
+
- {/* The receipt is the hero's picture and must be visible on first paint — no reveal. */} - -
+
- {/* Proof strip: three honest product facts, not invented numbers. */} -
- - - -
- - + {/* The chains are part of the promise, so they are stated, not implied by logos. */} +
+

+ + Settles to + + {CHAINS.join(" · ")} +

+

+ + Refunds also on + + Bitcoin +

+
+ - {/* The paper ledger. */} -
- {/* How it works: the product's own progress, enlarged into one bar. */} -
-

How it works

-
- - +
+

+ Three steps, and only the first one is yours. +

+

+ The client never installs anything, and never has to learn which chain their money is + on. +

+
+
+ + - - + - - +
{/* The one live thing: the client's token choice, working. */} -
-
-
+
+
+
+
-

+

+ Live, right here +

+

They pay what they hold.

-

+

Pick a token and the deposit amount follows — for the same $1,500 invoice. The price is shown before your client confirms, in dollars, on the same screen as the amount they are sending.

+

+ Illustrative fixed rates, chosen so the figures add up — this is the interaction, + not a live quote. +

@@ -155,56 +242,97 @@ export default async function Page({
- {/* The statement: one sentence, set large, given room. */} -
+ {/* The statement: one sentence, given the room the shaft is pointing at. */} +
-

- Getting paid shouldn’t require a crypto education. +

+ Getting paid shouldn’t require a{" "} + + crypto education. +

- {/* What the client sees: the real pay page and the deposit panel. */} -
-

- What the client sees -

-

- One page, no account, no app. This is the whole payment flow — the amount is fixed for a - few minutes, then re-quoted, so nobody pays against an old price. -

- -
- + {/* The receipt: the one paper object in the night, because it is what leaves the browser. */} +
+
+
+

+ The same numbers, twice +

+

+ The receipt both sides keep. +

+

+ What the client paid, what the conversion cost, what you received — the same three + numbers on both screens, and every step is a transaction you can look up. That is + what replaces “trust me”. +

+

+ The receipt is a plain page with a print stylesheet. No app, no account, no login. +

- + +
+ +
+
+
+
+ + {/* What the client never has to think about. */} +
+
+ } + /> + } + /> + } + /> +
{/* Questions a real invoice raises. */} -
-

+
+

Questions a real invoice raises

-
- +
+ - + - + - +
-

- {/* The closing field: the settled state again, bookending the page. */} -
-
-
-
-

+ {/* The close: the shaft again, one sentence, two doors. */} +
+
+
+
+
+ +

The invoice, settled.

-

+

Write an invoice in dollars. Get paid the exact amount, at your own address.

-
- +
+ - Try the demo + See the demo
-
-
-
+
+ -