UIPackage
Menu

Framework

Change language

Boilerplate repo

Matrix Swot Analysis

blockproductivity

Strategic planning 2x2 SWOT analysis matrix (Strengths, Weaknesses, Opportunities, Threats) with dynamic card management, quadrant counters, TOWS strategic initiatives, and PDF/share toolbar.

Also available for React ->

Installation

$npx shadcn-vue@latest add https://uipkge.dev/r/vue/matrix-swot-analysis.json
Named registry:npx shadcn-vue@latest add @uipkge/matrix-swot-analysisInstalls to:app/components/blocks/

Variants

Loading interactive previews…

Props

NameType / ValuesDefaultRequired
titlestring'SWOT Strategic Analysis Canvas'optional
projectstring'UIPKGE Unbundled UI Registry Strategy · Q3 2026'optional
lastUpdatedstring'Aug 21optional
initialStrengthsSwotItem[]optional
initialWeaknessesSwotItem[]optional
initialOpportunitiesSwotItem[]optional
initialThreatsSwotItem[]optional
initiativesStrategicInitiative[]optional
readonlybooleanfalseoptional
classHTMLAttributes['class']optional

Schema

Type aliases exported from this item's source. Use these to shape the data you pass in.

SwotItem
interface SwotItem {
  id: string
  title: string
  description: string
  impact: 'high' | 'medium' | 'low'
  tag: string
}
StrategicInitiative
interface StrategicInitiative {
  id: string
  title: string
  description: string
  strategyType: 'SO' | 'WO' | 'ST' | 'WT'
  strategyLabel: string
  priority: 'Critical' | 'High' | 'Strategic'
  timeframe: string
  targetMetric: string
}

npm dependencies

Files installed (1)

  • app/components/blocks/MatrixSwotAnalysis.vue33.1 kB
    <script setup lang="ts">
    import { computed, ref, type HTMLAttributes } from 'vue'
    import {
      AlertTriangle,
      Calendar,
      Check,
      Clock,
      Download,
      Layers,
      Plus,
      Share2,
      ShieldAlert,
      ShieldCheck,
      Trash2,
      TrendingUp,
      Zap,
    } from 'lucide-vue-next'
    import { cn } from '@/lib/utils'
    import { Badge } from '@/components/ui/badge'
    import { Button } from '@/components/ui/button'
    import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
    import { Input } from '@/components/ui/input'
    
    export interface SwotItem {
      id: string
      title: string
      description: string
      impact: 'high' | 'medium' | 'low'
      tag: string
    }
    
    export interface StrategicInitiative {
      id: string
      title: string
      description: string
      strategyType: 'SO' | 'WO' | 'ST' | 'WT'
      strategyLabel: string
      priority: 'Critical' | 'High' | 'Strategic'
      timeframe: string
      targetMetric: string
    }
    
    interface Props {
      title?: string
      project?: string
      lastUpdated?: string
      initialStrengths?: SwotItem[]
      initialWeaknesses?: SwotItem[]
      initialOpportunities?: SwotItem[]
      initialThreats?: SwotItem[]
      initiatives?: StrategicInitiative[]
      readonly?: boolean
      class?: HTMLAttributes['class']
    }
    
    const props = withDefaults(defineProps<Props>(), {
      title: 'SWOT Strategic Analysis Canvas',
      project: 'UIPKGE Unbundled UI Registry Strategy · Q3 2026',
      lastUpdated: 'Aug 21, 2026',
      readonly: false,
    })
    
    const defaultStrengths: SwotItem[] = [
      {
        id: 's-1',
        title: 'Zero npm dependency overhead',
        description:
          'Components are copied directly into consumer project codebases, eliminating semver dependency locks, version skew, and transitive package bloat.',
        impact: 'high',
        tag: 'Core Architecture',
      },
      {
        id: 's-2',
        title: '100% code ownership for consumers',
        description:
          'Engineering teams retain complete control over markup, styles, accessibility props, and internal behaviors with zero upstream constraints.',
        impact: 'high',
        tag: 'Developer Autonomy',
      },
      {
        id: 's-3',
        title: 'Dual-framework Vue & React parity',
        description:
          'Strict visual, token, and functional symmetry between Vue 3 (Reka UI) and React (Radix UI) enables cross-stack design system alignment.',
        impact: 'high',
        tag: 'Market Breadth',
      },
      {
        id: 's-4',
        title: 'Tailwind v4 OKLCH token architecture',
        description:
          'CSS-first theme configuration using semantic surface variables, calibrated dark-mode layering, and WCAG AA compliant contrast ratios.',
        impact: 'medium',
        tag: 'Design System',
      },
    ]
    
    const defaultWeaknesses: SwotItem[] = [
      {
        id: 'w-1',
        title: 'Manual upgrade path without semver',
        description:
          'Consuming codebases must manually pull code diffs or re-execute CLI add commands when library enhancements or bug fixes are published.',
        impact: 'high',
        tag: 'Maintenance Model',
      },
      {
        id: 'w-2',
        title: 'Initial learning curve for monorepos',
        description:
          'Junior developers accustomed to single-command npm package installs may face an initial cognitive hurdle managing raw unbundled components.',
        impact: 'medium',
        tag: 'Developer DX',
      },
      {
        id: 'w-3',
        title: 'Build time for 1,300+ SSG pages',
        description:
          'Synthesizing static island previews, AST code extraction, and multi-format LLM documentation across dual registries requires high memory in CI.',
        impact: 'medium',
        tag: 'Tooling Scale',
      },
    ]
    
    const defaultOpportunities: SwotItem[] = [
      {
        id: 'o-1',
        title: 'AI Copilot workflows demanding raw source code',
        description:
          'LLM coding assistants (Claude, Cursor, Copilot) perform substantially better when refactoring transparent local SFCs than navigating opaque compiled dependencies.',
        impact: 'high',
        tag: 'AI Ergonomics',
      },
      {
        id: 'o-2',
        title: 'Enterprise design system white-labeling',
        description:
          'B2B organizations seeking unbranded, custom-styled design systems can adopt UIPKGE as their modular, open-source architectural baseline.',
        impact: 'high',
        tag: 'Enterprise B2B',
      },
      {
        id: 'o-3',
        title: 'E-Commerce and Healthcare template verticals',
        description:
          'Surging industry appetite for pre-built reference architectures (HRMS, Hospital Management, Logistics tracking) powered by composable blocks.',
        impact: 'high',
        tag: 'Vertical Growth',
      },
      {
        id: 'o-4',
        title: 'Community contributions of domain blocks',
        description:
          'Open-source contributors can rapidly publish specialized workflow blocks (radar charts, KYC wizards, audit logs) without semver packaging bottlenecks.',
        impact: 'medium',
        tag: 'Community',
      },
    ]
    
    const defaultThreats: SwotItem[] = [
      {
        id: 't-1',
        title: 'Official shadcn expanding into Vue ecosystem',
        description:
          'Potential first-party Vue support by upstream shadcn could consolidate developer attention away from independent unbundled registries.',
        impact: 'high',
        tag: 'Competitive Risk',
      },
      {
        id: 't-2',
        title: 'Upstream Reka UI headless breaking changes',
        description:
          'Substantial architectural shifts or breaking release cycles in upstream headless libraries require immediate parity synchronization.',
        impact: 'high',
        tag: 'Upstream Drift',
      },
      {
        id: 't-3',
        title: 'Browser CSS specification shifts',
        description:
          'Rapid evolution in CSS @scope, anchor positioning, and native light-dark() CSS functions may require recurring baseline refactoring.',
        impact: 'medium',
        tag: 'Web Standards',
      },
    ]
    
    const defaultInitiatives: StrategicInitiative[] = [
      {
        id: 'init-1',
        title: 'Automate Upstream Sync & Breaking Change Detection',
        description:
          'Implement automated CI regression monitoring against Reka UI and upstream shadcn tokens to maintain 100% runtime compatibility without breaking consumer projects.',
        strategyType: 'SO',
        strategyLabel: 'SO Strategy (Strengths × Opportunities)',
        priority: 'Critical',
        timeframe: 'Q4 2026',
        targetMetric: '< 24h Upstream Parity Turnaround',
      },
      {
        id: 'init-2',
        title: 'AI-Native Context & CLI Smart Upgrade Engine',
        description:
          'Ship an intelligent CLI diff command and fine-tuned llms.txt vector context to let AI coding agents auto-patch and upgrade local consumer components directly.',
        strategyType: 'WO',
        strategyLabel: 'WO Strategy (Weaknesses × Opportunities)',
        priority: 'High',
        timeframe: 'Q4 2026',
        targetMetric: 'Zero-Friction AST Upgrade Patches',
      },
      {
        id: 'init-3',
        title: 'Curated Vertical Template Packs & Enterprise Baseline',
        description:
          'Deliver production-grade Nuxt 4 and Next.js vertical boilerplate templates (HRMS, HMS, Logistics) to eliminate monorepo onboarding friction for enterprise builders.',
        strategyType: 'ST',
        strategyLabel: 'ST Strategy (Strengths × Threats)',
        priority: 'Strategic',
        timeframe: 'Q1 2027',
        targetMetric: '4 Turnkey Vertical Reference Apps',
      },
    ]
    
    const strengths = ref<SwotItem[]>(props.initialStrengths ?? defaultStrengths)
    const weaknesses = ref<SwotItem[]>(props.initialWeaknesses ?? defaultWeaknesses)
    const opportunities = ref<SwotItem[]>(props.initialOpportunities ?? defaultOpportunities)
    const threats = ref<SwotItem[]>(props.initialThreats ?? defaultThreats)
    const strategicInitiatives = ref<StrategicInitiative[]>(props.initiatives ?? defaultInitiatives)
    
    const inputStrength = ref('')
    const inputWeakness = ref('')
    const inputOpportunity = ref('')
    const inputThreat = ref('')
    
    const isCopied = ref(false)
    const isExporting = ref(false)
    
    function addStrength() {
      const text = inputStrength.value.trim()
      if (!text) return
      strengths.value.push({
        id: `s-${Date.now()}`,
        title: text,
        description: 'Custom strategic strength added during canvas session.',
        impact: 'high',
        tag: 'Internal Strategic Advantage',
      })
      inputStrength.value = ''
    }
    
    function removeStrength(id: string) {
      strengths.value = strengths.value.filter((item) => item.id !== id)
    }
    
    function addWeakness() {
      const text = inputWeakness.value.trim()
      if (!text) return
      weaknesses.value.push({
        id: `w-${Date.now()}`,
        title: text,
        description: 'Custom strategic vulnerability identified for remediation.',
        impact: 'medium',
        tag: 'Internal Remediation',
      })
      inputWeakness.value = ''
    }
    
    function removeWeakness(id: string) {
      weaknesses.value = weaknesses.value.filter((item) => item.id !== id)
    }
    
    function addOpportunity() {
      const text = inputOpportunity.value.trim()
      if (!text) return
      opportunities.value.push({
        id: `o-${Date.now()}`,
        title: text,
        description: 'External market dynamic or technological growth tailwind.',
        impact: 'high',
        tag: 'External Opportunity',
      })
      inputOpportunity.value = ''
    }
    
    function removeOpportunity(id: string) {
      opportunities.value = opportunities.value.filter((item) => item.id !== id)
    }
    
    function addThreat() {
      const text = inputThreat.value.trim()
      if (!text) return
      threats.value.push({
        id: `t-${Date.now()}`,
        title: text,
        description: 'External industry risk factor requiring proactive defensive mitigation.',
        impact: 'high',
        tag: 'External Threat',
      })
      inputThreat.value = ''
    }
    
    function removeThreat(id: string) {
      threats.value = threats.value.filter((item) => item.id !== id)
    }
    
    function handleShare() {
      if (typeof navigator !== 'undefined' && navigator.clipboard) {
        navigator.clipboard.writeText(window.location.href)
        isCopied.value = true
        setTimeout(() => {
          isCopied.value = false
        }, 2000)
      }
    }
    
    function handleExportPdf() {
      isExporting.value = true
      setTimeout(() => {
        isExporting.value = false
      }, 1500)
    }
    
    const totalCards = computed(
      () => strengths.value.length + weaknesses.value.length + opportunities.value.length + threats.value.length,
    )
    </script>
    
    <template>
      <div data-slot="matrix-swot-analysis" :class="cn('w-full space-y-6', props.class)">
        <!-- Header Surface -->
        <div
          class="bg-card text-card-foreground flex flex-col gap-5 rounded-xl border p-5 shadow-xs sm:p-6 lg:flex-row lg:items-center lg:justify-between"
        >
          <div class="space-y-1.5">
            <div class="flex flex-wrap items-center gap-2">
              <h2 class="text-xl font-bold tracking-tight sm:text-2xl">{{ props.title }}</h2>
              <Badge wrap variant="outline" class="bg-primary/5 text-primary border-primary/20 text-xs font-medium">
                2×2 TOWS Matrix
              </Badge>
              <Badge wrap variant="secondary" class="text-xs font-normal"> {{ totalCards }} Strategic Factors </Badge>
            </div>
    
            <div class="text-muted-foreground flex flex-wrap items-center gap-x-4 gap-y-1 text-xs sm:text-sm">
              <div class="flex items-center gap-1.5">
                <Layers class="size-3.5" />
                <span class="text-foreground font-medium">{{ props.project }}</span>
              </div>
              <span class="hidden sm:inline">·</span>
              <div class="flex items-center gap-1.5">
                <Calendar class="size-3.5" />
                <span>Updated {{ props.lastUpdated }}</span>
              </div>
            </div>
          </div>
    
          <!-- Action Toolbar -->
          <div class="flex flex-wrap items-center gap-2.5">
            <Button
              aria-label="Download attachment"
              variant="outline"
              size="sm"
              class="h-9 gap-2 text-xs shadow-xs"
              :disabled="isExporting"
              @click="handleExportPdf"
            >
              <Download v-if="!isExporting" class="size-3.5" />
              <Clock v-else class="size-3.5 animate-spin" />
              <span>{{ isExporting ? 'Generating PDF...' : 'Export Canvas PDF' }}</span>
            </Button>
    
            <Button variant="default" size="sm" class="h-9 gap-2 text-xs shadow-xs" @click="handleShare">
              <Check v-if="isCopied" class="size-3.5" />
              <Share2 v-else class="size-3.5" />
              <span>{{ isCopied ? 'Link Copied!' : 'Share Strategy' }}</span>
            </Button>
          </div>
        </div>
    
        <!-- 2x2 Matrix Axis Guide Legend -->
        <div class="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4">
          <div
            class="border-success/20 bg-success/5 text-success text-success flex items-center justify-between rounded-lg border px-3.5 py-2.5 text-xs font-medium shadow-xs"
          >
            <div class="flex items-center gap-2">
              <ShieldCheck class="text-success size-4 shrink-0" />
              <span>Internal Attributes</span>
            </div>
            <Badge wrap variant="outline" class="border-success/30 bg-success/10 text-xs">Helpful (+)</Badge>
          </div>
    
          <div
            class="border-destructive/20 bg-destructive/5 text-destructive text-destructive flex items-center justify-between rounded-lg border px-3.5 py-2.5 text-xs font-medium shadow-xs"
          >
            <div class="flex items-center gap-2">
              <AlertTriangle class="text-destructive size-4 shrink-0" />
              <span>Internal Attributes</span>
            </div>
            <Badge wrap variant="outline" class="border-destructive/30 bg-destructive/10 text-xs">Harmful (−)</Badge>
          </div>
    
          <div
            class="border-info/20 bg-info/5 text-info text-info flex items-center justify-between rounded-lg border px-3.5 py-2.5 text-xs font-medium shadow-xs"
          >
            <div class="flex items-center gap-2">
              <TrendingUp class="text-info size-4 shrink-0" />
              <span>External Dynamics</span>
            </div>
            <Badge wrap variant="outline" class="border-info/30 bg-info/10 text-xs">Helpful (+)</Badge>
          </div>
    
          <div
            class="border-warning/20 bg-warning/5 text-warning flex items-center justify-between rounded-lg border px-3.5 py-2.5 text-xs font-medium shadow-xs"
          >
            <div class="flex items-center gap-2">
              <ShieldAlert class="text-warning size-4 shrink-0" />
              <span>External Dynamics</span>
            </div>
            <Badge wrap variant="outline" class="border-warning/30 bg-warning/10 text-warning text-xs">Harmful (−)</Badge>
          </div>
        </div>
    
        <!-- 2x2 SWOT Grid -->
        <div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
          <!-- QUADRANT 1: STRENGTHS (Internal / Positive) -->
          <Card class="border-success/30 border-success/20 flex flex-col justify-between shadow-xs">
            <CardHeader class="border-border/60 bg-success/5 dark:bg-success/10 border-b pb-4">
              <div class="flex items-start justify-between gap-3">
                <div class="flex items-center gap-3">
                  <div
                    class="border-success/20 bg-success/10 text-success text-success flex size-9 items-center justify-center rounded-lg border shadow-xs"
                  >
                    <ShieldCheck class="size-5" />
                  </div>
                  <div>
                    <div class="flex items-center gap-2">
                      <CardTitle class="text-base font-bold sm:text-lg">Strengths</CardTitle>
                      <Badge wrap class="border-success/20 bg-success/15 text-success text-xs font-semibold">
                        {{ strengths.length }}
                      </Badge>
                    </div>
                    <CardDescription class="text-xs">
                      Internal attributes &amp; distinct technical capabilities
                    </CardDescription>
                  </div>
                </div>
    
                <div class="flex items-center gap-1.5">
                  <Badge
                    wrap
                    variant="outline"
                    class="border-success/30 text-success text-success hidden text-xs sm:inline-flex"
                  >
                    Internal
                  </Badge>
                  <Badge wrap variant="outline" class="border-success/30 text-success text-xs"> Positive </Badge>
                </div>
              </div>
            </CardHeader>
    
            <CardContent class="flex-1 space-y-3 p-4 sm:p-5">
              <div
                v-for="item in strengths"
                :key="item.id"
                class="group bg-card hover:border-success/40 relative rounded-lg border p-3.5 shadow-xs transition-[border-color,box-shadow]"
              >
                <div class="flex items-start justify-between gap-2">
                  <div class="space-y-1">
                    <div class="flex flex-wrap items-center gap-2">
                      <h4 class="text-foreground text-sm leading-snug font-semibold">{{ item.title }}</h4>
                      <Badge
                        wrap
                        variant="outline"
                        class="border-success/20 bg-success/10 text-success text-xs font-normal"
                      >
                        {{ item.tag }}
                      </Badge>
                    </div>
                    <p class="text-muted-foreground text-xs leading-relaxed">{{ item.description }}</p>
                  </div>
    
                  <Button
                    v-if="!props.readonly"
                    variant="ghost"
                    size="sm"
                    class="text-muted-foreground hover:text-destructive hover:bg-destructive/10 -mt-1 -mr-1 size-7 shrink-0 p-0 opacity-80 group-hover:opacity-100"
                    aria-label="Remove strength item"
                    @click="removeStrength(item.id)"
                  >
                    <Trash2 class="size-3.5" />
                  </Button>
                </div>
              </div>
    
              <div
                v-if="strengths.length === 0"
                class="text-muted-foreground rounded-lg border border-dashed py-8 text-center text-xs"
              >
                No strength items listed. Add a core advantage below.
              </div>
            </CardContent>
    
            <CardFooter v-if="!props.readonly" class="border-border/60 bg-muted/20 border-t p-3 sm:p-4">
              <form class="flex w-full items-center gap-2" @submit.prevent="addStrength">
                <Input
                  v-model="inputStrength"
                  type="text"
                  placeholder="Add key strength... (Press Enter)"
                  class="h-8.5 text-xs shadow-xs"
                />
                <Button
                  type="submit"
                  size="sm"
                  class="bg-success hover:bg-success/90 h-8.5 shrink-0 gap-1.5 text-xs text-white shadow-xs"
                >
                  <Plus class="size-3.5" />
                  <span>Add</span>
                </Button>
              </form>
            </CardFooter>
          </Card>
    
          <!-- QUADRANT 2: WEAKNESSES (Internal / Negative) -->
          <Card class="border-destructive/30 border-destructive/20 flex flex-col justify-between shadow-xs">
            <CardHeader class="border-border/60 bg-destructive/5 dark:bg-destructive/10 border-b pb-4">
              <div class="flex items-start justify-between gap-3">
                <div class="flex items-center gap-3">
                  <div
                    class="border-destructive/20 bg-destructive/10 text-destructive text-destructive flex size-9 items-center justify-center rounded-lg border shadow-xs"
                  >
                    <AlertTriangle class="size-5" />
                  </div>
                  <div>
                    <div class="flex items-center gap-2">
                      <CardTitle class="text-base font-bold sm:text-lg">Weaknesses</CardTitle>
                      <Badge wrap class="border-destructive/20 bg-destructive/15 text-destructive text-xs font-semibold">
                        {{ weaknesses.length }}
                      </Badge>
                    </div>
                    <CardDescription class="text-xs">
                      Internal limitations &amp; operational friction points
                    </CardDescription>
                  </div>
                </div>
    
                <div class="flex items-center gap-1.5">
                  <Badge
                    wrap
                    variant="outline"
                    class="border-destructive/30 text-destructive text-destructive hidden text-xs sm:inline-flex"
                  >
                    Internal
                  </Badge>
                  <Badge wrap variant="outline" class="border-destructive/30 text-destructive text-xs"> Negative </Badge>
                </div>
              </div>
            </CardHeader>
    
            <CardContent class="flex-1 space-y-3 p-4 sm:p-5">
              <div
                v-for="item in weaknesses"
                :key="item.id"
                class="group bg-card hover:border-destructive/40 relative rounded-lg border p-3.5 shadow-xs transition-[border-color,box-shadow]"
              >
                <div class="flex items-start justify-between gap-2">
                  <div class="space-y-1">
                    <div class="flex flex-wrap items-center gap-2">
                      <h4 class="text-foreground text-sm leading-snug font-semibold">{{ item.title }}</h4>
                      <Badge
                        wrap
                        variant="outline"
                        class="border-destructive/20 bg-destructive/10 text-destructive text-xs font-normal"
                      >
                        {{ item.tag }}
                      </Badge>
                    </div>
                    <p class="text-muted-foreground text-xs leading-relaxed">{{ item.description }}</p>
                  </div>
    
                  <Button
                    v-if="!props.readonly"
                    variant="ghost"
                    size="sm"
                    class="text-muted-foreground hover:text-destructive hover:bg-destructive/10 -mt-1 -mr-1 size-7 shrink-0 p-0 opacity-80 group-hover:opacity-100"
                    aria-label="Remove weakness item"
                    @click="removeWeakness(item.id)"
                  >
                    <Trash2 class="size-3.5" />
                  </Button>
                </div>
              </div>
    
              <div
                v-if="weaknesses.length === 0"
                class="text-muted-foreground rounded-lg border border-dashed py-8 text-center text-xs"
              >
                No weakness items listed. Add an area for improvement below.
              </div>
            </CardContent>
    
            <CardFooter v-if="!props.readonly" class="border-border/60 bg-muted/20 border-t p-3 sm:p-4">
              <form class="flex w-full items-center gap-2" @submit.prevent="addWeakness">
                <Input
                  v-model="inputWeakness"
                  type="text"
                  placeholder="Add key weakness... (Press Enter)"
                  class="h-8.5 text-xs shadow-xs"
                />
                <Button
                  type="submit"
                  size="sm"
                  class="bg-destructive hover:bg-destructive/90 h-8.5 shrink-0 gap-1.5 text-xs text-white shadow-xs"
                >
                  <Plus class="size-3.5" />
                  <span>Add</span>
                </Button>
              </form>
            </CardFooter>
          </Card>
    
          <!-- QUADRANT 3: OPPORTUNITIES (External / Positive) -->
          <Card class="border-info/30 border-info/20 flex flex-col justify-between shadow-xs">
            <CardHeader class="border-border/60 bg-info/5 dark:bg-info/10 border-b pb-4">
              <div class="flex items-start justify-between gap-3">
                <div class="flex items-center gap-3">
                  <div
                    class="border-info/20 bg-info/10 text-info text-info flex size-9 items-center justify-center rounded-lg border shadow-xs"
                  >
                    <TrendingUp class="size-5" />
                  </div>
                  <div>
                    <div class="flex items-center gap-2">
                      <CardTitle class="text-base font-bold sm:text-lg">Opportunities</CardTitle>
                      <Badge wrap class="border-info/20 bg-info/15 text-info text-xs font-semibold">
                        {{ opportunities.length }}
                      </Badge>
                    </div>
                    <CardDescription class="text-xs">
                      External tailwinds &amp; strategic expansion opportunities
                    </CardDescription>
                  </div>
                </div>
    
                <div class="flex items-center gap-1.5">
                  <Badge wrap variant="outline" class="border-info/30 text-info text-info hidden text-xs sm:inline-flex">
                    External
                  </Badge>
                  <Badge wrap variant="outline" class="border-info/30 text-info text-xs"> Positive </Badge>
                </div>
              </div>
            </CardHeader>
    
            <CardContent class="flex-1 space-y-3 p-4 sm:p-5">
              <div
                v-for="item in opportunities"
                :key="item.id"
                class="group bg-card hover:border-info/40 relative rounded-lg border p-3.5 shadow-xs transition-[border-color,box-shadow]"
              >
                <div class="flex items-start justify-between gap-2">
                  <div class="space-y-1">
                    <div class="flex flex-wrap items-center gap-2">
                      <h4 class="text-foreground text-sm leading-snug font-semibold">{{ item.title }}</h4>
                      <Badge wrap variant="outline" class="border-info/20 bg-info/10 text-info text-xs font-normal">
                        {{ item.tag }}
                      </Badge>
                    </div>
                    <p class="text-muted-foreground text-xs leading-relaxed">{{ item.description }}</p>
                  </div>
    
                  <Button
                    v-if="!props.readonly"
                    variant="ghost"
                    size="sm"
                    class="text-muted-foreground hover:text-destructive hover:bg-destructive/10 -mt-1 -mr-1 size-7 shrink-0 p-0 opacity-80 group-hover:opacity-100"
                    aria-label="Remove opportunity item"
                    @click="removeOpportunity(item.id)"
                  >
                    <Trash2 class="size-3.5" />
                  </Button>
                </div>
              </div>
    
              <div
                v-if="opportunities.length === 0"
                class="text-muted-foreground rounded-lg border border-dashed py-8 text-center text-xs"
              >
                No opportunity items listed. Add a market growth vector below.
              </div>
            </CardContent>
    
            <CardFooter v-if="!props.readonly" class="border-border/60 bg-muted/20 border-t p-3 sm:p-4">
              <form class="flex w-full items-center gap-2" @submit.prevent="addOpportunity">
                <Input
                  v-model="inputOpportunity"
                  type="text"
                  placeholder="Add key opportunity... (Press Enter)"
                  class="h-8.5 text-xs shadow-xs"
                />
                <Button
                  type="submit"
                  size="sm"
                  class="bg-info hover:bg-info/90 h-8.5 shrink-0 gap-1.5 text-xs text-white shadow-xs"
                >
                  <Plus class="size-3.5" />
                  <span>Add</span>
                </Button>
              </form>
            </CardFooter>
          </Card>
    
          <!-- QUADRANT 4: THREATS (External / Negative) -->
          <Card class="border-warning/30 dark:border-warning/20 flex flex-col justify-between shadow-xs">
            <CardHeader class="border-border/60 bg-warning/5 dark:bg-warning/10 border-b pb-4">
              <div class="flex items-start justify-between gap-3">
                <div class="flex items-center gap-3">
                  <div
                    class="border-warning/20 bg-warning/10 text-warning flex size-9 items-center justify-center rounded-lg border shadow-xs"
                  >
                    <ShieldAlert class="size-5" />
                  </div>
                  <div>
                    <div class="flex items-center gap-2">
                      <CardTitle class="text-base font-bold sm:text-lg">Threats</CardTitle>
                      <Badge wrap class="border-warning/20 bg-warning/15 text-warning text-xs font-semibold">
                        {{ threats.length }}
                      </Badge>
                    </div>
                    <CardDescription class="text-xs">
                      External risks, competitive forces &amp; ecosystem shifts
                    </CardDescription>
                  </div>
                </div>
    
                <div class="flex items-center gap-1.5">
                  <Badge wrap variant="outline" class="border-warning/30 text-warning hidden text-xs sm:inline-flex">
                    External
                  </Badge>
                  <Badge wrap variant="outline" class="border-warning/30 text-warning text-xs"> Negative </Badge>
                </div>
              </div>
            </CardHeader>
    
            <CardContent class="flex-1 space-y-3 p-4 sm:p-5">
              <div
                v-for="item in threats"
                :key="item.id"
                class="bg-card hover:border-warning/40 relative rounded-lg border p-3.5 shadow-xs transition-[border-color,box-shadow]"
              >
                <div class="flex items-start justify-between gap-2">
                  <div class="space-y-1">
                    <div class="flex flex-wrap items-center gap-2">
                      <h4 class="text-foreground text-sm leading-snug font-semibold">{{ item.title }}</h4>
                      <Badge
                        wrap
                        variant="outline"
                        class="border-warning/20 bg-warning/10 text-warning text-xs font-normal"
                      >
                        {{ item.tag }}
                      </Badge>
                    </div>
                    <p class="text-muted-foreground text-xs leading-relaxed">{{ item.description }}</p>
                  </div>
    
                  <Button
                    v-if="!props.readonly"
                    variant="ghost"
                    size="sm"
                    class="text-muted-foreground hover:text-destructive hover:bg-destructive/10 -mt-1 -mr-1 size-7 shrink-0 p-0 opacity-80 group-hover:opacity-100"
                    aria-label="Remove threat item"
                    @click="removeThreat(item.id)"
                  >
                    <Trash2 class="size-3.5" />
                  </Button>
                </div>
              </div>
    
              <div
                v-if="threats.length === 0"
                class="text-muted-foreground rounded-lg border border-dashed py-8 text-center text-xs"
              >
                No threat items listed. Add an external risk factor below.
              </div>
            </CardContent>
    
            <CardFooter v-if="!props.readonly" class="border-border/60 bg-muted/20 border-t p-3 sm:p-4">
              <form class="flex w-full items-center gap-2" @submit.prevent="addThreat">
                <Input
                  v-model="inputThreat"
                  type="text"
                  placeholder="Add key threat... (Press Enter)"
                  class="h-8.5 text-xs shadow-xs"
                />
                <Button
                  type="submit"
                  size="sm"
                  class="bg-warning text-warning-foreground hover:bg-warning/90 h-8.5 shrink-0 gap-1.5 text-xs shadow-xs"
                >
                  <Plus class="size-3.5" />
                  <span>Add</span>
                </Button>
              </form>
            </CardFooter>
          </Card>
        </div>
    
        <!-- Strategic Synthesis & Key Next Actions Card (TOWS Strategy) -->
        <Card class="shadow-xs">
          <CardHeader class="border-border/60 bg-muted/15 border-b pb-4">
            <div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
              <div class="space-y-1">
                <div class="flex items-center gap-2">
                  <Zap class="text-primary size-4" />
                  <CardTitle class="text-base font-bold sm:text-lg">Strategic Synthesis &amp; Key Next Actions</CardTitle>
                </div>
                <CardDescription class="text-xs">
                  Actionable strategic initiatives bridging internal capabilities with external market dynamics (TOWS matrix
                  formulation).
                </CardDescription>
              </div>
    
              <Badge wrap variant="outline" class="w-fit text-xs font-medium"> 3 Active Initiatives </Badge>
            </div>
          </CardHeader>
    
          <CardContent class="p-4 sm:p-6">
            <div class="grid grid-cols-1 gap-4 lg:grid-cols-3">
              <div
                v-for="initiative in strategicInitiatives"
                :key="initiative.id"
                class="bg-muted/30 hover:border-primary/40 flex flex-col justify-between rounded-xl border p-4 shadow-xs transition-[border-color,box-shadow]"
              >
                <div class="space-y-3">
                  <div class="flex items-center justify-between gap-2">
                    <Badge
                      wrap
                      variant="outline"
                      :class="
                        cn(
                          'text-xs font-semibold',
                          initiative.strategyType === 'SO' && 'border-success/20 bg-success/10 text-success',
                          initiative.strategyType === 'WO' && 'border-info/20 bg-info/10 text-info',
                          initiative.strategyType === 'ST' && 'border-warning/20 bg-warning/10 text-warning',
                          initiative.strategyType === 'WT' && 'border-destructive/20 bg-destructive/10 text-destructive',
                        )
                      "
                    >
                      {{ initiative.strategyType }} Strategy
                    </Badge>
    
                    <Badge
                      :variant="initiative.priority === 'Critical' ? 'destructive' : 'secondary'"
                      class="text-xs font-medium whitespace-normal"
                    >
                      {{ initiative.priority }}
                    </Badge>
                  </div>
    
                  <div class="space-y-1.5">
                    <h4 class="text-foreground text-sm leading-snug font-semibold">{{ initiative.title }}</h4>
                    <p class="text-muted-foreground text-xs leading-relaxed">{{ initiative.description }}</p>
                  </div>
                </div>
    
                <div class="border-border/50 mt-4 flex items-center justify-between gap-2 border-t pt-3 text-xs">
                  <div class="text-muted-foreground flex items-center gap-1.5">
                    <Clock class="size-3.5" />
                    <span>{{ initiative.timeframe }}</span>
                  </div>
                  <span class="bg-primary/10 text-primary rounded px-2 py-0.5 text-xs font-medium">
                    {{ initiative.targetMetric }}
                  </span>
                </div>
              </div>
            </div>
          </CardContent>
        </Card>
      </div>
    </template>
    

Raw manifest:https://uipkge.dev/r/vue/matrix-swot-analysis.json