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.
This commit is contained in:
614
src/components/ContentDetailModal.vue
Normal file
614
src/components/ContentDetailModal.vue
Normal file
@@ -0,0 +1,614 @@
|
||||
<template>
|
||||
<Transition name="modal-fade">
|
||||
<div v-if="isOpen && content" class="detail-overlay" @click.self="$emit('close')">
|
||||
<div class="detail-container">
|
||||
<!-- Scrollable content area -->
|
||||
<div class="detail-scroll" ref="scrollContainer">
|
||||
<!-- Backdrop Hero -->
|
||||
<div class="detail-hero">
|
||||
<img
|
||||
:src="content.backdrop || content.thumbnail"
|
||||
:alt="content.title"
|
||||
class="w-full h-full object-cover object-center"
|
||||
/>
|
||||
<div class="hero-gradient-overlay"></div>
|
||||
|
||||
<!-- Close Button -->
|
||||
<button @click="$emit('close')" class="absolute top-4 right-4 z-10 p-2 bg-black/50 backdrop-blur-md rounded-full text-white/80 hover:text-white transition-colors">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Hero Content Overlay -->
|
||||
<div class="absolute bottom-0 left-0 right-0 p-6 md:p-8">
|
||||
<h1 class="text-3xl md:text-4xl lg:text-5xl font-bold text-white mb-3 drop-shadow-lg" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;">
|
||||
{{ content.title }}
|
||||
</h1>
|
||||
|
||||
<!-- Meta Row -->
|
||||
<div class="flex flex-wrap items-center gap-2.5 text-sm text-white/80 mb-4">
|
||||
<span v-if="content.rating" class="bg-white/20 backdrop-blur-sm px-2.5 py-0.5 rounded text-white">{{ content.rating }}</span>
|
||||
<span v-if="content.releaseYear">{{ content.releaseYear }}</span>
|
||||
<span v-if="content.duration">{{ content.duration }} min</span>
|
||||
<span v-if="content.type" class="capitalize">{{ content.type }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<!-- Play Button -->
|
||||
<button @click="handlePlay" class="play-btn flex items-center gap-2">
|
||||
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M8 5v14l11-7z"/>
|
||||
</svg>
|
||||
Play
|
||||
</button>
|
||||
|
||||
<!-- Add to My List -->
|
||||
<button @click="toggleMyList" class="action-btn" :class="{ 'action-btn-active': isInMyList }">
|
||||
<svg v-if="!isInMyList" class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
<svg v-else class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z"/>
|
||||
</svg>
|
||||
<span class="hidden sm:inline">My List</span>
|
||||
</button>
|
||||
|
||||
<!-- Like Button -->
|
||||
<button @click="handleLike" class="action-btn" :class="{ 'action-btn-active': userReaction === '+' }">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 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>
|
||||
<span v-if="reactionCounts.positive > 0" class="text-xs">{{ reactionCounts.positive }}</span>
|
||||
</button>
|
||||
|
||||
<!-- Dislike Button -->
|
||||
<button @click="handleDislike" class="action-btn" :class="{ 'action-btn-active': userReaction === '-' }">
|
||||
<svg class="w-5 h-5 rotate-180" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 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>
|
||||
<span v-if="reactionCounts.negative > 0" class="text-xs">{{ reactionCounts.negative }}</span>
|
||||
</button>
|
||||
|
||||
<!-- Share Button -->
|
||||
<button @click="handleShare" class="action-btn">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z" />
|
||||
</svg>
|
||||
<span class="hidden sm:inline">Share</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content Body -->
|
||||
<div class="detail-body">
|
||||
<!-- Description -->
|
||||
<div class="mb-6">
|
||||
<p class="text-white/80 text-base leading-relaxed">{{ content.description }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Categories -->
|
||||
<div v-if="content.categories && content.categories.length > 0" class="flex flex-wrap gap-2 mb-6">
|
||||
<span
|
||||
v-for="category in content.categories"
|
||||
:key="category"
|
||||
class="category-tag"
|
||||
>
|
||||
{{ category }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Creator Attribution -->
|
||||
<div v-if="content.creator" class="flex items-center gap-3 mb-8 text-white/60 text-sm">
|
||||
<span>Directed by</span>
|
||||
<span class="text-white font-medium">{{ content.creator }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Divider -->
|
||||
<div class="border-t border-white/10 mb-6"></div>
|
||||
|
||||
<!-- Comments Section -->
|
||||
<div class="comments-section">
|
||||
<h3 class="text-lg font-semibold text-white mb-4 flex items-center gap-2">
|
||||
Comments
|
||||
<span class="text-sm font-normal text-white/50">({{ comments.length }})</span>
|
||||
<span v-if="isDev" class="text-xs bg-white/10 text-white/40 px-2 py-0.5 rounded-full ml-auto">Demo Mode</span>
|
||||
</h3>
|
||||
|
||||
<!-- Comment Input -->
|
||||
<div v-if="isAuthenticated" class="comment-input-wrap mb-6">
|
||||
<div class="flex gap-3">
|
||||
<div class="profile-avatar flex-shrink-0">
|
||||
<span>{{ userInitials }}</span>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<textarea
|
||||
v-model="newComment"
|
||||
placeholder="Share your thoughts..."
|
||||
class="comment-textarea"
|
||||
rows="2"
|
||||
@keydown.meta.enter="submitComment"
|
||||
@keydown.ctrl.enter="submitComment"
|
||||
></textarea>
|
||||
<div class="flex justify-end mt-2">
|
||||
<button
|
||||
@click="submitComment"
|
||||
:disabled="!newComment.trim()"
|
||||
class="submit-comment-btn"
|
||||
:class="{ 'opacity-40 cursor-not-allowed': !newComment.trim() }"
|
||||
>
|
||||
Post
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sign in prompt for comments -->
|
||||
<div v-else class="text-center py-4 mb-6 bg-white/5 rounded-xl">
|
||||
<p class="text-white/50 text-sm">
|
||||
<button @click="$emit('openAuth')" class="text-white underline hover:text-white/80">Sign in</button>
|
||||
to leave a comment
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Comments List -->
|
||||
<div v-if="isLoadingComments" class="text-center py-8">
|
||||
<div class="text-white/40 text-sm">Loading comments...</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="comments.length === 0" class="text-center py-8">
|
||||
<div class="text-white/40 text-sm">No comments yet. Be the first!</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="space-y-4">
|
||||
<div
|
||||
v-for="comment in comments"
|
||||
:key="comment.id"
|
||||
class="comment-item"
|
||||
>
|
||||
<div class="flex gap-3">
|
||||
<!-- Author Avatar -->
|
||||
<img
|
||||
v-if="getProfile(comment.pubkey)?.picture"
|
||||
:src="getProfile(comment.pubkey).picture"
|
||||
:alt="getProfile(comment.pubkey)?.name || 'User'"
|
||||
class="w-8 h-8 rounded-full flex-shrink-0 object-cover"
|
||||
/>
|
||||
<div v-else class="w-8 h-8 rounded-full flex-shrink-0 bg-gradient-to-br from-orange-500 to-pink-500 flex items-center justify-center text-xs font-bold text-white">
|
||||
{{ (getProfile(comment.pubkey)?.name || 'A')[0].toUpperCase() }}
|
||||
</div>
|
||||
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<span class="text-white text-sm font-medium truncate">
|
||||
{{ getProfile(comment.pubkey)?.name || 'Anonymous' }}
|
||||
</span>
|
||||
<span class="text-white/30 text-xs flex-shrink-0">
|
||||
{{ formatTimeAgo(comment.created_at) }}
|
||||
</span>
|
||||
</div>
|
||||
<p class="text-white/70 text-sm leading-relaxed">{{ comment.content }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sub-modals triggered from within this modal -->
|
||||
<VideoPlayer
|
||||
:isOpen="showVideoPlayer"
|
||||
:content="content"
|
||||
@close="showVideoPlayer = false"
|
||||
/>
|
||||
|
||||
<SubscriptionModal
|
||||
:isOpen="showSubscriptionModal"
|
||||
@close="showSubscriptionModal = false"
|
||||
@success="handleSubscriptionSuccess"
|
||||
/>
|
||||
|
||||
<RentalModal
|
||||
:isOpen="showRentalModal"
|
||||
:content="content"
|
||||
@close="showRentalModal = false"
|
||||
@success="handleRentalSuccess"
|
||||
@openSubscription="openSubscriptionFromRental"
|
||||
/>
|
||||
</div>
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, computed } from 'vue'
|
||||
import { useAuth } from '../composables/useAuth'
|
||||
import { useNostr } from '../composables/useNostr'
|
||||
import type { Content } from '../types/content'
|
||||
import VideoPlayer from './VideoPlayer.vue'
|
||||
import SubscriptionModal from './SubscriptionModal.vue'
|
||||
import RentalModal from './RentalModal.vue'
|
||||
|
||||
interface Props {
|
||||
isOpen: boolean
|
||||
content: Content | null
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'close'): void
|
||||
(e: 'openAuth'): void
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
defineEmits<Emits>()
|
||||
|
||||
const { isAuthenticated, hasActiveSubscription, user } = useAuth()
|
||||
|
||||
const isDev = import.meta.env.DEV
|
||||
const newComment = ref('')
|
||||
const isInMyList = ref(false)
|
||||
const userReaction = ref<string | null>(null)
|
||||
const showVideoPlayer = ref(false)
|
||||
const showSubscriptionModal = ref(false)
|
||||
const showRentalModal = ref(false)
|
||||
|
||||
// Nostr social data -- initialized per content
|
||||
const nostr = useNostr()
|
||||
const comments = computed(() => nostr.comments.value)
|
||||
const reactionCounts = computed(() => nostr.reactionCounts.value)
|
||||
const isLoadingComments = computed(() => nostr.isLoading.value)
|
||||
|
||||
const userInitials = computed(() => {
|
||||
if (!user.value?.legalName) return 'U'
|
||||
const names = user.value.legalName.split(' ')
|
||||
return names.length > 1
|
||||
? `${names[0][0]}${names[names.length - 1][0]}`
|
||||
: names[0][0]
|
||||
})
|
||||
|
||||
// Fetch social data when content changes
|
||||
watch(() => props.content?.id, async (newId) => {
|
||||
if (newId && props.isOpen) {
|
||||
await loadSocialData(newId)
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => props.isOpen, async (open) => {
|
||||
if (open && props.content?.id) {
|
||||
await loadSocialData(props.content.id)
|
||||
}
|
||||
})
|
||||
|
||||
async function loadSocialData(contentId: string) {
|
||||
userReaction.value = null
|
||||
await Promise.all([
|
||||
nostr.fetchComments(contentId),
|
||||
nostr.fetchReactions(contentId),
|
||||
])
|
||||
nostr.subscribeToComments(contentId)
|
||||
nostr.subscribeToReactions(contentId)
|
||||
}
|
||||
|
||||
function getProfile(pubkey: string) {
|
||||
return nostr.profiles.value.get(pubkey)
|
||||
}
|
||||
|
||||
function handlePlay() {
|
||||
if (!isAuthenticated.value) {
|
||||
// Will be caught by parent via openAuth emit
|
||||
return
|
||||
}
|
||||
|
||||
if (hasActiveSubscription.value) {
|
||||
showVideoPlayer.value = true
|
||||
return
|
||||
}
|
||||
|
||||
// No subscription -- show rental modal
|
||||
showRentalModal.value = true
|
||||
}
|
||||
|
||||
function toggleMyList() {
|
||||
isInMyList.value = !isInMyList.value
|
||||
}
|
||||
|
||||
async function handleLike() {
|
||||
if (!isAuthenticated.value) return
|
||||
userReaction.value = userReaction.value === '+' ? null : '+'
|
||||
if (props.content?.id) {
|
||||
try {
|
||||
await nostr.postReaction(true, props.content.id)
|
||||
} catch (err) {
|
||||
console.error('Failed to post reaction:', err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDislike() {
|
||||
if (!isAuthenticated.value) return
|
||||
userReaction.value = userReaction.value === '-' ? null : '-'
|
||||
if (props.content?.id) {
|
||||
try {
|
||||
await nostr.postReaction(false, props.content.id)
|
||||
} catch (err) {
|
||||
console.error('Failed to post reaction:', err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleShare() {
|
||||
const url = `${window.location.origin}/content/${props.content?.id}`
|
||||
if (navigator.share) {
|
||||
navigator.share({
|
||||
title: props.content?.title,
|
||||
text: props.content?.description,
|
||||
url,
|
||||
}).catch(() => {
|
||||
// User cancelled share
|
||||
})
|
||||
} else {
|
||||
navigator.clipboard.writeText(url)
|
||||
}
|
||||
}
|
||||
|
||||
async function submitComment() {
|
||||
if (!newComment.value.trim() || !props.content?.id) return
|
||||
|
||||
try {
|
||||
await nostr.postComment(newComment.value.trim(), props.content.id)
|
||||
newComment.value = ''
|
||||
} catch (err) {
|
||||
console.error('Failed to post comment:', err)
|
||||
}
|
||||
}
|
||||
|
||||
function handleSubscriptionSuccess() {
|
||||
showSubscriptionModal.value = false
|
||||
}
|
||||
|
||||
function handleRentalSuccess() {
|
||||
showRentalModal.value = false
|
||||
showVideoPlayer.value = true
|
||||
}
|
||||
|
||||
function openSubscriptionFromRental() {
|
||||
showRentalModal.value = false
|
||||
showSubscriptionModal.value = true
|
||||
}
|
||||
|
||||
function formatTimeAgo(timestamp: number): string {
|
||||
const now = Math.floor(Date.now() / 1000)
|
||||
const diff = now - timestamp
|
||||
|
||||
if (diff < 60) return 'just now'
|
||||
if (diff < 3600) return `${Math.floor(diff / 60)}m ago`
|
||||
if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`
|
||||
if (diff < 604800) return `${Math.floor(diff / 86400)}d ago`
|
||||
return new Date(timestamp * 1000).toLocaleDateString()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.detail-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 1000;
|
||||
background: rgba(0, 0, 0, 0.85);
|
||||
backdrop-filter: blur(8px);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.detail-overlay {
|
||||
padding: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 900px;
|
||||
max-height: 100vh;
|
||||
background: #141414;
|
||||
border-radius: 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.detail-container {
|
||||
max-height: 90vh;
|
||||
border-radius: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-scroll {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
|
||||
.detail-scroll::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.detail-scroll::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
/* Hero */
|
||||
.detail-hero {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.detail-hero {
|
||||
height: 400px;
|
||||
}
|
||||
}
|
||||
|
||||
.hero-gradient-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(
|
||||
to top,
|
||||
#141414 0%,
|
||||
rgba(20, 20, 20, 0.85) 30%,
|
||||
rgba(20, 20, 20, 0.3) 60%,
|
||||
rgba(20, 20, 20, 0) 100%
|
||||
);
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.play-btn {
|
||||
position: relative;
|
||||
padding: 10px 28px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
border-radius: 16px;
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
color: rgba(0, 0, 0, 0.9);
|
||||
box-shadow:
|
||||
0 12px 32px rgba(0, 0, 0, 0.4),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 1);
|
||||
backdrop-filter: blur(24px);
|
||||
-webkit-backdrop-filter: blur(24px);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.play-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
box-shadow:
|
||||
0 16px 40px rgba(0, 0, 0, 0.5),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 1);
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 8px 14px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
border-radius: 12px;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.action-btn-active {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border-color: rgba(255, 255, 255, 0.3);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Category Tags */
|
||||
.category-tag {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
border-radius: 20px;
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
/* Content Body */
|
||||
.detail-body {
|
||||
padding: 0 1.5rem 2rem;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.detail-body {
|
||||
padding: 0 2rem 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Comments */
|
||||
.comment-textarea {
|
||||
width: 100%;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 12px;
|
||||
padding: 12px 14px;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
resize: none;
|
||||
outline: none;
|
||||
transition: border-color 0.2s ease;
|
||||
}
|
||||
|
||||
.comment-textarea::placeholder {
|
||||
color: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.comment-textarea:focus {
|
||||
border-color: rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
|
||||
.submit-comment-btn {
|
||||
padding: 6px 20px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
border-radius: 10px;
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
color: rgba(0, 0, 0, 0.9);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.submit-comment-btn:hover:not(:disabled) {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.comment-item {
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.comment-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* Transitions */
|
||||
.modal-fade-enter-active {
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.modal-fade-leave-active {
|
||||
transition: opacity 0.25s ease;
|
||||
}
|
||||
|
||||
.modal-fade-enter-from,
|
||||
.modal-fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user