712 lines
23 KiB
HTML
712 lines
23 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Index - travel-website</title>
|
||
<style>
|
||
:root {
|
||
--bg-primary: #ffffff;
|
||
--bg-secondary: #f5f5f5;
|
||
--bg-card: #fafafa;
|
||
--text-primary: #1a1a1a;
|
||
--text-secondary: #666666;
|
||
--border-color: #e0e0e0;
|
||
--accent-color: rgb(0, 148, 202);
|
||
--accent-gradient: linear-gradient(135deg, rgb(0, 148, 202) 0%, rgb(0, 120, 170) 100%);
|
||
--hover-bg: #f0f0f0;
|
||
--shadow: rgba(0, 148, 202, 0.1);
|
||
--folder-icon: rgb(237, 137, 54);
|
||
--file-icon: rgb(0, 148, 202);
|
||
}
|
||
|
||
[data-theme="dark"] {
|
||
--bg-primary: #1a1a2e;
|
||
--bg-secondary: #16213e;
|
||
--bg-card: #0f3460;
|
||
--text-primary: #eaeaea;
|
||
--text-secondary: #a0a0a0;
|
||
--border-color: #404040;
|
||
--accent-color: rgb(0, 148, 202);
|
||
--accent-gradient: linear-gradient(135deg, rgb(0, 148, 202) 0%, rgb(0, 120, 170) 100%);
|
||
--hover-bg: #1e2a3a;
|
||
--shadow: rgba(0, 0, 0, 0.3);
|
||
--folder-icon: rgb(246, 173, 85);
|
||
--file-icon: rgb(0, 148, 202);
|
||
}
|
||
|
||
* {
|
||
box-sizing: border-box;
|
||
margin: 0;
|
||
padding: 0;
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||
}
|
||
|
||
body {
|
||
background: var(--bg-primary);
|
||
color: var(--text-primary);
|
||
min-height: 100vh;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.container {
|
||
max-width: 1400px;
|
||
margin: 0 auto;
|
||
background: var(--bg-secondary);
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 8px var(--shadow);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.quick-nav-container {
|
||
position: relative;
|
||
}
|
||
|
||
.quick-nav-btn {
|
||
background: rgba(255, 255, 255, 0.2);
|
||
border: none;
|
||
color: white;
|
||
padding: 8px 16px;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
font-size: 0.9rem;
|
||
font-weight: 500;
|
||
transition: all 0.2s;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
|
||
.quick-nav-btn:hover {
|
||
background: rgba(255, 255, 255, 0.3);
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
.quick-nav-dropdown {
|
||
position: absolute;
|
||
top: 100%;
|
||
left: 0;
|
||
margin-top: 8px;
|
||
background: var(--bg-card);
|
||
border: 1px solid var(--border-color);
|
||
border-radius: 8px;
|
||
box-shadow: 0 4px 20px var(--shadow);
|
||
min-width: 250px;
|
||
max-height: 400px;
|
||
overflow-y: auto;
|
||
display: none;
|
||
animation: slideDown 0.2s ease;
|
||
}
|
||
|
||
@keyframes slideDown {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(-10px);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
|
||
.quick-nav-dropdown.active {
|
||
display: block;
|
||
}
|
||
|
||
.quick-nav-item {
|
||
display: block;
|
||
padding: 12px 16px;
|
||
color: var(--text-primary);
|
||
text-decoration: none;
|
||
transition: all 0.2s;
|
||
border-bottom: 1px solid var(--border-color);
|
||
}
|
||
|
||
.quick-nav-item:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.quick-nav-item:hover {
|
||
background: var(--hover-bg);
|
||
padding-left: 20px;
|
||
}
|
||
|
||
header {
|
||
background: var(--bg-primary);
|
||
padding: 30px;
|
||
border-bottom: 1px solid var(--border-color);
|
||
}
|
||
|
||
h1 {
|
||
font-size: 1.75rem;
|
||
font-weight: 600;
|
||
color: var(--text-primary);
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
h1:hover {
|
||
color: var(--accent-color);
|
||
transform: translateX(5px);
|
||
}
|
||
|
||
.breadcrumb {
|
||
color: var(--text-secondary);
|
||
font-size: 0.9rem;
|
||
margin-bottom: 15px;
|
||
display: flex;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
}
|
||
|
||
.breadcrumb a {
|
||
color: var(--accent-color);
|
||
text-decoration: none;
|
||
transition: all 0.2s;
|
||
padding: 2px 6px;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.breadcrumb a:hover {
|
||
background: var(--hover-bg);
|
||
text-decoration: underline;
|
||
}
|
||
|
||
.breadcrumb-separator {
|
||
color: var(--text-secondary);
|
||
opacity: 0.5;
|
||
}
|
||
|
||
.stats {
|
||
display: inline-block;
|
||
background: var(--bg-card);
|
||
padding: 8px 16px;
|
||
border-radius: 4px;
|
||
font-size: 0.85rem;
|
||
color: var(--text-secondary);
|
||
border: 1px solid var(--border-color);
|
||
}
|
||
|
||
/* ===== SEARCH RESULTS ===== */
|
||
.search-results {
|
||
margin-top: 20px;
|
||
display: none;
|
||
}
|
||
|
||
.search-results.active {
|
||
display: block;
|
||
animation: fadeIn 0.3s ease;
|
||
}
|
||
|
||
@keyframes fadeIn {
|
||
from { opacity: 0; }
|
||
to { opacity: 1; }
|
||
}
|
||
|
||
.search-results-header {
|
||
font-size: 1.1rem;
|
||
font-weight: 600;
|
||
margin-bottom: 15px;
|
||
color: var(--text-primary);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.search-result-item {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 12px;
|
||
background: var(--bg-primary);
|
||
border: 1px solid var(--border-color);
|
||
border-radius: 6px;
|
||
margin-bottom: 8px;
|
||
text-decoration: none;
|
||
color: inherit;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.search-result-item:hover {
|
||
background: var(--hover-bg);
|
||
transform: translateX(5px);
|
||
border-color: var(--accent-color);
|
||
}
|
||
|
||
.search-result-icon {
|
||
width: 36px;
|
||
height: 36px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: var(--bg-secondary);
|
||
border-radius: 4px;
|
||
margin-right: 12px;
|
||
font-size: 1.2rem;
|
||
}
|
||
|
||
.search-result-info {
|
||
flex: 1;
|
||
}
|
||
|
||
.search-result-name {
|
||
font-weight: 500;
|
||
color: var(--text-primary);
|
||
margin-bottom: 2px;
|
||
}
|
||
|
||
.search-result-meta {
|
||
font-size: 0.8rem;
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
.highlight {
|
||
background: rgba(102, 126, 234, 0.2);
|
||
padding: 0 2px;
|
||
border-radius: 2px;
|
||
}
|
||
|
||
.content {
|
||
padding: 30px;
|
||
}
|
||
|
||
.section {
|
||
margin-bottom: 40px;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 1.44rem;
|
||
font-weight: 600;
|
||
color: var(--text-primary);
|
||
margin-bottom: 20px;
|
||
padding-bottom: 10px;
|
||
border-bottom: 2px solid var(--border-color);
|
||
}
|
||
|
||
.file-list {
|
||
list-style: none;
|
||
display: grid;
|
||
gap: 10px;
|
||
}
|
||
|
||
.file-item {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 15px;
|
||
background: var(--bg-card);
|
||
border: 1px solid var(--border-color);
|
||
border-radius: 6px;
|
||
text-decoration: none;
|
||
color: inherit;
|
||
transition: all 0.2s ease;
|
||
animation: fadeInUp 0.3s ease;
|
||
animation-fill-mode: both;
|
||
}
|
||
|
||
@keyframes fadeInUp {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(10px);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
|
||
.file-item:hover {
|
||
background: var(--hover-bg);
|
||
transform: translateY(-2px);
|
||
border-color: var(--accent-color);
|
||
box-shadow: 0 4px 12px var(--shadow);
|
||
}
|
||
|
||
.file-icon {
|
||
width: 40px;
|
||
height: 40px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: var(--file-icon);
|
||
border-radius: 4px;
|
||
margin-right: 15px;
|
||
font-size: 0.8rem;
|
||
font-weight: 600;
|
||
color: white;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
.file-icon.folder {
|
||
background: var(--folder-icon);
|
||
}
|
||
|
||
.file-info {
|
||
flex: 1;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.file-name {
|
||
font-weight: 500;
|
||
color: var(--text-primary);
|
||
margin-bottom: 4px;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
font-size: 1.06rem;
|
||
}
|
||
|
||
.file-meta {
|
||
font-size: 0.875rem;
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
.empty-folder {
|
||
text-align: center;
|
||
padding: 60px 20px;
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
.back-link {
|
||
display: inline-block;
|
||
margin-bottom: 20px;
|
||
color: var(--accent-color);
|
||
text-decoration: none;
|
||
padding: 8px 16px;
|
||
background: var(--bg-card);
|
||
border: 1px solid var(--border-color);
|
||
border-radius: 4px;
|
||
transition: all 0.2s;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.back-link:hover {
|
||
background: var(--hover-bg);
|
||
transform: translateX(-5px);
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.file-list {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
h1 {
|
||
font-size: 1.5rem;
|
||
}
|
||
|
||
.header-top {
|
||
flex-direction: column;
|
||
gap: 15px;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
}
|
||
</style>
|
||
<!-- External CSS for global search -->
|
||
<link rel="stylesheet" href="../../../../../../../css/global-search.css">
|
||
<!-- Shared navbar styles -->
|
||
<link rel="stylesheet" href="../../../../../../../css/navbar.css">
|
||
</head>
|
||
<body>
|
||
<!-- Navbar will be injected here -->
|
||
<div data-navbar-placeholder></div>
|
||
|
||
<div class="container">
|
||
<div class="content">
|
||
<div class="section">
|
||
<h2 class="section-title">Folders</h2>
|
||
<ul class="file-list">
|
||
<li>
|
||
<a href="./egypt/" class="file-item">
|
||
<div class="file-icon folder">DIR</div>
|
||
<div class="file-info">
|
||
<div class="file-name">egypt</div>
|
||
<div class="file-meta">26 items</div>
|
||
</div>
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<!-- External JavaScript for global search -->
|
||
<script src="../../../../../../../js/global-search.js"></script>
|
||
|
||
<script>
|
||
// Theme toggle functionality
|
||
function toggleTheme() {
|
||
const html = document.documentElement;
|
||
const buttons = document.querySelectorAll('.theme-toggle-nav');
|
||
|
||
if (!html) return;
|
||
|
||
const currentTheme = html.getAttribute('data-theme');
|
||
|
||
if (currentTheme === 'dark') {
|
||
html.setAttribute('data-theme', 'light');
|
||
buttons.forEach(btn => btn.textContent = '🌓 Theme');
|
||
localStorage.setItem('theme', 'light');
|
||
} else {
|
||
html.setAttribute('data-theme', 'dark');
|
||
buttons.forEach(btn => btn.textContent = '☀️ Theme');
|
||
localStorage.setItem('theme', 'dark');
|
||
}
|
||
}
|
||
|
||
// Initialize theme on page load - DARK MODE IS DEFAULT
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
const savedTheme = localStorage.getItem('theme') || 'dark'; // Default to dark mode
|
||
const html = document.documentElement;
|
||
const buttons = document.querySelectorAll('.theme-toggle-nav');
|
||
|
||
if (html) {
|
||
html.setAttribute('data-theme', savedTheme);
|
||
buttons.forEach(btn => {
|
||
btn.textContent = savedTheme === 'dark' ? '☀️ Theme' : '🌓 Theme';
|
||
});
|
||
}
|
||
|
||
// Initialize global search
|
||
if (typeof initGlobalSearch === 'function') {
|
||
initGlobalSearch();
|
||
}
|
||
|
||
// Check for search parameter in URL
|
||
const urlParams = new URLSearchParams(window.location.search);
|
||
const searchTerm = urlParams.get('search');
|
||
if (searchTerm) {
|
||
const searchInput = document.getElementById('searchInput');
|
||
if (searchInput) {
|
||
searchInput.value = searchTerm;
|
||
// Enable entire site search by default when using URL parameter
|
||
if (!isEntireSiteSearch) {
|
||
toggleSearchScope();
|
||
}
|
||
performSearch(searchTerm);
|
||
}
|
||
}
|
||
});
|
||
|
||
// Quick Navigation Toggle
|
||
function toggleQuickNav() {
|
||
const dropdown = document.getElementById('quickNavDropdown');
|
||
if (dropdown) {
|
||
dropdown.classList.toggle('active');
|
||
}
|
||
}
|
||
|
||
// Close quick nav when clicking outside
|
||
document.addEventListener('click', function(e) {
|
||
const container = document.querySelector('.quick-nav-container');
|
||
const dropdown = document.getElementById('quickNavDropdown');
|
||
|
||
if (container && dropdown && !container.contains(e.target)) {
|
||
dropdown.classList.remove('active');
|
||
}
|
||
});
|
||
|
||
// Search functionality
|
||
let isEntireSiteSearch = false;
|
||
let searchIndex = null;
|
||
|
||
// Load search index for entire site search
|
||
async function loadSearchIndex() {
|
||
if (searchIndex) return searchIndex;
|
||
|
||
try {
|
||
const response = await fetch('/search_index.json');
|
||
if (response.ok) {
|
||
searchIndex = await response.json();
|
||
console.log('✅ Global search index loaded:', searchIndex.total_items, 'items');
|
||
return searchIndex;
|
||
}
|
||
} catch (error) {
|
||
console.log('ℹ️ Using local folder search only');
|
||
}
|
||
return null;
|
||
}
|
||
|
||
function toggleSearchScope() {
|
||
isEntireSiteSearch = !isEntireSiteSearch;
|
||
const toggle = document.getElementById('scopeToggle');
|
||
const label = document.getElementById('scopeLabel');
|
||
const input = document.getElementById('searchInput');
|
||
|
||
if (toggle) {
|
||
toggle.classList.toggle('active', isEntireSiteSearch);
|
||
}
|
||
if (label) {
|
||
label.textContent = isEntireSiteSearch ? 'Entire Site' : 'Current Folder';
|
||
}
|
||
if (input) {
|
||
input.placeholder = isEntireSiteSearch ? 'Search entire site...' : 'Search in current folder...';
|
||
}
|
||
|
||
// Re-perform search if there's text
|
||
const currentValue = input ? input.value : '';
|
||
if (currentValue) {
|
||
performSearch(currentValue);
|
||
}
|
||
}
|
||
|
||
function handleSearch(event) {
|
||
if (event.key === 'Escape') {
|
||
clearSearch();
|
||
return;
|
||
}
|
||
|
||
const searchTerm = event.target.value.trim();
|
||
if (searchTerm.length > 0) {
|
||
performSearch(searchTerm);
|
||
} else {
|
||
clearSearch();
|
||
}
|
||
}
|
||
|
||
async function performSearch(searchTerm) {
|
||
const resultsDiv = document.getElementById('searchResults');
|
||
if (!resultsDiv) return;
|
||
|
||
if (isEntireSiteSearch) {
|
||
await performGlobalSearch(searchTerm, resultsDiv);
|
||
} else {
|
||
performLocalSearch(searchTerm, resultsDiv);
|
||
}
|
||
}
|
||
|
||
function performLocalSearch(searchTerm, resultsDiv) {
|
||
const term = searchTerm.toLowerCase();
|
||
const items = document.querySelectorAll('.file-item');
|
||
const results = [];
|
||
|
||
items.forEach(item => {
|
||
const nameElement = item.querySelector('.file-name');
|
||
if (nameElement) {
|
||
const name = nameElement.textContent.toLowerCase();
|
||
if (name.includes(term)) {
|
||
results.push({
|
||
element: item,
|
||
name: nameElement.textContent,
|
||
href: item.href,
|
||
type: item.querySelector('.file-icon').textContent,
|
||
meta: item.querySelector('.file-meta').textContent
|
||
});
|
||
}
|
||
}
|
||
});
|
||
|
||
displaySearchResults(results, searchTerm, resultsDiv, false);
|
||
}
|
||
|
||
async function performGlobalSearch(searchTerm, resultsDiv) {
|
||
const index = await loadSearchIndex();
|
||
if (!index) {
|
||
resultsDiv.innerHTML = '<div class="search-results-header">ℹ️ Global search not available. Showing local results only.</div>';
|
||
resultsDiv.classList.add('active');
|
||
performLocalSearch(searchTerm, resultsDiv);
|
||
return;
|
||
}
|
||
|
||
const term = searchTerm.toLowerCase();
|
||
const matches = index.items.filter(item =>
|
||
item.name.toLowerCase().includes(term)
|
||
).slice(0, 50); // Limit to 50 results
|
||
|
||
displaySearchResults(matches, searchTerm, resultsDiv, true);
|
||
}
|
||
|
||
function displaySearchResults(results, searchTerm, resultsDiv, isGlobal) {
|
||
if (results.length === 0) {
|
||
resultsDiv.innerHTML = `
|
||
<div class="search-results-header">😕 No results found</div>
|
||
<p style="color: var(--text-secondary); padding: 10px;">Try different keywords or check your spelling.</p>
|
||
`;
|
||
resultsDiv.classList.add('active');
|
||
return;
|
||
}
|
||
|
||
const scopeText = isGlobal ? '🌐 Global Search' : '📁 Local Search';
|
||
let html = `<div class="search-results-header">${scopeText} Results (${results.length} found)</div>`;
|
||
|
||
results.forEach(result => {
|
||
const highlightedName = highlightMatch(result.name, searchTerm);
|
||
const icon = result.type === 'DIR' || result.type === 'Folder' ? '[DIR]' : '[FILE]';
|
||
|
||
html += `
|
||
<a href="${result.href || result.path}" class="search-result-item">
|
||
<div class="search-result-icon">${icon}</div>
|
||
<div class="search-result-info">
|
||
<div class="search-result-name">${highlightedName}</div>
|
||
<div class="search-result-meta">${result.type || 'File'} • ${result.meta || result.size || ''} ${result.location ? '• ' + result.location : ''}</div>
|
||
</div>
|
||
</a>
|
||
`;
|
||
});
|
||
|
||
resultsDiv.innerHTML = html;
|
||
resultsDiv.classList.add('active');
|
||
}
|
||
|
||
function highlightMatch(text, searchTerm) {
|
||
const escaped = escapeRegex(searchTerm);
|
||
const regex = new RegExp('(' + escaped + ')', 'gi');
|
||
return text.replace(regex, '<span class="highlight">$1</span>');
|
||
}
|
||
|
||
function escapeRegex(string) {
|
||
// Escape special regex characters
|
||
return string.split('').map(function(char) {
|
||
if ('[]/{}()*+?.-^$|\\'.indexOf(char) !== -1) {
|
||
return '\\' + char;
|
||
}
|
||
return char;
|
||
}).join('');
|
||
}
|
||
|
||
function clearSearch() {
|
||
const input = document.getElementById('searchInput');
|
||
const resultsDiv = document.getElementById('searchResults');
|
||
|
||
if (input) input.value = '';
|
||
if (resultsDiv) {
|
||
resultsDiv.classList.remove('active');
|
||
resultsDiv.innerHTML = '';
|
||
}
|
||
}
|
||
|
||
// ===== AUTO-REFRESH TO DETECT CHANGES (works on remote hosting) =====
|
||
(function() {
|
||
const REFRESH_INTERVAL = 30000; // 30 seconds
|
||
const STORAGE_KEY = 'last_index_update_' + window.location.pathname;
|
||
|
||
// Store initial load time
|
||
if (!sessionStorage.getItem(STORAGE_KEY)) {
|
||
sessionStorage.setItem(STORAGE_KEY, Date.now());
|
||
}
|
||
|
||
// Check for content changes periodically
|
||
setInterval(function() {
|
||
fetch(window.location.href, { cache: 'no-store' })
|
||
.then(response => response.text())
|
||
.then(html => {
|
||
const parser = new DOMParser();
|
||
const newDoc = parser.parseFromString(html, 'text/html');
|
||
const currentStats = document.querySelector('.stats');
|
||
const newStats = newDoc.querySelector('.stats');
|
||
|
||
// If stats changed (folder/file count), reload the page
|
||
if (currentStats && newStats && currentStats.innerHTML !== newStats.innerHTML) {
|
||
console.log('📄 Directory contents changed, refreshing...');
|
||
window.location.reload();
|
||
}
|
||
})
|
||
.catch(err => console.log('Checking for updates...'));
|
||
}, REFRESH_INTERVAL);
|
||
})();
|
||
</script>
|
||
<!-- Load shared navbar component -->
|
||
<script src="../../../../../../../js/navbar-loader.js"></script>
|
||
</body>
|
||
</html>
|