<?php
// --- 1. CONFIGURATION & SETUP ---
$sort_by = isset($_GET['sort']) ? $_GET['sort'] : 'name_asc';
$search_query = isset($_GET['q']) ? trim($_GET['q']) : '';
// Array for sort options to keep the code clean
$sort_options = [
'name_asc' => 'Name (A-Z)',
'name_desc' => 'Name (Z-A)',
'date_desc' => 'Date (Newest)',
'date_asc' => 'Date (Oldest)'
];
// --- 2. SCANNING & DATA GATHERING ---
$items = [];
$scan = scandir('.');
foreach ($scan as $item) {
if ($item == '.' || $item == '..' || $item == basename(__FILE__)) {
continue;
}
if ($search_query && stripos($item, $search_query) === false) {
continue;
}
$items[] = [
'name' => $item,
'type' => is_dir($item) ? 'folder' : 'file',
'date' => filemtime($item)
];
}
// --- 3. SORTING LOGIC ---
usort($items, function ($a, $b) use ($sort_by) {
switch ($sort_by) {
case 'name_desc': return strnatcasecmp($b['name'], $a['name']);
case 'date_desc': return $b['date'] <=> $a['date'];
case 'date_asc': return $a['date'] <=> $b['date'];
case 'name_asc': default: return strnatcasecmp($a['name'], $b['name']);
}
});
// --- 4. SEPARATE FOR DISPLAY ---
$folders = [];
$files = [];
foreach($items as $item) {
if ($item['type'] === 'folder') {
$folders[] = $item;
} else {
$files[] = $item;
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Localhost Projects</title>
<style>
:root {
--bg-color: #1a1a1a;
--card-bg-color: #2c2c2c;
--text-color: #e0e0e0;
--title-color: #ffffff;
--hover-bg-color: #383838;
--shadow-color: rgba(0, 0, 0, 0.2);
--icon-color: #9e9e9e;
--accent-color: #007bff;
--border-color: #444;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
margin: 0;
padding: 2rem;
}
.container { max-width: 1200px; margin: 0 auto; }
.header { border-bottom: 1px solid var(--border-color); padding-bottom: 1rem; margin-bottom: 2rem; }
.header h1 { color: var(--title-color); margin: 0; }
.header p { margin: 0.25rem 0 0; color: #b0b0b0; }
/* --- CONTROLS: Search & Sort --- */
.controls {
display: flex;
flex-wrap: wrap;
gap: 1rem;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
}
.search-box {
flex-grow: 1;
min-width: 250px;
}
#search-input {
width: 100%;
padding: 0.75rem 1rem;
border-radius: 6px;
border: 1px solid var(--border-color);
background-color: var(--card-bg-color);
color: var(--text-color);
font-size: 1rem;
box-sizing: border-box;
}
#search-input:focus { outline: none; border-color: var(--accent-color); }
.sort-box label {
color: #b0b0b0;
margin-right: 0.5rem;
font-size: 0.9rem;
}
#sort-select {
padding: 0.75rem 1rem;
border-radius: 6px;
border: 1px solid var(--border-color);
background-color: var(--card-bg-color);
color: var(--text-color);
font-size: 1rem;
cursor: pointer;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%239e9e9e' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: right 0.75rem center;
background-size: 1em;
padding-right: 2.5rem; /* Make space for the arrow */
}
#sort-select:focus { outline: none; border-color: var(--accent-color); }
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 1.5rem; padding: 0; list-style-type: none; }
.card a { display: flex; align-items: center; padding: 1rem; background-color: var(--card-bg-color); border-radius: 8px; text-decoration: none; color: var(--text-color); transition: all 0.2s ease; height: 100%; box-shadow: 0 2px 5px var(--shadow-color); box-sizing: border-box; }
.card a:hover { background-color: var(--hover-bg-color); transform: translateY(-5px); box-shadow: 0 8px 15px var(--shadow-color); }
.card .icon { flex-shrink: 0; margin-right: 1rem; width: 24px; height: 24px; color: var(--icon-color); }
.card .name { font-weight: 500; word-break: break-all; }
h2 { color: #c7c7c7; margin-top: 3rem; margin-bottom: 1rem; font-weight: 500; }
.empty-state { text-align: center; padding: 3rem; color: #777; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Index of /</h1>
<p><?php echo htmlspecialchars($_SERVER['SERVER_NAME'] . ':' . $_SERVER['SERVER_PORT']); ?></p>
</div>
<div class="controls">
<div class="search-box">
<input type="text" id="search-input" placeholder="Search files and folders..." value="<?= htmlspecialchars($search_query) ?>">
</div>
<form id="sort-form" method="get" class="sort-box">
<input type="hidden" name="q" value="<?= htmlspecialchars($search_query) ?>">
<label for="sort-select">Sort by:</label>
<select name="sort" id="sort-select" onchange="this.form.submit()">
<?php foreach ($sort_options as $value => $label): ?>
<option value="<?= $value ?>" <?= ($value == $sort_by) ? 'selected' : '' ?>>
<?= $label ?>
</option>
<?php endforeach; ?>
</select>
</form>
</div>
<?php
$folderIcon = '<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M4 5h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2zm0 2v2h16V7H4zm0 4v8h16v-8H4z"/></svg>';
$fileIcon = '<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M6 2a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6H6zm8 7h-2V4h2v5zm-2 2h2v5h-2v-5z"/></svg>';
if (empty($folders) && empty($files)) {
echo "<div class='empty-state'>No files or folders found matching your search.</div>";
}
if (!empty($folders)) {
echo '<h2 id="folders-header">Folders</h2>';
echo '<ul class="grid" id="folders-grid">';
foreach ($folders as $folder) {
echo '<li class="card" data-name="' . htmlspecialchars($folder['name']) . '">';
echo '<a href="' . htmlspecialchars($folder['name']) . '">';
echo $folderIcon;
echo '<span class="name">' . htmlspecialchars($folder['name']) . '</span>';
echo '</a>';
echo '</li>';
}
echo '</ul>';
}
if (!empty($files)) {
echo '<h2 id="files-header">Files</h2>';
echo '<ul class="grid" id="files-grid">';
foreach ($files as $file) {
echo '<li class="card" data-name="' . htmlspecialchars($file['name']) . '">';
echo '<a href="' . htmlspecialchars($file['name']) . '">';
echo $fileIcon;
echo '<span class="name">' . htmlspecialchars($file['name']) . '</span>';
echo '</a>';
echo '</li>';
}
echo '</ul>';
}
?>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const searchInput = document.getElementById('search-input');
const sortForm = document.getElementById('sort-form');
const hiddenSearchInput = sortForm.querySelector('input[name="q"]');
// Live search filtering
searchInput.addEventListener('keyup', function() {
const query = this.value.toLowerCase().trim();
hiddenSearchInput.value = query; // Update hidden input for sorting
filterItems(query);
});
function filterItems(query) {
let folderCount = 0, fileCount = 0;
document.querySelectorAll('.card').forEach(card => {
const name = card.dataset.name.toLowerCase();
const isVisible = name.includes(query);
card.style.display = isVisible ? '' : 'none';
if (isVisible) {
if (card.parentElement.id === 'folders-grid') folderCount++;
else fileCount++;
}
});
const foldersHeader = document.getElementById('folders-header');
const filesHeader = document.getElementById('files-header');
if (foldersHeader) foldersHeader.style.display = folderCount > 0 ? '' : 'none';
if (filesHeader) filesHeader.style.display = fileCount > 0 ? '' : 'none';
}
// Apply initial filter if needed
if (searchInput.value) {
filterItems(searchInput.value.toLowerCase().trim());
}
});
</script>
</body>
</html>