1003 lines
19 KiB
Vue
1003 lines
19 KiB
Vue
<template>
|
||
<div class="gamemaster-explorer">
|
||
<!-- Loading State -->
|
||
<div v-if="loading" class="loading-state">
|
||
<div class="spinner"></div>
|
||
<p>Loading Gamemaster Explorer...</p>
|
||
</div>
|
||
|
||
<!-- Error State -->
|
||
<div v-else-if="error" class="error-state">
|
||
<p class="error-message">{{ error }}</p>
|
||
<button @click="filesState.loadStatus()" class="btn-retry">Retry</button>
|
||
</div>
|
||
|
||
<!-- No Files State -->
|
||
<div v-else-if="!hasFiles" class="no-files-state">
|
||
<h2>No Gamemaster Files Available</h2>
|
||
<p>Please process gamemaster data first in the Gamemaster Manager.</p>
|
||
<router-link to="/gamemaster" class="btn-primary">
|
||
Go to Gamemaster Manager
|
||
</router-link>
|
||
</div>
|
||
|
||
<!-- Main Explorer Interface -->
|
||
<div v-else class="explorer-container">
|
||
<header class="explorer-header">
|
||
<div class="header-left">
|
||
<router-link to="/" class="back-button">← Back Home</router-link>
|
||
<h1>🔍 Gamemaster Explorer</h1>
|
||
</div>
|
||
<div class="header-controls">
|
||
<button
|
||
@click="showHelp = !showHelp"
|
||
class="btn-icon"
|
||
title="Keyboard Shortcuts (?)"
|
||
>
|
||
<span v-if="!showHelp">?</span>
|
||
<span v-else>✕</span>
|
||
</button>
|
||
<button
|
||
@click="showSettings = !showSettings"
|
||
class="btn-icon"
|
||
title="Settings"
|
||
>
|
||
⚙️
|
||
</button>
|
||
</div>
|
||
</header>
|
||
|
||
<div v-if="showHelp" class="help-panel">
|
||
<h3>Keyboard Shortcuts</h3>
|
||
<ul>
|
||
<li><kbd>Ctrl</kbd>+<kbd>F</kbd> - Focus search</li>
|
||
<li><kbd>Ctrl</kbd>+<kbd>C</kbd> - Copy selected lines</li>
|
||
<li><kbd>Ctrl</kbd>+<kbd>G</kbd> - Go to next search result</li>
|
||
<li>
|
||
<kbd>Shift</kbd>+<kbd>Ctrl</kbd>+<kbd>G</kbd> - Go to previous
|
||
result
|
||
</li>
|
||
<li><kbd>Escape</kbd> - Clear selection / Close dialogs</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div v-if="showSettings" class="settings-panel">
|
||
<h3>Settings</h3>
|
||
<label>
|
||
<input type="checkbox" v-model="preferences.lineWrap" />
|
||
Line Wrap
|
||
</label>
|
||
<label>
|
||
<input type="checkbox" v-model="preferences.darkMode" />
|
||
Dark Mode
|
||
</label>
|
||
<label>
|
||
<input type="checkbox" v-model="preferences.showLineNumbers" />
|
||
Show Line Numbers
|
||
</label>
|
||
<label>
|
||
Performance:
|
||
<select v-model="preferences.performanceMode">
|
||
<option value="auto">Auto</option>
|
||
<option value="high">High</option>
|
||
<option value="medium">Medium</option>
|
||
<option value="low">Low</option>
|
||
</select>
|
||
</label>
|
||
</div>
|
||
|
||
<FileSelector :files-state="filesState" />
|
||
|
||
<SearchBar
|
||
v-if="fileContent"
|
||
:file-lines="fileLines"
|
||
:display-lines="displayLines"
|
||
:search-state="searchState"
|
||
/>
|
||
|
||
<FilterPanel
|
||
v-if="fileContent"
|
||
:data="filterData"
|
||
:filter-state="filterState"
|
||
/>
|
||
|
||
<div v-if="operationProgress.active" class="progress-bar-container">
|
||
<div
|
||
class="progress-bar"
|
||
:class="{ complete: operationProgress.complete }"
|
||
>
|
||
<div
|
||
class="progress-fill"
|
||
:style="{ width: `${operationProgress.percent}%` }"
|
||
></div>
|
||
</div>
|
||
<div class="progress-text">{{ operationProgress.message }}</div>
|
||
</div>
|
||
|
||
<JsonViewer
|
||
v-if="fileContent"
|
||
:display-lines="displayLines"
|
||
:file-content="fileContent"
|
||
:selected-file="selectedFile"
|
||
:file-too-large="fileTooLarge"
|
||
:preferences="preferences"
|
||
:search-results="searchResults"
|
||
:current-result-index="currentResultIndex"
|
||
:highlight-config="highlightConfig"
|
||
:line-height="lineHeight"
|
||
:selection-state="selectionState"
|
||
/>
|
||
|
||
<ActionToolbar
|
||
v-if="fileContent"
|
||
:display-lines="displayLines"
|
||
:file-content="fileContent"
|
||
:selected-file="selectedFile"
|
||
:selection-state="selectionState"
|
||
/>
|
||
|
||
<div v-if="clipboard.copied.value" class="toast success">
|
||
✓ Copied to clipboard!
|
||
</div>
|
||
<div v-if="clipboard.error.value" class="toast error">
|
||
✗ {{ clipboard.error.value }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed, watch, onMounted } from 'vue';
|
||
import { GamemasterClient } from '@/utilities/gamemaster-client.js';
|
||
import { useKeyboardShortcuts } from '@/composables/useKeyboardShortcuts.js';
|
||
import { useUrlState } from '@/composables/useUrlState.js';
|
||
import { useClipboard } from '@/composables/useClipboard.js';
|
||
import { useGamemasterFiles } from '@/composables/useGamemasterFiles.js';
|
||
import { useGamemasterSearch } from '@/composables/useGamemasterSearch.js';
|
||
import { useLineSelection } from '@/composables/useLineSelection.js';
|
||
import useJsonFilter from '@/composables/useJsonFilter.js';
|
||
import SearchBar from '@/components/gamemaster/SearchBar.vue';
|
||
import FileSelector from '@/components/gamemaster/FileSelector.vue';
|
||
import JsonViewer from '@/components/gamemaster/JsonViewer.vue';
|
||
import FilterPanel from '@/components/gamemaster/FilterPanel.vue';
|
||
import ActionToolbar from '@/components/gamemaster/ActionToolbar.vue';
|
||
|
||
const client = new GamemasterClient();
|
||
const filesState = useGamemasterFiles(client);
|
||
const {
|
||
selectedFile,
|
||
fileContent,
|
||
fileLines,
|
||
displayLines,
|
||
isLoading,
|
||
fileError,
|
||
preferences,
|
||
hasFiles,
|
||
fileTooLarge,
|
||
loadStatus
|
||
} = filesState;
|
||
|
||
const searchState = useGamemasterSearch(fileLines, displayLines);
|
||
const { searchResults, currentResultIndex } = searchState;
|
||
const selectionState = useLineSelection(
|
||
displayLines,
|
||
fileContent,
|
||
selectedFile
|
||
);
|
||
const filterState = useJsonFilter();
|
||
|
||
const showHelp = ref(false);
|
||
const showSettings = ref(false);
|
||
const operationProgress = ref({
|
||
active: false,
|
||
percent: 0,
|
||
message: '',
|
||
complete: false
|
||
});
|
||
|
||
const loading = computed(() => isLoading.value);
|
||
const error = computed(() => fileError.value);
|
||
|
||
const lineHeight = computed(() => (globalThis.innerWidth < 768 ? 24 : 20));
|
||
const highlightConfig = computed(() => ({
|
||
theme: preferences.value.darkMode ? 'github-dark' : 'github',
|
||
language: 'json'
|
||
}));
|
||
|
||
const filterData = computed(() => {
|
||
if (!fileContent.value) return [];
|
||
try {
|
||
const parsed = JSON.parse(fileContent.value);
|
||
return Array.isArray(parsed) ? parsed : [];
|
||
} catch {
|
||
return [];
|
||
}
|
||
});
|
||
|
||
useUrlState({
|
||
file: selectedFile,
|
||
search: searchState.searchQuery,
|
||
filter: filterState.filterProperty,
|
||
value: filterState.filterValue
|
||
});
|
||
|
||
const clipboard = useClipboard();
|
||
|
||
useKeyboardShortcuts({
|
||
'ctrl+f': () => {
|
||
showHelp.value = false;
|
||
showSettings.value = false;
|
||
},
|
||
'ctrl+c': selectionState.copySelected,
|
||
'ctrl+g': searchState.goToNextResult,
|
||
'shift+ctrl+g': searchState.goToPrevResult,
|
||
escape: () => {
|
||
if (showHelp.value || showSettings.value) {
|
||
showHelp.value = false;
|
||
showSettings.value = false;
|
||
} else if (selectionState.selectedLines.value.size > 0) {
|
||
selectionState.selectedLines.value.clear();
|
||
} else if (searchState.searchQuery.value) {
|
||
searchState.clearSearch();
|
||
}
|
||
}
|
||
});
|
||
|
||
watch(selectedFile, () => {
|
||
if (selectionState.selectedLines.value.size > 0) {
|
||
selectionState.clearSelection();
|
||
}
|
||
});
|
||
|
||
// Lifecycle
|
||
onMounted(async () => {
|
||
await loadStatus();
|
||
});
|
||
</script>
|
||
|
||
<style scoped>
|
||
.gamemaster-explorer {
|
||
min-height: 100vh;
|
||
padding: 1rem 0.5rem;
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
}
|
||
|
||
.explorer-container {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
background: white;
|
||
border-radius: 8px;
|
||
padding: 1rem;
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||
}
|
||
|
||
/* Loading & Error States */
|
||
.loading-state,
|
||
.error-state,
|
||
.no-files-state {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-height: 60vh;
|
||
text-align: center;
|
||
gap: 1rem;
|
||
}
|
||
|
||
.error-message {
|
||
color: #c0392b;
|
||
font-weight: 600;
|
||
max-width: 500px;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.spinner {
|
||
width: 40px;
|
||
height: 40px;
|
||
border: 4px solid #f3f3f3;
|
||
border-top: 4px solid #667eea;
|
||
border-radius: 50%;
|
||
animation: spin 1s linear infinite;
|
||
}
|
||
|
||
@keyframes spin {
|
||
0% {
|
||
transform: rotate(0deg);
|
||
}
|
||
100% {
|
||
transform: rotate(360deg);
|
||
}
|
||
}
|
||
|
||
/* Header */
|
||
.explorer-header {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: space-between;
|
||
gap: 0.5rem;
|
||
margin-bottom: 1rem;
|
||
padding: 0.5rem 0;
|
||
}
|
||
|
||
.header-left {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0.5rem;
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.explorer-header h1 {
|
||
margin: 0;
|
||
font-size: 1.5rem;
|
||
color: #333;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.back-button {
|
||
padding: 0.4rem 0.8rem;
|
||
background: #667eea;
|
||
color: white;
|
||
text-decoration: none;
|
||
border-radius: 6px;
|
||
font-weight: 600;
|
||
transition: all 0.3s ease;
|
||
display: inline-block;
|
||
width: fit-content;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.back-button:hover {
|
||
background: #5568d3;
|
||
transform: translateX(-2px);
|
||
}
|
||
|
||
.header-controls {
|
||
display: flex;
|
||
gap: 0.4rem;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.btn-icon {
|
||
width: 40px;
|
||
height: 40px;
|
||
border-radius: 8px;
|
||
border: 1px solid #e9ecef;
|
||
background: #f8f9fa;
|
||
cursor: pointer;
|
||
font-size: 1.1rem;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: all 0.3s ease;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.btn-icon:hover {
|
||
background: #e9ecef;
|
||
border-color: #dee2e6;
|
||
}
|
||
|
||
/* Help & Settings Panels */
|
||
.help-panel,
|
||
.settings-panel {
|
||
background: #f8f9fa;
|
||
border: 1px solid #e9ecef;
|
||
border-radius: 6px;
|
||
padding: 1rem;
|
||
margin-bottom: 1rem;
|
||
font-size: 0.95rem;
|
||
}
|
||
|
||
.help-panel h3,
|
||
.settings-panel h3 {
|
||
margin-top: 0;
|
||
margin-bottom: 1rem;
|
||
color: #495057;
|
||
font-size: 1.1rem;
|
||
}
|
||
|
||
.help-panel ul {
|
||
list-style: none;
|
||
padding: 0;
|
||
margin: 0;
|
||
}
|
||
|
||
.help-panel li {
|
||
padding: 0.4rem 0;
|
||
}
|
||
|
||
.help-panel kbd {
|
||
background: #fff;
|
||
border: 1px solid #ccc;
|
||
border-radius: 3px;
|
||
padding: 2px 5px;
|
||
font-family: monospace;
|
||
font-size: 0.85em;
|
||
}
|
||
|
||
.settings-panel label {
|
||
display: block;
|
||
margin: 0.75rem 0;
|
||
font-size: 0.95rem;
|
||
}
|
||
|
||
/* File Selector */
|
||
.file-selector {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0.5rem;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.file-selector label {
|
||
font-weight: 600;
|
||
font-size: 0.95rem;
|
||
}
|
||
|
||
.file-selector select {
|
||
width: 100%;
|
||
padding: 0.5rem;
|
||
font-size: 1rem;
|
||
border: 1px solid #ccc;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
/* Search Bar */
|
||
.search-bar {
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.search-input-wrapper {
|
||
position: relative;
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
|
||
.search-input {
|
||
width: 100%;
|
||
padding: 0.75rem;
|
||
padding-right: 2.5rem;
|
||
font-size: 1rem;
|
||
border: 2px solid #ccc;
|
||
border-radius: 6px;
|
||
}
|
||
|
||
.search-input:focus {
|
||
outline: none;
|
||
border-color: #3498db;
|
||
}
|
||
|
||
.btn-clear {
|
||
position: absolute;
|
||
right: 0.5rem;
|
||
background: none;
|
||
border: none;
|
||
font-size: 1.25rem;
|
||
cursor: pointer;
|
||
color: #999;
|
||
}
|
||
|
||
.search-results {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
|
||
.btn-nav {
|
||
padding: 0.25rem 0.75rem;
|
||
border: 1px solid #ccc;
|
||
background: white;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.btn-nav:disabled {
|
||
opacity: 0.5;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.search-history {
|
||
display: flex;
|
||
gap: 0.5rem;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.history-item {
|
||
padding: 0.25rem 0.75rem;
|
||
background: #f0f0f0;
|
||
border: 1px solid #ccc;
|
||
border-radius: 16px;
|
||
cursor: pointer;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.history-item:hover {
|
||
background: #e0e0e0;
|
||
}
|
||
|
||
/* Property Filter */
|
||
.property-filter {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0.5rem;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.property-filter label {
|
||
font-weight: 600;
|
||
font-size: 0.95rem;
|
||
}
|
||
|
||
.property-filter select,
|
||
.filter-value-input {
|
||
width: 100%;
|
||
padding: 0.5rem;
|
||
border: 1px solid #ccc;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.filter-mode {
|
||
display: flex;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
/* Progress Bar */
|
||
.progress-bar-container {
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.progress-bar {
|
||
height: 8px;
|
||
background: #f0f0f0;
|
||
border-radius: 4px;
|
||
overflow: hidden;
|
||
position: relative;
|
||
}
|
||
|
||
.progress-fill {
|
||
height: 100%;
|
||
background: #3498db;
|
||
transition: width 0.3s ease;
|
||
}
|
||
|
||
.progress-bar.complete .progress-fill {
|
||
background: #2ecc71;
|
||
animation: blink 0.5s ease-in-out;
|
||
}
|
||
|
||
@keyframes blink {
|
||
0%,
|
||
100% {
|
||
opacity: 1;
|
||
}
|
||
50% {
|
||
opacity: 0.5;
|
||
}
|
||
}
|
||
|
||
.progress-text {
|
||
font-size: 0.9rem;
|
||
color: #666;
|
||
margin-top: 0.25rem;
|
||
}
|
||
|
||
/* Content Viewer */
|
||
.content-viewer {
|
||
background: #ffffff;
|
||
border: 1px solid #e0e0e0;
|
||
border-radius: 6px;
|
||
margin-bottom: 1rem;
|
||
max-height: 50vh;
|
||
overflow: auto;
|
||
color: #24292e;
|
||
}
|
||
|
||
.content-viewer.dark-mode {
|
||
background: #1e1e1e;
|
||
color: #d4d4d4;
|
||
border-color: #404040;
|
||
}
|
||
|
||
.scroller {
|
||
height: 50vh;
|
||
}
|
||
|
||
.lines-container {
|
||
padding: 0.75rem;
|
||
}
|
||
|
||
.line {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
padding: 0.25rem 0.5rem;
|
||
cursor: pointer;
|
||
min-height: 20px;
|
||
background: #ffffff !important;
|
||
}
|
||
|
||
.content-viewer.dark-mode .line {
|
||
background: #1e1e1e !important;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.line {
|
||
min-height: 24px;
|
||
}
|
||
}
|
||
|
||
.line:hover {
|
||
background: rgba(102, 126, 234, 0.08) !important;
|
||
}
|
||
|
||
.content-viewer.dark-mode .line:hover {
|
||
background: rgba(102, 126, 234, 0.15) !important;
|
||
}
|
||
|
||
.line.selected {
|
||
background: rgba(102, 126, 234, 0.15) !important;
|
||
}
|
||
|
||
.content-viewer.dark-mode .line.selected {
|
||
background: rgba(102, 126, 234, 0.25) !important;
|
||
}
|
||
|
||
.line.highlight-match {
|
||
background: rgba(255, 235, 59, 0.3) !important;
|
||
border-left: 3px solid #ffc107;
|
||
}
|
||
|
||
.content-viewer.dark-mode .line.highlight-match {
|
||
background: rgba(255, 235, 59, 0.15) !important;
|
||
}
|
||
|
||
.line.current-result {
|
||
background: rgba(255, 193, 7, 0.4) !important;
|
||
border-left: 5px solid #ff9800 !important;
|
||
box-shadow: inset 0 0 0 1px #ff9800;
|
||
}
|
||
|
||
.content-viewer.dark-mode .line.current-result {
|
||
background: rgba(255, 193, 7, 0.25) !important;
|
||
}
|
||
|
||
.line-number {
|
||
width: 4rem;
|
||
text-align: right;
|
||
color: #999;
|
||
font-family: monospace;
|
||
margin-right: 1rem;
|
||
user-select: none;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.content-viewer.dark-mode .line-number {
|
||
color: #666;
|
||
}
|
||
|
||
.line pre {
|
||
margin: 0;
|
||
flex: 1;
|
||
white-space: pre;
|
||
overflow-x: auto;
|
||
background: transparent !important;
|
||
color: inherit;
|
||
}
|
||
|
||
.line pre code {
|
||
background: transparent !important;
|
||
color: inherit !important;
|
||
padding: 0 !important;
|
||
}
|
||
|
||
/* Light mode syntax highlighting - high contrast colors */
|
||
.content-viewer:not(.dark-mode) .line pre code {
|
||
background-color: transparent !important;
|
||
color: #24292e !important;
|
||
}
|
||
|
||
.content-viewer:not(.dark-mode) .hljs {
|
||
background: transparent !important;
|
||
color: #24292e !important;
|
||
}
|
||
|
||
.content-viewer:not(.dark-mode) .hljs-attr,
|
||
.content-viewer:not(.dark-mode) .hljs-attribute {
|
||
color: #6f42c1 !important;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.content-viewer:not(.dark-mode) .hljs-string {
|
||
color: #032f62 !important;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.content-viewer:not(.dark-mode) .hljs-number {
|
||
color: #005a9c !important;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.content-viewer:not(.dark-mode) .hljs-literal {
|
||
color: #d73a49 !important;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.content-viewer:not(.dark-mode) .hljs-name {
|
||
color: #24292e !important;
|
||
font-weight: 600;
|
||
}
|
||
|
||
/* Dark mode syntax highlighting */
|
||
.content-viewer.dark-mode .line pre code {
|
||
background-color: transparent !important;
|
||
color: #e1e4e8 !important;
|
||
}
|
||
|
||
.content-viewer.dark-mode .hljs {
|
||
background: transparent !important;
|
||
color: #e1e4e8 !important;
|
||
}
|
||
|
||
.content-viewer.dark-mode .hljs-attr,
|
||
.content-viewer.dark-mode .hljs-attribute {
|
||
color: #79b8ff !important;
|
||
}
|
||
|
||
.content-viewer.dark-mode .hljs-string {
|
||
color: #85e89d !important;
|
||
}
|
||
|
||
.content-viewer.dark-mode .hljs-number {
|
||
color: #79b8ff !important;
|
||
}
|
||
|
||
.content-viewer.dark-mode .hljs-literal {
|
||
color: #f97583 !important;
|
||
}
|
||
|
||
.line-wrap .line pre {
|
||
white-space: pre-wrap;
|
||
word-break: break-all;
|
||
}
|
||
|
||
.warning-banner {
|
||
background: #fff3cd;
|
||
border: 1px solid #ffc107;
|
||
color: #856404;
|
||
padding: 1rem;
|
||
text-align: center;
|
||
font-weight: bold;
|
||
}
|
||
|
||
/* Action Bar */
|
||
.action-bar {
|
||
display: grid;
|
||
grid-template-columns: 1fr;
|
||
gap: 0.75rem;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.btn-action {
|
||
padding: 0.75rem 1rem;
|
||
background: #667eea;
|
||
color: #ffffff;
|
||
border: none;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
font-size: 0.95rem;
|
||
font-weight: 600;
|
||
min-height: 44px;
|
||
transition: all 0.3s ease;
|
||
width: 100%;
|
||
}
|
||
|
||
.btn-action:hover {
|
||
background: #5568d3;
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
||
}
|
||
|
||
.btn-action:disabled {
|
||
background: #95a5a6;
|
||
color: #ffffff;
|
||
cursor: not-allowed;
|
||
opacity: 0.6;
|
||
}
|
||
|
||
/* Toast */
|
||
.toast {
|
||
position: fixed;
|
||
bottom: 1rem;
|
||
right: 1rem;
|
||
left: 1rem;
|
||
padding: 1rem;
|
||
border-radius: 6px;
|
||
font-weight: bold;
|
||
z-index: 1000;
|
||
animation: slideIn 0.3s ease-out;
|
||
font-size: 0.95rem;
|
||
}
|
||
|
||
.toast.success {
|
||
background: #229954;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.toast.error {
|
||
background: #c0392b;
|
||
color: #ffffff;
|
||
}
|
||
|
||
@keyframes slideIn {
|
||
from {
|
||
transform: translateX(100%);
|
||
opacity: 0;
|
||
}
|
||
to {
|
||
transform: translateX(0);
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
/* Responsive */
|
||
@media (min-width: 769px) {
|
||
.gamemaster-explorer {
|
||
padding: 2rem 1rem;
|
||
}
|
||
|
||
.explorer-container {
|
||
padding: 2rem;
|
||
border-radius: 12px;
|
||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
||
}
|
||
|
||
.explorer-header {
|
||
align-items: center;
|
||
gap: 1rem;
|
||
}
|
||
|
||
.header-left {
|
||
flex-direction: row;
|
||
align-items: center;
|
||
gap: 1rem;
|
||
}
|
||
|
||
.explorer-header h1 {
|
||
font-size: 2.5rem;
|
||
}
|
||
|
||
.back-button {
|
||
padding: 0.5rem 1rem;
|
||
font-size: 1rem;
|
||
}
|
||
|
||
.btn-icon {
|
||
width: 44px;
|
||
height: 44px;
|
||
font-size: 1.25rem;
|
||
}
|
||
|
||
.help-panel,
|
||
.settings-panel {
|
||
padding: 1.5rem;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.file-selector {
|
||
flex-direction: row;
|
||
align-items: center;
|
||
gap: 1rem;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.file-selector label {
|
||
margin: 0;
|
||
}
|
||
|
||
.file-selector select {
|
||
width: auto;
|
||
flex: 1;
|
||
min-width: 200px;
|
||
}
|
||
|
||
.file-info {
|
||
color: #666;
|
||
font-size: 0.9rem;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.search-input {
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.property-filter {
|
||
flex-direction: row;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.property-filter label {
|
||
margin: 0;
|
||
}
|
||
|
||
.property-filter select,
|
||
.filter-value-input {
|
||
width: auto;
|
||
}
|
||
|
||
.action-bar {
|
||
display: flex;
|
||
grid-template-columns: unset;
|
||
gap: 0.5rem;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.btn-action {
|
||
width: auto;
|
||
font-size: 1rem;
|
||
}
|
||
|
||
.content-viewer {
|
||
max-height: 70vh;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.scroller {
|
||
height: 70vh;
|
||
}
|
||
|
||
.lines-container {
|
||
padding: 1rem;
|
||
}
|
||
|
||
.line-number {
|
||
width: 4rem;
|
||
}
|
||
|
||
.toast {
|
||
bottom: 2rem;
|
||
right: 2rem;
|
||
left: auto;
|
||
width: auto;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
/* Mobile styles already in defaults above */
|
||
}
|
||
|
||
/* Primary button styles */
|
||
.btn-primary,
|
||
.btn-retry {
|
||
padding: 0.75rem 1.5rem;
|
||
background: #667eea;
|
||
color: white;
|
||
text-decoration: none;
|
||
border: none;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
font-size: 1rem;
|
||
font-weight: 600;
|
||
min-height: 44px;
|
||
transition: all 0.3s ease;
|
||
display: inline-block;
|
||
margin-right: 0.5rem;
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
|
||
.btn-primary:hover:not(:disabled),
|
||
.btn-retry:hover:not(:disabled) {
|
||
background: #5568d3;
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
||
}
|
||
|
||
.btn-primary:disabled {
|
||
opacity: 0.6;
|
||
cursor: not-allowed;
|
||
}
|
||
</style>
|