Files
indee-demo/src/components/ContentRow.vue
Dorian c970f5b29f Enhance deployment script and update package dependencies
- Added detailed labels to the deployment script for IndeedHub, including title, version, description, license, icon, and repository URL.
- Updated package dependencies in package.json and package-lock.json, including upgrading 'nostr-tools' to version 2.23.0 and adding 'axios' and '@tanstack/vue-query'.
- Improved README with a modern description of the platform and updated project structure details.

This commit enhances the clarity of the deployment process and ensures the project is using the latest dependencies for better performance and features.
2026-02-12 10:30:47 +00:00

200 lines
6.3 KiB
Vue

<template>
<div class="content-row">
<h2 class="content-row-title text-xl md:text-2xl font-bold text-white mb-4 px-4 uppercase">
{{ title }}
</h2>
<div class="relative group">
<!-- Scroll Left Button -->
<button
v-if="canScrollLeft"
@click="scrollLeft"
class="scroll-nav-button hidden md:flex items-center justify-center absolute left-0 top-0 bottom-0 z-10 w-12 transition-all"
>
<svg class="w-8 h-8" fill="currentColor" viewBox="0 0 24 24">
<path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/>
</svg>
</button>
<!-- Content Slider -->
<div
ref="sliderRef"
class="flex gap-8 overflow-x-auto overflow-y-visible scrollbar-hide scroll-smooth px-4 pt-6 pb-8"
@scroll="handleScroll"
>
<div
v-for="content in contents"
:key="content.id"
class="content-card flex-shrink-0 w-[200px] md:w-[280px] group/card cursor-pointer"
@click="$emit('content-click', content)"
>
<div class="glass-card rounded-lg p-1.5 transition-all duration-300 relative">
<img
:src="content.thumbnail"
:alt="content.title"
class="w-full aspect-[2/3] object-contain rounded-md bg-neutral-900"
loading="lazy"
/>
<!-- Social Indicators -->
<div class="absolute bottom-3 left-3 flex items-center gap-2">
<span class="social-badge" v-if="getReactionCount(content.id) > 0">
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 24 24"><path d="M14 10h4.764a2 2 0 011.789 2.894l-3.5 7A2 2 0 0115.263 21h-4.017c-.163 0-.326-.02-.485-.06L7 20m7-10V5a2 2 0 00-2-2h-.095c-.5 0-.905.405-.905.905 0 .714-.211 1.412-.608 2.006L7 11v9m7-10h-2M7 20H5a2 2 0 01-2-2v-6a2 2 0 012-2h2.5"/></svg>
{{ getReactionCount(content.id) }}
</span>
<span class="social-badge" v-if="getCommentCount(content.id) > 0">
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/></svg>
{{ getCommentCount(content.id) }}
</span>
</div>
</div>
<div class="mt-2">
<h3 class="text-base md:text-xl font-semibold md:font-bold text-white truncate">{{ content.title }}</h3>
<p class="text-base text-white/60 truncate hidden md:block">{{ content.description }}</p>
</div>
</div>
</div>
<!-- Scroll Right Button -->
<button
v-if="canScrollRight"
@click="scrollRight"
class="scroll-nav-button hidden md:flex items-center justify-center absolute right-0 top-0 bottom-0 z-10 w-12 transition-all"
>
<svg class="w-8 h-8" fill="currentColor" viewBox="0 0 24 24">
<path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/>
</svg>
</button>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { getMockReactionCounts, getMockCommentCount } from '../data/mockSocialData'
import type { Content } from '../types/content'
interface Props {
title: string
contents: Content[]
}
defineProps<Props>()
defineEmits<{
'content-click': [content: Content]
}>()
function getReactionCount(contentId: string): number {
return getMockReactionCounts(contentId).positive
}
function getCommentCount(contentId: string): number {
return getMockCommentCount(contentId)
}
const sliderRef = ref<HTMLElement | null>(null)
const canScrollLeft = ref(false)
const canScrollRight = ref(true)
const handleScroll = () => {
if (!sliderRef.value) return
const { scrollLeft, scrollWidth, clientWidth } = sliderRef.value
canScrollLeft.value = scrollLeft > 0
canScrollRight.value = scrollLeft < scrollWidth - clientWidth - 10
}
const scrollLeft = () => {
if (!sliderRef.value) return
sliderRef.value.scrollBy({ left: -600, behavior: 'smooth' })
}
const scrollRight = () => {
if (!sliderRef.value) return
sliderRef.value.scrollBy({ left: 600, behavior: 'smooth' })
}
onMounted(() => {
if (sliderRef.value) {
sliderRef.value.addEventListener('scroll', handleScroll)
handleScroll()
}
})
onUnmounted(() => {
if (sliderRef.value) {
sliderRef.value.removeEventListener('scroll', handleScroll)
}
})
</script>
<style scoped>
.scrollbar-hide {
-ms-overflow-style: none;
scrollbar-width: none;
}
.scrollbar-hide::-webkit-scrollbar {
display: none;
}
.content-row-title {
background: linear-gradient(to right, #fafafa, #9ca3af);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
letter-spacing: 0.05em; /* 5% character spacing */
}
.scroll-nav-button {
background: rgba(0, 0, 0, 0.35);
backdrop-filter: blur(24px);
-webkit-backdrop-filter: blur(24px);
border: 1px solid rgba(255, 255, 255, 0.08);
box-shadow:
0 8px 24px rgba(0, 0, 0, 0.45),
inset 0 1px 0 rgba(255, 255, 255, 0.15);
color: rgba(255, 255, 255, 0.8);
opacity: 0.7;
cursor: pointer;
}
.scroll-nav-button:hover {
background: rgba(0, 0, 0, 0.45);
border-color: rgba(255, 255, 255, 0.15);
opacity: 1;
color: rgba(255, 255, 255, 1);
box-shadow:
0 12px 32px rgba(0, 0, 0, 0.6),
inset 0 1px 0 rgba(255, 255, 255, 0.2);
}
.glass-card {
background: linear-gradient(135deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.02));
border: 1px solid rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.group\/card:hover .glass-card {
background: linear-gradient(135deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.05));
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2), 0 0 20px rgba(255, 255, 255, 0.05);
transform: translateY(-4px);
}
.social-badge {
display: flex;
align-items: center;
gap: 3px;
padding: 2px 6px;
font-size: 11px;
font-weight: 600;
color: rgba(255, 255, 255, 0.85);
background: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-radius: 6px;
}
</style>