39656-vm/city.php
2026-04-15 16:05:12 +00:00

192 lines
10 KiB
PHP

<?php
declare(strict_types=1);
require_once __DIR__ . '/urban_hikes.php';
$city = trim((string)($_GET['city'] ?? ''));
$routes = $city !== '' ? urban_hikes_find_by_city($city) : [];
$cityStats = $city !== '' ? urban_hikes_city_stats($city) : null;
$allCities = urban_hikes_cities();
$otherCities = array_values(array_filter($allCities, static function (array $item) use ($city): bool {
return (string)($item['city'] ?? '') !== $city;
}));
if (!$cityStats) {
http_response_code(404);
}
$routeCount = $cityStats ? (int)$cityStats['route_count'] : 0;
$avgDistance = $cityStats ? number_format((float)$cityStats['avg_distance'], 1) : '0.0';
$avgDuration = $cityStats ? number_format((float)$cityStats['avg_duration'], 1) : '0.0';
$areaCount = $cityStats ? (int)$cityStats['area_count'] : 0;
$pageTitle = $cityStats
? 'Urban hiking in ' . $cityStats['city'] . ' | ' . urban_hikes_project_name()
: 'City not found | ' . urban_hikes_project_name();
$pageDescription = $cityStats
? 'Browse ' . $routeCount . ' urban hiking route' . ($routeCount === 1 ? '' : 's') . ' in ' . $cityStats['city'] . ', compare difficulty, distance, and route highlights, and plan a full walking day.'
: 'The requested city guide could not be found.';
$shortestRoute = $routes ? $routes[0] : null;
$longestRoute = $routes ? $routes[count($routes) - 1] : null;
urban_hikes_render_head($pageTitle, $pageDescription, $cityStats ? 'index, follow' : 'noindex, nofollow');
urban_hikes_render_nav('cities');
?>
<main>
<section class="hero-shell border-bottom">
<div class="container-lg px-3 px-lg-4 py-4 py-lg-5">
<?php if (!$cityStats): ?>
<div class="panel-card empty-state text-center">
<span class="eyebrow">City missing</span>
<h1 class="section-title mt-2 mb-2">We could not find that city guide.</h1>
<p class="text-muted mb-4">Try opening a city from the directory instead.</p>
<a class="btn btn-dark" href="index.php#cities">Back to cities</a>
</div>
<?php else: ?>
<nav aria-label="breadcrumb" class="mb-3">
<ol class="breadcrumb small mb-0">
<li class="breadcrumb-item"><a href="index.php">Directory</a></li>
<li class="breadcrumb-item"><a href="index.php#cities">Cities</a></li>
<li class="breadcrumb-item active" aria-current="page"><?= htmlspecialchars((string)$cityStats['city']) ?></li>
</ol>
</nav>
<div class="row g-4 align-items-end">
<div class="col-lg-7">
<span class="eyebrow">City guide</span>
<h1 class="display-title mt-3 mb-3">Urban hiking in <?= htmlspecialchars((string)$cityStats['city']) ?></h1>
<p class="lead-copy mb-4">
Use this city page as your quick planner: compare route length, difficulty, starting point, and route highlights before heading out.
</p>
<div class="d-flex flex-wrap gap-2">
<a class="btn btn-dark px-4" href="#city-routes">Browse routes</a>
<a class="btn btn-outline-secondary px-4" href="index.php?city=<?= rawurlencode((string)$cityStats['city']) ?>#results">Open filtered directory</a>
</div>
</div>
<div class="col-lg-5">
<div class="panel-card stats-panel h-100">
<div class="row g-3">
<div class="col-6 col-md-3 col-lg-6">
<p class="metric-label">Routes</p>
<p class="metric-value"><?= $routeCount ?></p>
</div>
<div class="col-6 col-md-3 col-lg-6">
<p class="metric-label">Avg km</p>
<p class="metric-value"><?= htmlspecialchars($avgDistance) ?></p>
</div>
<div class="col-6 col-md-3 col-lg-6">
<p class="metric-label">Avg hours</p>
<p class="metric-value"><?= htmlspecialchars($avgDuration) ?></p>
</div>
<div class="col-6 col-md-3 col-lg-6">
<p class="metric-label">Areas</p>
<p class="metric-value"><?= $areaCount ?></p>
</div>
</div>
<div class="divider my-4"></div>
<ul class="compact-list mb-0">
<li><?= (int)$cityStats['easy_count'] ?> easy · <?= (int)$cityStats['moderate_count'] ?> moderate · <?= (int)$cityStats['challenging_count'] ?> challenging</li>
<?php if ($shortestRoute): ?>
<li>Shortest option: <?= htmlspecialchars((string)$shortestRoute['title']) ?> at <?= htmlspecialchars(number_format((float)$shortestRoute['distance_km'], 1)) ?> km</li>
<?php endif; ?>
<?php if ($longestRoute && $longestRoute !== $shortestRoute): ?>
<li>Longest option: <?= htmlspecialchars((string)$longestRoute['title']) ?> at <?= htmlspecialchars(number_format((float)$longestRoute['distance_km'], 1)) ?> km</li>
<?php endif; ?>
</ul>
</div>
</div>
</div>
<?php endif; ?>
</div>
</section>
<?php if ($cityStats): ?>
<section class="section-shell border-bottom" id="city-routes">
<div class="container-lg px-3 px-lg-4 py-4 py-lg-5">
<div class="row g-4 align-items-start">
<div class="col-lg-8">
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-end gap-3 mb-3">
<div>
<span class="eyebrow"><?= htmlspecialchars((string)$cityStats['city']) ?> routes</span>
<h2 class="section-title mb-1 mt-2">Pick the walk that fits your day.</h2>
<p class="text-muted mb-0">Every route below links to a full detail page and an external map.</p>
</div>
<a class="btn btn-outline-secondary btn-sm align-self-start align-self-md-auto" href="admin.php">Add a route</a>
</div>
<div class="row g-3">
<?php foreach ($routes as $route): ?>
<?php $highlights = array_slice(urban_hikes_highlight_items((string)$route['highlights']), 0, 3); ?>
<div class="col-12">
<article class="panel-card route-card h-100">
<div class="d-flex flex-column flex-lg-row gap-3 justify-content-between">
<div class="flex-grow-1">
<div class="d-flex flex-wrap gap-2 mb-3">
<span class="badge text-bg-light border"><?= htmlspecialchars((string)$route['difficulty']) ?></span>
<span class="badge text-bg-light border"><?= htmlspecialchars((string)$route['neighborhood']) ?></span>
<span class="badge text-bg-light border">Start: <?= htmlspecialchars((string)$route['start_point']) ?></span>
</div>
<h2 class="route-title mb-2"><a href="route.php?id=<?= (int)$route['id'] ?>"><?= htmlspecialchars((string)$route['title']) ?></a></h2>
<p class="text-muted mb-3"><?= htmlspecialchars((string)$route['summary']) ?></p>
<div class="route-meta mb-3">
<span><?= htmlspecialchars(number_format((float)$route['distance_km'], 1)) ?> km</span>
<span><?= htmlspecialchars(number_format((float)$route['duration_hours'], 1)) ?> hr</span>
<span><?= htmlspecialchars((string)$route['best_for']) ?></span>
</div>
<?php if ($highlights): ?>
<ul class="highlights-list mb-0">
<?php foreach ($highlights as $item): ?>
<li><?= htmlspecialchars($item) ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</div>
<div class="route-actions d-flex flex-lg-column justify-content-between gap-2">
<a class="btn btn-dark" href="route.php?id=<?= (int)$route['id'] ?>">View details</a>
<a class="btn btn-outline-secondary" href="admin.php?id=<?= (int)$route['id'] ?>">Edit route</a>
<a class="btn btn-outline-secondary" href="<?= htmlspecialchars((string)$route['map_url']) ?>" target="_blank" rel="noopener">Open map</a>
</div>
</div>
</article>
</div>
<?php endforeach; ?>
</div>
</div>
<div class="col-lg-4">
<div class="panel-card mb-3">
<span class="eyebrow">Planning notes</span>
<h2 class="section-title h5 mt-2">How to use this city page</h2>
<ul class="compact-list mb-0 mt-3">
<li>Start with distance and duration to match your available time.</li>
<li>Open a route map only after the route summary looks right.</li>
<li>Use the filtered directory view if you want broader search controls.</li>
</ul>
</div>
<div class="panel-card">
<div class="d-flex justify-content-between align-items-center mb-3">
<div>
<span class="eyebrow">Explore more</span>
<h2 class="section-title h5 mt-2 mb-0">Other city guides</h2>
</div>
<a class="small text-decoration-none" href="index.php#cities">All cities</a>
</div>
<?php if (!$otherCities): ?>
<p class="text-muted mb-0">Add another city route to expand the guide.</p>
<?php else: ?>
<div class="vstack gap-3">
<?php foreach (array_slice($otherCities, 0, 5) as $cityRow): ?>
<a class="subroute-link" href="city.php?city=<?= rawurlencode((string)$cityRow['city']) ?>">
<strong><?= htmlspecialchars((string)$cityRow['city']) ?></strong>
<span><?= (int)$cityRow['route_count'] ?> route<?= (int)$cityRow['route_count'] === 1 ? '' : 's' ?></span>
</a>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
</section>
<?php endif; ?>
</main>
<?php urban_hikes_render_footer(); ?>