This commit is contained in:
@@ -0,0 +1,721 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from "vue";
|
||||
import type {
|
||||
HighlightClip,
|
||||
HighlightSettings,
|
||||
HighlightType,
|
||||
} from "../types/timeline";
|
||||
import {
|
||||
DEFAULT_HIGHLIGHT_SETTINGS,
|
||||
getHighlightTypeColor,
|
||||
getHighlightTypeIcon,
|
||||
formatDuration,
|
||||
} from "../types/timeline";
|
||||
|
||||
// Props
|
||||
const props = defineProps<{
|
||||
highlights: HighlightClip[];
|
||||
currentVideoTime: number;
|
||||
videoDuration: number;
|
||||
isPlaying: boolean;
|
||||
isExporting: boolean;
|
||||
exportingClipId: number | null;
|
||||
}>();
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
(e: "seek-to", time: number): void;
|
||||
(e: "play-clip", clip: HighlightClip): void;
|
||||
(e: "play-all"): void;
|
||||
(e: "export-clip", clip: HighlightClip): void;
|
||||
(e: "export-all"): void;
|
||||
(e: "settings-changed", settings: HighlightSettings): void;
|
||||
}>();
|
||||
|
||||
// Settings panel state
|
||||
const showSettings = ref(false);
|
||||
const settings = ref<HighlightSettings>({ ...DEFAULT_HIGHLIGHT_SETTINGS });
|
||||
|
||||
// Type filter toggles
|
||||
const typeToggles = ref<Record<HighlightType, boolean>>({
|
||||
kill: DEFAULT_HIGHLIGHT_SETTINGS.included_types.includes("kill"),
|
||||
death: DEFAULT_HIGHLIGHT_SETTINGS.included_types.includes("death"),
|
||||
assist: DEFAULT_HIGHLIGHT_SETTINGS.included_types.includes("assist"),
|
||||
objective: DEFAULT_HIGHLIGHT_SETTINGS.included_types.includes("objective"),
|
||||
multi_kill: DEFAULT_HIGHLIGHT_SETTINGS.included_types.includes("multi_kill"),
|
||||
});
|
||||
|
||||
// Watch type toggles and update settings
|
||||
watch(
|
||||
typeToggles,
|
||||
(toggles) => {
|
||||
const included_types = (Object.entries(toggles) as [HighlightType, boolean][])
|
||||
.filter(([, enabled]) => enabled)
|
||||
.map(([type]) => type);
|
||||
settings.value = { ...settings.value, included_types };
|
||||
emit("settings-changed", settings.value);
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
// Watch buffer settings and emit
|
||||
watch(
|
||||
() => [settings.value.buffer_before, settings.value.buffer_after, settings.value.merge_overlapping, settings.value.merge_gap_secs],
|
||||
() => {
|
||||
emit("settings-changed", settings.value);
|
||||
},
|
||||
);
|
||||
|
||||
// Currently active clip (the one whose time range contains the current video time)
|
||||
const activeClipId = computed(() => {
|
||||
for (const clip of props.highlights) {
|
||||
if (
|
||||
props.currentVideoTime >= clip.start_time &&
|
||||
props.currentVideoTime <= clip.end_time
|
||||
) {
|
||||
return clip.id;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
// Stats
|
||||
const killCount = computed(() => props.highlights.filter(h => h.highlight_type === "kill").length);
|
||||
const deathCount = computed(() => props.highlights.filter(h => h.highlight_type === "death").length);
|
||||
const objectiveCount = computed(() => props.highlights.filter(h => h.highlight_type === "objective").length);
|
||||
const multiKillCount = computed(() => props.highlights.filter(h => h.highlight_type === "multi_kill").length);
|
||||
const assistCount = computed(() => props.highlights.filter(h => h.highlight_type === "assist").length);
|
||||
const totalDuration = computed(() => {
|
||||
const total = props.highlights.reduce((sum, h) => sum + (h.end_time - h.start_time), 0);
|
||||
return total;
|
||||
});
|
||||
|
||||
// Seek to the start of a clip
|
||||
function seekToClip(clip: HighlightClip) {
|
||||
emit("seek-to", clip.start_time);
|
||||
}
|
||||
|
||||
// Play a specific clip
|
||||
function playClip(clip: HighlightClip) {
|
||||
emit("play-clip", clip);
|
||||
}
|
||||
|
||||
// Export a single clip
|
||||
function exportClip(clip: HighlightClip) {
|
||||
emit("export-clip", clip);
|
||||
}
|
||||
|
||||
// Format clip time range
|
||||
function formatClipTime(clip: HighlightClip): string {
|
||||
return `${formatDuration(clip.start_time)} - ${formatDuration(clip.end_time)}`;
|
||||
}
|
||||
|
||||
// Format clip duration
|
||||
function formatClipDuration(clip: HighlightClip): string {
|
||||
return formatDuration(clip.end_time - clip.start_time);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="highlights-panel">
|
||||
<!-- Header -->
|
||||
<div class="highlights-header">
|
||||
<h3>
|
||||
<span class="header-icon">🎬</span>
|
||||
Highlights
|
||||
</h3>
|
||||
<button
|
||||
class="settings-toggle"
|
||||
:class="{ active: showSettings }"
|
||||
@click="showSettings = !showSettings"
|
||||
title="Highlight settings"
|
||||
>
|
||||
⚙️
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Settings Panel (collapsible) -->
|
||||
<div v-if="showSettings" class="settings-panel">
|
||||
<div class="settings-group">
|
||||
<label class="settings-label">Buffer Before</label>
|
||||
<div class="slider-row">
|
||||
<input
|
||||
type="range"
|
||||
min="2"
|
||||
max="30"
|
||||
step="1"
|
||||
v-model.number="settings.buffer_before"
|
||||
class="settings-slider"
|
||||
/>
|
||||
<span class="slider-value">{{ settings.buffer_before }}s</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<label class="settings-label">Buffer After</label>
|
||||
<div class="slider-row">
|
||||
<input
|
||||
type="range"
|
||||
min="2"
|
||||
max="30"
|
||||
step="1"
|
||||
v-model.number="settings.buffer_after"
|
||||
class="settings-slider"
|
||||
/>
|
||||
<span class="slider-value">{{ settings.buffer_after }}s</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<label class="settings-label">Include Types</label>
|
||||
<div class="type-toggles">
|
||||
<label
|
||||
v-for="typeInfo in [
|
||||
{ type: 'kill' as HighlightType, label: 'Kills', icon: '⚔️' },
|
||||
{ type: 'death' as HighlightType, label: 'Deaths', icon: '💀' },
|
||||
{ type: 'assist' as HighlightType, label: 'Assists', icon: '🤝' },
|
||||
{ type: 'objective' as HighlightType, label: 'Objectives', icon: '🏰' },
|
||||
{ type: 'multi_kill' as HighlightType, label: 'Multi Kills', icon: '🔥' },
|
||||
]"
|
||||
:key="typeInfo.type"
|
||||
class="type-toggle"
|
||||
:style="{ borderColor: typeToggles[typeInfo.type] ? getHighlightTypeColor(typeInfo.type) : 'rgba(255,255,255,0.1)' }"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="typeToggles[typeInfo.type]"
|
||||
class="toggle-checkbox"
|
||||
/>
|
||||
<span class="toggle-icon">{{ typeInfo.icon }}</span>
|
||||
<span class="toggle-label">{{ typeInfo.label }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<label class="settings-row-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="settings.merge_overlapping"
|
||||
class="toggle-checkbox"
|
||||
/>
|
||||
Merge overlapping clips
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div v-if="settings.merge_overlapping" class="settings-group">
|
||||
<label class="settings-label">Merge Gap</label>
|
||||
<div class="slider-row">
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="20"
|
||||
step="1"
|
||||
v-model.number="settings.merge_gap_secs"
|
||||
class="settings-slider"
|
||||
/>
|
||||
<span class="slider-value">{{ settings.merge_gap_secs }}s</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Stats -->
|
||||
<div class="highlights-stats">
|
||||
<div class="stat-pill" v-if="killCount > 0">
|
||||
<span>⚔️</span> {{ killCount }}
|
||||
</div>
|
||||
<div class="stat-pill" v-if="deathCount > 0">
|
||||
<span>💀</span> {{ deathCount }}
|
||||
</div>
|
||||
<div class="stat-pill" v-if="assistCount > 0">
|
||||
<span>🤝</span> {{ assistCount }}
|
||||
</div>
|
||||
<div class="stat-pill" v-if="objectiveCount > 0">
|
||||
<span>🏰</span> {{ objectiveCount }}
|
||||
</div>
|
||||
<div class="stat-pill" v-if="multiKillCount > 0">
|
||||
<span>🔥</span> {{ multiKillCount }}
|
||||
</div>
|
||||
<div class="stat-pill total">
|
||||
<span>⏱️</span> {{ formatDuration(totalDuration) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="highlights-actions">
|
||||
<button
|
||||
class="action-btn play-all"
|
||||
@click="emit('play-all')"
|
||||
:disabled="highlights.length === 0"
|
||||
>
|
||||
▶ Play All
|
||||
</button>
|
||||
<button
|
||||
class="action-btn export-all"
|
||||
@click="emit('export-all')"
|
||||
:disabled="highlights.length === 0 || isExporting"
|
||||
>
|
||||
{{ isExporting ? "Exporting..." : "📦 Export All" }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div v-if="highlights.length === 0" class="empty-highlights">
|
||||
<div class="empty-icon">🎬</div>
|
||||
<p>No highlights detected</p>
|
||||
<p class="empty-hint">Highlights are generated from kills, deaths, and objectives during the game.</p>
|
||||
</div>
|
||||
|
||||
<!-- Highlights List -->
|
||||
<div v-else class="highlights-list">
|
||||
<div
|
||||
v-for="clip in highlights"
|
||||
:key="clip.id"
|
||||
class="highlight-card"
|
||||
:class="{
|
||||
active: activeClipId === clip.id,
|
||||
[clip.highlight_type]: true,
|
||||
}"
|
||||
@click="seekToClip(clip)"
|
||||
>
|
||||
<div class="highlight-indicator" :style="{ backgroundColor: getHighlightTypeColor(clip.highlight_type) }"></div>
|
||||
|
||||
<div class="highlight-content">
|
||||
<div class="highlight-title-row">
|
||||
<span class="highlight-icon">{{ getHighlightTypeIcon(clip.highlight_type) }}</span>
|
||||
<span class="highlight-title">{{ clip.title }}</span>
|
||||
<span class="highlight-duration">{{ formatClipDuration(clip) }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="clip.subtitle" class="highlight-subtitle">
|
||||
{{ clip.subtitle }}
|
||||
</div>
|
||||
|
||||
<div class="highlight-time-row">
|
||||
<span class="highlight-time">{{ formatClipTime(clip) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="highlight-actions">
|
||||
<button
|
||||
class="mini-btn play"
|
||||
@click.stop="playClip(clip)"
|
||||
title="Play this clip"
|
||||
>
|
||||
▶
|
||||
</button>
|
||||
<button
|
||||
class="mini-btn export"
|
||||
@click.stop="exportClip(clip)"
|
||||
:disabled="isExporting && exportingClipId === clip.id"
|
||||
title="Export this clip"
|
||||
>
|
||||
💾
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.highlights-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.highlights-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.highlights-header h3 {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
color: #c8aa6e;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.settings-toggle {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 6px;
|
||||
padding: 0.3rem 0.5rem;
|
||||
cursor: pointer;
|
||||
font-size: 0.85rem;
|
||||
transition: all 0.2s;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.settings-toggle:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.settings-toggle.active {
|
||||
background: rgba(200, 170, 110, 0.15);
|
||||
border-color: #c8aa6e;
|
||||
}
|
||||
|
||||
/* Settings Panel */
|
||||
.settings-panel {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 8px;
|
||||
padding: 0.75rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.settings-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
.settings-label {
|
||||
font-size: 0.75rem;
|
||||
color: #888;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.settings-row-label {
|
||||
font-size: 0.8rem;
|
||||
color: #ccc;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.slider-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.settings-slider {
|
||||
flex: 1;
|
||||
height: 4px;
|
||||
-webkit-appearance: none;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.settings-slider::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
background: #c8aa6e;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.slider-value {
|
||||
font-family: monospace;
|
||||
font-size: 0.8rem;
|
||||
color: #c8aa6e;
|
||||
min-width: 2.5rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.type-toggles {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.type-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
padding: 0.3rem 0.6rem;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.type-toggle:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.toggle-checkbox {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.toggle-icon {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.toggle-label {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
/* Quick Stats */
|
||||
.highlights-stats {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.stat-pill {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding: 0.25rem 0.6rem;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 12px;
|
||||
font-size: 0.75rem;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.stat-pill.total {
|
||||
color: #c8aa6e;
|
||||
background: rgba(200, 170, 110, 0.1);
|
||||
}
|
||||
|
||||
/* Action Buttons */
|
||||
.highlights-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
flex: 1;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.action-btn:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.action-btn.play-all {
|
||||
background: rgba(74, 222, 128, 0.15);
|
||||
border-color: rgba(74, 222, 128, 0.3);
|
||||
color: #4ade80;
|
||||
}
|
||||
|
||||
.action-btn.play-all:hover:not(:disabled) {
|
||||
background: rgba(74, 222, 128, 0.25);
|
||||
}
|
||||
|
||||
.action-btn.export-all {
|
||||
background: rgba(200, 170, 110, 0.15);
|
||||
border-color: rgba(200, 170, 110, 0.3);
|
||||
color: #c8aa6e;
|
||||
}
|
||||
|
||||
.action-btn.export-all:hover:not(:disabled) {
|
||||
background: rgba(200, 170, 110, 0.25);
|
||||
}
|
||||
|
||||
/* Empty State */
|
||||
.empty-highlights {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rem 1rem;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.empty-highlights p {
|
||||
margin: 0.25rem 0;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.empty-hint {
|
||||
font-size: 0.75rem !important;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
/* Highlights List */
|
||||
.highlights-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
padding-right: 0.25rem;
|
||||
}
|
||||
|
||||
.highlight-card {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
gap: 0;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.highlight-card:hover {
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border-color: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.highlight-card.active {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.highlight-card.active.kill {
|
||||
border-color: rgba(74, 222, 128, 0.4);
|
||||
background: rgba(74, 222, 128, 0.08);
|
||||
}
|
||||
|
||||
.highlight-card.active.death {
|
||||
border-color: rgba(248, 113, 113, 0.4);
|
||||
background: rgba(248, 113, 113, 0.08);
|
||||
}
|
||||
|
||||
.highlight-card.active.assist {
|
||||
border-color: rgba(167, 139, 250, 0.4);
|
||||
background: rgba(167, 139, 250, 0.08);
|
||||
}
|
||||
|
||||
.highlight-card.active.objective {
|
||||
border-color: rgba(251, 191, 36, 0.4);
|
||||
background: rgba(251, 191, 36, 0.08);
|
||||
}
|
||||
|
||||
.highlight-card.active.multi_kill {
|
||||
border-color: rgba(249, 115, 22, 0.4);
|
||||
background: rgba(249, 115, 22, 0.08);
|
||||
}
|
||||
|
||||
.highlight-indicator {
|
||||
width: 4px;
|
||||
min-height: 100%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.highlight-content {
|
||||
flex: 1;
|
||||
padding: 0.5rem 0.65rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.highlight-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
.highlight-icon {
|
||||
font-size: 0.85rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.highlight-title {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.highlight-duration {
|
||||
font-family: monospace;
|
||||
font-size: 0.7rem;
|
||||
color: #888;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.highlight-subtitle {
|
||||
font-size: 0.72rem;
|
||||
color: #999;
|
||||
margin-top: 0.15rem;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.highlight-time-row {
|
||||
margin-top: 0.2rem;
|
||||
}
|
||||
|
||||
.highlight-time {
|
||||
font-family: monospace;
|
||||
font-size: 0.65rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.highlight-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 0.25rem;
|
||||
padding: 0.35rem 0.5rem;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.highlight-card:hover .highlight-actions,
|
||||
.highlight-card.active .highlight-actions {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.mini-btn {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
border-radius: 4px;
|
||||
padding: 0.2rem 0.4rem;
|
||||
cursor: pointer;
|
||||
font-size: 0.7rem;
|
||||
transition: all 0.15s;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.mini-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.mini-btn.play:hover {
|
||||
background: rgba(74, 222, 128, 0.2);
|
||||
border-color: rgba(74, 222, 128, 0.3);
|
||||
}
|
||||
|
||||
.mini-btn.export:hover {
|
||||
background: rgba(200, 170, 110, 0.2);
|
||||
border-color: rgba(200, 170, 110, 0.3);
|
||||
}
|
||||
|
||||
.mini-btn:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user