UI/UX Strategy Board

UI/UX Strategy Board

UI/UX Strategy Board

UI/UX Strategy Board

Building with NoteBookLM and AI Studio, to empower designers to map skills, visualize growth, and optimize resource allocation with an intuitive, data-driven dashboard.

Tools:

NotebookLM +

Google AI Studio

Role:

UX&UI Designer

AI Orchestrator

Date:

Jan 2026

✨ The Result

A functional, responsive web prototype delivered in 1.2 hours!

📝 Executive Summary

In a post-pandemic market saturated with UI/UX designers proficient in Figma but lacking in strategy, I built The Strategy Board. This project is an interactive, AI-powered prototype designed to help designers identify and grow essential soft skills (problem-solving, communication, and strategy).

👁️ What I saw

There are so many people transitioning to UIUX jobs after pandamic. Plenty online courses to teach you how to become a UIUX designer by using which tool.


But the truth is, no one to tell, only when you land on the job and realize knowing those tools is solving just half of the problem. The other half is the soft skills, like problem solving, cross-functional communication, design strategy, and more.


That is the pain that most designs miss from my past 3 years mentorship.


That's why I want to build a strategy board to list the essential skills for a UIUX designer. Furthermore, you can get the AI Coach feedback based on your skill maturity and learning effort, without struggling to know how to apply.

file manager tool
file manager tool
file manager tool

⚠️ The Problem: The "Tool-Only" Trap

Most UI/UX courses focus on the how (Figma, Auto-layout, Components) but ignore the why (Business logic, Cross-functional communication).


The Pain Point:

Designers often land jobs only to realize they lack the strategic framework to solve complex problems..

🤓☝️ The Solution

A platform that categorizes "invisible" skills and provides an AI Coach to give personalized feedback based on a user’s current maturity level.

🎯 The Goal

Prove that AI can compress the discovery-to-prototype cycle without losing human-centric quality.

જ⁀➴ The Tech Stack: An AI-First Workflow

I bypassed the traditional linear design process (Sketching > Wireframing > Prototyping) by using a specialized AI ecosystem:

Loading...

🛠️ The Process: From Data to Interaction

Phase 1: Curating the Signal from the Noise

Phase 1: Curating the Signal from the Noise

Phase 1: Curating the Signal from the Noise

Using NotebookLM, I analyzed industry reports and job descriptions. I deselected low-quality sources to ensure the "AI Coach" wasn't giving generic advice.


💡Key Insight

I realized that "Communication" isn't just one skill—it’s three: Stakeholder Management, Design Advocacy, and Empathy.

Phase 2: Vibe Coding the Prototype

Instead of drawing rectangles in Figma, I used Google AI Studio to generate a functional web prototype. I used natural language to describe the "vibe"—simplistic, intuitive, and light-themed.

Phase 3: The "Human-in-the-Loop" Correction

AI-generated code often misses the "polish" a designer provides. I manually intervened when the AI:

  • Generated redundant buttons that cluttered the UX.

  • Broke the layout on mobile views.

  • Used a dark theme that didn't align with the "approachable coach" brand.

  • Correction: I truncated text for readability and adjusted card heights via code to ensure a clean, rhythmic UI.

import React, { useState, useMemo } from 'react';
import { ScatterChart, Scatter, XAxis, YAxis, ZAxis, Tooltip, ResponsiveContainer, Cell, ReferenceLine, Label } from 'recharts';
import { SKILL_DATABASE } from './constants';
import { SkillCard } from './components/SkillCard';
import { Category, SkillData, StrategicAction } from './types';
import { LayoutGrid, BarChart2, Filter, Info, Layers, X, RefreshCw, ChevronDown, Menu } from 'lucide-react';

// --- Helper Logic for Strategic Action ---
const getStrategicAction = (value: number, maturity: number): StrategicAction => {
  const isHighValue = value >= 4;
  const isMature = maturity >= 3;

  if (isHighValue && !isMature) return StrategicAction.Invest;
  if (isHighValue && isMature) return StrategicAction.Leverage;
  if (!isHighValue && isMature) return StrategicAction.Maintain;
  return StrategicAction.Monitor;
};

const App: React.FC = () => {
  // State
  // Map skill ID to { maturity, effort }
  const [userState, setUserState] = useState<Record<string, { maturity: number, effort: number }>>({});
  
  const [activeCategory, setActiveCategory] = useState<Category | 'All'>('All');
  const [activeAction, setActiveAction] = useState<StrategicAction | 'All'>('All');
  const [viewMode, setViewMode] = useState<'board' | 'matrix'>('board');
  const [isSidebarOpen, setIsSidebarOpen] = useState(false);

  // Computed Data
  const fullSkillData: SkillData[] = useMemo(() => {
    return SKILL_DATABASE.map(skill => {
      const state = userState[skill.id] || { maturity: 1, effort: skill.defaultCost };
      return {
        ...skill,
        maturity: state.maturity,
        effort: state.effort,
        strategicAction: getStrategicAction(skill.defaultValue, state.maturity)
      };
    });
  }, [userState]);

  const filteredSkills = useMemo(() => {
    return fullSkillData.filter(skill => {
      const catMatch = activeCategory === 'All' || skill.category === activeCategory;
      const actionMatch = activeAction === 'All' || skill.strategicAction === activeAction;
      return catMatch && actionMatch;
    });
  }, [fullSkillData, activeCategory, activeAction]);

  const isFiltered = activeCategory !== 'All' || activeAction !== 'All';

  // Handlers
  const handleUpdateMaturity = (id: string, val: number) => {
    setUserState(prev => ({
      ...prev,
      [id]: {
        ...(prev[id] || { effort: SKILL_DATABASE.find(s => s.id === id)?.defaultCost || 3 }),
        maturity: val
      }
    }));
  };

  const handleUpdateEffort = (id: string, val: number) => {
    setUserState(prev => ({
      ...prev,
      [id]: {
        ...(prev[id] || { maturity: 1 }),
        effort: val
      }
    }));
  };

  const handleResetFilters = () => {
    setActiveCategory('All');
    setActiveAction('All');
  };

  // Stats for Dashboard Header
  const stats = useMemo(() => {
    return {
      invest: fullSkillData.filter(s => s.strategicAction === StrategicAction.Invest).length,
      leverage: fullSkillData.filter(s => s.strategicAction === StrategicAction.Leverage).length,
      total: fullSkillData.length
    }
  }, [fullSkillData]);

  // Render Tooltip for Chart
  const CustomTooltip = ({ active, payload }: any) => {
    if (active && payload && payload.length) {
      const data = payload[0].payload;
      return (
        <div className="bg-white border border-slate-200 p-3 rounded-lg shadow-xl text-xs">
          <p className="font-bold text-slate-900">{data.name}</p>
          <p className="text-slate-500 mb-2">{data.subCategory}</p>
          <div className="grid grid-cols-2 gap-x-4 gap-y-1 text-slate-600">
             <span>Val: {data.defaultValue}</span>
             <span>Mat: {data.maturity}</span>
             <span className="col-span-2 text-slate-400">Effort: {data.effort}</span>
          </div>
        </div>
      );
    }
    return null;
  };

  return (
    <div className="flex h-screen bg-slate-50 text-slate-900 overflow-hidden font-sans relative">
      
      {/* Mobile Sidebar Overlay */}
      {isSidebarOpen && (
        <div 
          className="fixed inset-0 bg-slate-900/20 backdrop-blur-sm z-30 md:hidden transition-opacity"
          onClick={() => setIsSidebarOpen(false)}
        />
      )}

      {/* Sidebar / Filter Panel */}
      <aside className={`
        fixed inset-y-0 left-0 z-40 w-72 bg-white border-r border-slate-200 flex flex-col shrink-0 transition-transform duration-300 ease-out shadow-2xl md:shadow-none md:relative md:translate-x-0
        ${isSidebarOpen ? 'translate-x-0' : '-translate-x-full'}
      `}>
        <div className="p-6 border-b border-slate-100 flex justify-between items-center">
          <div>
            <h1 className="text-xl font-bold text-slate-900 tracking-tight flex items-center gap-2">
              <Layers className="text-emerald-500" />
              UI/UX Strategy
            </h1>
            <p className="text-xs text-slate-500 mt-1">Skill Mapping & Growth Board</p>
          </div>
          <button 
            onClick={() => setIsSidebarOpen(false)}
            className="md:hidden text-slate-400 hover:text-slate-600 p-1 rounded-md hover:bg-slate-100"
          >
            <X size={20} />
          </button>
        </div>

        <div className="flex-1 overflow-y-auto p-5 space-y-8 custom-scrollbar">
          
          {/* View Toggle */}
          <div>
            <label className="text-xs font-bold text-slate-400 uppercase tracking-wider mb-3 block">View Mode</label>
            <div className="grid grid-cols-2 gap-1 bg-slate-100 p-1 rounded-lg">
              <button
                onClick={() => setViewMode('board')}
                className={`flex items-center justify-center gap-2 px-3 py-2 rounded-md text-xs font-semibold transition-all shadow-sm ${viewMode === 'board' ? 'bg-white text-slate-900 shadow-slate-200' : 'text-slate-500 hover:text-slate-700 bg-transparent shadow-none'}`}
              >
                <LayoutGrid size={14} /> Board
              </button>
              <button
                onClick={() => setViewMode('matrix')}
                className={`flex items-center justify-center gap-2 px-3 py-2 rounded-md text-xs font-semibold transition-all shadow-sm ${viewMode === 'matrix' ? 'bg-white text-slate-900 shadow-slate-200' : 'text-slate-500 hover:text-slate-700 bg-transparent shadow-none'}`}
              >
                <BarChart2 size={14} /> Matrix
              </button>
            </div>
          </div>

          {/* Filters Section */}
          <div>
            <div className="flex justify-between items-end mb-3">
              <label className="text-xs font-bold text-slate-400 uppercase tracking-wider flex items-center gap-2">
                <Filter size={12} /> Filters
              </label>
              {isFiltered && (
                <button 
                  onClick={handleResetFilters}
                  className="text-[10px] font-medium text-indigo-600 hover:text-indigo-800 flex items-center gap-1 bg-indigo-50 px-2 py-1 rounded-full transition-colors"
                >
                  <RefreshCw size={10} /> Reset
                </button>
              )}
            </div>
            
            <div className="space-y-4">
              {/* Category Dropdown */}
              <div>
                <label className="text-xs font-medium text-slate-500 mb-1.5 ml-1 block">Domain</label>
                <div className="relative">
                  <select
                    value={activeCategory}
                    onChange={(e) => setActiveCategory(e.target.value as Category | 'All')}
                    className="w-full appearance-none bg-slate-50 border border-slate-200 text-slate-700 text-xs font-medium rounded-lg py-2.5 px-3 pr-8 focus:outline-none focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-500 transition-all cursor-pointer hover:border-slate-300 shadow-sm"
                  >
                    <option value="All">All Domains</option>
                    {Object.values(Category).map((cat) => (
                      <option key={cat} value={cat}>{cat}</option>
                    ))}
                  </select>
                  <ChevronDown size={14} className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 pointer-events-none" />
                </div>
              </div>

              {/* Action Dropdown */}
              <div>
                <label className="text-xs font-medium text-slate-500 mb-1.5 ml-1 block">Strategic Action</label>
                 <div className="relative">
                  <select
                    value={activeAction}
                    onChange={(e) => setActiveAction(e.target.value as StrategicAction | 'All')}
                    className="w-full appearance-none bg-slate-50 border border-slate-200 text-slate-700 text-xs font-medium rounded-lg py-2.5 px-3 pr-8 focus:outline-none focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-500 transition-all cursor-pointer hover:border-slate-300 shadow-sm"
                  >
                    <option value="All">All Actions</option>
                    {Object.values(StrategicAction).map((action) => (
                      <option key={action} value={action}>{action}</option>
                    ))}
                  </select>
                  <ChevronDown size={14} className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 pointer-events-none" />
                </div>
              </div>
            </div>
          </div>
        </div>

        {/* User Stats Mini */}
        <div className="p-5 bg-slate-50/50 border-t border-slate-200 text-xs">
          <div className="flex justify-between mb-2">
            <span className="text-slate-500 font-medium">Growth Opportunities</span>
            <span className="text-emerald-600 font-bold bg-emerald-100 px-2 py-0.5 rounded-full">{stats.invest}</span>
          </div>
          <div className="flex justify-between">
            <span className="text-slate-500 font-medium">Core Strengths</span>
            <span className="text-blue-600 font-bold bg-blue-100 px-2 py-0.5 rounded-full">{stats.leverage}</span>
          </div>
        </div>
      </aside>

      {/* Main Content Area */}
      <main className="flex-1 overflow-y-auto bg-slate-50 relative custom-scrollbar w-full">
        {/* Top Bar Overlay Gradient */}
        <div className="sticky top-0 z-20 bg-white/80 backdrop-blur-md px-4 py-4 md:px-8 md:py-6 border-b border-slate-200 shadow-sm transition-all">
           <div className="flex flex-col md:flex-row md:justify-between md:items-end gap-4 md:gap-0">
             <div className="flex items-center gap-3">
               {/* Mobile Menu Trigger */}
               <button 
                 onClick={() => setIsSidebarOpen(true)}
                 className="md:hidden p-2 -ml-2 text-slate-500 hover:bg-slate-100 rounded-lg hover:text-slate-900 transition-colors"
                 aria-label="Open Menu"
               >
                 <Menu size={24} />
               </button>

               <div>
                 <h2 className="text-xl md:text-2xl font-bold text-slate-900 tracking-tight">
                   {viewMode === 'board' ? 'Skill Inventory' : 'Strategic Matrix'}
                 </h2>
                 <p className="text-xs md:text-sm text-slate-500 mt-0.5 md:mt-1 font-medium">
                   {activeCategory === 'All' ? 'All Skills' : activeCategory}<span className="text-slate-900">{filteredSkills.length}</span> items
                 </p>
               </div>
             </div>
             
             {/* Legend */}
             <div className="flex gap-3 md:gap-4 text-[10px] md:text-xs font-medium text-slate-600 overflow-x-auto pb-1 md:pb-0 hide-scrollbar">
                <div className="flex items-center gap-1.5 md:gap-2 whitespace-nowrap">
                  <span className="w-2 h-2 md:w-2.5 md:h-2.5 rounded-full bg-emerald-500"></span> Invest
                </div>
                <div className="flex items-center gap-1.5 md:gap-2 whitespace-nowrap">
                  <span className="w-2 h-2 md:w-2.5 md:h-2.5 rounded-full bg-blue-500"></span> Leverage
                </div>
                <div className="flex items-center gap-1.5 md:gap-2 whitespace-nowrap">
                  <span className="w-2 h-2 md:w-2.5 md:h-2.5 rounded-full bg-slate-400"></span> Maintain
                </div>
             </div>
           </div>
        </div>

        <div className="p-4 md:p-8">
          {viewMode === 'board' ? (
            /* --- BOARD VIEW --- */
            <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6 pb-20">
              {filteredSkills.map(skill => (
                <SkillCard 
                  key={skill.id} 
                  skill={skill} 
                  onUpdateMaturity={handleUpdateMaturity} 
                  onUpdateEffort={handleUpdateEffort}
                />
              ))}
              {filteredSkills.length === 0 && (
                <div className="col-span-full h-80 flex flex-col items-center justify-center text-slate-400 border-2 border-dashed border-slate-200 rounded-xl bg-slate-50/50">
                  <Info size={40} className="mb-3 text-slate-300" />
                  <p className="font-medium">No skills match current filters.</p>
                  <button onClick={handleResetFilters} className="mt-4 text-sm text-indigo-600 hover:text-indigo-700 font-semibold">
                    Clear Filters
                  </button>
                </div>
              )}
            </div>
          ) : (
            /* --- MATRIX VIEW --- */
            <div className="h-[400px] md:h-[600px] w-full bg-white rounded-xl border border-slate-200 p-4 md:p-6 relative shadow-sm">
               <ResponsiveContainer width="100%" height="100%">
                <ScatterChart margin={{ top: 20, right: 20, bottom: 20, left: 20 }}>
                  <XAxis 
                    type="number" 
                    dataKey="maturity" 
                    name="Maturity" 
                    domain={[0, 6]} 
                    tickCount={6}
                    stroke="#94a3b8"
                    tick={{ fill: '#64748b', fontSize: 12 }}
                    label={{ value: 'Maturity', position: 'insideBottom', offset: -10, fill: '#64748b', fontSize: 12 }}
                  />
                  <YAxis 
                    type="number" 
                    dataKey="defaultValue" 
                    name="Business Value" 
                    domain={[0, 6]} 
                    tickCount={6}
                    stroke="#94a3b8"
                    tick={{ fill: '#64748b', fontSize: 12 }}
                    label={{ value: 'Value', angle: -90, position: 'insideLeft', fill: '#64748b', fontSize: 12 }}
                  />
                  <ZAxis type="number" dataKey="effort" range={[50, 400]} name="Effort" />
                  <Tooltip content={<CustomTooltip />} cursor={{ strokeDasharray: '3 3', stroke: '#cbd5e1' }} />
                  
                  {/* Quadrant Lines */}
                  <ReferenceLine x={2.5} stroke="#e2e8f0" strokeDasharray="3 3" />
                  <ReferenceLine y={3.5} stroke="#e2e8f0" strokeDasharray="3 3" />

                  {/* Quadrant Labels */}
                  <Label value="INVEST" position="top" offset={10} fill="#64748b" fontSize={10} />
                  
                  <Scatter name="Skills" data={filteredSkills} onClick={() => {}}>
                    {filteredSkills.map((entry, index) => {
                      let color = '#94a3b8'; // Default Maintain/Monitor
                      if (entry.strategicAction === StrategicAction.Invest) color = '#10b981';
                      if (entry.strategicAction === StrategicAction.Leverage) color = '#3b82f6';
                      if (entry.strategicAction === StrategicAction.Monitor) color = '#fbbf24';

                      return <Cell key={`cell-${index}`} fill={color} fillOpacity={0.8} stroke={color} />;
                    })}
                  </Scatter>
                </ScatterChart>
              </ResponsiveContainer>
              
              {/* Matrix Overlay Text */}
              <div className="absolute top-4 left-4 text-[10px] md:text-xs font-bold text-emerald-600/70 pointer-events-none">INVEST</div>
              <div className="absolute top-4 right-4 text-[10px] md:text-xs font-bold text-blue-600/70 pointer-events-none">LEVERAGE</div>
              <div className="absolute bottom-12 left-4 text-[10px] md:text-xs font-bold text-amber-500/70 pointer-events-none">MONITOR</div>
              <div className="absolute bottom-12 right-4 text-[10px] md:text-xs font-bold text-slate-500/70 pointer-events-none">MAINTAIN</div>
            </div>
          )}
        </div>
      </main>
    </div>
  );
};

export default App;

This is the code generated by AI Studio. I optimized the interface by adjusting parts of the code, making the prototype more intuitive and user-friendly.

🚀 The Impact

  • Efficiency: Reduced a 1-week discovery and prototyping sprint into 1.2 hours.

  • Accessibility: Leveraging AI allowed me to implement ARIA labels and responsive breakpoints that are often overlooked in rapid manual prototyping.

  • Validation: The result is a tangible, interactive tool that allows designers to "self-audit" their soft skills—a feature Figma mockups can't easily simulate.

🤔💭 Personal Reflection

  • Efficiency: Reduced a 2-3 days discovery and prototyping sprint into 1.2 hours.

  • Accessibility: Leveraging AI allowed me to implement ARIA labels and responsive breakpoints that are often overlooked in rapid manual prototyping.

  • Validation: The result is a tangible, interactive tool that allows designers to "self-audit" their soft skills—a feature Figma mockups can't easily simulate.