From be377cfb2be879b5f9ab5cbc9ad948731daaee60 Mon Sep 17 00:00:00 2001 From: haradahinata Date: Mon, 18 May 2026 06:01:38 +0900 Subject: [PATCH 1/2] feat: add first-run welcome guide --- src/App.svelte | 17 +- src/components/header/Nav.svelte | 41 ++++- src/components/layout/Modals.svelte | 7 +- src/components/modals/Modal.svelte | 3 +- src/components/modals/Welcome.svelte | 234 +++++++++++++++++++++++++++ 5 files changed, 290 insertions(+), 12 deletions(-) create mode 100644 src/components/modals/Welcome.svelte diff --git a/src/App.svelte b/src/App.svelte index 87c26f1..11acce6 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -11,14 +11,24 @@ import { USD_CONVERSION_MARKETS } from '@lib/config' import { checkCountry, loadRoute, catchLinks, navigateTo } from '@lib/routing' import { component, address, pageName, countryDisallowed } from '@lib/stores' - import { hidePopoversOnKeydown, hidePopoversOnClick } from '@lib/ui' - import { runAndInterval, hashString, getChainData } from '@lib/utils' + import { hidePopoversOnKeydown, hidePopoversOnClick, showModal } from '@lib/ui' + import { runAndInterval, hashString, getChainData, getUserSetting } from '@lib/utils' import { getUserAssetBalances } from '@api/assets' import { listenToEvents } from '@api/listener' import { getMarketPrices } from '@api/prices' let interval1; + let checkedWelcome = false; + + function maybeShowWelcome() { + if (checkedWelcome || $pageName == 'Home' || getUserSetting('hasSeenWelcome')) return; + + checkedWelcome = true; + setTimeout(() => { + if (!getUserSetting('hasSeenWelcome')) showModal('Welcome'); + }, 500); + } onMount(async () => { @@ -39,6 +49,7 @@ // Listener $: listenToEvents($address); + $: if ($pageName) maybeShowWelcome(); @@ -257,4 +268,4 @@
{/if} - \ No newline at end of file + diff --git a/src/components/header/Nav.svelte b/src/components/header/Nav.svelte index 514ad9d..d17e9f6 100644 --- a/src/components/header/Nav.svelte +++ b/src/components/header/Nav.svelte @@ -5,11 +5,17 @@ import Rewards from './Rewards.svelte' import { pageName, showMobileNav } from '@lib/stores' - import { TROPHY_ICON, BULLET_LIST_ICON } from '@lib/icons' + import { TROPHY_ICON, BULLET_LIST_ICON, BOOK_ICON } from '@lib/icons' + import { showModal } from '@lib/ui' function toggleMobileNav() { showMobileNav.set(!$showMobileNav); } + + function openGuide() { + showMobileNav.set(false); + showModal('Welcome'); + } @@ -27,18 +33,25 @@ gap: 8px; } - a { + a, + button.nav-action { color: var(--text0); text-decoration: none; padding: 8px 12px; border-radius: var(--base-radius); transition: all 100ms ease-in-out; font-weight: 500; + border: none; + background: transparent; + font: inherit; + cursor: pointer; } - a:hover { + a:hover, + button.nav-action:hover { background-color: var(--layer1); } - a.active { + a.active, + button.nav-action.active { color: var(--primary); background-color: var(--primary-highlighted); } @@ -57,6 +70,16 @@ height: 18px; } + button.guide-link { + display: flex; + align-items: center; + gap: 6px; + } + button.guide-link :global(svg) { + fill: currentColor; + height: 16px; + } + .mobile-nav { display: block; position: absolute; @@ -78,7 +101,8 @@ fill: inherit; } - .mobile-nav a { + .mobile-nav a, + .mobile-nav button.nav-action { display: none; padding: 10px 8px; } @@ -104,7 +128,8 @@ bottom: 0; width: 100%; } - .mobile-nav a { + .mobile-nav a, + .mobile-nav button.nav-action { display: block; font-size: 120%; padding: 16px 8px; @@ -121,6 +146,7 @@ Trade Pool Stake + @@ -132,6 +158,7 @@ Trade Pool Stake + Docs {/if} @@ -140,4 +167,4 @@ {@html BULLET_LIST_ICON} - \ No newline at end of file + diff --git a/src/components/layout/Modals.svelte b/src/components/layout/Modals.svelte index 03b829d..a4335a8 100644 --- a/src/components/layout/Modals.svelte +++ b/src/components/layout/Modals.svelte @@ -14,6 +14,7 @@ import UnstakeCAP from '../modals/UnstakeCAP.svelte' import HistoryOrderStatus from '../modals/HistoryOrderStatus.svelte' import Settings from '../modals/Settings.svelte' + import Welcome from '../modals/Welcome.svelte' @@ -21,6 +22,10 @@ {/if} +{#if $activeModal && $activeModal.name == 'Welcome'} + +{/if} + {#if $activeModal && $activeModal.name == 'AssetSelect'} {/if} @@ -67,4 +72,4 @@ {#if $activeModal && $activeModal.name == 'MarketInfo'} -{/if} \ No newline at end of file +{/if} diff --git a/src/components/modals/Modal.svelte b/src/components/modals/Modal.svelte index 9d62346..31f8afa 100644 --- a/src/components/modals/Modal.svelte +++ b/src/components/modals/Modal.svelte @@ -42,6 +42,7 @@ .modal { width: var(--modal-width); + max-width: calc(100vw - (var(--base-padding) * 2)); border-radius: var(--base-radius); background-color: var(--layer25); border: 1px solid var(--layer200); @@ -127,4 +128,4 @@ - \ No newline at end of file + diff --git a/src/components/modals/Welcome.svelte b/src/components/modals/Welcome.svelte new file mode 100644 index 0000000..a8eca19 --- /dev/null +++ b/src/components/modals/Welcome.svelte @@ -0,0 +1,234 @@ + + + + + + + +
+
+ CAP is a decentralized perpetuals trading dashboard on Arbitrum. Use it to trade supported markets, provide liquidity through pools, and stake CAP while keeping custody in your wallet. +
+ +
+
{@html BOOK_ICON}Start with the main flows
+
+
+
1. Fund
+
Connect a wallet and bring trading collateral such as USDC or ETH to Arbitrum before placing orders.
+
+
+
2. Trade
+
Choose a market, collateral, leverage, and size, then review fees, take-profit, stop-loss, and liquidation risk.
+
+
+
3. Earn
+
Pool and Stake pages let liquidity providers and CAP stakers manage deposits, withdrawals, and rewards.
+
+
+
+ +
+
{@html INFO_ICON_CIRCLE}Bridge funds to Arbitrum
+
+
+ If your wallet funds are on another network, bridge them to Arbitrum first. Use the official Arbitrum bridge, then return to CAP and confirm the connected network before trading. +
+
+
{@html CHECKMARK_CIRCLE_ICON}Use the same wallet address on both networks.
+
{@html CHECKMARK_CIRCLE_ICON}Keep ETH on Arbitrum for gas.
+
{@html CHECKMARK_CIRCLE_ICON}Wait for the bridge transaction to finish before placing an order.
+
+
+
+ +
+ + Open bridge + Read docs + + +
+
+
From 095a69743882b3d049b1b205534a76d1240b1cc5 Mon Sep 17 00:00:00 2001 From: haradahinata Date: Mon, 18 May 2026 06:13:28 +0900 Subject: [PATCH 2/2] fix: widen welcome modal on mobile --- src/components/modals/Modal.svelte | 1 + src/components/modals/Welcome.svelte | 7 +------ 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/components/modals/Modal.svelte b/src/components/modals/Modal.svelte index 31f8afa..0ff6413 100644 --- a/src/components/modals/Modal.svelte +++ b/src/components/modals/Modal.svelte @@ -43,6 +43,7 @@ .modal { width: var(--modal-width); max-width: calc(100vw - (var(--base-padding) * 2)); + box-sizing: border-box; border-radius: var(--base-radius); background-color: var(--layer25); border: 1px solid var(--layer200); diff --git a/src/components/modals/Welcome.svelte b/src/components/modals/Welcome.svelte index a8eca19..8b87676 100644 --- a/src/components/modals/Welcome.svelte +++ b/src/components/modals/Welcome.svelte @@ -8,9 +8,6 @@ const bridgeUrl = 'https://bridge.arbitrum.io/'; const docsUrl = 'https://docs.cap.io'; - let innerWidth = 640; - $: modalWidth = innerWidth <= 600 ? 280 : 640; - function dismissWelcome() { saveUserSetting('hasSeenWelcome', true); hideModal(); @@ -183,9 +180,7 @@ } - - - +
CAP is a decentralized perpetuals trading dashboard on Arbitrum. Use it to trade supported markets, provide liquidity through pools, and stake CAP while keeping custody in your wallet.