From 9ffc95365baaaad3e600066684fc04c9d244616a Mon Sep 17 00:00:00 2001 From: plind-dm Date: Wed, 13 May 2026 01:29:10 +0900 Subject: [PATCH 1/2] refactor(onboard): clean up page, use theme tokens, share OnboardingCard --- src/components/onboard/AboutContent.tsx | 392 ++---------- src/components/onboard/GettingStarted.tsx | 714 +++++++++++++++++----- src/components/onboard/Scoring.tsx | 437 +++++++++---- src/components/onboarding-card.tsx | 143 +++++ src/pages/OnboardPage.tsx | 145 ++--- 5 files changed, 1101 insertions(+), 730 deletions(-) create mode 100644 src/components/onboarding-card.tsx diff --git a/src/components/onboard/AboutContent.tsx b/src/components/onboard/AboutContent.tsx index 82819436..0aed990e 100644 --- a/src/components/onboard/AboutContent.tsx +++ b/src/components/onboard/AboutContent.tsx @@ -1,351 +1,69 @@ import React from 'react'; -import { - Box, - Typography, - Grid, - Button, - Stack, - alpha, - useTheme, - useMediaQuery, -} from '@mui/material'; -import OpenInNewIcon from '@mui/icons-material/OpenInNew'; -import CodeIcon from '@mui/icons-material/Code'; -import MonetizationOnIcon from '@mui/icons-material/MonetizationOn'; -import VerifiedUserIcon from '@mui/icons-material/VerifiedUser'; -import { useMonthlyRewards } from '../../hooks/useMonthlyRewards'; +import { Box, Typography } from '@mui/material'; +import { useLinkBehavior } from '../common/linkBehavior'; +import { OnboardingCard } from '../onboarding-card'; export const AboutContent: React.FC = () => { - const theme = useTheme(); - const isMobile = useMediaQuery(theme.breakpoints.down('sm')); - const monthlyRewards = useMonthlyRewards(); + const minerGuide = useLinkBehavior( + '/onboard?tab=getting-started', + ); + const registerRepo = useLinkBehavior( + '/repository-registration', + ); + const minerDocs = useLinkBehavior( + 'https://docs.gittensor.io/miner.html', + ); + const maintainerDocs = useLinkBehavior( + 'https://docs.gittensor.io/register-repository.html', + ); return ( - + + + Get started with Gittensor + + + Pick your path. Miners earn alpha tokens by contributing code. + Maintainers get free, incentivized contributions to their open source + projects. + + - {/* 1. Context: What is Gittensor? */} - - - The Marketplace for Open Source - - - - - Open source software powers the world, yet its builders are - rarely compensated for the immense value they create. Gittensor - changes this by turning code contributions into direct, on-chain - rewards. - - - Submit Pull Requests to whitelisted repositories and earn when - they merge. Discover open issues that others later solve and - earn from a separate pool. Two ways to earn, one network:{' '} - - Code, Merge, Earn. - - - - - - - How It Works - - - {[ - { - role: 'Miners (You)', - desc: 'Merge PRs to OSS repos and discover open issues for others to solve.', - }, - { - role: 'Validators', - desc: 'Score contributions, verify merges, and set on-chain weights.', - }, - { - role: 'The Network', - desc: 'Distributes emissions across two pools: OSS contributions and issue discovery.', - }, - ].map((item, i) => ( - - - - - {item.role} - - - {item.desc} - - - - ))} - - - - - - - {/* 2. Recruitment: Why Mine? */} - - - Why Become a Miner? - - - {[ - { - icon: , - title: 'Direct Incentives', - desc: monthlyRewards - ? `Stop coding for free. Compete for a share of the $${monthlyRewards.toLocaleString( - undefined, - { - minimumFractionDigits: 2, - maximumFractionDigits: 2, - }, - )} monthly reward pool through OSS contributions or Issue Discovery.` - : 'Stop coding for free. Earn alpha tokens through two tracks: merge PRs to OSS repositories or discover issues for others to solve.', - }, - { - icon: , - title: 'On-Chain Resume', - desc: 'Build a verifiable reputation. Your contributions are permanently recorded on-chain, creating proof of your engineering skills.', - }, - { - icon: , - title: 'Freedom to Build', - desc: 'Work on your terms. No managers, no set hours. Contribute code and get paid for the value you create.', - }, - ].map((card, i) => ( - - - - {card.icon} - - - {card.title} - - - {card.desc} - - - - ))} - - - - {/* 3. CTA: Check the Docs / Get Started */} - theme.palette.status.info} + content={{ + kicker: 'Ready to contribute?', + headline: 'Become a miner', + body: 'Read the quickstart guide to get set up. No complex infrastructure or always-on servers required.', + primaryLabel: 'Miner guide', + primaryLink: minerGuide, + secondaryLabel: 'Read docs', + secondaryLink: minerDocs, }} - > - - Ready to Start earning? - - - We have prepared a comprehensive guide to help you set up your - miner, register on the network, and make your first submission. - - - - - {/* Community Section (Footer) */} - + theme.palette.status.merged} + content={{ + kicker: 'Maintain a repo?', + headline: 'Become a maintainer', + body: 'Install the GitHub App and submit a quick form. The team reviews each repo before listing.', + primaryLabel: 'Register a repo', + primaryLink: registerRepo, + secondaryLabel: 'Read docs', + secondaryLink: maintainerDocs, }} - > - - Community - - - Stay up to date with announcements and news in the{' '} - - Bittensor community - - . - - - Review our codebase and get started mining by checking out the - readme on our{' '} - - Github - - . - - + /> ); diff --git a/src/components/onboard/GettingStarted.tsx b/src/components/onboard/GettingStarted.tsx index dae4efb0..6317e013 100644 --- a/src/components/onboard/GettingStarted.tsx +++ b/src/components/onboard/GettingStarted.tsx @@ -11,10 +11,15 @@ import { import OpenInNewIcon from '@mui/icons-material/OpenInNew'; import ContentCopyIcon from '@mui/icons-material/ContentCopy'; import CheckIcon from '@mui/icons-material/Check'; -import { alpha, darken } from '@mui/material/styles'; -import { scrollbarSx, tooltipSlotProps } from '../../theme'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; +import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; +import { alpha, darken, useTheme } from '@mui/material/styles'; +import { STATUS_COLORS, scrollbarSx, tooltipSlotProps } from '../../theme'; import { useClipboardCopy } from '../../hooks/useClipboardCopy'; +const GREEN = STATUS_COLORS.merged; +const BLUE = STATUS_COLORS.info; + const MONO = '"JetBrains Mono", monospace'; const MAINNET_NETUID = 74; @@ -369,32 +374,181 @@ uv pip install -e .`} - You're all set! Start contributing to whitelisted repositories — no - miner process needs to run. Validators score your merged PRs - automatically every 2 hours. + You're all set! No miner process needs to run — validators score + your contributions automatically every 2 hours. There are{' '} + two ways to earn: + + + {/* OSS Path */} + + `1px solid ${alpha(t.palette.primary.main, 0.25)}`, + background: (t) => + `linear-gradient(180deg, ${alpha(t.palette.primary.main, 0.06)} 0%, transparent 100%)`, + }} + > + + + 30% + + + emissions + + + + OSS Contributions + + + Submit pull requests to whitelisted repos. Higher repo weight + and structural code changes earn more. + + + ⟶ 5 valid merged PRs · 80% credibility + + + + {/* Issue Discovery Path */} + + + + 10% + + + emissions + + + + Issue Discovery + + + Open detailed, actionable issues on mirror-enabled repos. You + earn when another miner solves your issue with a merged PR. + + + ⟶ 7+ solved issues · 80% issue credibility + + + +
  • - Browse recognized repositories in the{' '} + Browse whitelisted repositories in the{' '} Repositories tab
  • - OSS eligibility: 5 valid merged PRs (token score ≥ 5) and 80% - credibility (merged / total, with one closed-PR mulligan) -
  • -
  • - Issue Discovery eligibility: 7+ solved issues and 80% issue - credibility + Find open bounties in the Bounties tab
  • See the Scoring tab for how rewards are @@ -438,216 +592,440 @@ const NetworkTabs: React.FC<{ ); export const GettingStarted: React.FC = () => { + const theme = useTheme(); const [activeStep, setActiveStep] = useState(0); + const current = steps[activeStep]; + const isFinal = activeStep === steps.length - 1; + const accent = current.active ? GREEN : BLUE; + const nextStepIndex = Math.min(steps.length - 1, activeStep + 1); + const nextAccent = nextStepIndex === steps.length - 1 ? GREEN : BLUE; + const progressPct = + steps.length > 1 ? (activeStep / (steps.length - 1)) * 100 : 0; return ( - - - Miner Setup - + + {/* HERO — editorial split */} + + + + + 7 steps · about 10 minutes + + + + Set up your + + + miner + + - {/* Step indicators */} - - {/* Connecting Line (Desktop) */} + {/* PROGRESS STRIP */} + + {/* Track */} - `linear-gradient(90deg, ${theme.palette.border.subtle} 0%, ${theme.palette.border.medium} 50%, ${theme.palette.border.subtle} 100%)`, - display: { xs: 'none', md: 'block' }, + top: 22, + left: { xs: 22, md: '7%' }, + right: { xs: 'auto', md: '7%' }, + width: { xs: 2, md: 'auto' }, + height: { xs: 'calc(100% - 22px)', md: 2 }, + bgcolor: alpha('#ffffff', 0.08), zIndex: 0, }} /> + {/* Filled progress */} + - {steps.map((item, index) => ( - setActiveStep(index)} - sx={{ - display: 'flex', - flexDirection: { xs: 'row', md: 'column' }, - alignItems: 'center', - gap: { xs: 1.5, md: 1 }, - width: { xs: '100%', md: '100%' }, - minWidth: 0, - cursor: 'pointer', - '&:hover .step-circle': { - borderColor: 'border.medium', - }, - }} - > - { + const isActive = activeStep === index; + const isComplete = index < activeStep; + const isLastStep = index === steps.length - 1; + const stepAccent = isLastStep ? GREEN : BLUE; + + return ( + setActiveStep(index)} + disableRipple sx={{ - width: 48, - height: 48, - borderRadius: '50%', - bgcolor: (theme) => - activeStep === index - ? index === steps.length - 1 - ? darken(theme.palette.secondary.main, 0.85) - : darken(theme.palette.primary.main, 0.85) - : theme.palette.background.default, - border: '2px solid', - borderColor: item.active - ? 'secondary.main' - : activeStep === index - ? 'primary.main' - : 'border.subtle', - color: item.active - ? 'secondary.main' - : activeStep === index - ? 'primary.main' - : 'text.tertiary', display: 'flex', + flexDirection: { xs: 'row', md: 'column' }, alignItems: 'center', - justifyContent: 'center', - fontFamily: MONO, - fontWeight: 'bold', - fontSize: '1.1rem', - boxShadow: item.active - ? (theme) => - `0 0 20px ${alpha(theme.palette.secondary.main, 0.15)}` - : activeStep === index - ? (theme) => - `0 0 15px ${alpha(theme.palette.primary.main, 0.2)}` - : 'none', - transition: 'all 0.2s ease', - flexShrink: 0, + gap: { xs: 1.5, md: 1 }, + width: '100%', + cursor: 'pointer', + '&:hover .step-tile': { + borderColor: alpha(stepAccent, 0.5), + }, + '&:focus-visible .step-tile': { + outline: `2px solid ${stepAccent}`, + outlineOffset: 2, + }, }} > - {item.step} - - - - {item.title} - - + ) : ( + String(item.step).padStart(2, '0') + )} + + - {item.subtitle} - - - - ))} + + {item.title} + + + {item.subtitle} + + + + ); + })} + {/* STEP CONTENT CARD */} - - Step {steps[activeStep].step}: {steps[activeStep].title} - - + /> + + + {/* Step header */} + + + {String(current.step).padStart(2, '0')} + + + + {current.subtitle} + + + {current.title} + + + + + {/* Step detail content */} + + + + + {/* Prev / Next navigation */} + + + + + {String(activeStep + 1).padStart(2, '0')} /{' '} + {String(steps.length).padStart(2, '0')} + + + + + + {/* DOCUMENTATION CTA */} - `linear-gradient(180deg, ${alpha(theme.palette.background.default, 0)} 0%, ${theme.palette.surface.subtle} 100%)`, - border: '1px solid', - borderColor: 'border.subtle', + p: { xs: 3, md: 4 }, + borderRadius: 2, + border: `1px solid ${alpha('#ffffff', 0.08)}`, + background: `linear-gradient(135deg, ${alpha(theme.palette.primary.main, 0.08)} 0%, ${alpha(GREEN, 0.04)} 100%)`, + display: 'flex', + flexDirection: { xs: 'column', md: 'row' }, + alignItems: { xs: 'flex-start', md: 'center' }, + justifyContent: 'space-between', + gap: 2, }} > - - Full Documentation - - - For advanced configuration and troubleshooting, see the complete miner - guide. - + + + Need more detail? + + + Advanced configuration and troubleshooting. + + diff --git a/src/components/onboard/Scoring.tsx b/src/components/onboard/Scoring.tsx index e7111cd7..67c68736 100644 --- a/src/components/onboard/Scoring.tsx +++ b/src/components/onboard/Scoring.tsx @@ -1,206 +1,407 @@ import React, { useState } from 'react'; -import { - Box, - Typography, - Button, - Grid, - Tabs, - Tab, - alpha, - useTheme, -} from '@mui/material'; +import { Box, Typography, ButtonBase, Button, alpha } from '@mui/material'; import OpenInNewIcon from '@mui/icons-material/OpenInNew'; +import { CREDIBILITY_COLORS } from '../../theme'; + +const MONO = '"JetBrains Mono", monospace'; +const AMBER = CREDIBILITY_COLORS.moderate; type ScoringCategory = 'oss' | 'discovery'; -interface ScoringCard { +const SCORING_RULES: { + category: ScoringCategory; + num: string; title: string; desc: string; - category: ScoringCategory; -} - -const SCORING_CARDS: ScoringCard[] = [ + highlight?: string; +}[] = [ { category: 'oss', - title: 'Merge PRs', - desc: 'Target high-weight repositories like Bitcoin, Ethereum, or PyTorch. Contribution scores decay over time — merge frequently and promptly. Requires at least 5 valid merged PRs to qualify.', + num: '01', + title: 'Repository Weight', + desc: 'Target high-weight repos like Bitcoin, Ethereum, or PyTorch. Repo weight directly multiplies your contribution score.', + highlight: 'higher weight = higher score', }, { category: 'oss', + num: '02', title: 'Code Quality', - desc: 'Write meaningful code. AST-based token scoring rewards structural changes (functions, classes, logic) over simple text, configs, or whitespace edits.', + desc: 'AST-based token scoring rewards structural changes — functions, classes, logic — over text edits, configs, or whitespace.', + highlight: 'structure beats volume', }, { category: 'oss', + num: '03', + title: 'Time Decay', + desc: 'PR scores decay over a 35-day window. Merge frequently and promptly to maintain rolling earnings.', + highlight: '35 day window', + }, + { + category: 'oss', + num: '04', title: 'Credibility', - desc: 'Keep your merge rate high. You need at least 80% credibility (merged / total, with one closed-PR mulligan) and 5 valid merged PRs to become eligible.', + desc: 'Maintain ≥80% merged-to-total ratio (one closed-PR mulligan). Need 5 valid merged PRs to be eligible.', + highlight: '80% merge rate', }, { category: 'discovery', + num: '01', + title: 'Open Issues', + desc: 'Open detailed, actionable issues on whitelisted repos. Quality issues attract solvers and earn discovery rewards.', + highlight: 'detail wins', + }, + { + category: 'discovery', + num: '02', title: 'Issue Multiplier', - desc: "Link your PR to the issue it resolves (e.g. 'Closes #123') for a 1.33× score boost — or 1.66× if the issue was opened by a project maintainer.", + desc: 'Solvers get a 1.33× boost when their PR closes a linked issue — 1.66× if the issue was opened by a project maintainer.', + highlight: '1.33× / 1.66×', }, { category: 'discovery', - title: 'Issue Discovery', - desc: 'Earn from a dedicated 30% emission pool by finding open issues that others later solve with a merged PR. Requires 7+ solved issues and 80% issue credibility to qualify.', + num: '03', + title: 'Eligibility', + desc: '7+ solved issues and 80% issue credibility unlock the discovery emission pool.', + highlight: '7 solved · 80% cred', }, ]; const CATEGORIES: { value: ScoringCategory; label: string; + allocation: string; + color: string; docsUrl: string; }[] = [ { value: 'oss', label: 'OSS Contributions', + allocation: '30%', + color: '#1d37fc', docsUrl: 'https://docs.gittensor.io/oss-contributions.html', }, { value: 'discovery', label: 'Issue Discovery', + allocation: '10%', + color: AMBER, docsUrl: 'https://docs.gittensor.io/issue-discovery.html', }, ]; export const Scoring: React.FC = () => { - const theme = useTheme(); const [category, setCategory] = useState('oss'); - const activeCategory = - CATEGORIES.find((c) => c.value === category) ?? CATEGORIES[0]; - const visibleCards = SCORING_CARDS.filter((c) => c.category === category); + const activeCategory = CATEGORIES.find((c) => c.value === category)!; + const visibleRules = SCORING_RULES.filter((r) => r.category === category); return ( - - + + + ⟶ How rewards are calculated + + + Maximize your + + + rewards. + + + + {/* Allocation toggle */} + - Maximize Your Rewards - + {CATEGORIES.map((cat) => { + const isActive = category === cat.value; + return ( + setCategory(cat.value)} + disableRipple + sx={{ + p: { xs: 2.5, md: 3 }, + display: 'flex', + flexDirection: 'column', + alignItems: 'flex-start', + background: isActive + ? `linear-gradient(180deg, ${alpha(cat.color, 0.15)} 0%, ${alpha(cat.color, 0.05)} 100%)` + : 'transparent', + borderRight: + cat.value === 'oss' + ? `1px solid ${alpha('#ffffff', 0.1)}` + : 'none', + position: 'relative', + transition: 'all 0.25s', + '&:hover': { + background: isActive ? undefined : alpha('#ffffff', 0.02), + }, + '&::after': isActive + ? { + content: '""', + position: 'absolute', + bottom: 0, + left: 0, + right: 0, + height: 2, + background: cat.color, + boxShadow: `0 0 12px ${cat.color}`, + } + : {}, + }} + > + + + {cat.allocation} + + + emissions + + + + {cat.label} + + + ); + })} + + {/* Rules */} - setCategory(v)} - sx={{ - width: { xs: '100%', sm: 'auto' }, - '& .MuiTab-root': { - textTransform: 'none', - minWidth: { xs: 0, sm: 'auto' }, - flex: { xs: 1, sm: 'initial' }, - px: { xs: 1, sm: 2 }, - fontSize: { xs: '0.82rem', sm: '0.9rem' }, - color: alpha(theme.palette.common.white, 0.55), - '&.Mui-selected': { color: 'text.primary' }, - }, - '& .MuiTabs-indicator': { - backgroundColor: theme.palette.common.white, - }, - }} - > - {CATEGORIES.map((c) => ( - - ))} - - - - - {visibleCards.map((item) => ( - + {visibleRules.map((rule) => ( + - {item.title} + {rule.num} - {item.desc} + {rule.title} - + + {rule.desc} + + {rule.highlight && ( + + + + {rule.highlight} + + + )} + ))} - + + {/* Deep dive CTA */} - - Dive Deeper - - - Learn about the exact formulas, multipliers, and weight calculations - in our detailed documentation. - + + + Deep dive · {activeCategory.label} + + + Exact formulas, multipliers, and weight calculations. + + diff --git a/src/components/onboarding-card.tsx b/src/components/onboarding-card.tsx new file mode 100644 index 00000000..7dc4e5d7 --- /dev/null +++ b/src/components/onboarding-card.tsx @@ -0,0 +1,143 @@ +import React from 'react'; +import { Box, Button, Stack, Typography } from '@mui/material'; +import { alpha, SxProps, Theme } from '@mui/material/styles'; +import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; +import { useLinkBehavior } from './common/linkBehavior'; + +export type OnboardingCardContent = { + kicker: string; + headline: React.ReactNode; + body: React.ReactNode; + primaryLabel: string; + primaryLink: ReturnType>; + secondaryLabel: string; + secondaryLink: ReturnType>; +}; + +interface OnboardingCardProps { + content: OnboardingCardContent; + /** Theme palette key for the accent stripe, gradient, and primary CTA. Defaults to `status.merged`. */ + accent?: (theme: Theme) => string; + /** Optional sx merged into the outer card (e.g. entrance animation). */ + sx?: SxProps; +} + +const defaultAccent = (theme: Theme) => theme.palette.status.merged; + +export const OnboardingCard: React.FC = ({ + content, + accent = defaultAccent, + sx, +}) => ( + ({ + width: '100%', + display: 'flex', + flexDirection: 'column', + justifyContent: 'space-between', + gap: 3, + p: { xs: 2, md: 2.5 }, + borderRadius: 2, + backgroundColor: theme.palette.surface.subtle, + color: theme.palette.text.primary, + border: `1px solid ${theme.palette.border.medium}`, + borderTop: `3px solid ${accent(theme)}`, + position: 'relative', + overflow: 'hidden', + minHeight: 260, + }), + ...(Array.isArray(sx) ? sx : sx ? [sx] : []), + ]} + > + ({ + position: 'absolute', + top: 0, + left: 0, + right: 0, + height: 100, + background: `linear-gradient(to bottom, ${alpha(accent(theme), 0.1)} 0%, transparent 100%)`, + pointerEvents: 'none', + })} + /> + + + {content.kicker} + + + {content.headline} + + ({ + color: alpha(theme.palette.text.primary, 0.62), + fontSize: '0.82rem', + lineHeight: 1.6, + })} + > + {content.body} + + + + + + + +); diff --git a/src/pages/OnboardPage.tsx b/src/pages/OnboardPage.tsx index e7eed002..db7ec422 100644 --- a/src/pages/OnboardPage.tsx +++ b/src/pages/OnboardPage.tsx @@ -1,45 +1,32 @@ import React from 'react'; -import { Box, Tabs, Tab, Card, CardContent } from '@mui/material'; +import { Box, Tab, Tabs } from '@mui/material'; import { Page } from '../components/layout'; import { SEO } from '../components'; import { useSearchParams } from 'react-router-dom'; import { AboutContent } from '../components/onboard/AboutContent'; import { FAQContent } from '../components/onboard/FAQContent'; - import { GettingStarted } from '../components/onboard/GettingStarted'; -import { Scoring } from '../components/onboard/Scoring'; import { LanguageWeightsTable } from '../components/repositories'; -const OnboardPage: React.FC = () => { - const [searchParams, setSearchParams] = useSearchParams(); +const TABS = [ + { key: 'about', label: 'Overview' }, + { key: 'getting-started', label: 'Setup' }, + { key: 'languages', label: 'Languages' }, + { key: 'faq', label: 'FAQ' }, +] as const; - // Determine active tab from URL query param or default to 0 - const tabParam = searchParams.get('tab'); - const tabNameToIndex: Record = { - about: 0, - 'getting-started': 1, - scoring: 2, - languages: 3, - faq: 4, - }; +type TabKey = (typeof TABS)[number]['key']; - const indexToTabName: Record = { - 0: 'about', - 1: 'getting-started', - 2: 'scoring', - 3: 'languages', - 4: 'faq', - }; - - const activeTab = - tabParam && tabNameToIndex[tabParam] !== undefined - ? tabNameToIndex[tabParam] - : 0; +const OnboardPage: React.FC = () => { + const [searchParams, setSearchParams] = useSearchParams(); + const tabParam = searchParams.get('tab') as TabKey | null; + const activeTab: TabKey = + tabParam && TABS.some((t) => t.key === tabParam) ? tabParam : 'about'; - const handleTabChange = (_event: React.SyntheticEvent, newValue: number) => { - const newParams = new URLSearchParams(searchParams); - newParams.set('tab', indexToTabName[newValue]); - setSearchParams(newParams, { replace: true }); + const handleTabChange = (_: React.SyntheticEvent, key: TabKey) => { + const next = new URLSearchParams(searchParams); + next.set('tab', key); + setSearchParams(next, { replace: true }); }; return ( @@ -48,97 +35,41 @@ const OnboardPage: React.FC = () => { title="Getting Started - Gittensor" description="Start mining on Gittensor. Setup guide, documentation, and resources." /> - - + ({ position: 'sticky', top: 0, - zIndex: 2, - maxWidth: 1200, - width: '100%', - px: { xs: 2, sm: 3, md: 0 }, - mx: 'auto', - mb: 4, + zIndex: 10, backgroundColor: theme.palette.background.default, - borderBottom: '1px solid', - borderColor: theme.palette.border.light, + '& .MuiTab-root.Mui-selected': { + color: theme.palette.common.white, + }, })} > - ({ - px: 0, - '& .MuiTabs-scrollButtons.Mui-disabled': { - display: 'none', - }, - '& .MuiTabs-flexContainer': { - gap: { xs: 3, sm: 4 }, - }, - '& .MuiTab-root': { - textTransform: 'none', - fontWeight: 500, - minWidth: 'auto', - minHeight: 48, - paddingLeft: 0, - paddingRight: 0, - fontSize: { xs: '0.95rem', sm: '1rem' }, - color: theme.palette.text.secondary, - '&.Mui-selected': { - color: theme.palette.primary.main, - }, - }, - })} - > - - - - - - - + {TABS.map((tab) => ( + + ))} + - {activeTab === 0 && } - {activeTab === 1 && } - {activeTab === 2 && } - {activeTab === 3 && ( - ({ - borderRadius: 3, - border: '1px solid', - borderColor: theme.palette.border.light, - backgroundColor: theme.palette.surface.transparent, - width: '100%', - })} - elevation={0} - > - - - - - )} - {activeTab === 4 && } + {activeTab === 'about' && } + {activeTab === 'getting-started' && } + {activeTab === 'languages' && } + {activeTab === 'faq' && } From 195377dc7d6bc41570b013e4e46011e5758e2ef2 Mon Sep 17 00:00:00 2001 From: plind-dm Date: Wed, 13 May 2026 22:18:39 +0900 Subject: [PATCH 2/2] refactor(onboard): replace hex literals with UI_COLORS/STATUS_COLORS tokens --- src/components/onboard/GettingStarted.tsx | 75 ++++++++++++----------- src/components/onboard/Scoring.tsx | 42 +++++++------ 2 files changed, 63 insertions(+), 54 deletions(-) diff --git a/src/components/onboard/GettingStarted.tsx b/src/components/onboard/GettingStarted.tsx index 6317e013..03f8d8f5 100644 --- a/src/components/onboard/GettingStarted.tsx +++ b/src/components/onboard/GettingStarted.tsx @@ -14,7 +14,12 @@ import CheckIcon from '@mui/icons-material/Check'; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; import { alpha, darken, useTheme } from '@mui/material/styles'; -import { STATUS_COLORS, scrollbarSx, tooltipSlotProps } from '../../theme'; +import { + STATUS_COLORS, + UI_COLORS, + scrollbarSx, + tooltipSlotProps, +} from '../../theme'; import { useClipboardCopy } from '../../hooks/useClipboardCopy'; const GREEN = STATUS_COLORS.merged; @@ -467,8 +472,8 @@ uv pip install -e .`} sx={{ p: 2.5, borderRadius: 1.5, - border: `1px solid ${alpha('#facc15', 0.3)}`, - background: `linear-gradient(180deg, ${alpha('#facc15', 0.06)} 0%, transparent 100%)`, + border: `1px solid ${alpha(STATUS_COLORS.warning, 0.3)}`, + background: `linear-gradient(180deg, ${alpha(STATUS_COLORS.warning, 0.06)} 0%, transparent 100%)`, }} > fontFamily: MONO, fontSize: '1.4rem', fontWeight: 800, - color: '#facc15', + color: STATUS_COLORS.warning, letterSpacing: '-0.03em', lineHeight: 1, }} @@ -524,7 +529,7 @@ uv pip install -e .`} sx={{ fontFamily: MONO, fontSize: '0.72rem', - color: '#facc15', + color: STATUS_COLORS.warning, fontWeight: 600, letterSpacing: '0.02em', }} @@ -634,7 +639,7 @@ export const GettingStarted: React.FC = () => { fontSize: { xs: '2.5rem', md: '4rem' }, fontWeight: 800, letterSpacing: '-0.03em', - color: '#ffffff', + color: UI_COLORS.white, lineHeight: 1, }} > @@ -646,7 +651,7 @@ export const GettingStarted: React.FC = () => { fontWeight: 200, fontStyle: 'italic', letterSpacing: '-0.03em', - color: alpha('#ffffff', 0.5), + color: alpha(UI_COLORS.white, 0.5), lineHeight: 1, }} > @@ -665,7 +670,7 @@ export const GettingStarted: React.FC = () => { right: { xs: 'auto', md: '7%' }, width: { xs: 2, md: 'auto' }, height: { xs: 'calc(100% - 22px)', md: 2 }, - bgcolor: alpha('#ffffff', 0.08), + bgcolor: alpha(UI_COLORS.white, 0.08), zIndex: 0, }} /> @@ -735,11 +740,11 @@ export const GettingStarted: React.FC = () => { borderColor: isActive || isComplete ? stepAccent - : alpha('#ffffff', 0.12), + : alpha(UI_COLORS.white, 0.12), color: isActive || isComplete ? stepAccent - : alpha('#ffffff', 0.45), + : alpha(UI_COLORS.white, 0.45), display: 'flex', alignItems: 'center', justifyContent: 'center', @@ -771,10 +776,10 @@ export const GettingStarted: React.FC = () => { fontSize: '0.78rem', fontWeight: isActive ? 600 : 500, color: isActive - ? '#ffffff' + ? UI_COLORS.white : isComplete - ? alpha('#ffffff', 0.75) - : alpha('#ffffff', 0.55), + ? alpha(UI_COLORS.white, 0.75) + : alpha(UI_COLORS.white, 0.55), letterSpacing: '-0.005em', mb: 0.25, transition: 'color 0.2s', @@ -786,7 +791,7 @@ export const GettingStarted: React.FC = () => { sx={{ fontFamily: MONO, fontSize: '0.65rem', - color: alpha('#ffffff', 0.35), + color: alpha(UI_COLORS.white, 0.35), letterSpacing: '0.02em', maxWidth: { md: 110 }, mx: { md: 'auto' }, @@ -806,9 +811,9 @@ export const GettingStarted: React.FC = () => { { fontSize: '0.65rem', fontWeight: 700, letterSpacing: '0.2em', - color: alpha('#ffffff', 0.4), + color: alpha(UI_COLORS.white, 0.4), textTransform: 'uppercase', mb: 0.5, }} @@ -864,7 +869,7 @@ export const GettingStarted: React.FC = () => { sx={{ fontSize: { xs: '1.5rem', md: '2rem' }, fontWeight: 700, - color: '#ffffff', + color: UI_COLORS.white, letterSpacing: '-0.02em', lineHeight: 1.1, }} @@ -886,7 +891,7 @@ export const GettingStarted: React.FC = () => { alignItems: 'center', justifyContent: 'space-between', pt: 3, - borderTop: `1px solid ${alpha('#ffffff', 0.06)}`, + borderTop: `1px solid ${alpha(UI_COLORS.white, 0.06)}`, gap: 2, flexWrap: 'wrap', }} @@ -896,20 +901,20 @@ export const GettingStarted: React.FC = () => { disabled={activeStep === 0} startIcon={} sx={{ - color: alpha('#ffffff', 0.7), + color: alpha(UI_COLORS.white, 0.7), fontWeight: 500, textTransform: 'none', px: 2, py: 0.75, borderRadius: 1.5, - border: `1px solid ${alpha('#ffffff', 0.12)}`, + border: `1px solid ${alpha(UI_COLORS.white, 0.12)}`, '&:hover': { - background: alpha('#ffffff', 0.04), - borderColor: alpha('#ffffff', 0.25), + background: alpha(UI_COLORS.white, 0.04), + borderColor: alpha(UI_COLORS.white, 0.25), }, '&.Mui-disabled': { - color: alpha('#ffffff', 0.2), - borderColor: alpha('#ffffff', 0.06), + color: alpha(UI_COLORS.white, 0.2), + borderColor: alpha(UI_COLORS.white, 0.06), }, }} > @@ -920,7 +925,7 @@ export const GettingStarted: React.FC = () => { sx={{ fontFamily: MONO, fontSize: '0.7rem', - color: alpha('#ffffff', 0.4), + color: alpha(UI_COLORS.white, 0.4), letterSpacing: '0.1em', }} > @@ -938,7 +943,7 @@ export const GettingStarted: React.FC = () => { background: isFinal ? 'transparent' : `linear-gradient(135deg, ${nextAccent} 0%, ${alpha(nextAccent, 0.8)} 100%)`, - color: nextAccent === BLUE ? '#000000' : '#ffffff', + color: nextAccent === BLUE ? UI_COLORS.black : UI_COLORS.white, fontWeight: 600, textTransform: 'none', px: 2.5, @@ -954,7 +959,7 @@ export const GettingStarted: React.FC = () => { : `0 0 24px ${alpha(nextAccent, 0.55)}`, }, '&.Mui-disabled': { - color: alpha('#ffffff', 0.2), + color: alpha(UI_COLORS.white, 0.2), }, }} > @@ -969,7 +974,7 @@ export const GettingStarted: React.FC = () => { sx={{ p: { xs: 3, md: 4 }, borderRadius: 2, - border: `1px solid ${alpha('#ffffff', 0.08)}`, + border: `1px solid ${alpha(UI_COLORS.white, 0.08)}`, background: `linear-gradient(135deg, ${alpha(theme.palette.primary.main, 0.08)} 0%, ${alpha(GREEN, 0.04)} 100%)`, display: 'flex', flexDirection: { xs: 'column', md: 'row' }, @@ -996,7 +1001,7 @@ export const GettingStarted: React.FC = () => { sx={{ fontSize: { xs: '1.1rem', md: '1.3rem' }, fontWeight: 600, - color: '#ffffff', + color: UI_COLORS.white, letterSpacing: '-0.01em', }} > @@ -1009,9 +1014,9 @@ export const GettingStarted: React.FC = () => { rel="noopener noreferrer" endIcon={} sx={{ - background: alpha('#ffffff', 0.08), - border: `1px solid ${alpha('#ffffff', 0.2)}`, - color: '#ffffff', + background: alpha(UI_COLORS.white, 0.08), + border: `1px solid ${alpha(UI_COLORS.white, 0.2)}`, + color: UI_COLORS.white, fontWeight: 600, fontSize: '0.9rem', textTransform: 'none', @@ -1020,8 +1025,8 @@ export const GettingStarted: React.FC = () => { borderRadius: 1.5, whiteSpace: 'nowrap', '&:hover': { - background: alpha('#ffffff', 0.15), - borderColor: alpha('#ffffff', 0.35), + background: alpha(UI_COLORS.white, 0.15), + borderColor: alpha(UI_COLORS.white, 0.35), }, }} > diff --git a/src/components/onboard/Scoring.tsx b/src/components/onboard/Scoring.tsx index 67c68736..16d86278 100644 --- a/src/components/onboard/Scoring.tsx +++ b/src/components/onboard/Scoring.tsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { Box, Typography, ButtonBase, Button, alpha } from '@mui/material'; import OpenInNewIcon from '@mui/icons-material/OpenInNew'; -import { CREDIBILITY_COLORS } from '../../theme'; +import { CREDIBILITY_COLORS, UI_COLORS } from '../../theme'; const MONO = '"JetBrains Mono", monospace'; const AMBER = CREDIBILITY_COLORS.moderate; @@ -77,7 +77,7 @@ const CATEGORIES: { value: 'oss', label: 'OSS Contributions', allocation: '30%', - color: '#1d37fc', + color: UI_COLORS.primary, docsUrl: 'https://docs.gittensor.io/oss-contributions.html', }, { @@ -103,7 +103,7 @@ export const Scoring: React.FC = () => { fontSize: '0.65rem', fontWeight: 700, letterSpacing: '0.2em', - color: alpha('#ffffff', 0.4), + color: alpha(UI_COLORS.white, 0.4), textTransform: 'uppercase', mb: 2, }} @@ -115,7 +115,7 @@ export const Scoring: React.FC = () => { fontSize: { xs: '2.5rem', md: '4rem' }, fontWeight: 800, letterSpacing: '-0.03em', - color: '#ffffff', + color: UI_COLORS.white, lineHeight: 1, }} > @@ -127,7 +127,7 @@ export const Scoring: React.FC = () => { fontWeight: 200, fontStyle: 'italic', letterSpacing: '-0.03em', - color: alpha('#ffffff', 0.5), + color: alpha(UI_COLORS.white, 0.5), lineHeight: 1, }} > @@ -141,7 +141,7 @@ export const Scoring: React.FC = () => { display: 'grid', gridTemplateColumns: '1fr 1fr', mb: 4, - border: `1px solid ${alpha('#ffffff', 0.1)}`, + border: `1px solid ${alpha(UI_COLORS.white, 0.1)}`, borderRadius: 2, overflow: 'hidden', }} @@ -163,12 +163,14 @@ export const Scoring: React.FC = () => { : 'transparent', borderRight: cat.value === 'oss' - ? `1px solid ${alpha('#ffffff', 0.1)}` + ? `1px solid ${alpha(UI_COLORS.white, 0.1)}` : 'none', position: 'relative', transition: 'all 0.25s', '&:hover': { - background: isActive ? undefined : alpha('#ffffff', 0.02), + background: isActive + ? undefined + : alpha(UI_COLORS.white, 0.02), }, '&::after': isActive ? { @@ -197,7 +199,7 @@ export const Scoring: React.FC = () => { fontFamily: MONO, fontSize: { xs: '1.5rem', md: '2rem' }, fontWeight: 800, - color: isActive ? cat.color : alpha('#ffffff', 0.3), + color: isActive ? cat.color : alpha(UI_COLORS.white, 0.3), letterSpacing: '-0.03em', textShadow: isActive ? `0 0 16px ${alpha(cat.color, 0.5)}` @@ -213,7 +215,7 @@ export const Scoring: React.FC = () => { fontSize: '0.6rem', fontWeight: 600, letterSpacing: '0.18em', - color: alpha('#ffffff', 0.35), + color: alpha(UI_COLORS.white, 0.35), textTransform: 'uppercase', }} > @@ -224,7 +226,9 @@ export const Scoring: React.FC = () => { sx={{ fontSize: { xs: '0.95rem', md: '1.1rem' }, fontWeight: 600, - color: isActive ? '#ffffff' : alpha('#ffffff', 0.5), + color: isActive + ? UI_COLORS.white + : alpha(UI_COLORS.white, 0.5), letterSpacing: '-0.01em', }} > @@ -249,13 +253,13 @@ export const Scoring: React.FC = () => { key={rule.title} sx={{ p: { xs: 3, md: 3.5 }, - border: `1px solid ${alpha('#ffffff', 0.08)}`, + border: `1px solid ${alpha(UI_COLORS.white, 0.08)}`, borderRadius: 2, - background: alpha('#ffffff', 0.02), + background: alpha(UI_COLORS.white, 0.02), transition: 'all 0.25s', '&:hover': { borderColor: alpha(activeCategory.color, 0.4), - background: alpha('#ffffff', 0.03), + background: alpha(UI_COLORS.white, 0.03), '& .rule-num': { color: activeCategory.color, textShadow: `0 0 24px ${alpha(activeCategory.color, 0.5)}`, @@ -273,7 +277,7 @@ export const Scoring: React.FC = () => { fontSize: { xs: '2.5rem', md: '3.5rem' }, fontWeight: 200, lineHeight: 1, - color: alpha('#ffffff', 0.2), + color: alpha(UI_COLORS.white, 0.2), letterSpacing: '-0.04em', transition: 'all 0.3s', }} @@ -284,7 +288,7 @@ export const Scoring: React.FC = () => { sx={{ fontSize: { xs: '1.15rem', md: '1.3rem' }, fontWeight: 700, - color: '#ffffff', + color: UI_COLORS.white, letterSpacing: '-0.01em', flex: 1, }} @@ -295,7 +299,7 @@ export const Scoring: React.FC = () => { { sx={{ p: { xs: 3, md: 4 }, borderRadius: 2, - border: `1px solid ${alpha('#ffffff', 0.08)}`, + border: `1px solid ${alpha(UI_COLORS.white, 0.08)}`, background: `linear-gradient(135deg, ${alpha(activeCategory.color, 0.08)} 0%, transparent 60%)`, display: 'flex', flexDirection: { xs: 'column', md: 'row' }, @@ -372,7 +376,7 @@ export const Scoring: React.FC = () => { sx={{ fontSize: { xs: '1.1rem', md: '1.3rem' }, fontWeight: 600, - color: '#ffffff', + color: UI_COLORS.white, letterSpacing: '-0.01em', }} >