| name | design-component |
| description | Design Component skill for Empathy Ledger. Use for storyteller cards, story cards, profile displays, and any UI component requiring cultural sensitivity. Provides data mapping, AI enrichment patterns, and design system guidelines. |
Design Component Skill
This skill provides comprehensive guidance for designing and implementing UI components in Empathy Ledger, with special focus on storyteller cards, data display patterns, and AI-powered content enrichment.
Design System Foundation
Color Palette (CSS Variables)
--background
--foreground
--card
--card-foreground
--muted
--muted-foreground
--popover
--border
--primary
--accent
--destructive
Cultural Color Meanings
| Color | Meaning | Usage |
|---|
| Amber/Gold | Elder wisdom, featured | Elder badges, featured indicators |
| Emerald | Growth, community | Story counts, active status |
| Purple | Sacred, knowledge | Knowledge keeper badges |
| Terracotta | Earth, connection | Cultural affiliations |
| Sage | Calm, respectful | General UI elements |
Storyteller Card Data Model
Core Display Fields
interface StorytellerCardData {
id: string
display_name: string
avatar_url?: string
pronouns?: string
cultural_background?: string
cultural_affiliations?: string[]
traditional_territory?: string
languages_spoken?: string[]
is_elder: boolean
is_featured: boolean
status: 'active' | 'inactive' | 'pending'
traditional_knowledge_keeper?: boolean
story_count: number
featured_quote?: string
expertise_themes?: string[]
occupation?: string
years_of_experience?: number
specialties?: string[]
ai_summary?: string
theme_expertise?: string[]
connection_strength?:
?: []
}
Data Priority Hierarchy
TIER 1 - Always Display:
├── display_name
├── avatar (or initials fallback)
├── cultural_background
└── story_count
TIER 2 - Show on Card:
├── elder_status badge
├── featured badge
├── top 3 specialties
├── primary location
└── featured_quote (if available)
TIER 3 - Show on Hover/Expand:
├── full bio
├── all specialties
├── languages
├── organisations
└── theme expertise
TIER 4 - Profile Page Only:
├── contact info
├── full story list
├── connection graph
└── detailed analytics
Card Variants
Default Card
<StorytellerCard
storyteller={storyteller}
variant="default"
showStories={true}
showActions={true}
/>
- 320px min width, responsive
- Avatar, name, cultural background
- Story count, specialties (max 3)
- Elder/Featured badges
- Hover effect with arrow
Compact Card
<StorytellerCard
storyteller={storyteller}
variant="compact"
/>
- 280px width, inline layout
- Avatar (smaller), name only
- Story count as badge
- Good for sidebars, lists
Featured Card
<StorytellerCard
storyteller={storyteller}
variant="featured"
/>
- Full width, larger avatar
- Featured quote displayed
- Theme expertise badges
- Gradient background
- Enhanced hover animations
List View Row
<StorytellerListCard storyteller={storyteller} />
- Full width horizontal
- More data visible
- Action buttons on right
- Good for admin views
AI Enrichment Opportunities
1. Bio Enhancement
interface BioEnhancement {
original_bio: string
enhanced_bio: string
key_themes: string[]
suggested_specialties: string[]
cultural_keywords: string[]
}
2. Featured Quote Extraction
interface QuoteExtraction {
quotes: Array<{
text: string
story_id: string
themes: string[]
impact_score: number
}>
suggested_featured: string
}
3. Theme Expertise Analysis
interface ThemeExpertise {
primary_themes: string[]
secondary_themes: string[]
unique_perspective: string
theme_depth_scores: Record<string, number>
}
4. Connection Suggestions
interface ConnectionSuggestion {
storyteller_id: string
connection_type: 'theme_overlap' | 'geographic' | 'community'
overlap_score: number
reason: string
}
5. Summary Generation
interface StoritellerSummary {
one_liner: string
card_summary: string
full_summary: string
voice_style: string
}
Component Implementation Patterns
Avatar with Status Indicators
<div className="relative">
<Avatar
src={storyteller.avatar_url}
fallback={getInitials(storyteller.display_name)}
className="w-16 h-16 border-2 border-background shadow-md"
/>
{}
<div className="absolute -top-1 -right-1 flex gap-1">
{storyteller.is_featured && (
<Badge variant="featured" size="icon">
<Star className="w-3 h-3" />
</Badge>
)}
{storyteller.is_elder && (
<Badge variant="elder" size="icon">
<Crown className="w-3 h-3" />
</Badge>
)}
</div>
</div>
Cultural Background Display
{}
<div className="flex items-center gap-2 text-muted-foreground">
<MapPin className="w-4 h-4 text-terracotta-500" />
<span className="text-sm">
{storyteller.cultural_background}
{storyteller.traditional_territory && (
<span className="text-xs ml-1">
({storyteller.traditional_territory})
</span>
)}
</span>
</div>
Story Metrics Display
<div className="flex items-center gap-4">
<div className="flex items-center gap-1.5">
<BookOpen className="w-4 h-4 text-emerald-500" />
<span className="font-semibold">{storyteller.story_count}</span>
<span className="text-muted-foreground text-sm">
{storyteller.story_count === 1 ? 'Story' : 'Stories'}
</span>
</div>
{storyteller.years_of_experience && (
<div className="flex items-center gap-1.5">
<Calendar className="w-4 h-4 text-blue-500" />
<span className="font-semibold">{storyteller.years_of_experience}</span>
<span className="text-muted-foreground text-sm">Years</span>
</div>
)}
</div>
Theme/Specialty Badges
{}
<div className="flex flex-wrap gap-1.5">
{storyteller.specialties?.slice(0, 3).map((specialty, i) => (
<Badge
key={specialty}
variant="secondary"
className={cn(
"text-xs",
specialtyColors[i % specialtyColors.length]
)}
>
{specialty}
</Badge>
))}
{(storyteller.specialties?.length || 0) > 3 && (
<Badge variant="outline" className="text-xs text-muted-foreground">
+{storyteller.specialties!.length - 3} more
</Badge>
)}
</div>
Featured Quote Display
{storyteller.featured_quote && (
<div className="mt-4 p-3 bg-muted/50 rounded-lg border-l-2 border-accent">
<Quote className="w-4 h-4 text-accent mb-1" />
<p className="text-sm italic text-foreground/80 line-clamp-2">
"{storyteller.featured_quote}"
</p>
</div>
)}
AI Enrichment API Patterns
Trigger Enrichment
async function enrichStoryteller(storytellerId: string) {
const response = await fetch(`/api/storytellers/${storytellerId}/enrich`, {
method: 'POST',
body: JSON.stringify({
enrich_bio: true,
extract_quotes: true,
analyze_themes: true,
suggest_connections: true
})
})
return response.json()
}
Display Enriched Data
const hasAIData = storyteller.ai_summary || storyteller.theme_expertise?.length
{hasAIData && (
<div className="flex items-center gap-1 text-xs text-muted-foreground">
<Sparkles className="w-3 h-3 text-accent" />
<span>AI Enhanced</span>
</div>
)}
Database Fields for AI Enrichment
Profiles Table Additions
ALTER TABLE profiles ADD COLUMN ai_summary TEXT;
ALTER TABLE profiles ADD COLUMN theme_expertise TEXT[];
ALTER TABLE profiles ADD COLUMN featured_quote TEXT;
ALTER TABLE profiles ADD COLUMN featured_quote_story_id UUID;
ALTER TABLE profiles ADD COLUMN connection_scores JSONB DEFAULT '{}';
ALTER TABLE profiles ADD COLUMN ai_enrichment_status TEXT DEFAULT 'pending';
ALTER TABLE profiles ADD COLUMN ai_enriched_at TIMESTAMPTZ;
Story Suggestions View
CREATE VIEW storyteller_connections AS
SELECT
p1.id as storyteller_id,
p2.id as connected_storyteller_id,
array_length(
ARRAY(SELECT unnest(p1.theme_expertise) INTERSECT SELECT unnest(p2.theme_expertise)),
1
) as theme_overlap,
p1.cultural_background = p2.cultural_background as same_culture
FROM profiles p1
CROSS JOIN profiles p2
WHERE p1.id != p2.id
AND p1.is_storyteller = true
AND p2.is_storyteller = true;
Component Checklist
Before Creating a Card Component
Cultural Sensitivity
AI Enrichment
Reference Components
| Component | Location | Purpose |
|---|
StorytellerCard | src/components/storyteller/storyteller-card.tsx | Main card component |
UnifiedStorytellerCard | src/components/storyteller/unified-storyteller-card.tsx | Flexible card variants |
ElegantStorytellerCard | src/components/storyteller/elegant-storyteller-card.tsx | Premium design variant |
StoryCard | src/components/story/story-card.tsx | Story display card |
QuoteCard | src/components/ui/quote-card.tsx | Quote display |
ThemeBadge | src/components/ui/theme-badge.tsx | Theme display |
Syndication Dashboard Design Patterns (NEW - Sprint 4)
Consent Status Badge
Purpose: Visual indicator of syndication consent status
Variants:
type ConsentStatus = 'approved' | 'pending' | 'revoked' | 'expired'
const statusConfig = {
approved: { color: 'sage', icon: CheckCircle, label: 'Active' },
pending: { color: 'amber', icon: Clock, label: 'Pending' },
revoked: { color: 'ember', icon: XCircle, label: 'Revoked' },
expired: { color: 'muted', icon: AlertCircle, label: 'Expired' }
}
Design:
<Badge className={cn(
"flex items-center gap-1.5",
status === 'approved' && "bg-sage-100 text-sage-900",
status === 'pending' && "bg-amber-100 text-amber-900",
status === 'revoked' && "bg-ember-100 text-ember-900",
status === 'expired' && "bg-muted text-muted-foreground"
)}>
<Icon className="h-3 w-3" />
{label}
</Badge>
Consent Card Layout
Purpose: Display individual syndication consent with site info and controls
Structure:
┌─────────────────────────────────────────────┐
│ [Site Logo] JusticeHub │
│ justicehub.org.au │
│ │
│ Status: [Active Badge] │
│ Cultural Level: Public │
│ Created: Jan 5, 2026 │
│ │
│ 📊 456 views • Last accessed 2 hours ago │
│ │
│ [View Analytics] [Revoke Access] │
└─────────────────────────────────────────────┘
Colors:
- Card Border: Use site brand color (if provided)
- Status Badge: Follow status config above
- Cultural Level: Use cultural color (clay/sage/sky)
- Actions: Primary (sage) for analytics, destructive (ember) for revoke
Embed Token Display
Purpose: Show token with security-conscious masking
Pattern:
<div className="flex items-center gap-2">
<code className="flex-1 px-3 py-2 bg-muted rounded font-mono text-sm">
{tokenMasked ? 'LRK••••••••••XA' : token}
</code>
<Button variant="ghost" size="sm" onClick={() => setTokenMasked(!tokenMasked)}>
{tokenMasked ? <Eye className="h-4 w-4" /> : <EyeOff className="h-4 w-4" />}
</Button>
<Button variant="ghost" size="sm" onClick={handleCopy}>
<Copy className="h-4 w-4" />
</Button>
</div>
Security:
- Default: Token masked
- Click to reveal (temporary, 10 seconds)
- Copy button with confirmation toast
- Never log unmasked tokens
Analytics Chart Styling
Purpose: Consistent chart design across dashboards
Colors (Recharts):
const siteColors = {
justicehub: '#C85A54',
actfarm: '#6B8E72',
theharvest: '#D97757',
actplacemat: '#4A90A4'
}
<Line
type="monotone"
dataKey="justicehub"
stroke={siteColors.justicehub}
strokeWidth={2}
dot={{ fill: siteColors.justicehub, r: 4 }}
activeDot={{ r: 6 }}
/>
Grid & Axes:
<CartesianGrid strokeDasharray="3 3" stroke="hsl(var(--border))" />
<XAxis
dataKey="date"
stroke="hsl(var(--muted-foreground))"
fontSize={12}
/>
<YAxis
stroke="hsl(var(--muted-foreground))"
fontSize={12}
/>
Revocation Dialog
Purpose: Confirm consent revocation with cultural messaging
Layout:
<Dialog>
<DialogHeader>
<DialogTitle className="text-ember-900">
Revoke Consent for JusticeHub?
</DialogTitle>
<DialogDescription>
This will immediately remove JusticeHub's access to your story.
They will no longer be able to display it on their platform.
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div className="rounded-lg bg-sky-50 p-4 border border-sky-200">
<p className="text-sm text-sky-900 font-medium">
✨ You maintain full control
</p>
<p className="text-sm text-sky-700 mt-1">
Your story remains on Empathy Ledger. You can grant consent
again at any time.
</p>
</div>
<div className="space-y-2">
<Label htmlFor="reason">Reason for revoking (optional)</Label>
<Textarea
id="reason"
placeholder=
=
/>
This helps us improve the platform (shared anonymously with
JusticeHub if consent was active).
</>
Messaging Principles:
- ✅ Affirm storyteller control ("You maintain full control")
- ✅ Explain consequences clearly ("immediately remove access")
- ✅ Reassure reversibility ("grant consent again")
- ❌ No guilt-tripping ("Are you sure?")
- ❌ No fear language ("This cannot be undone")
Cultural Permission Level Indicator
Purpose: Show content sensitivity level with cultural context
Levels:
const culturalLevels = {
public: {
color: 'sage',
icon: Globe,
label: 'Public',
description: 'Safe to share widely'
},
community: {
color: 'clay',
icon: Users,
label: 'Community',
description: 'Indigenous communities only'
},
restricted: {
color: 'amber',
icon: Lock,
label: 'Restricted',
description: 'Requires elder approval'
},
sacred: {
color: 'ember',
icon: ShieldAlert,
label: 'Sacred',
description: 'Not for external sharing'
}
}
Component:
<div className="flex items-center gap-2">
<Icon className="h-4 w-4 text-{color}-700" />
<div>
<p className="text-sm font-medium">{label}</p>
<p className="text-xs text-muted-foreground">{description}</p>
</div>
</div>
Empty States for Syndication
No Consents Yet:
<div className="text-center py-12 space-y-4">
<div className="mx-auto w-16 h-16 rounded-full bg-sage-100 flex items-center justify-center">
<Share2 className="h-8 w-8 text-sage-700" />
</div>
<div>
<h3 className="text-lg font-semibold">No syndication consents yet</h3>
<p className="text-muted-foreground mt-1">
Your stories are safe with you. When you're ready to share with
external platforms like JusticeHub, you'll see them here.
</p>
</div>
<Button onClick={handleCreateConsent}>
<Plus className="mr-2 h-4 w-4" />
Share a Story
</Button>
</div>
All Consents Revoked:
<div className="text-center py-12 space-y-4">
<div className="mx-auto w-16 h-16 rounded-full bg-sky-100 flex items-center justify-center">
<ShieldCheck className="h-8 w-8 text-sky-700" />
</div>
<div>
<h3 className="text-lg font-semibold">You're in control</h3>
<p className="text-muted-foreground mt-1">
All your stories have been removed from external platforms.
You can re-share whenever you're ready.
</p>
</div>
</div>
When to Use This Skill
Invoke when:
- Designing new card components
- Adding fields to storyteller/story cards
- Implementing AI enrichment features
- Creating profile displays
- Building list views with storyteller data
- Adding cultural indicators to UI
- Implementing hover/expand states
- Creating loading skeletons
- Designing syndication dashboard UI
- Building consent management interfaces
- Creating analytics visualizations
- Implementing revocation workflows