diff --git a/package-lock.json b/package-lock.json index 1aaf797..e6777d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,16 @@ { - "name": "ai-website-clone-template", - "version": "0.3.1", + "name": "franklin-run", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "ai-website-clone-template", - "version": "0.3.1", + "name": "franklin-run", + "version": "1.0.0", "license": "MIT", "dependencies": { "@base-ui/react": "^1.3.0", + "@tanstack/react-query": "^5.90.12", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "gray-matter": "^4.0.3", @@ -24,7 +25,9 @@ "remark-gfm": "^4.0.1", "shadcn": "^4.1.0", "tailwind-merge": "^3.5.0", - "tw-animate-css": "^1.4.0" + "tw-animate-css": "^1.4.0", + "viem": "^2.43.2", + "wagmi": "^3.1.0" }, "devDependencies": { "@tailwindcss/postcss": "^4", @@ -40,6 +43,12 @@ "node": ">=24" } }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz", + "integrity": "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==", + "license": "MIT" + }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -2002,6 +2011,42 @@ "dev": true, "license": "MIT" }, + "node_modules/@scure/base": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", + "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", + "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.9.0", + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz", + "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@sec-ant/readable-stream": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", @@ -2300,6 +2345,32 @@ "tailwindcss": "4.2.2" } }, + "node_modules/@tanstack/query-core": { + "version": "5.100.13", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.100.13.tgz", + "integrity": "sha512-mlKVKMTzZWGTKAC1CKOgt7axAjJ921emkEvYIp27I/PdP1yEYL/BteLY8iK35gn8hoYeKB4mgJ/ve3lrDI6/Fw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.100.13", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.100.13.tgz", + "integrity": "sha512-HSBr8CycQEAoXsJR7KNDawBnINJEJ96Eme8oE0hCXjyodE2I97vg3IDzDJBDu18LsbzpVVJcKo80eqLfVCykxw==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.100.13" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, "node_modules/@ts-morph/common": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.27.0.tgz", @@ -3071,6 +3142,27 @@ "win32" ] }, + "node_modules/abitype": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.2.3.tgz", + "integrity": "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3.22.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, "node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -5199,6 +5291,12 @@ "node": ">= 0.6" } }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, "node_modules/eventsource": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", @@ -6883,6 +6981,21 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, + "node_modules/isows": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz", + "integrity": "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, "node_modules/iterator.prototype": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", @@ -8630,6 +8743,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/mipd": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mipd/-/mipd-0.0.7.tgz", + "integrity": "sha512-aAPZPNDQ3uMTdKbuO2YmAw2TxLHO0moa4YKAyETM/DTj5FloZo+a+8tU+iv4GmW+sOxKLSRwcSFuczk+Cpt6fg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wagmi-dev" + } + ], + "license": "MIT", + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -9209,6 +9342,51 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/ox": { + "version": "0.14.22", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.14.22.tgz", + "integrity": "sha512-nb5msL8qWbPglhIfZbGJAfw3cqiJjFMiWmACt7kgyWtLib12tcctbHufMT9Hb0Lr6Pt4k9I3dbpueTpbhvbqvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "^1.11.0", + "@noble/ciphers": "^1.3.0", + "@noble/curves": "1.9.1", + "@noble/hashes": "^1.8.0", + "@scure/bip32": "^1.7.0", + "@scure/bip39": "^1.6.0", + "abitype": "^1.2.3", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/ox/node_modules/@noble/curves": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", + "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -11598,6 +11776,167 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/viem": { + "version": "2.50.4", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.50.4.tgz", + "integrity": "sha512-rf98F4s3Vlb+uJZEKfay3IbBw3CNCbVtx5Y3UIljlO2tSX420g/J0WQSYsjzBSasUFgxgsXabji14O9kGbiqgg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@noble/curves": "1.9.1", + "@noble/hashes": "1.8.0", + "@scure/bip32": "1.7.0", + "@scure/bip39": "1.6.0", + "abitype": "1.2.3", + "isows": "1.0.7", + "ox": "0.14.22", + "ws": "8.20.1" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/viem/node_modules/@noble/curves": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", + "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/wagmi": { + "version": "3.6.15", + "resolved": "https://registry.npmjs.org/wagmi/-/wagmi-3.6.15.tgz", + "integrity": "sha512-/CmbLqS7VNiK3Rv9RPTcog1MSF/7oE1/QRVXHxlcv4oSHIMMpgUMunXtZx1MepV9Bmc0vGX9JvrPn6RAYjcvrA==", + "license": "MIT", + "dependencies": { + "@wagmi/connectors": "8.0.14", + "@wagmi/core": "3.4.12", + "use-sync-external-store": "1.4.0" + }, + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "@tanstack/react-query": ">=5.0.0", + "react": ">=18", + "typescript": ">=5.7.3", + "viem": "2.x" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/wagmi/node_modules/@wagmi/connectors": { + "version": "8.0.14", + "resolved": "https://registry.npmjs.org/@wagmi/connectors/-/connectors-8.0.14.tgz", + "integrity": "sha512-B+iMcT2wGBDujL8dX3g1vk0iZ94h0BK+S+6kQckItGWDkoNt7mterVIqFoYePtAqw3dmG4Y/W50cTxwplBXOjQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "@base-org/account": "^2.5.1", + "@coinbase/wallet-sdk": "^4.3.6", + "@metamask/connect-evm": "^1.0.0", + "@safe-global/safe-apps-provider": "~0.18.6", + "@safe-global/safe-apps-sdk": "^9.1.0", + "@wagmi/core": "3.4.12", + "@walletconnect/ethereum-provider": "^2.21.1", + "accounts": "~0.10", + "porto": "~0.2.35", + "typescript": ">=5.7.3", + "viem": "2.x" + }, + "peerDependenciesMeta": { + "@base-org/account": { + "optional": true + }, + "@coinbase/wallet-sdk": { + "optional": true + }, + "@metamask/connect-evm": { + "optional": true + }, + "@safe-global/safe-apps-provider": { + "optional": true + }, + "@safe-global/safe-apps-sdk": { + "optional": true + }, + "@walletconnect/ethereum-provider": { + "optional": true + }, + "accounts": { + "optional": true + }, + "porto": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/wagmi/node_modules/@wagmi/core": { + "version": "3.4.12", + "resolved": "https://registry.npmjs.org/@wagmi/core/-/core-3.4.12.tgz", + "integrity": "sha512-q/NYoq+Up6JNfWNE6G0iXj9cHBSYgOyC8toqJLBWTeUnY1Ha9Q4OyFUHnXfvGTudbC0Gxhh7uUkqW3/LGdsQfg==", + "license": "MIT", + "dependencies": { + "eventemitter3": "5.0.1", + "mipd": "0.0.7", + "zustand": "5.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "@tanstack/query-core": ">=5.0.0", + "accounts": "~0.12", + "typescript": ">=5.7.3", + "viem": "2.x" + }, + "peerDependenciesMeta": { + "@tanstack/query-core": { + "optional": true + }, + "accounts": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/wagmi/node_modules/use-sync-external-store": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz", + "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/web-streams-polyfill": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", @@ -11782,6 +12121,27 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/ws": { + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", + "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/wsl-utils": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.3.1.tgz", @@ -11964,6 +12324,35 @@ "zod": "^3.25.0 || ^4.0.0" } }, + "node_modules/zustand": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.0.tgz", + "integrity": "sha512-LE+VcmbartOPM+auOjCCLQOsQ05zUTp8RkgwRzefUk+2jISdMMFnxvyTjA4YNWr5ZGXYbVsEMZosttuxUBkojQ==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/package.json b/package.json index 8362c8b..6f8c1b8 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,10 @@ "remark-gfm": "^4.0.1", "shadcn": "^4.1.0", "tailwind-merge": "^3.5.0", - "tw-animate-css": "^1.4.0" + "tw-animate-css": "^1.4.0", + "wagmi": "^3.1.0", + "viem": "^2.43.2", + "@tanstack/react-query": "^5.90.12" }, "devDependencies": { "@tailwindcss/postcss": "^4", diff --git a/src/app/api/blockrun/[...path]/route.ts b/src/app/api/blockrun/[...path]/route.ts new file mode 100644 index 0000000..8eb96ad --- /dev/null +++ b/src/app/api/blockrun/[...path]/route.ts @@ -0,0 +1,78 @@ +import { NextRequest } from "next/server"; + +// Transparent proxy: franklin.run/api/blockrun/ -> https://blockrun.ai/api/ +// +// The /try playground is a browser-wallet client for BlockRun's existing +// x402-paid API (chat / images / videos). Calling blockrun.ai cross-origin +// from the browser would hit CORS and would not expose the 402 "payment-required" +// header, so we forward through this same-origin proxy instead. It passes the +// X-Payment header upstream and relays the payment-required header back to the +// client verbatim — settlement still happens on BlockRun's side, paid to +// BlockRun's configured wallet. + +const UPSTREAM = process.env.BLOCKRUN_API_BASE || "https://blockrun.ai/api"; + +// Headers we forward from the client to BlockRun. +const FORWARD_REQ_HEADERS = ["content-type", "accept", "x-payment", "authorization"]; +// Headers we relay from BlockRun back to the client (x402 lives in these). +const FORWARD_RES_HEADERS = [ + "content-type", + "payment-required", + "x-payment-required", + "x-payment-response", + "cache-control", +]; + +async function proxy(req: NextRequest, path: string[]) { + const search = req.nextUrl.search || ""; + const target = `${UPSTREAM}/${path.join("/")}${search}`; + + const headers = new Headers(); + for (const h of FORWARD_REQ_HEADERS) { + const v = req.headers.get(h); + if (v) headers.set(h, v); + } + + const init: RequestInit = { method: req.method, headers }; + if (req.method !== "GET" && req.method !== "HEAD") { + init.body = await req.text(); + } + + let upstream: Response; + try { + upstream = await fetch(target, init); + } catch { + return new Response( + JSON.stringify({ error: "Could not reach the Franklin backend. Try again." }), + { status: 502, headers: { "content-type": "application/json" } }, + ); + } + + const resHeaders = new Headers(); + for (const h of FORWARD_RES_HEADERS) { + const v = upstream.headers.get(h); + if (v) resHeaders.set(h, v); + } + + return new Response(upstream.body, { + status: upstream.status, + statusText: upstream.statusText, + headers: resHeaders, + }); +} + +export async function POST( + req: NextRequest, + { params }: { params: Promise<{ path: string[] }> }, +) { + const { path } = await params; + return proxy(req, path); +} + +export async function GET( + req: NextRequest, + { params }: { params: Promise<{ path: string[] }> }, +) { + const { path } = await params; + return proxy(req, path); +} diff --git a/src/app/globals.css b/src/app/globals.css index 2269a45..61efe81 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -3186,3 +3186,263 @@ h2.dark-h, /* Replace ornate-rule gap with a clean section gap */ .light + .light, .dark-section + .dark-section { border-top: 1px solid var(--border); } + +/* ===================================================== + /try — in-browser Franklin playground + Warm-minimal, matching the rest of the site. + ===================================================== */ +.try-main { + min-height: 100vh; + padding: 68px 0 0; + background: var(--bg); + display: flex; + flex-direction: column; +} + +.try-chat { + flex: 1; + width: 100%; + max-width: 820px; + margin: 0 auto; + display: flex; + flex-direction: column; + min-height: calc(100vh - 68px); +} + +/* Top bar — model picker + wallet */ +.try-chat-bar { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + padding: 18px 24px; + border-bottom: 1px solid var(--border); + position: sticky; + top: 68px; + background: rgba(255, 255, 255, 0.86); + backdrop-filter: saturate(140%) blur(10px); + -webkit-backdrop-filter: saturate(140%) blur(10px); + z-index: 5; +} +.try-model { + display: flex; + align-items: center; + gap: 10px; +} +.try-model-label { + font-family: var(--font-mono); + font-size: 11px; + letter-spacing: 0.16em; + text-transform: uppercase; + color: var(--fg-subtle); +} +.try-model-select { + font-family: var(--font-sans); + font-size: 14px; + color: var(--fg); + background: var(--bg); + border: 1px solid var(--border-strong); + border-radius: var(--radius); + padding: 7px 10px; + cursor: pointer; +} +.try-model-select:focus-visible { outline: 2px solid var(--gold); } +.try-bar-right { display: flex; align-items: center; gap: 12px; } +.try-reset { + display: inline-flex; + align-items: center; + gap: 5px; + font-size: 13px; + color: var(--fg-muted); + background: none; + border: none; + cursor: pointer; +} +.try-reset:hover { color: var(--fg); } + +/* Wallet chip */ +.try-wallet { display: flex; align-items: center; gap: 10px; } +.try-wallet-bal { + font-family: var(--font-mono); + font-size: 13px; + color: var(--gold-dim); +} +.try-wallet-addr { + font-family: var(--font-mono); + font-size: 13px; + color: var(--fg-muted); +} +.try-wallet-disconnect { + display: inline-flex; + padding: 6px; + color: var(--fg-subtle); + background: none; + border: none; + cursor: pointer; + border-radius: var(--radius); +} +.try-wallet-disconnect:hover { color: var(--fg); background: var(--bg-alt); } + +/* Message stream */ +.try-messages { + flex: 1; + overflow-y: auto; + padding: 32px 24px 24px; + display: flex; + flex-direction: column; + gap: 24px; +} + +.try-empty { + margin: auto 0; + max-width: 620px; + padding: 24px 0; +} +.try-empty-eyebrow { + font-family: var(--font-mono); + font-size: 12px; + letter-spacing: 0.22em; + text-transform: uppercase; + color: var(--fg-subtle); + margin-bottom: 16px; +} +.try-empty-title { + font-family: var(--font-serif); + font-size: 40px; + line-height: 1.05; + letter-spacing: -0.015em; + color: var(--fg); + margin-bottom: 16px; +} +.try-empty-title em { font-style: italic; color: var(--gold); } +.try-empty-sub { + font-size: 16px; + line-height: 1.65; + color: var(--fg-muted); + margin-bottom: 28px; +} +.try-suggestions { display: flex; flex-direction: column; gap: 10px; } +.try-suggestion { + text-align: left; + font-size: 14px; + color: var(--fg); + background: var(--bg-alt); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 12px 14px; + cursor: pointer; + transition: border-color 0.15s, background 0.15s; +} +.try-suggestion:hover:not(:disabled) { border-color: var(--gold-line); } +.try-suggestion:disabled { opacity: 0.5; cursor: not-allowed; } + +/* Bubbles */ +.try-msg { display: flex; flex-direction: column; gap: 6px; } +.try-msg-role { + font-family: var(--font-mono); + font-size: 11px; + letter-spacing: 0.14em; + text-transform: uppercase; + color: var(--fg-subtle); +} +.try-msg-assistant .try-msg-role { color: var(--gold-dim); } +.try-msg-body { + font-size: 15.5px; + line-height: 1.7; + color: var(--fg); + white-space: pre-wrap; + word-break: break-word; +} +.try-msg-user .try-msg-body { + background: var(--bg-alt); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 12px 14px; +} + +.try-status { + display: inline-flex; + align-items: center; + gap: 8px; + font-size: 14px; + color: var(--fg-muted); +} +.try-error { + font-size: 14px; + color: #9a3412; + background: #fff4ec; + border: 1px solid #f3d4be; + border-radius: var(--radius); + padding: 12px 14px; +} + +/* Composer */ +.try-input-wrap { + border-top: 1px solid var(--border); + padding: 16px 24px 20px; + background: var(--bg); + position: sticky; + bottom: 0; +} +.try-input-hint { + font-size: 13px; + color: var(--fg-muted); + margin-bottom: 10px; +} +.try-input-row { + display: flex; + align-items: flex-end; + gap: 10px; + border: 1px solid var(--border-strong); + border-radius: 12px; + padding: 8px 8px 8px 14px; + background: var(--bg); + transition: border-color 0.15s; +} +.try-input-row:focus-within { border-color: var(--gold-line); } +.try-input { + flex: 1; + resize: none; + border: none; + outline: none; + background: none; + font-family: var(--font-sans); + font-size: 15.5px; + line-height: 1.5; + color: var(--fg); + max-height: 200px; + padding: 6px 0; +} +.try-send { + display: inline-flex; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; + border-radius: 8px; + background: var(--accent); + color: #fff; + border: none; + cursor: pointer; + flex-shrink: 0; + transition: background 0.15s; +} +.try-send:hover:not(:disabled) { background: var(--accent-hover); } +.try-send:disabled { opacity: 0.4; cursor: not-allowed; } +.try-disclaimer { + margin-top: 10px; + font-size: 12px; + line-height: 1.5; + color: var(--fg-subtle); + text-align: center; +} + +/* Nav highlight for Try Franklin */ +.nav-link-try { color: var(--gold-dim); } + +@media (max-width: 640px) { + .try-empty-title { font-size: 30px; } + .try-chat-bar { padding: 14px 16px; } + .try-messages { padding: 24px 16px; } + .try-input-wrap { padding: 12px 16px 16px; } +} diff --git a/src/app/try/page.tsx b/src/app/try/page.tsx new file mode 100644 index 0000000..484f16b --- /dev/null +++ b/src/app/try/page.tsx @@ -0,0 +1,26 @@ +import type { Metadata } from "next"; +import { Header } from "@/components/Header"; +import { Footer } from "@/components/Footer"; +import { WalletProvider } from "@/components/try/WalletProvider"; +import { FranklinChat } from "@/components/try/FranklinChat"; + +export const metadata: Metadata = { + title: "Try Franklin — the AI agent with a wallet, in your browser", + description: + "A web preview of Franklin. Connect a browser wallet and chat with frontier AI models, paying per message in USDC via x402. No account, no subscription.", + alternates: { canonical: "https://franklin.run/try" }, +}; + +export default function TryPage() { + return ( + <> +
+
+ + + +
+