You are an expert Vue 3 frontend developer and UI/UX designer. Your task is to build the complete DevQuiz frontend — a developer quiz platform with a polished, trustworthy, and addictive interface.
## PROJECT CONTEXT
Backend is already deployed at: https://devquiz.fastapicloud.dev/
GraphQL endpoint: https://devquiz.fastapicloud.dev/graphql
Health check: https://devquiz.fastapicloud.dev/health
Read all documentation files in the docs/ folder before writing any code.
The docs describe all available GraphQL queries and mutations — use every single one.
## TECH STACK
- Vue 3 (Composition API, <script setup>)
- Vue Router 4
- Pinia (state management)
- Apollo Client (@apollo/client + @vue/apollo-composable)
- Tailwind CSS v3
- Vite
- Project is already initialized inside app/ folder
## DESIGN PHILOSOPHY — MOST IMPORTANT
The UI must feel: calm, trustworthy, focused, slightly gamified — like a tool a senior developer would actually use and enjoy. NOT a rainbow toy, NOT a generic SaaS template.
### Color Palette (use EXACTLY these):
```css
:root {
--bg-primary: #0f1117; /* deep dark background */
--bg-secondary: #1a1d27; /* card background */
--bg-tertiary: #242736; /* hover states, input bg */
--border: #2e3147; /* subtle borders */
--accent: #6c63ff; /* primary purple — buttons, active states */
--accent-hover: #5a52d5; /* darker purple on hover */
--accent-glow: rgba(108, 99, 255, 0.15); /* soft glow for cards */
--success: #22c55e; /* correct answer */
--error: #ef4444; /* wrong answer */
--warning: #f59e0b; /* streak, hints */
--text-primary: #e8eaf0; /* main text */
--text-secondary: #8b90a7; /* muted text */
--text-muted: #4a4f6a; /* very muted */
--xp-color: #f59e0b; /* XP gold */
--badge-color: #a78bfa; /* badge purple-light */
}
- Font: Inter (Google Fonts)
- Code snippets in questions: JetBrains Mono
- Headings: font-weight 700, slightly tight letter-spacing
- Body: font-weight 400, line-height 1.6
- Dark theme ONLY — no light mode toggle
- Rounded corners: 12px for cards, 8px for buttons, 6px for inputs
- Subtle shadows: box-shadow with accent-glow on hover
- Smooth transitions: 200ms ease on all interactive elements
- NO gradients except: subtle radial on hero section and XP bar
- NO heavy animations — only purposeful micro-interactions
- Cards lift slightly on hover (translateY(-2px))
- Active states use accent border + glow, not background fill
- Page padding: px-6 on mobile, px-12 on desktop
- Card padding: p-6
- Section gaps: gap-6 or gap-8
- Never cramped, never wasteful
app/
├── src/
│ ├── main.js
│ ├── App.vue
│ ├── apollo.js # Apollo Client setup with auth headers
│ ├── router/
│ │ └── index.js # All routes with auth guards
│ ├── stores/
│ │ ├── auth.js # Pinia: user, token, login/logout/register
│ │ └── quiz.js # Pinia: current quiz state, answers, timer
│ ├── graphql/
│ │ ├── auth.js # REGISTER, LOGIN mutations
│ │ ├── topics.js # TOPICS, TOPIC queries
│ │ ├── quiz.js # QUIZ query, SUBMIT_QUIZ mutation
│ │ ├── user.js # ME, MY_STATS, MY_BADGES, MY_HISTORY
│ │ ├── leaderboard.js # LEADERBOARD, TOPIC_LEADERBOARD
│ │ ├── challenge.js # CREATE_CHALLENGE, SUBMIT_CHALLENGE, CHALLENGE_LEADERBOARD
│ │ └── profile.js # PUBLIC_PROFILE query
│ ├── views/
│ │ ├── HomeView.vue
│ │ ├── LoginView.vue
│ │ ├── RegisterView.vue
│ │ ├── DashboardView.vue
│ │ ├── TopicsView.vue
│ │ ├── QuizView.vue
│ │ ├── QuizResultView.vue
│ │ ├── LeaderboardView.vue
│ │ ├── ProfileView.vue
│ │ ├── PublicProfileView.vue
│ │ └── ChallengeView.vue
│ └── components/
│ ├── layout/
│ │ ├── AppNav.vue
│ │ └── AppFooter.vue
│ ├── ui/
│ │ ├── AppButton.vue
│ │ ├── AppCard.vue
│ │ ├── AppBadge.vue
│ │ ├── AppInput.vue
│ │ ├── LoadingSpinner.vue
│ │ └── EmptyState.vue
│ ├── quiz/
│ │ ├── QuestionCard.vue
│ │ ├── ChoiceOption.vue
│ │ ├── ProgressBar.vue
│ │ ├── QuizTimer.vue
│ │ └── ResultCard.vue
│ ├── gamification/
│ │ ├── XPBar.vue
│ │ ├── BadgeCard.vue
│ │ ├── StreakBadge.vue
│ │ └── LevelBadge.vue
│ └── leaderboard/
│ ├── LeaderboardTable.vue
│ └── LeaderboardRow.vue
- Hero section: large heading "Test Your Dev Skills", subtitle, two CTA buttons (Start Quiz → /topics, Sign Up → /register)
- Subtle radial glow behind hero text using --accent-glow
- Three feature cards below: "Track Progress", "Compete", "Earn Badges" with icons
- If user is logged in: redirect to /dashboard
- Centered card, max-width 420px
- Logo/title at top
- Form with AppInput components
- Error messages inline under fields
- Link to opposite page (Login ↔ Register)
- On success: redirect to /dashboard
- Show loading state on button during request
Layout: two columns on desktop, single on mobile
Left column:
- Welcome message with username
- UserStats card: XP bar with level name, current XP / next level XP, streak flame 🔥 with count, total quizzes, total correct
- Recent badges (last 3 earned) with BadgeCard
- "View all badges" link
Right column:
- "Quick Start" — list of topics as clickable cards, each shows topic name and description
- "Your History" — last 5 QuizResults: topic name, score/total, percentage, date
- "Active Challenges" section with join by code input
- Grid of topic cards (2 cols mobile, 3 cols desktop)
- Each card: topic name, description, "Start Quiz" button
- Clicking card → /quiz/:topicId
- Filter by level (easy/medium/hard) — tabs above grid
This is the CORE experience — make it exceptional:
- Full page focus mode — hide nav during quiz
- Top bar: topic name, progress (3/10), level badge
- Progress bar animates smoothly between questions
- Question displayed in large readable text
- If question text contains backticks → render as code block with JetBrains Mono
- 4 choices as large clickable cards
- Default: --bg-secondary border --border
- Hover: border --accent, slight glow
- Selected: border --accent, bg --accent-glow
- After submit: correct → border --success bg green/10, wrong → border --error bg red/10
- "Next Question" button appears after selection
- Last question: "See Results" button
- Hint button (💡) — calls getHint query, shows hint in a subtle tooltip/panel below question. Only once per quiz session.
- Store all answers in quiz Pinia store
- On finish: navigate to /quiz/result with full answers payload
- Score header: big number "7/10", percentage, level badge
- XP earned this quiz: animated counter +80 XP
- New badges earned (if any): show with animation — slide in from bottom
- Streak info: "🔥 3 day streak!"
- Per-question breakdown:
- ✅ or ❌ icon
- Question text
- Your answer (green if correct, red if wrong)
- Correct answer (always shown in green)
- Explanation in muted text
- Two buttons: "Try Again" (same topic) / "Back to Topics"
- "Challenge a friend" button → opens create challenge modal
- Two tabs: Global / By Topic
- Global tab: LeaderboardTable with rank, username, level badge, XP, total correct
- By Topic tab: topic selector dropdown, then TopicLeaderboardTable
- Top 3 rows highlighted: gold/silver/bronze subtle left border
- Current user row highlighted with --accent border
- Avatar placeholder (initials in colored circle based on username)
- Username, level badge, member since date
- Stats grid: XP, streak, longest streak, total quizzes, total correct
- XP bar showing progress to next level
- All badges grid — earned ones full color, locked ones grayscale with lock icon
- Quiz history table: topic, score, percentage, date
- "Create Challenge" button
- Same as ProfileView but read-only, no history table
- Shows: username, level, stats, earned badges only
- Two sections:
- "Create Challenge" — select topic, level, number of questions → createChallenge mutation → show generated code in big copyable box
- "Join Challenge" — enter code → fetch challengeLeaderboard to preview → start quiz → submitChallenge
- Fixed top, height 64px, backdrop-blur with semi-transparent bg
- Logo left: "DevQuiz" with subtle accent color on "Quiz"
- Nav links: Topics, Leaderboard (always visible)
- If logged in: Dashboard, Profile, logout button
- If logged out: Login, Register buttons
- Mobile: hamburger menu
Props: currentXp, level, nextLevelXp
- Label: "Level: Mid • 540 XP"
- Progress bar with gradient fill (--accent to --badge-color)
- Animated on mount using CSS transition
- Show percentage of current level completion
Props: badge (name, icon, description, earnedAt), locked (bool)
- Square card with icon large centered
- Badge name below
- Locked state: grayscale filter + lock emoji overlay
- Tooltip on hover: description + earned date
Props: question, questionNumber, total
- Renders question.text — detect backtick code blocks and render with styled pre/code
- Shows "Question N of Total" subtitle
- Smooth fade-in animation on mount
Props: choice, selected, revealed, isCorrect
- Large button-like card, full width
- States: default / hover / selected / correct / wrong
- Smooth color transition between states
- Letter prefix: A, B, C, D in small badge left side
Props: current, total
- Thin bar (4px height) at top of quiz
- Smooth width transition
- Color: --accent
Props: streak
- Shows 🔥 + number
- If streak >= 7: add subtle pulsing glow animation
- If streak == 0: show in --text-muted
Props: level
- Small pill badge
- Colors per level:
- Beginner: gray
- Junior: blue
- Mid: green
- Senior: purple (--accent)
- Expert: gold (--xp-color)
// apollo.js
import { ApolloClient, InMemoryCache, createHttpLink, from } from '@apollo/client/core'
import { setContext } from '@apollo/client/link/context'
import { onError } from '@apollo/client/link/error'
const httpLink = createHttpLink({
uri: 'https://devquiz.fastapicloud.dev/graphql',
})
const authLink = setContext((_, { headers }) => {
const token = localStorage.getItem('token')
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : '',
}
}
})
const errorLink = onError(({ graphQLErrors }) => {
if (graphQLErrors?.some(e => e.message.includes('Authentication required'))) {
localStorage.removeItem('token')
window.location.href = '/login'
}
})
export const apolloClient = new ApolloClient({
link: from([errorLink, authLink, httpLink]),
cache: new InMemoryCache(),
})state: {
user: null, // {id, email, username}
token: null, // JWT string
stats: null, // UserStats
badges: [],
}
actions:
- login(email, password) → mutation → save token to localStorage + store
- register(email, username, password) → mutation → auto-login after
- logout() → clear store + localStorage → redirect /
- fetchMe() → query me + myStats + myBadges → populate store
- initFromStorage() → on app mount, restore token, call fetchMestate: {
questions: [], // current quiz questions
answers: {}, // { questionId: choiceId }
topicId: null,
level: null,
hintUsed: false,
result: null, // QuizSubmitResultType after submit
}
actions:
- startQuiz(topicId, level, questions)
- selectAnswer(questionId, choiceId)
- useHint()
- submitQuiz() → mutation → save result → return result
- reset()const routes = [
{ path: '/', component: HomeView },
{ path: '/login', component: LoginView },
{ path: '/register', component: RegisterView },
{ path: '/dashboard', component: DashboardView, meta: { requiresAuth: true } },
{ path: '/topics', component: TopicsView },
{ path: '/quiz/:topicId', component: QuizView, meta: { requiresAuth: true } },
{ path: '/quiz/result', component: QuizResultView, meta: { requiresAuth: true } },
{ path: '/leaderboard', component: LeaderboardView },
{ path: '/profile', component: ProfileView, meta: { requiresAuth: true } },
{ path: '/profile/:username', component: PublicProfileView },
{ path: '/challenge', component: ChallengeView, meta: { requiresAuth: true } },
]
// Navigation guard: if requiresAuth and no token → redirect /login
// If logged in and visiting /login or /register → redirect /dashboardWrite all queries and mutations as gql`` tagged template literals. Every query/mutation must match exactly what the backend accepts. Group them by feature in separate files under src/graphql/.
Include ALL fields that exist in the backend types:
- UserStats: xp, level, streak, longestStreak, totalQuizzes, totalCorrect
- QuizResult: score, total, percentage, results { questionText, yourChoice, correctChoice, isCorrect, explanation }
- Badge: name, icon, description, earnedAt
- LeaderboardEntry: rank, username, xp, level, totalCorrect
- ChallengeType: id, code, topicId, topicName, level, limit, createdAt, creatorUsername
- Loading states: every async operation shows LoadingSpinner, never blank page
- Error states: inline error messages, never silent failures
- Empty states: EmptyState component with icon + message (e.g. "No quizzes yet — start one!")
- Page transitions: simple fade (opacity 0→1, 150ms) on route change
- Quiz answers: once selected, cannot change (disabled state)
- Quiz navigation: no back button during quiz — forward only
- Result page: scroll to top on mount
- Token expiry: errorLink catches auth errors → logout → redirect login
- Responsive: every page works on 375px mobile and 1440px desktop
- No page requires refresh to update — reactivity via Pinia + Apollo
npm install @apollo/client @vue/apollo-composable graphql
npm install pinia
npm install vue-router@4
npm install @tailwindcss/typography
npm install -D tailwindcss postcss autoprefixer- Write every file completely — no TODOs, no placeholders
- Start with: package.json → vite.config.js → tailwind.config.js → src/main.js → apollo.js → router → stores → graphql documents → components (ui first, then quiz, then gamification) → views
- Each component must be self-contained and immediately usable
- Use <script setup> syntax everywhere
- All styles via Tailwind utility classes + CSS variables in style blocks where needed
---
Копируй в Sonnet 4.8. После генерации скинь если будут ошибки — разберём вместе.