# Encht storybook

Read this before any UI or copy change. It describes what exists today, taken from the code. Open `docs/storybook/index.html` in a browser to see every screen and state rendered from the real source. PNGs of each story are in `docs/storybook/shots/`.

Rebuild after a UI change:

```
python3 docs/storybook/build.py          # writes stories/*.html, stories.json, index.html
python3 docs/storybook/shots.py          # optional, needs playwright and Chrome
```

## Product rules

* Real money is home. Demo is optional and lives on the same login (one account, a Real and Demo switch).
* The app is the main UI. The Telegram chat is only a launcher plus minimal alerts. Do not add new inline button grids to chat messages.
* Game simple. One main action per screen, short labels, big numbers.
* No emojis anywhere. Icons are Lucide style line icons (24 viewBox, round caps and joins).
* Mascot teal is the accent. Dark first.
* No borders, outlines or strokes on surfaces, buttons or inputs. Separate things with fill, radius and soft shadow (`box-shadow`), as the widget CSS does.
* No dashes in copy (no "—" and no " - "). Use a period or a comma. Negative money uses the minus sign "−".
* Never mention the lead trader, copy bot, paper trading or where signals come from. Say "We copy winning soccer bets for you."
* Notifications are minimal: New trade, Trade result, optional Daily summary, quiet hours and pauses that fold held alerts into one summary line.
* Encht is non custodial. Copy says the account "can only place bets" and "can never move your money". Cash out is an owner signature in the Mini App.

## Surfaces as built

| Surface | Source | Role |
|---|---|---|
| In chat app widget | `wsrc/app.js`, `wsrc/app.css`, `wsrc/common.css`, built into `widgets.py` by `wsrc/build.py` | The home "app" users know: onboarding, home, Bets, Results, Settings. Sent by `send_home` with `sendWidget` (Bot API 10.4 HTML widgets), pinned. State is `bot.app_state` passed as `chat_data`; taps arrive as `widget_query` |
| Bet card widget | `wsrc/bet.js`, `wsrc/bet.css` | One card per bet, edited in place to won, lost or winnings pending |
| Plain fallback | `bot.home_rows`, `bot.home_text`, `texts.py` | Text plus inline buttons. Used only when Telegram refuses `sendWidget`. Do not grow it |
| Mini App, account | `miniapp-src/src/main.jsx` and `style.css`, built into `miniapp/`, copy in `webapp.COPY` | Privy sign in, create Real account, owner signatures (account setup, cash out) |
| Mini App, notifications | `notifications.html` served at `/app/notifications` | Alert toggles, quiet hours, pause |
| Chat menu button | Bot setting (`setChatMenuButton`) | `Open Encht`, opens `/app` |
| Alerts | `texts.py` (`settle_text`, `skipped`, `limit_hit`, `LIVE_UNFILLED`) and the held summary in `bot.flush_notifications` | Short plain messages, respecting notification settings |

Widget limits from Telegram: HTML at most 32768 bytes and at most 4096 bytes after zlib level 9; `chat_data` at most 1024 bytes. Today `app` is 9915 bytes and 4078 compressed, so it has almost no room left. Any widget change must be checked with `zlib.compress(html, 9)`. If Telegram refuses the widget (for example `INPUT_MEDIA_WIDGET_NOT_ALLOWED`), the user gets the plain fallback grid.

## Design tokens

From `wsrc/common.css` (widgets) and `miniapp-src/src/style.css` (Mini App).

| Token | Widget light | Widget dark | Mini App |
|---|---|---|---|
| Accent (teal) `--a` | `#00A99D` | `#14BFAA` | `--a #00BAAD`, `--a2 #00D9C9` |
| Loss `--c` | `#F2836B` | `#F2836B` | none |
| Card fill `--cd` | `rgba(127,127,127,.10)` | `rgba(255,255,255,.055)` | `linear-gradient(180deg,#151a1c,#101315)` |
| Highlight `--hi` | `rgba(127,127,127,.18)` | `rgba(255,255,255,.11)` | none |
| Background | Telegram `--tg-theme-bg-color` plus teal radial glow `rgba(20,191,170,.10)` | `linear-gradient(#0E1114,#090B0D)` plus glow `.14` | `--bg #0A0C0D` plus glow `rgba(0,186,173,.16)` |
| Text | Telegram text color | `#F2F4F6` | `--t #F2F5F5` |
| Muted | Telegram hint color | Telegram hint color | `--m #8A9496` |
| Main button | text color fill, bg color text (`.m`) | `#F2F4F6` fill, `#0B0D0F` text | teal gradient `var(--a2)` to `var(--a)`, text `#03201E`, glow `0 10px 30px rgba(0,186,173,.35)` |
| Logo tile | gradient `#5EE6D6` to `#00BAAD`, icon `#03201E` | same | round mascot photo `brand/encht_logo_v2.jpg`, 96px |

Type: widgets `system-ui` 15px/1.4, letter spacing `-.01em`; Mini App `-apple-system, SF Pro Text, Inter` 15px/1.45. Numbers use `font-variant-numeric: tabular-nums` and `-.03em` (`.n`).

| Use | Size and weight |
|---|---|
| Screen title `.t1` | 28px 700 |
| Balance `.big` | 42px 700 |
| Bet title `.tt` | 22px 700 |
| Header `.hd b` | 17px |
| Button | 16px 600 (Mini App 17px 600, 56px tall) |
| Hint `.h`, label `.lb` | 13px |
| Pill | 12px 600 |

Radii: button 18, card 22, segmented 16 outer and 12 inner, tile button 20, back button 12, logo tile 16, toggle 16, pill 99. Mini App: button 16, card 18.

Spacing: body padding 18 16 14; row gap 8; button padding 15 vertical; card padding 14 16; list row 12 vertical.

Icons (widget path set `P` in `app.js`): `go` arrow right, `bk` chevron left, `ls` list, `ch` bar chart, `st` sliders, `ok` check, `fl` flame, `tr` trending up, `us` user. Bet card: `ok`, `x`, `ck` clock. Stroke 1.6, 18px (20px in icon rows). Notifications page uses the Lucide bell.

Motion: press scale `.97`; balance count up 700ms ease out (Demo); toggle knob spring `cubic-bezier(.3,1.4,.5,1)`; win glow 2.4s plus one time confetti; Mini App fade in `in .5s` and check `pop`.

## Screens and states (exact copy)

### In chat app widget

| Story | State (`chat_data.v` or view) | Copy and controls |
|---|---|---|
| app-welcome | `welcome` | Logo tile with trend icon. "Encht". "We copy winning soccer bets for you." Main button "Get started" with arrow. Step dots 1 of 3 |
| app-size | `size` | "Bet size". "How much each time?" Three tiles: Small $5, Medium $10, Large $25. Dots 2 |
| app-go | `go` | Check tile. "How to start". `gs` + `sb` + "." (open: "Real is home. Add money to start. Try Demo with $250."). Main "Real money", then "Try a demo". Dots 3 |
| app-home-real-* | `home`, `md=real` | Real/Demo segmented. "Balance", big balance, note `hn` (one of "Create your account, then add money." / "Real account is ready. Add money to start." / "Real account is ready."). Card "Auto copy" with "Starts soon" and toggle. Icon row: "Bets N", "Results", "Settings". Buttons "Add money", "Withdraw". If no account: main "Create my account" with user icon |
| app-home-demo-* | `home`, `md=demo` | "Demo balance", flame "N wins in a row" when streak > 1, animated balance, "Today +$x" in teal or loss color. "Auto copy" with "Copying every bet" or "Paused". Icon row. If no account: teal text button "Create my account" |
| app-home-msg | any | Status line under the screen, e.g. "Reply in the chat below.", "Open the bot chat to use this.", "Please try again." |
| app-bets, app-bets-empty | view `bets` | Back header "Bets". List rows: label and "$10 at 54¢", "winnings pending", or a teal check for won. Empty: "No bets yet." |
| app-results, app-results-empty | view `res` | Back header "Results". Segmented "7 days", "30 days", "All". Big total in teal or loss color, "Our bets: W won, L lost" or "No results yet". Rows: short name and whole dollar result |
| app-settings-* | view `set` | Back header "Settings". Labels "Mode" (Real, Demo), "Bet size" ($5, $10, $25 and "2% of balance", "5% of balance"), "Daily stop" ("Auto $X", $50, $100, "Custom" or the custom amount), "Preferences" button "Notifications", "Account": "Ready" row or main "Create my account" |

### Bet card widget

| Story | Copy |
|---|---|
| bet-open-demo / bet-open-real | "New bet" with pill "Demo" or "Real". Title (label). Card: "Bet $10", "Price 54¢", "Could win $18.52" (teal). Status "Waiting for the match" with clock |
| bet-claimable | Status "Winnings pending" |
| bet-won | Status "Won +$8.52" teal, glow, confetti once per device |
| bet-lost | Status "Lost −$10.00" in loss color |

Caption under the card: "Demo bet: Arsenal to win, $10 at 54¢" (`texts.bet_caption`).

### Mini App, account (`webapp.COPY`)

| Story | Copy |
|---|---|
| mini-check | "One moment", "Getting things ready", spinner |
| mini-ready | Pill "Real account". "Your account". "One tap to make your account. It can only place bets. It can never move your money." Rows: "Places bets for you: Yes", "Can move your money: Never", "Lasts: 180 days". Button "Create my account". Note "Add money to start. Try Demo any time." |
| mini-signin | "Sign in", "Use your Telegram account to keep going.", "Continue with Telegram" |
| mini-busy | "Making your account", spinner |
| mini-sign-account | "Real account", "Sign with your account to finish. This cannot move money unless you are cashing out.", "Sign" |
| mini-sign-cash | "Cash out", "Sign to send pUSD to the Polymarket bridge. USDC then arrives at your Polygon address.", "Sign" |
| mini-done | Check badge, pill, "Real account ready", "Your Real account is ready. Add money to start.", rows with Wallet short address, "Back to chat" |
| mini-blocked | "Not open here yet", "Real money is not open where you are. You can still use Demo.", quiet button "Back to chat" |
| mini-err | "Something went wrong", "Please try again.", "Try again" |
| mini-notg | "Open in Telegram", "Open this from the Encht bot in Telegram." |

A "Settings" text link (top right, teal) to `/app/notifications` is injected on every account page by `webapp.SETTINGS_LINK`.

### Mini App, notifications (`notifications.html`)

Bell icon, "Notifications", "Back", "Choose which alerts you receive." Toggle "Alerts". Section "What to get": "New trade", "Trade result", "Daily summary". Section "Quiet hours": hint "Alerts are held during quiet hours and pauses, then sent as one short summary.", "Start", "End", "Time zone", device suggestion, buttons "Save hours and time zone", "Clear hours". Section "Pause alerts": "1 hour", "8 hours", "Until tomorrow", "Resume alerts". Status "Saving…", "Saved", errors. Outside Telegram: "Open this screen from the bot in Telegram."

Known drift from the rules (fix when touched): this page uses `border` on row dividers and inputs, uppercase section labels, and its own colors instead of the tokens above. The Mini App spinner `.sp` also uses a border.

### Chat surfaces

| Story | What it must look like |
|---|---|
| chat-menu | Menu button "Open Encht" opening `/app` |
| chat-start-target | Target for `/start`: one short card, "Encht" and "Winning soccer bets, copied for you." with one web_app button "Open Encht". Not built yet |
| chat-home-fallback-* | Today's plain fallback (text plus Turn on or Pause, Bets N, Results, Settings, Add money, Withdraw, Try Demo or Switch to Real). Only shown when the widget is refused. Do not add rows to it |
| chat-acct-invite | "Create your account in one tap." with web_app button "Create my account" |
| chat-cashout | `texts.out_text` with web_app button "Sign cash out" |
| alert-* | `settle_text` ("Demo: Arsenal won  +$8.52" then streak or "Today ..."), "Skipped Porto / The price jumped too high.", "Daily stop reached / We start again tomorrow.", "The Real order was not filled. The money is back.", held summary "While alerts were held: 2 new trades, 1 trade result. Daily summary: 3 results, +$14.20." No buttons on alerts |

Bot commands (`setMyCommands`): start Home, bets Bets, results Results, pause Pause, account Account, help Help.

## Checklist for a UI pull request

1. Reuse the classes and components above. Do not add a new color, radius, font or icon style without adding it here.
2. No new chat button grids. New screens go in the app, reached from the app.
3. Copy: no emojis, no dashes, no lead trader, plain short words. Put user facing text in `texts.py` or `webapp.COPY`.
4. Widgets: rebuild with `cd wsrc && python3 build.py`, and keep the compressed size under 4096 bytes.
5. Update this file and the stories in `build.py`, run `build.py` (and `shots.py` if you can), and commit the result in the same PR.
