feat: Topics page loads with all categories collapsed; expand/collapse…
- "frontend/src/pages/TopicsBrowse.tsx" - "frontend/src/App.css" GSD-Task: S02/T01
This commit is contained in:
parent
717f6c0785
commit
41bf06e431
12 changed files with 720 additions and 6 deletions
|
|
@ -6,7 +6,7 @@ Transform Chrysopedia from functionally adequate to engaging and accessible. Add
|
||||||
## Slice Overview
|
## Slice Overview
|
||||||
| ID | Slice | Risk | Depends | Done | After this |
|
| ID | Slice | Risk | Depends | Done | After this |
|
||||||
|----|-------|------|---------|------|------------|
|
|----|-------|------|---------|------|------------|
|
||||||
| S01 | Interaction Delight & Discovery | medium | — | ⬜ | Cards animate on hover with scale+shadow. Card grids stagger entrance. Featured technique has glow treatment. Random Technique button navigates to a random page. |
|
| S01 | Interaction Delight & Discovery | medium | — | ✅ | Cards animate on hover with scale+shadow. Card grids stagger entrance. Featured technique has glow treatment. Random Technique button navigates to a random page. |
|
||||||
| S02 | Topics, Creator Stats & Card Polish | medium | — | ⬜ | Topics page loads collapsed, expands with animation. Creator stats show colored topic pills. Cards limit tags to 4 with +N more. Empty subtopics show Coming soon. |
|
| S02 | Topics, Creator Stats & Card Polish | medium | — | ⬜ | Topics page loads collapsed, expands with animation. Creator stats show colored topic pills. Cards limit tags to 4 with +N more. Empty subtopics show Coming soon. |
|
||||||
| S03 | Global Search & Mobile Navigation | medium | — | ⬜ | Compact search bar in nav on all pages. Cmd+K focuses it. Mobile viewport shows hamburger menu. |
|
| S03 | Global Search & Mobile Navigation | medium | — | ⬜ | Compact search bar in nav on all pages. Cmd+K focuses it. Mobile viewport shows hamburger menu. |
|
||||||
| S04 | Accessibility & SEO Fixes | low | — | ⬜ | Every page has single H1, skip-to-content link, AA contrast text, and descriptive browser tab title. |
|
| S04 | Accessibility & SEO Fixes | low | — | ⬜ | Every page has single H1, skip-to-content link, AA contrast text, and descriptive browser tab title. |
|
||||||
|
|
|
||||||
106
.gsd/milestones/M011/slices/S01/S01-SUMMARY.md
Normal file
106
.gsd/milestones/M011/slices/S01/S01-SUMMARY.md
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
---
|
||||||
|
id: S01
|
||||||
|
parent: M011
|
||||||
|
milestone: M011
|
||||||
|
provides:
|
||||||
|
- Card hover animation pattern (scale+shadow) on all card types
|
||||||
|
- Stagger entrance animation utility class
|
||||||
|
- GET /api/v1/techniques/random endpoint
|
||||||
|
- Random Technique button on homepage
|
||||||
|
requires:
|
||||||
|
[]
|
||||||
|
affects:
|
||||||
|
[]
|
||||||
|
key_files:
|
||||||
|
- frontend/src/App.css
|
||||||
|
- frontend/src/pages/Home.tsx
|
||||||
|
- frontend/src/pages/TopicsBrowse.tsx
|
||||||
|
- frontend/src/pages/CreatorDetail.tsx
|
||||||
|
- frontend/src/pages/SubTopicPage.tsx
|
||||||
|
- frontend/src/pages/SearchResults.tsx
|
||||||
|
- backend/routers/techniques.py
|
||||||
|
- frontend/src/api/public-client.ts
|
||||||
|
key_decisions:
|
||||||
|
- CSS stagger pattern uses --stagger-index custom property with calc() delay rather than nth-child selectors — works with dynamic lists
|
||||||
|
- Dedicated /random endpoint returning just {slug} rather than reusing sort=random on list endpoint — cleaner API surface
|
||||||
|
- /random route placed before /{slug} to avoid FastAPI slug capture
|
||||||
|
patterns_established:
|
||||||
|
- card-stagger utility class: add className='card-stagger' and style={{ '--stagger-index': i }} to any .map() loop for entrance animations
|
||||||
|
- Featured section visual treatment: gradient border-image + double box-shadow glow
|
||||||
|
observability_surfaces:
|
||||||
|
- none
|
||||||
|
drill_down_paths:
|
||||||
|
- .gsd/milestones/M011/slices/S01/tasks/T01-SUMMARY.md
|
||||||
|
- .gsd/milestones/M011/slices/S01/tasks/T02-SUMMARY.md
|
||||||
|
duration: ""
|
||||||
|
verification_result: passed
|
||||||
|
completed_at: 2026-03-31T08:25:58.040Z
|
||||||
|
blocker_discovered: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# S01: Interaction Delight & Discovery
|
||||||
|
|
||||||
|
**Added card hover animations (scale+shadow) on all 6 card types, staggered entrance animations across 5 page components, gradient-border glow on featured technique, and a Random Technique button with backend endpoint.**
|
||||||
|
|
||||||
|
## What Happened
|
||||||
|
|
||||||
|
Two tasks delivered CSS interaction polish and a random discovery feature.
|
||||||
|
|
||||||
|
T01 added `transform: scale(1.02)` hover transitions with `will-change: transform` to all 6 card types (recent-card, creator-technique-card, subtopic-technique-card, search-result-card, topic-card, nav-card). Created a `@keyframes cardEnter` animation (opacity 0→1, translateY 12px→0, 300ms ease-out) with a `.card-stagger` utility class driven by a `--stagger-index` CSS custom property. Applied stagger indices via JSX style props across Home.tsx, TopicsBrowse.tsx, CreatorDetail.tsx, SubTopicPage.tsx, and SearchResults.tsx. The featured technique section got a gradient `border-image` and double box-shadow glow treatment.
|
||||||
|
|
||||||
|
T02 added a `GET /api/v1/techniques/random` backend endpoint (placed before the `/{slug}` route to avoid capture) that returns `{slug}` via `ORDER BY random() LIMIT 1`, with 404 when no techniques exist. Frontend gets `fetchRandomTechnique()` in the API client and a 🎲 Random Technique button on the homepage with loading and error states.
|
||||||
|
|
||||||
|
Both tasks verified with TypeScript (`tsc --noEmit`) and Vite production build (50 modules). All grep checks confirm features landed in expected files.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
All slice-level checks pass:
|
||||||
|
- `npx tsc --noEmit` — exit 0, no type errors
|
||||||
|
- `npm run build` — exit 0, 50 modules, 806ms build
|
||||||
|
- `grep cardEnter App.css` — found
|
||||||
|
- `grep card-stagger App.css` — found
|
||||||
|
- `grep fetchRandomTechnique public-client.ts` — found
|
||||||
|
- `grep /random techniques.py` — found
|
||||||
|
- `grep Random Home.tsx` — found
|
||||||
|
- `grep stagger-index` across all 5 page components — found in each (Home:3, TopicsBrowse:1, CreatorDetail:1, SubTopicPage:1, SearchResults:1)
|
||||||
|
|
||||||
|
## Requirements Advanced
|
||||||
|
|
||||||
|
- R016 — All 6 card types have scale(1.02) hover with smooth 200ms transition. 5 page components use staggered cardEnter animation.
|
||||||
|
- R017 — Featured technique section has gradient border-image and double box-shadow glow, visually distinct from regular cards.
|
||||||
|
- R018 — Random Technique button on homepage calls GET /random endpoint and navigates to result. Loading and error states implemented.
|
||||||
|
|
||||||
|
## Requirements Validated
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
## New Requirements Surfaced
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
## Requirements Invalidated or Re-scoped
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
## Deviations
|
||||||
|
|
||||||
|
SearchResultCard needed a staggerIndex prop threaded through (not anticipated in plan). topic-card had no existing :hover rule — one was added. border-image removes border-radius on the featured card (CSS limitation) but the glow box-shadow still provides the visual treatment.
|
||||||
|
|
||||||
|
## Known Limitations
|
||||||
|
|
||||||
|
border-image CSS property strips border-radius on the featured technique card. The glow effect (box-shadow) still provides visual distinction but the card corners are square rather than rounded.
|
||||||
|
|
||||||
|
## Follow-ups
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
|
||||||
|
- `frontend/src/App.css` — Added card hover scale(1.02) transitions, @keyframes cardEnter, .card-stagger utility, .home-featured glow treatment, .btn--random and .home-random styles
|
||||||
|
- `frontend/src/pages/Home.tsx` — Added stagger indices to nav-cards and recent cards, Random Technique button with loading/error states
|
||||||
|
- `frontend/src/pages/TopicsBrowse.tsx` — Added card-stagger class and stagger-index to topic cards
|
||||||
|
- `frontend/src/pages/CreatorDetail.tsx` — Added card-stagger class and stagger-index to creator technique cards
|
||||||
|
- `frontend/src/pages/SubTopicPage.tsx` — Added card-stagger class and stagger-index to subtopic technique cards
|
||||||
|
- `frontend/src/pages/SearchResults.tsx` — Added card-stagger class and stagger-index to search result cards, threaded staggerIndex prop
|
||||||
|
- `backend/routers/techniques.py` — Added GET /random endpoint before /{slug} route
|
||||||
|
- `frontend/src/api/public-client.ts` — Added fetchRandomTechnique() API client function
|
||||||
77
.gsd/milestones/M011/slices/S01/S01-UAT.md
Normal file
77
.gsd/milestones/M011/slices/S01/S01-UAT.md
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
# S01: Interaction Delight & Discovery — UAT
|
||||||
|
|
||||||
|
**Milestone:** M011
|
||||||
|
**Written:** 2026-03-31T08:25:58.041Z
|
||||||
|
|
||||||
|
# S01 UAT: Interaction Delight & Discovery
|
||||||
|
|
||||||
|
## Preconditions
|
||||||
|
- Chrysopedia running at http://ub01:8096
|
||||||
|
- At least 1 technique page exists in the database
|
||||||
|
- Modern browser (Chrome/Firefox/Safari)
|
||||||
|
|
||||||
|
## Test Cases
|
||||||
|
|
||||||
|
### TC-01: Card Hover Animation — Homepage Recent Cards
|
||||||
|
1. Navigate to http://ub01:8096
|
||||||
|
2. Hover over any card in the "Recently Added" section
|
||||||
|
3. **Expected:** Card smoothly scales up (~2%) with enhanced shadow over ~200ms
|
||||||
|
4. Move mouse away from card
|
||||||
|
5. **Expected:** Card smoothly returns to original size
|
||||||
|
|
||||||
|
### TC-02: Card Hover Animation — All Card Types
|
||||||
|
1. Navigate to Topics page → hover a topic card
|
||||||
|
2. Navigate to any topic → hover a subtopic technique card
|
||||||
|
3. Navigate to Creators page → click a creator → hover a creator technique card
|
||||||
|
4. Use search → hover a search result card
|
||||||
|
5. On homepage → hover a nav card (Topics/Creators)
|
||||||
|
6. **Expected:** All 5 card types exhibit the same scale+shadow hover effect
|
||||||
|
|
||||||
|
### TC-03: Staggered Entrance Animation — Homepage
|
||||||
|
1. Hard refresh http://ub01:8096 (Ctrl+Shift+R)
|
||||||
|
2. Observe the nav cards and recently added cards
|
||||||
|
3. **Expected:** Cards fade in and slide up sequentially (not all at once). Each card appears ~60ms after the previous one.
|
||||||
|
|
||||||
|
### TC-04: Staggered Entrance — Other Pages
|
||||||
|
1. Navigate to Topics page — cards should stagger in
|
||||||
|
2. Navigate to a Creator detail page — technique cards should stagger in
|
||||||
|
3. Navigate to a SubTopic page — technique cards should stagger in
|
||||||
|
4. Perform a search — result cards should stagger in
|
||||||
|
5. **Expected:** All pages show sequential card entrance, not simultaneous
|
||||||
|
|
||||||
|
### TC-05: Featured Technique Glow
|
||||||
|
1. Navigate to homepage
|
||||||
|
2. Scroll to the "Featured Technique" section
|
||||||
|
3. **Expected:** The featured technique card has a visible gradient border and soft glow (cyan-tinted box shadow) that distinguishes it from regular cards
|
||||||
|
4. **Note:** Corners may be square due to border-image CSS limitation — this is known
|
||||||
|
|
||||||
|
### TC-06: Random Technique Button — Happy Path
|
||||||
|
1. Navigate to homepage
|
||||||
|
2. Locate the 🎲 "Random Technique" button (between nav cards and featured section)
|
||||||
|
3. Click the button
|
||||||
|
4. **Expected:** Button shows brief loading state, then navigates to a technique page
|
||||||
|
5. Use browser back, click the button again
|
||||||
|
6. **Expected:** May navigate to a different technique (randomized server-side)
|
||||||
|
|
||||||
|
### TC-07: Random Technique Button — Loading State
|
||||||
|
1. On homepage, open browser DevTools Network tab
|
||||||
|
2. Throttle network to "Slow 3G"
|
||||||
|
3. Click the Random Technique button
|
||||||
|
4. **Expected:** Button shows a loading indicator while the request is in flight
|
||||||
|
5. Remove throttle
|
||||||
|
|
||||||
|
### TC-08: Random Technique API — Direct
|
||||||
|
1. Open http://ub01:8096/api/v1/techniques/random in browser or curl
|
||||||
|
2. **Expected:** JSON response `{"slug": "some-technique-slug"}` with 200 status
|
||||||
|
3. Refresh multiple times
|
||||||
|
4. **Expected:** Different slugs returned (assuming multiple techniques exist)
|
||||||
|
|
||||||
|
### Edge Cases
|
||||||
|
|
||||||
|
### TC-09: Card Stagger with Few Items
|
||||||
|
1. If a creator has only 1 technique, navigate to their detail page
|
||||||
|
2. **Expected:** Single card still animates in (no delay needed for index 0)
|
||||||
|
|
||||||
|
### TC-10: Featured Technique Glow in Light/Contrast
|
||||||
|
1. On homepage, inspect the featured technique section
|
||||||
|
2. **Expected:** Glow is subtle — visible but not overpowering (cyan tint, not neon)
|
||||||
42
.gsd/milestones/M011/slices/S01/tasks/T02-VERIFY.json
Normal file
42
.gsd/milestones/M011/slices/S01/tasks/T02-VERIFY.json
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"taskId": "T02",
|
||||||
|
"unitId": "M011/S01/T02",
|
||||||
|
"timestamp": 1774945478573,
|
||||||
|
"passed": false,
|
||||||
|
"discoverySource": "task-plan",
|
||||||
|
"checks": [
|
||||||
|
{
|
||||||
|
"command": "cd frontend",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 5,
|
||||||
|
"verdict": "pass"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "npx tsc --noEmit",
|
||||||
|
"exitCode": 1,
|
||||||
|
"durationMs": 720,
|
||||||
|
"verdict": "fail"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "npm run build",
|
||||||
|
"exitCode": 254,
|
||||||
|
"durationMs": 96,
|
||||||
|
"verdict": "fail"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "grep -q 'fetchRandomTechnique' src/api/public-client.ts",
|
||||||
|
"exitCode": 2,
|
||||||
|
"durationMs": 7,
|
||||||
|
"verdict": "fail"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "grep -q '/random' ../backend/routers/techniques.py",
|
||||||
|
"exitCode": 2,
|
||||||
|
"durationMs": 6,
|
||||||
|
"verdict": "fail"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"retryAttempt": 1,
|
||||||
|
"maxRetries": 2
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,125 @@
|
||||||
# S02: Topics, Creator Stats & Card Polish
|
# S02: Topics, Creator Stats & Card Polish
|
||||||
|
|
||||||
**Goal:** Improve information density and visual clarity on Topics and Creator pages
|
**Goal:** Topics page loads collapsed with smooth expand/collapse animation. Creator stats show colored topic pills. Cards limit tags to 4 with +N overflow. Empty subtopics show "Coming soon" badge.
|
||||||
**Demo:** After this: Topics page loads collapsed, expands with animation. Creator stats show colored topic pills. Cards limit tags to 4 with +N more. Empty subtopics show Coming soon.
|
**Demo:** After this: Topics page loads collapsed, expands with animation. Creator stats show colored topic pills. Cards limit tags to 4 with +N more. Empty subtopics show Coming soon.
|
||||||
|
|
||||||
## Tasks
|
## Tasks
|
||||||
|
- [x] **T01: Topics page loads with all categories collapsed; expand/collapse uses smooth CSS grid-template-rows animation instead of conditional render** — Change TopicsBrowse.tsx to start with all categories collapsed and add smooth CSS grid-template-rows animation for expand/collapse transitions.
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. In `frontend/src/pages/TopicsBrowse.tsx`, change the `useEffect` that calls `setExpanded(new Set(data.map((c) => c.name)))` to `setExpanded(new Set())` so all categories start collapsed.
|
||||||
|
|
||||||
|
2. Refactor the subtopics rendering from conditional `{isExpanded && (<div className="topic-subtopics">...)}` to always-rendered with a CSS grid animation wrapper. Use the `display: grid; grid-template-rows: 0fr / 1fr` technique:
|
||||||
|
- Wrap the subtopics in a `<div className="topic-subtopics-wrapper" data-expanded={isExpanded}>` that has `display: grid; grid-template-rows: 0fr; transition: grid-template-rows 300ms ease;`
|
||||||
|
- When `data-expanded="true"`, set `grid-template-rows: 1fr`
|
||||||
|
- The inner `<div className="topic-subtopics">` gets `overflow: hidden; min-height: 0;`
|
||||||
|
- Always render both wrapper and inner div (remove the conditional render)
|
||||||
|
|
||||||
|
3. Add the CSS in `frontend/src/App.css` near the existing `.topic-subtopics` rule (~line 2318):
|
||||||
|
- `.topic-subtopics-wrapper { display: grid; grid-template-rows: 0fr; transition: grid-template-rows 300ms ease; }`
|
||||||
|
- `.topic-subtopics-wrapper[data-expanded="true"] { grid-template-rows: 1fr; }`
|
||||||
|
- `.topic-subtopics { overflow: hidden; min-height: 0; }` (modify existing rule)
|
||||||
|
|
||||||
|
4. Verify: `cd frontend && npx tsc --noEmit && npm run build`
|
||||||
|
- Estimate: 30m
|
||||||
|
- Files: frontend/src/pages/TopicsBrowse.tsx, frontend/src/App.css
|
||||||
|
- Verify: cd frontend && npx tsc --noEmit && npm run build
|
||||||
|
- [ ] **T02: Creator stats topic-colored pill badges** — Replace the run-on text stats on CreatorDetail page with colored pill badges using existing badge CSS classes.
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. In `frontend/src/pages/CreatorDetail.tsx`, add import: `import { catSlug } from "../utils/catSlug";`
|
||||||
|
|
||||||
|
2. Find the stats section (~line 108-126) that renders `<span className="creator-detail__topic-stat">` with dot separators. Replace the entire `.map(([cat, count], i) => ...)` block with pills:
|
||||||
|
```tsx
|
||||||
|
.map(([cat, count]) => (
|
||||||
|
<span key={cat} className={`badge badge--cat-${catSlug(cat)}`}>
|
||||||
|
{cat}: {count}
|
||||||
|
</span>
|
||||||
|
))
|
||||||
|
```
|
||||||
|
Remove the dot separator spans (`queue-card__separator`) between them.
|
||||||
|
|
||||||
|
3. Wrap the topic stat pills in a flex container: `<span className="creator-detail__topic-pills">` and add CSS in `frontend/src/App.css`:
|
||||||
|
```css
|
||||||
|
.creator-detail__topic-pills {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.375rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Remove the old `.creator-detail__topic-stat` class from App.css if it exists (or leave it — no harm).
|
||||||
|
|
||||||
|
5. Verify: `cd frontend && npx tsc --noEmit && npm run build`
|
||||||
|
- Estimate: 20m
|
||||||
|
- Files: frontend/src/pages/CreatorDetail.tsx, frontend/src/App.css
|
||||||
|
- Verify: cd frontend && npx tsc --noEmit && npm run build
|
||||||
|
- [ ] **T03: TagList component, tag overflow limit, and empty subtopic handling** — Create a shared TagList component for tag overflow (R027), apply it across all 5 tag-rendering sites, and add empty subtopic handling in TopicsBrowse (R028).
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. Create `frontend/src/components/TagList.tsx`:
|
||||||
|
```tsx
|
||||||
|
interface TagListProps {
|
||||||
|
tags: string[];
|
||||||
|
max?: number;
|
||||||
|
}
|
||||||
|
export default function TagList({ tags, max = 4 }: TagListProps) {
|
||||||
|
const visible = tags.slice(0, max);
|
||||||
|
const overflow = tags.length - max;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{visible.map(tag => <span key={tag} className="pill">{tag}</span>)}
|
||||||
|
{overflow > 0 && <span className="pill pill--overflow">+{overflow} more</span>}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Add `.pill--overflow` CSS in `frontend/src/App.css`:
|
||||||
|
```css
|
||||||
|
.pill--overflow {
|
||||||
|
background: var(--color-surface-2);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Replace tag rendering in all 5 sites with `<TagList tags={...} />`:
|
||||||
|
- `frontend/src/pages/Home.tsx` line ~201 (featured technique tags) — replace `featured.topic_tags.map(...)` with `<TagList tags={featured.topic_tags} />`
|
||||||
|
- `frontend/src/pages/Home.tsx` line ~247 (recent technique card tags) — replace `t.topic_tags.map(...)` with `<TagList tags={t.topic_tags} />`
|
||||||
|
- `frontend/src/pages/SearchResults.tsx` line ~145 — replace `item.topic_tags.map(...)` with `<TagList tags={item.topic_tags} />`
|
||||||
|
- `frontend/src/pages/SubTopicPage.tsx` line ~149 — replace `t.topic_tags.map(...)` with `<TagList tags={t.topic_tags} />`
|
||||||
|
- `frontend/src/pages/CreatorDetail.tsx` line ~157 — replace `t.topic_tags.map(...)` with `<TagList tags={t.topic_tags} />`
|
||||||
|
Each site: add `import TagList from "../components/TagList";` and replace the `.map(tag => <span className="pill">{tag}</span>)` with `<TagList tags={...} />`.
|
||||||
|
|
||||||
|
4. In `frontend/src/pages/TopicsBrowse.tsx`, update the subtopic rendering inside the `.topic-subtopics` div. For subtopics with `st.technique_count === 0`:
|
||||||
|
- Render as a `<span>` (not `<Link>`) with class `topic-subtopic topic-subtopic--empty` instead of a clickable link
|
||||||
|
- Add a small "Coming soon" badge: `<span className="pill pill--coming-soon">Coming soon</span>`
|
||||||
|
- Keep the subtopic name visible so users know the topic exists
|
||||||
|
Use a conditional: `st.technique_count === 0 ? <span className="topic-subtopic topic-subtopic--empty">...</span> : <Link ...>...</Link>`
|
||||||
|
|
||||||
|
5. Add CSS for empty subtopic in `frontend/src/App.css`:
|
||||||
|
```css
|
||||||
|
.topic-subtopic--empty {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
.topic-subtopic--empty:hover {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
.pill--coming-soon {
|
||||||
|
font-size: 0.65rem;
|
||||||
|
background: var(--color-surface-2);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
6. Verify: `cd frontend && npx tsc --noEmit && npm run build`
|
||||||
|
- Estimate: 40m
|
||||||
|
- Files: frontend/src/components/TagList.tsx, frontend/src/pages/Home.tsx, frontend/src/pages/SearchResults.tsx, frontend/src/pages/SubTopicPage.tsx, frontend/src/pages/CreatorDetail.tsx, frontend/src/pages/TopicsBrowse.tsx, frontend/src/App.css
|
||||||
|
- Verify: cd frontend && npx tsc --noEmit && npm run build
|
||||||
|
|
|
||||||
96
.gsd/milestones/M011/slices/S02/S02-RESEARCH.md
Normal file
96
.gsd/milestones/M011/slices/S02/S02-RESEARCH.md
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
# S02 Research: Topics, Creator Stats & Card Polish
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Straightforward frontend-only changes across 5–6 files. All patterns (CSS color system, catSlug utility, badge classes, expand/collapse state) already exist. No new dependencies, no backend changes, no unfamiliar technology.
|
||||||
|
|
||||||
|
## Targeted Requirements
|
||||||
|
|
||||||
|
| Req | Description | Status |
|
||||||
|
|-----|-------------|--------|
|
||||||
|
| R019 | Topics page default-collapsed with expand animation | Active — this slice |
|
||||||
|
| R026 | Creator stats topic-colored pills | Active — this slice |
|
||||||
|
| R027 | Tag overflow limit (4 + "+N more") | Active — this slice |
|
||||||
|
| R028 | Empty subtopic handling | Active — this slice |
|
||||||
|
|
||||||
|
## Implementation Landscape
|
||||||
|
|
||||||
|
### 1. Topics Default-Collapsed (R019)
|
||||||
|
|
||||||
|
**File:** `frontend/src/pages/TopicsBrowse.tsx`
|
||||||
|
|
||||||
|
- **Current:** Line ~42: `setExpanded(new Set(data.map((c) => c.name)))` — starts all-expanded.
|
||||||
|
- **Change:** `setExpanded(new Set())` — starts all-collapsed.
|
||||||
|
- **Animation:** The subtopics container (`div.topic-subtopics`) currently renders conditionally (`{isExpanded && ...}`). For smooth expand/collapse, replace the conditional render with CSS `max-height` + `overflow: hidden` + `transition`. Use a wrapper with `max-height: 0` when collapsed and `max-height: <computed>px` when expanded, or use CSS `grid-template-rows: 0fr / 1fr` technique (better, no fixed height needed).
|
||||||
|
- **CSS file:** `frontend/src/App.css` — `.topic-subtopics` starts at line 2318. Add transition properties.
|
||||||
|
|
||||||
|
**Recommendation:** Use the CSS `display: grid; grid-template-rows: 0fr → 1fr` pattern for the smoothest transition without needing to know content height. Wrap subtopics in a grid container that transitions `grid-template-rows`.
|
||||||
|
|
||||||
|
### 2. Creator Stats Colored Pills (R026)
|
||||||
|
|
||||||
|
**File:** `frontend/src/pages/CreatorDetail.tsx`
|
||||||
|
|
||||||
|
- **Current:** Lines ~97–110: Stats render as `<span className="creator-detail__topic-stat">Cat: N</span>` with dot separators.
|
||||||
|
- **Change:** Replace with `<span className="badge badge--cat-{slug}">{cat}: {count}</span>` using `catSlug()` from `../utils/catSlug`.
|
||||||
|
- **Existing CSS:** `.badge--cat-sound-design`, `.badge--cat-mixing`, etc. already defined in App.css with bg/text color pairs for all 7 categories.
|
||||||
|
- **Import needed:** `import { catSlug } from "../utils/catSlug";` (not currently imported in CreatorDetail.tsx).
|
||||||
|
- **Layout:** Replace the run-on text with a flex-wrap container holding pill badges.
|
||||||
|
|
||||||
|
### 3. Tag Overflow Limit (R027)
|
||||||
|
|
||||||
|
**Files affected (5 tag-rendering sites):**
|
||||||
|
|
||||||
|
| File | Line | Context |
|
||||||
|
|------|------|---------|
|
||||||
|
| `frontend/src/pages/Home.tsx` | ~201 | Featured technique tags |
|
||||||
|
| `frontend/src/pages/Home.tsx` | ~247 | Recent technique card tags |
|
||||||
|
| `frontend/src/pages/SearchResults.tsx` | ~143 | Search result card tags |
|
||||||
|
| `frontend/src/pages/SubTopicPage.tsx` | ~147 | Sub-topic technique card tags |
|
||||||
|
| `frontend/src/pages/CreatorDetail.tsx` | ~108 | Creator technique card tags |
|
||||||
|
|
||||||
|
**Pattern:** All 5 sites use the same idiom:
|
||||||
|
```tsx
|
||||||
|
{t.topic_tags.map(tag => <span className="pill">{tag}</span>)}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Change:** Extract a shared helper or inline the pattern:
|
||||||
|
```tsx
|
||||||
|
{tags.slice(0, 4).map(tag => <span className="pill">{tag}</span>)}
|
||||||
|
{tags.length > 4 && <span className="pill pill--overflow">+{tags.length - 4} more</span>}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Recommendation:** Create a small `TagList` component in `frontend/src/components/TagList.tsx` to DRY up the 5 sites. Props: `tags: string[]`, `max?: number` (default 4). The component renders pills with overflow. This avoids repeating the slice/overflow logic 5 times.
|
||||||
|
|
||||||
|
### 4. Empty Subtopic Handling (R028)
|
||||||
|
|
||||||
|
**File:** `frontend/src/pages/TopicsBrowse.tsx`
|
||||||
|
|
||||||
|
- **Current:** All subtopics render as clickable links regardless of `technique_count`.
|
||||||
|
- **Change:** When `st.technique_count === 0`, render as a non-clickable element with a "Coming soon" badge instead of a link. Keep it visible (don't hide) so users know the topic exists.
|
||||||
|
- **CSS:** Add a `.topic-subtopic--empty` modifier class with muted styling and a small "Coming soon" badge.
|
||||||
|
|
||||||
|
## Key Observations
|
||||||
|
|
||||||
|
1. **CSS color system is complete** — all 7 category color pairs exist as CSS custom properties and badge modifier classes. No new colors needed for creator stats pills.
|
||||||
|
2. **`catSlug()` utility exists** — converts category names to CSS-safe slugs (lowercase, spaces→hyphens). Already used in TopicsBrowse.tsx.
|
||||||
|
3. **S01 added `card-stagger` animation** — `@keyframes cardEnter` and `.card-stagger` class with `--stagger-index` CSS variable. Already applied to topic cards and creator technique cards.
|
||||||
|
4. **No existing expand/collapse animation** — subtopics use conditional rendering (`{isExpanded && ...}`), not CSS transitions. Needs refactoring for smooth animation.
|
||||||
|
5. **`grid-template-rows` transition** is well-supported (Chrome 57+, Firefox 66+, Safari 16+) and doesn't require knowing content height.
|
||||||
|
|
||||||
|
## Natural Task Seams
|
||||||
|
|
||||||
|
1. **Topics collapse + animation (R019)** — Self-contained in TopicsBrowse.tsx + App.css. Change default state, add CSS grid transition.
|
||||||
|
2. **Creator stats pills (R026)** — Self-contained in CreatorDetail.tsx + App.css. Import catSlug, replace text with badges.
|
||||||
|
3. **Tag overflow + empty subtopics (R027, R028)** — Create TagList component, update 5 files for tag overflow, update TopicsBrowse.tsx for empty subtopics. These can be one task since TagList is the shared piece.
|
||||||
|
|
||||||
|
**Risk ordering:** Topics collapse/animation is slightly riskier (CSS transition refactor), so do it first. The others are mechanical.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
- `cd frontend && npx tsc --noEmit` — TypeScript compiles clean
|
||||||
|
- `cd frontend && npm run build` — Vite production build succeeds
|
||||||
|
- Browser verification at http://ub01:8096:
|
||||||
|
- Topics page loads collapsed, click expands with animation
|
||||||
|
- Creator detail shows colored pills for topic stats
|
||||||
|
- Cards with >4 tags show exactly 4 + "+N more"
|
||||||
|
- Empty subtopics show "Coming soon" badge
|
||||||
40
.gsd/milestones/M011/slices/S02/tasks/T01-PLAN.md
Normal file
40
.gsd/milestones/M011/slices/S02/tasks/T01-PLAN.md
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
---
|
||||||
|
estimated_steps: 13
|
||||||
|
estimated_files: 2
|
||||||
|
skills_used: []
|
||||||
|
---
|
||||||
|
|
||||||
|
# T01: Topics page default-collapsed with expand/collapse animation
|
||||||
|
|
||||||
|
Change TopicsBrowse.tsx to start with all categories collapsed and add smooth CSS grid-template-rows animation for expand/collapse transitions.
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. In `frontend/src/pages/TopicsBrowse.tsx`, change the `useEffect` that calls `setExpanded(new Set(data.map((c) => c.name)))` to `setExpanded(new Set())` so all categories start collapsed.
|
||||||
|
|
||||||
|
2. Refactor the subtopics rendering from conditional `{isExpanded && (<div className="topic-subtopics">...)}` to always-rendered with a CSS grid animation wrapper. Use the `display: grid; grid-template-rows: 0fr / 1fr` technique:
|
||||||
|
- Wrap the subtopics in a `<div className="topic-subtopics-wrapper" data-expanded={isExpanded}>` that has `display: grid; grid-template-rows: 0fr; transition: grid-template-rows 300ms ease;`
|
||||||
|
- When `data-expanded="true"`, set `grid-template-rows: 1fr`
|
||||||
|
- The inner `<div className="topic-subtopics">` gets `overflow: hidden; min-height: 0;`
|
||||||
|
- Always render both wrapper and inner div (remove the conditional render)
|
||||||
|
|
||||||
|
3. Add the CSS in `frontend/src/App.css` near the existing `.topic-subtopics` rule (~line 2318):
|
||||||
|
- `.topic-subtopics-wrapper { display: grid; grid-template-rows: 0fr; transition: grid-template-rows 300ms ease; }`
|
||||||
|
- `.topic-subtopics-wrapper[data-expanded="true"] { grid-template-rows: 1fr; }`
|
||||||
|
- `.topic-subtopics { overflow: hidden; min-height: 0; }` (modify existing rule)
|
||||||
|
|
||||||
|
4. Verify: `cd frontend && npx tsc --noEmit && npm run build`
|
||||||
|
|
||||||
|
## Inputs
|
||||||
|
|
||||||
|
- `frontend/src/pages/TopicsBrowse.tsx`
|
||||||
|
- `frontend/src/App.css`
|
||||||
|
|
||||||
|
## Expected Output
|
||||||
|
|
||||||
|
- `frontend/src/pages/TopicsBrowse.tsx`
|
||||||
|
- `frontend/src/App.css`
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
cd frontend && npx tsc --noEmit && npm run build
|
||||||
76
.gsd/milestones/M011/slices/S02/tasks/T01-SUMMARY.md
Normal file
76
.gsd/milestones/M011/slices/S02/tasks/T01-SUMMARY.md
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
---
|
||||||
|
id: T01
|
||||||
|
parent: S02
|
||||||
|
milestone: M011
|
||||||
|
provides: []
|
||||||
|
requires: []
|
||||||
|
affects: []
|
||||||
|
key_files: ["frontend/src/pages/TopicsBrowse.tsx", "frontend/src/App.css"]
|
||||||
|
key_decisions: ["Used CSS grid-template-rows 0fr/1fr technique for smooth height animation without JS measurement"]
|
||||||
|
patterns_established: []
|
||||||
|
drill_down_paths: []
|
||||||
|
observability_surfaces: []
|
||||||
|
duration: ""
|
||||||
|
verification_result: "TypeScript type check (npx tsc --noEmit) and production build (npm run build) both pass cleanly with exit code 0."
|
||||||
|
completed_at: 2026-03-31T08:30:49.223Z
|
||||||
|
blocker_discovered: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# T01: Topics page loads with all categories collapsed; expand/collapse uses smooth CSS grid-template-rows animation instead of conditional render
|
||||||
|
|
||||||
|
> Topics page loads with all categories collapsed; expand/collapse uses smooth CSS grid-template-rows animation instead of conditional render
|
||||||
|
|
||||||
|
## What Happened
|
||||||
|
---
|
||||||
|
id: T01
|
||||||
|
parent: S02
|
||||||
|
milestone: M011
|
||||||
|
key_files:
|
||||||
|
- frontend/src/pages/TopicsBrowse.tsx
|
||||||
|
- frontend/src/App.css
|
||||||
|
key_decisions:
|
||||||
|
- Used CSS grid-template-rows 0fr/1fr technique for smooth height animation without JS measurement
|
||||||
|
duration: ""
|
||||||
|
verification_result: passed
|
||||||
|
completed_at: 2026-03-31T08:30:49.224Z
|
||||||
|
blocker_discovered: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# T01: Topics page loads with all categories collapsed; expand/collapse uses smooth CSS grid-template-rows animation instead of conditional render
|
||||||
|
|
||||||
|
**Topics page loads with all categories collapsed; expand/collapse uses smooth CSS grid-template-rows animation instead of conditional render**
|
||||||
|
|
||||||
|
## What Happened
|
||||||
|
|
||||||
|
Changed the useEffect in TopicsBrowse.tsx to initialize the expanded set as empty instead of pre-filling all category names. Replaced the conditional {isExpanded && (...)} render with an always-rendered wrapper div using the grid-template-rows: 0fr/1fr animation technique. Added CSS rules for the wrapper, expanded state, and updated the inner div with overflow: hidden and min-height: 0 for smooth clipping.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
TypeScript type check (npx tsc --noEmit) and production build (npm run build) both pass cleanly with exit code 0.
|
||||||
|
|
||||||
|
## Verification Evidence
|
||||||
|
|
||||||
|
| # | Command | Exit Code | Verdict | Duration |
|
||||||
|
|---|---------|-----------|---------|----------|
|
||||||
|
| 1 | `cd frontend && npx tsc --noEmit && npm run build` | 0 | ✅ pass | 3700ms |
|
||||||
|
|
||||||
|
|
||||||
|
## Deviations
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
## Known Issues
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
|
||||||
|
- `frontend/src/pages/TopicsBrowse.tsx`
|
||||||
|
- `frontend/src/App.css`
|
||||||
|
|
||||||
|
|
||||||
|
## Deviations
|
||||||
|
None.
|
||||||
|
|
||||||
|
## Known Issues
|
||||||
|
None.
|
||||||
52
.gsd/milestones/M011/slices/S02/tasks/T02-PLAN.md
Normal file
52
.gsd/milestones/M011/slices/S02/tasks/T02-PLAN.md
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
---
|
||||||
|
estimated_steps: 23
|
||||||
|
estimated_files: 2
|
||||||
|
skills_used: []
|
||||||
|
---
|
||||||
|
|
||||||
|
# T02: Creator stats topic-colored pill badges
|
||||||
|
|
||||||
|
Replace the run-on text stats on CreatorDetail page with colored pill badges using existing badge CSS classes.
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. In `frontend/src/pages/CreatorDetail.tsx`, add import: `import { catSlug } from "../utils/catSlug";`
|
||||||
|
|
||||||
|
2. Find the stats section (~line 108-126) that renders `<span className="creator-detail__topic-stat">` with dot separators. Replace the entire `.map(([cat, count], i) => ...)` block with pills:
|
||||||
|
```tsx
|
||||||
|
.map(([cat, count]) => (
|
||||||
|
<span key={cat} className={`badge badge--cat-${catSlug(cat)}`}>
|
||||||
|
{cat}: {count}
|
||||||
|
</span>
|
||||||
|
))
|
||||||
|
```
|
||||||
|
Remove the dot separator spans (`queue-card__separator`) between them.
|
||||||
|
|
||||||
|
3. Wrap the topic stat pills in a flex container: `<span className="creator-detail__topic-pills">` and add CSS in `frontend/src/App.css`:
|
||||||
|
```css
|
||||||
|
.creator-detail__topic-pills {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.375rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Remove the old `.creator-detail__topic-stat` class from App.css if it exists (or leave it — no harm).
|
||||||
|
|
||||||
|
5. Verify: `cd frontend && npx tsc --noEmit && npm run build`
|
||||||
|
|
||||||
|
## Inputs
|
||||||
|
|
||||||
|
- `frontend/src/pages/CreatorDetail.tsx`
|
||||||
|
- `frontend/src/App.css`
|
||||||
|
- `frontend/src/utils/catSlug.ts`
|
||||||
|
|
||||||
|
## Expected Output
|
||||||
|
|
||||||
|
- `frontend/src/pages/CreatorDetail.tsx`
|
||||||
|
- `frontend/src/App.css`
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
cd frontend && npx tsc --noEmit && npm run build
|
||||||
94
.gsd/milestones/M011/slices/S02/tasks/T03-PLAN.md
Normal file
94
.gsd/milestones/M011/slices/S02/tasks/T03-PLAN.md
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
---
|
||||||
|
estimated_steps: 56
|
||||||
|
estimated_files: 7
|
||||||
|
skills_used: []
|
||||||
|
---
|
||||||
|
|
||||||
|
# T03: TagList component, tag overflow limit, and empty subtopic handling
|
||||||
|
|
||||||
|
Create a shared TagList component for tag overflow (R027), apply it across all 5 tag-rendering sites, and add empty subtopic handling in TopicsBrowse (R028).
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. Create `frontend/src/components/TagList.tsx`:
|
||||||
|
```tsx
|
||||||
|
interface TagListProps {
|
||||||
|
tags: string[];
|
||||||
|
max?: number;
|
||||||
|
}
|
||||||
|
export default function TagList({ tags, max = 4 }: TagListProps) {
|
||||||
|
const visible = tags.slice(0, max);
|
||||||
|
const overflow = tags.length - max;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{visible.map(tag => <span key={tag} className="pill">{tag}</span>)}
|
||||||
|
{overflow > 0 && <span className="pill pill--overflow">+{overflow} more</span>}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Add `.pill--overflow` CSS in `frontend/src/App.css`:
|
||||||
|
```css
|
||||||
|
.pill--overflow {
|
||||||
|
background: var(--color-surface-2);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Replace tag rendering in all 5 sites with `<TagList tags={...} />`:
|
||||||
|
- `frontend/src/pages/Home.tsx` line ~201 (featured technique tags) — replace `featured.topic_tags.map(...)` with `<TagList tags={featured.topic_tags} />`
|
||||||
|
- `frontend/src/pages/Home.tsx` line ~247 (recent technique card tags) — replace `t.topic_tags.map(...)` with `<TagList tags={t.topic_tags} />`
|
||||||
|
- `frontend/src/pages/SearchResults.tsx` line ~145 — replace `item.topic_tags.map(...)` with `<TagList tags={item.topic_tags} />`
|
||||||
|
- `frontend/src/pages/SubTopicPage.tsx` line ~149 — replace `t.topic_tags.map(...)` with `<TagList tags={t.topic_tags} />`
|
||||||
|
- `frontend/src/pages/CreatorDetail.tsx` line ~157 — replace `t.topic_tags.map(...)` with `<TagList tags={t.topic_tags} />`
|
||||||
|
Each site: add `import TagList from "../components/TagList";` and replace the `.map(tag => <span className="pill">{tag}</span>)` with `<TagList tags={...} />`.
|
||||||
|
|
||||||
|
4. In `frontend/src/pages/TopicsBrowse.tsx`, update the subtopic rendering inside the `.topic-subtopics` div. For subtopics with `st.technique_count === 0`:
|
||||||
|
- Render as a `<span>` (not `<Link>`) with class `topic-subtopic topic-subtopic--empty` instead of a clickable link
|
||||||
|
- Add a small "Coming soon" badge: `<span className="pill pill--coming-soon">Coming soon</span>`
|
||||||
|
- Keep the subtopic name visible so users know the topic exists
|
||||||
|
Use a conditional: `st.technique_count === 0 ? <span className="topic-subtopic topic-subtopic--empty">...</span> : <Link ...>...</Link>`
|
||||||
|
|
||||||
|
5. Add CSS for empty subtopic in `frontend/src/App.css`:
|
||||||
|
```css
|
||||||
|
.topic-subtopic--empty {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
.topic-subtopic--empty:hover {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
.pill--coming-soon {
|
||||||
|
font-size: 0.65rem;
|
||||||
|
background: var(--color-surface-2);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
6. Verify: `cd frontend && npx tsc --noEmit && npm run build`
|
||||||
|
|
||||||
|
## Inputs
|
||||||
|
|
||||||
|
- `frontend/src/pages/Home.tsx`
|
||||||
|
- `frontend/src/pages/SearchResults.tsx`
|
||||||
|
- `frontend/src/pages/SubTopicPage.tsx`
|
||||||
|
- `frontend/src/pages/CreatorDetail.tsx`
|
||||||
|
- `frontend/src/pages/TopicsBrowse.tsx`
|
||||||
|
- `frontend/src/App.css`
|
||||||
|
|
||||||
|
## Expected Output
|
||||||
|
|
||||||
|
- `frontend/src/components/TagList.tsx`
|
||||||
|
- `frontend/src/pages/Home.tsx`
|
||||||
|
- `frontend/src/pages/SearchResults.tsx`
|
||||||
|
- `frontend/src/pages/SubTopicPage.tsx`
|
||||||
|
- `frontend/src/pages/CreatorDetail.tsx`
|
||||||
|
- `frontend/src/pages/TopicsBrowse.tsx`
|
||||||
|
- `frontend/src/App.css`
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
cd frontend && npx tsc --noEmit && npm run build
|
||||||
|
|
@ -2315,7 +2315,19 @@ a.app-footer__repo:hover {
|
||||||
|
|
||||||
/* ── Sub-topics inside card ───────────────────────────────────────────────── */
|
/* ── Sub-topics inside card ───────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
.topic-subtopics-wrapper {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 0fr;
|
||||||
|
transition: grid-template-rows 300ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topic-subtopics-wrapper[data-expanded="true"] {
|
||||||
|
grid-template-rows: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
.topic-subtopics {
|
.topic-subtopics {
|
||||||
|
overflow: hidden;
|
||||||
|
min-height: 0;
|
||||||
border-top: 1px solid var(--color-border);
|
border-top: 1px solid var(--color-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,8 @@ export default function TopicsBrowse() {
|
||||||
const data = await fetchTopics();
|
const data = await fetchTopics();
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
setCategories(data);
|
setCategories(data);
|
||||||
// All expanded by default
|
// Start collapsed
|
||||||
setExpanded(new Set(data.map((c) => c.name)));
|
setExpanded(new Set());
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
|
|
@ -151,7 +151,7 @@ export default function TopicsBrowse() {
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isExpanded && (
|
<div className="topic-subtopics-wrapper" data-expanded={isExpanded}>
|
||||||
<div className="topic-subtopics">
|
<div className="topic-subtopics">
|
||||||
{cat.sub_topics.map((st) => {
|
{cat.sub_topics.map((st) => {
|
||||||
const stSlug = st.name.toLowerCase().replace(/\s+/g, "-");
|
const stSlug = st.name.toLowerCase().replace(/\s+/g, "-");
|
||||||
|
|
@ -175,7 +175,7 @@ export default function TopicsBrowse() {
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue