Portfolio

Project Gallery

A visually appealing project gallery with filtering, sorting, and animated transitions.

A visually appealing project gallery with filtering, sorting, and animated transitions. Perfect for showcasing portfolios, case studies, and creative work.

Usage

Our Work

Explore our portfolio of successful projects and creative solutions.

Vue.jsReactTailwind CSSFigmaNode.jsTypeScriptGraphQLFirebaseNext.jsNuxt.js

E-commerce Platform Redesign

Complete redesign of a major e-commerce platform focusing on user experience and conversion optimization.

Vue.jsTailwind CSSFigma +1 more
TechCorp Inc.TechCorp Inc.
June 2023
Mobile Banking Application
Handheld app

Mobile Banking Application

Secure mobile banking application with biometric authentication and real-time transaction monitoring.

React NativeNode.jsGraphQL +1 more
FinanceGlobalFinanceGlobal
May 2023

Corporate Brand Identity

Comprehensive brand identity package including logo, color palette, typography, and brand guidelines.

IllustratorPhotoshopBrand Guidelines +1 more
StartupXYZStartupXYZ
April 2023

Health & Fitness Dashboard

Interactive dashboard for health tracking application with data visualization and personalized insights.

FigmaData chartsUser Research +1 more
Wellness Co.Wellness Co.
March 2023
SaaS Analytics Platform
Development

SaaS Analytics Platform

Enterprise analytics platform with customizable dashboards, reporting tools, and real-time data processing.

ReactNode.jsMongoDB +2 more
DataInsights Ltd.DataInsights Ltd.
February 2023

Restaurant Booking System

Online reservation system for restaurants with table management, waitlist features, and customer notifications.

Vue.jsFirebaseTwilio +1 more
Fine Dining GroupFine Dining Group
January 2023
<template>
  <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
    <!-- Header -->
    <div class="text-center mb-12">
      <MInView
        :variants="{ hidden: { opacity: 0, y: 20 }, visible: { opacity: 1, y: 0 } }"
        :transition="{ duration: 0.5 }"
      >
        <h1 class="text-4xl font-bold text-gray-900 dark:text-white mb-4">
          Our Work
        </h1>
      </MInView>
      
      <MInView
        :variants="{ hidden: { opacity: 0, y: 20 }, visible: { opacity: 1, y: 0 } }"
        :transition="{ duration: 0.5, delay: 0.1 }"
      >
        <p class="text-xl text-gray-600 dark:text-gray-400 max-w-2xl mx-auto">
          Explore our portfolio of successful projects and creative solutions.
        </p>
      </MInView>
    </div>
    
    <!-- Filters and Search -->
    <MInView
      :variants="{ hidden: { opacity: 0, y: 20 }, visible: { opacity: 1, y: 0 } }"
      :transition="{ duration: 0.5, delay: 0.2 }"
      class="mb-8"
    >
      <div class="flex flex-col md:flex-row gap-4">
        <div class="flex-1">
          <UInput
            v-model="searchQuery"
            placeholder="Search projects..."
            icon="i-heroicons-magnifying-glass"
            size="lg"
          />
        </div>
        <div class="flex gap-2">
          <USelectMenu
            v-model="categoryFilter"
            :items="categories"
            value-key="value"
            placeholder="All Categories"
            size="lg"
          />
          <USelectMenu
            v-model="sortBy"
            :items="sortOptions"
            value-key="value"
            placeholder="Sort By"
            size="lg"
          />
        </div>
      </div>
    </MInView>
    
    <!-- Filter Tags -->
    <MInView
      :variants="{ hidden: { opacity: 0, y: 20 }, visible: { opacity: 1, y: 0 } }"
      :transition="{ duration: 0.5, delay: 0.3 }"
      class="mb-8"
    >
      <div class="flex flex-wrap gap-2">
        <UBadge
          v-for="tag in allTags"
          :key="tag"
          :color="activeTags.includes(tag) ? 'primary' : 'gray'"
          variant="soft"
          class="cursor-pointer"
          @click="toggleTag(tag)"
        >
          {{ tag }}
        </UBadge>
      </div>
    </MInView>
    
    <!-- Projects Grid -->
    <MAnimatedGroup
      v-if="filteredProjects.length > 0"
      :stagger-children="0.1"
      preset="slide"
      initial="hidden"
      animate="visible"
      class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 mb-12"
    >
      <div
        v-for="project in paginatedProjects"
        :key="project.id"
      >
        <UCard
          class="h-full flex flex-col group hover:shadow-xl transition-all duration-300 overflow-hidden"
          :ui="{ body: { base: 'flex-1 flex flex-col p-6' } }"
        >
          <div class="relative overflow-hidden rounded-lg mb-4">
            <img
              :src="project.image"
              :alt="project.title"
              class="w-full h-48 object-cover transition-transform duration-500 group-hover:scale-105"
            >
            <div class="absolute inset-0 bg-black bg-opacity-0 group-hover:bg-opacity-30 transition-all duration-300 flex items-center justify-center">
              <UButton
                :to="`/portfolio/${project.slug}`"
                color="white"
                variant="solid"
                size="sm"
                class="opacity-0 group-hover:opacity-100 translate-y-4 group-hover:translate-y-0 transition-all duration-300"
              >
                View Project
              </UButton>
            </div>
            <div class="absolute top-4 left-4">
              <UBadge
                :color="getCategoryColor(project.category)"
                variant="solid"
              >
                {{ project.category }}
              </UBadge>
            </div>
          </div>
          
          <div class="flex-1">
            <h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-2">
              {{ project.title }}
            </h3>
            
            <p class="text-gray-600 dark:text-gray-400 mb-4 flex-1 line-clamp-2">
              {{ project.description }}
            </p>
            
            <div class="flex flex-wrap gap-1 mb-4">
              <UBadge
                v-for="tag in project.tags.slice(0, 3)"
                :key="tag"
                variant="soft"
                size="xs"
              >
                {{ tag }}
              </UBadge>
              <UBadge
                v-if="project.tags.length > 3"
                variant="soft"
                size="xs"
              >
                +{{ project.tags.length - 3 }} more
              </UBadge>
            </div>
          </div>
          
          <div class="flex items-center justify-between pt-4 border-t border-gray-200 dark:border-gray-800">
            <div class="flex items-center">
              <UAvatar
                :src="project.client.avatar"
                :alt="project.client.name"
                size="xs"
                class="mr-2"
              />
              <span class="text-gray-700 dark:text-gray-300 text-sm">{{ project.client.name }}</span>
            </div>
            <div class="text-sm text-gray-500 dark:text-gray-400">
              {{ project.date }}
            </div>
          </div>
        </UCard>
      </div>
    </MAnimatedGroup>
    
    <!-- Empty State -->
    <MInView
      v-if="filteredProjects.length === 0"
      :variants="{ hidden: { opacity: 0, y: 20 }, visible: { opacity: 1, y: 0 } }"
      :transition="{ duration: 0.5 }"
      class="text-center py-12"
    >
      <UIcon
        name="i-heroicons-photo"
        class="w-16 h-16 mx-auto text-gray-400 dark:text-gray-500"
      />
      <h3 class="mt-4 text-lg font-medium text-gray-900 dark:text-white">No projects found</h3>
      <p class="mt-1 text-gray-500 dark:text-gray-400">Try adjusting your search or filter criteria</p>
      <UButton
        class="mt-6"
        @click="resetFilters"
      >
        Reset Filters
      </UButton>
    </MInView>
    
    <!-- Pagination -->
    <MInView
      v-if="filteredProjects.length > 0"
      :variants="{ hidden: { opacity: 0, y: 20 }, visible: { opacity: 1, y: 0 } }"
      :transition="{ duration: 0.5 }"
      class="flex justify-center"
    >
      <UPagination
        v-model="page"
        :page-count="pageCount"
        :total="filteredProjects.length"
      />
    </MInView>
  </div>
</template>

<script setup lang="ts">
const searchQuery = ref('')
const categoryFilter = ref('all')
const sortBy = ref('date-desc')
const activeTags = ref<string[]>([])
const page = ref(1)
const projectsPerPage = 6

const categories = [
  { label: 'All Categories', value: 'all' },
  { label: 'Web Design', value: 'Web Design' },
  { label: 'Mobile App', value: 'Mobile App' },
  { label: 'Branding', value: 'Branding' },
  { label: 'UI/UX', value: 'UI/UX' },
  { label: 'Development', value: 'Development' }
]

const sortOptions = [
  { label: 'Newest First', value: 'date-desc' },
  { label: 'Oldest First', value: 'date-asc' },
  { label: 'Title A-Z', value: 'title-asc' },
  { label: 'Title Z-A', value: 'title-desc' }
]

const allTags = ['Vue.js', 'React', 'Tailwind CSS', 'Figma', 'Node.js', 'TypeScript', 'GraphQL', 'Firebase', 'Next.js', 'Nuxt.js']

const projects = ref([
  {
    id: 1,
    title: 'E-commerce Platform Redesign',
    description: 'Complete redesign of a major e-commerce platform focusing on user experience and conversion optimization.',
    category: 'Web Design',
    date: 'June 2023',
    image: 'https://images.unsplash.com/photo-1551650975-87deedd944c3?ixlib=rb-4.0.3&auto=format&fit=crop&w=600&q=80',
    client: {
      name: 'TechCorp Inc.',
      avatar: 'https://randomuser.me/api/portraits/men/32.jpg'
    },
    tags: ['Vue.js', 'Tailwind CSS', 'Figma', 'Firebase'],
    slug: 'ecommerce-platform-redesign'
  },
  {
    id: 2,
    title: 'Mobile Banking Application',
    description: 'Secure mobile banking application with biometric authentication and real-time transaction monitoring.',
    category: 'Mobile App',
    date: 'May 2023',
    image: 'https://images.unsplash.com/photo-1551836022-d5d88e9218df?ixlib=rb-4.0.3&auto=format&fit=crop&w=600&q=80',
    client: {
      name: 'FinanceGlobal',
      avatar: 'https://randomuser.me/api/portraits/women/44.jpg'
    },
    tags: ['React Native', 'Node.js', 'GraphQL', 'Biometrics'],
    slug: 'mobile-banking-application'
  },
  {
    id: 3,
    title: 'Corporate Brand Identity',
    description: 'Comprehensive brand identity package including logo, color palette, typography, and brand guidelines.',
    category: 'Branding',
    date: 'April 2023',
    image: 'https://images.unsplash.com/photo-1496171367470-9ed9a91ea931?ixlib=rb-4.0.3&auto=format&fit=crop&w=600&q=80',
    client: {
      name: 'StartupXYZ',
      avatar: 'https://randomuser.me/api/portraits/men/22.jpg'
    },
    tags: ['Illustrator', 'Photoshop', 'Brand Guidelines', 'Typography'],
    slug: 'corporate-brand-identity'
  },
  {
    id: 4,
    title: 'Health & Fitness Dashboard',
    description: 'Interactive dashboard for health tracking application with data visualization and personalized insights.',
    category: 'UI/UX',
    date: 'March 2023',
    image: 'https://images.unsplash.com/photo-1576091160550-2173dba999ef?ixlib=rb-4.0.3&auto=format&fit=crop&w=600&q=80',
    client: {
      name: 'Wellness Co.',
      avatar: 'https://randomuser.me/api/portraits/women/68.jpg'
    },
    tags: ['Figma', 'Data Visualization', 'User Research', 'Prototyping'],
    slug: 'health-fitness-dashboard'
  },
  {
    id: 5,
    title: 'SaaS Analytics Platform',
    description: 'Enterprise analytics platform with customizable dashboards, reporting tools, and real-time data processing.',
    category: 'Development',
    date: 'February 2023',
    image: 'https://images.unsplash.com/photo-1551288049-bebda4e38f71?ixlib=rb-4.0.3&auto=format&fit=crop&w=600&q=80',
    client: {
      name: 'DataInsights Ltd.',
      avatar: 'https://randomuser.me/api/portraits/men/56.jpg'
    },
    tags: ['React', 'Node.js', 'MongoDB', 'D3.js', 'WebSocket'],
    slug: 'saas-analytics-platform'
  },
  {
    id: 6,
    title: 'Restaurant Booking System',
    description: 'Online reservation system for restaurants with table management, waitlist features, and customer notifications.',
    category: 'Web Design',
    date: 'January 2023',
    image: 'https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?ixlib=rb-4.0.3&auto=format&fit=crop&w=600&q=80',
    client: {
      name: 'Fine Dining Group',
      avatar: 'https://randomuser.me/api/portraits/women/32.jpg'
    },
    tags: ['Vue.js', 'Firebase', 'Twilio', 'Stripe'],
    slug: 'restaurant-booking-system'
  },
  {
    id: 7,
    title: 'Educational Mobile App',
    description: 'Gamified learning platform for children with interactive lessons, progress tracking, and parental controls.',
    category: 'Mobile App',
    date: 'December 2022',
    image: 'https://images.unsplash.com/photo-1536924940846-227afb31e2a5?ixlib=rb-4.0.3&auto=format&fit=crop&w=600&q=80',
    client: {
      name: 'EduTech Solutions',
      avatar: 'https://randomuser.me/api/portraits/men/78.jpg'
    },
    tags: ['Flutter', 'Firebase', 'Gamification', 'Animation'],
    slug: 'educational-mobile-app'
  },
  {
    id: 8,
    title: 'Non-Profit Website Redesign',
    description: 'Modern, accessible website for a non-profit organization focused on fundraising and community engagement.',
    category: 'Web Design',
    date: 'November 2022',
    image: 'https://images.unsplash.com/photo-1523050854058-8df90110c9f1?ixlib=rb-4.0.3&auto=format&fit=crop&w=600&q=80',
    client: {
      name: 'Charity Foundation',
      avatar: 'https://randomuser.me/api/portraits/women/23.jpg'
    },
    tags: ['Nuxt.js', 'Tailwind CSS', 'Accessibility', 'Donation Integration'],
    slug: 'nonprofit-website-redesign'
  }
])

const filteredProjects = computed(() => {
  let result = [...projects.value]
  
  // Apply search filter
  if (searchQuery.value) {
    const query = searchQuery.value.toLowerCase()
    result = result.filter(project => 
      project.title.toLowerCase().includes(query) || 
      project.description.toLowerCase().includes(query) ||
      project.category.toLowerCase().includes(query) ||
      project.tags.some(tag => tag.toLowerCase().includes(query))
    )
  }
  
  // Apply category filter
  if (categoryFilter.value !== 'all') {
    result = result.filter(project => project.category === categoryFilter.value)
  }
  
  // Apply tag filters
  if (activeTags.value.length > 0) {
    result = result.filter(project => 
      activeTags.value.every(tag => project.tags.includes(tag))
    )
  }
  
  // Apply sorting
  switch (sortBy.value) {
    case 'date-desc':
      result.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
      break
    case 'date-asc':
      result.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime())
      break
    case 'title-asc':
      result.sort((a, b) => a.title.localeCompare(b.title))
      break
    case 'title-desc':
      result.sort((a, b) => b.title.localeCompare(a.title))
      break
  }
  
  return result
})

const pageCount = computed(() => Math.ceil(filteredProjects.value.length / projectsPerPage))

const startIndex = computed(() => (page.value - 1) * projectsPerPage)
const endIndex = computed(() => startIndex.value + projectsPerPage)

const paginatedProjects = computed(() => {
  return filteredProjects.value.slice(startIndex.value, endIndex.value)
})

const getCategoryColor = (category: string) => {
  switch (category) {
    case 'Web Design': return 'blue'
    case 'Mobile App': return 'purple'
    case 'Branding': return 'pink'
    case 'UI/UX': return 'green'
    case 'Development': return 'amber'
    default: return 'gray'
  }
}

const toggleTag = (tag: string) => {
  if (activeTags.value.includes(tag)) {
    activeTags.value = activeTags.value.filter(t => t !== tag)
  } else {
    activeTags.value.push(tag)
  }
  page.value = 1
}

const resetFilters = () => {
  searchQuery.value = ''
  categoryFilter.value = 'all'
  sortBy.value = 'date-desc'
  activeTags.value = []
  page.value = 1
}
</script>
Copyright © 2026