V13
@ -1,8 +1,9 @@
|
||||
/* Global Styles & Variables */
|
||||
:root {
|
||||
--turquoise: #40E0D0;
|
||||
--green: #228B22;
|
||||
--coral: #FF6B6B;
|
||||
--sandy-beige: #F4E8D8;
|
||||
--turquoise: #40E0D0;
|
||||
--ocean-blue: #1E90FF;
|
||||
--white: #FFFFFF;
|
||||
--off-white: #f8f9fa;
|
||||
--text-color: #333;
|
||||
@ -10,8 +11,8 @@
|
||||
--medium-gray: #ccc;
|
||||
--dark-gray: #555;
|
||||
|
||||
--font-heading: 'Inter', sans-serif;
|
||||
--font-body: 'Open Sans', sans-serif;
|
||||
--font-heading: 'Poppins', sans-serif;
|
||||
--font-body: 'Lato', sans-serif;
|
||||
|
||||
--shadow-soft: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
--border-radius: 16px;
|
||||
@ -20,7 +21,7 @@
|
||||
body {
|
||||
font-family: var(--font-body);
|
||||
color: var(--text-color);
|
||||
background-color: var(--white); /* Changed to white for a cleaner base */
|
||||
background-color: var(--off-white);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
@ -30,7 +31,6 @@ body {
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: var(--font-heading);
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5px; /* Reduced for better readability */
|
||||
}
|
||||
|
||||
a {
|
||||
@ -172,57 +172,6 @@ header {
|
||||
animation: fadeIn 0.5s ease-in-out forwards;
|
||||
}
|
||||
|
||||
/* Hero Section */
|
||||
.hero {
|
||||
position: relative;
|
||||
height: 400px;
|
||||
background-image: url('../pasted-20251014-230507-170c4564.jpg');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
color: var(--white);
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.hero::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-size: 3rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 1rem;
|
||||
text-shadow: 0 2px 4px rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
font-size: 1rem;
|
||||
border-radius: 50px;
|
||||
border: none;
|
||||
box-shadow: var(--shadow-soft);
|
||||
padding-left: 3rem;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23767676' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-search'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: 1rem center;
|
||||
}
|
||||
|
||||
/* Restaurant Grid */
|
||||
.page-title {
|
||||
font-size: 2rem;
|
||||
@ -260,28 +209,35 @@ header {
|
||||
}
|
||||
|
||||
.restaurant-card-content {
|
||||
padding: 1.5rem;
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.restaurant-card-content h3 {
|
||||
font-size: 1.25rem;
|
||||
font-size: 1.15rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 0.5rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.restaurant-card-content p {
|
||||
color: var(--dark-gray);
|
||||
font-size: 0.95rem;
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.restaurant-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.rating-display {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 1rem;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-color);
|
||||
}
|
||||
@ -291,18 +247,17 @@ header {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.rating-display .rating-count {
|
||||
.delivery-info {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
color: var(--dark-gray);
|
||||
margin-left: 0.5rem;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
padding: 3rem 0;
|
||||
margin-top: 3rem;
|
||||
background-color: var(--off-white);
|
||||
background-color: var(--white);
|
||||
border-top: 1px solid var(--light-gray);
|
||||
}
|
||||
|
||||
@ -738,7 +693,7 @@ footer {
|
||||
}
|
||||
|
||||
.order-confirmation h1 {
|
||||
color: var(--green);
|
||||
color: var(--ocean-blue);
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
@ -971,3 +926,107 @@ footer {
|
||||
.search-bar {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
/* Cuisine Carousel */
|
||||
.cuisine-carousel {
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
padding-bottom: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
scrollbar-width: thin; /* For Firefox */
|
||||
scrollbar-color: var(--medium-gray) var(--light-gray);
|
||||
}
|
||||
|
||||
.cuisine-carousel::-webkit-scrollbar {
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.cuisine-carousel::-webkit-scrollbar-track {
|
||||
background: var(--light-gray);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.cuisine-carousel::-webkit-scrollbar-thumb {
|
||||
background-color: var(--medium-gray);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.cuisine-card {
|
||||
position: relative;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.cuisine-card input[type="checkbox"] {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.cuisine-card label {
|
||||
display: block;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border: 1px solid var(--medium-gray);
|
||||
border-radius: 50px;
|
||||
background-color: var(--white);
|
||||
color: var(--text-color);
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.cuisine-card input[type="checkbox"]:checked + label {
|
||||
background-color: var(--coral);
|
||||
color: var(--white);
|
||||
border-color: var(--coral);
|
||||
}
|
||||
|
||||
.cuisine-card label:hover {
|
||||
background-color: var(--light-gray);
|
||||
}
|
||||
|
||||
.cuisine-card input[type="checkbox"]:checked + label:hover {
|
||||
background-color: #ff4f4f;
|
||||
}
|
||||
|
||||
/* New Filter Bar */
|
||||
.filter-bar-new {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
background-color: var(--white);
|
||||
padding: 1rem;
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: var(--shadow-soft);
|
||||
}
|
||||
|
||||
.filter-bar-new .form-group,
|
||||
.filter-bar-new .form-check {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.filter-bar-new .form-control {
|
||||
border-radius: 50px;
|
||||
border: 1px solid var(--medium-gray);
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
|
||||
.filter-bar-new .form-check-label {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.filter-bar-new .btn-secondary {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* Remove old column styles */
|
||||
.row {
|
||||
display: block;
|
||||
}
|
||||
.col-md-3,
|
||||
.col-md-9 {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
BIN
assets/pasted-20251015-151258-a85c3b51.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
assets/pasted-20251015-151954-a05facda.png
Normal file
|
After Width: | Height: | Size: 973 KiB |
BIN
assets/pasted-20251015-151955-4afb19e2.png
Normal file
|
After Width: | Height: | Size: 120 KiB |
BIN
assets/pasted-20251015-152035-29d5dbfa.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
assets/pasted-20251015-152039-5ff1195a.png
Normal file
|
After Width: | Height: | Size: 827 KiB |
BIN
assets/pasted-20251015-152137-402eb713.png
Normal file
|
After Width: | Height: | Size: 755 KiB |
BIN
assets/pasted-20251015-152159-f3bbf59e.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
assets/pasted-20251015-152218-471cd34e.png
Normal file
|
After Width: | Height: | Size: 104 KiB |
BIN
assets/pasted-20251015-152243-150e3dc0.png
Normal file
|
After Width: | Height: | Size: 150 KiB |
BIN
assets/pasted-20251015-152405-8fffc81d.png
Normal file
|
After Width: | Height: | Size: 106 KiB |
103
hero.php
Normal file
@ -0,0 +1,103 @@
|
||||
<?php
|
||||
function get_hero_image() {
|
||||
// Check if we have a cached image URL
|
||||
if (isset($_SESSION['hero_image_url'])) {
|
||||
return $_SESSION['hero_image_url'];
|
||||
}
|
||||
|
||||
// If not, fetch a new one from Pexels
|
||||
require_once __DIR__ . '/includes/pexels.php';
|
||||
$query = 'island food';
|
||||
$orientation = 'landscape';
|
||||
$url = 'https://api.pexels.com/v1/search?query=' . urlencode($query) . '&orientation=' . urlencode($orientation) . '&per_page=1&page=' . rand(1, 100);
|
||||
$data = pexels_get($url);
|
||||
|
||||
if (!empty($data['photos'])) {
|
||||
$photo = $data['photos'][0];
|
||||
$image_url = $photo['src']['large2x'] ?? $photo['src']['large'];
|
||||
|
||||
// Cache the URL in the session
|
||||
$_SESSION['hero_image_url'] = $image_url;
|
||||
|
||||
return $image_url;
|
||||
}
|
||||
|
||||
// Fallback image
|
||||
return 'assets/images/hero.jpg';
|
||||
}
|
||||
|
||||
$hero_image_url = get_hero_image();
|
||||
?>
|
||||
|
||||
<style>
|
||||
.hero-section {
|
||||
background-image: linear-gradient(rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.3)), url('<?= $hero_image_url ?>');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
color: white;
|
||||
text-align: left;
|
||||
padding: 100px 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.hero-content h1 {
|
||||
font-size: 3.5rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 1rem;
|
||||
text-shadow: 2px 2px 8px rgba(0,0,0,0.6);
|
||||
}
|
||||
|
||||
.hero-content p {
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.hero-search-form {
|
||||
display: flex;
|
||||
border-radius: 50px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.hero-search-input {
|
||||
flex-grow: 1;
|
||||
border: none;
|
||||
padding: 1rem 1.5rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.hero-search-input:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.hero-search-button {
|
||||
border: none;
|
||||
background-color: #FF6B6B;
|
||||
color: white;
|
||||
padding: 0 2rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.hero-search-button:hover {
|
||||
background-color: #ff4f4f;
|
||||
}
|
||||
</style>
|
||||
|
||||
<section class="hero-section">
|
||||
<div class="hero-content">
|
||||
<h1>Everything you crave, delivered.</h1>
|
||||
<p>Your favorite local restaurants, delivered to your door.</p>
|
||||
<form action="index.php" method="get" class="hero-search-form">
|
||||
<input type="text" name="search" class="hero-search-input" placeholder="Find restaurants" value="<?= isset($_GET['search']) ? htmlspecialchars($_GET['search']) : '' ?>">
|
||||
<button type="submit" class="hero-search-button">Find restaurants</button>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
85
index.php
@ -1,70 +1,55 @@
|
||||
<?php include 'header.php'; ?>
|
||||
|
||||
<main>
|
||||
<section class="hero">
|
||||
<div class="hero-content">
|
||||
<h1>Order from Majuro's best</h1>
|
||||
<form action="index.php" method="get" class="search-form">
|
||||
<input type="text" name="search" class="search-bar" placeholder="Search for restaurants..." value="<?= isset($_GET['search']) ? htmlspecialchars($_GET['search']) : '' ?>">
|
||||
<button type="submit" class="search-button">Search</button>
|
||||
</form>
|
||||
<div class="location-actions">
|
||||
<button class="location-button" id="pin-location">Pin a Location</button>
|
||||
<button class="location-button" id="my-location">My Location</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<?php include 'hero.php'; ?>
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<!-- Filter Sidebar -->
|
||||
<div class="col-md-3">
|
||||
<h4>Filter Results</h4>
|
||||
<form action="index.php" method="get" id="filter-form">
|
||||
<!-- Hidden search field to persist search query -->
|
||||
<?php if (isset($_GET['search'])):
|
||||
<input type="hidden" name="search" value="<?= htmlspecialchars($_GET['search']) ?>">
|
||||
<?php endif; ?>
|
||||
<input type="hidden" name="search" value="<?= isset($_GET['search']) ? htmlspecialchars($_GET['search']) : '' ?>">
|
||||
|
||||
<h5>By Cuisine</h5>
|
||||
<h2 class="page-title">Explore Cuisines</h2>
|
||||
<div class="cuisine-carousel">
|
||||
<?php
|
||||
$cuisine_stmt = db()->query("SELECT * FROM cuisines ORDER BY name");
|
||||
$all_cuisines = $cuisine_stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
$selected_cuisines = isset($_GET['cuisines']) && is_array($_GET['cuisines']) ? $_GET['cuisines'] : [];
|
||||
|
||||
foreach ($all_cuisines as $cuisine): ?>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="cuisines[]" value="<?= $cuisine['id'] ?>" id="cuisine-<?= $cuisine['id'] ?>" <?= in_array($cuisine['id'], $selected_cuisines) ? 'checked' : '' ?>>
|
||||
<label class="form-check-label" for="cuisine-<?= $cuisine['id'] ?>">
|
||||
<div class="cuisine-card">
|
||||
<input type="checkbox" name="cuisines[]" value="<?= $cuisine['id'] ?>" id="cuisine-<?= $cuisine['id'] ?>" <?= in_array($cuisine['id'], $selected_cuisines) ? 'checked' : '' ?> onchange="this.form.submit()">
|
||||
<label for="cuisine-<?= $cuisine['id'] ?>">
|
||||
<?= htmlspecialchars($cuisine['name']) ?>
|
||||
</label>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<h5 class="mt-4">By Rating</h5>
|
||||
<div class="filter-bar-new">
|
||||
<div class="form-group">
|
||||
<select name="min_rating" class="form-control">
|
||||
<option value="">Any Rating</option>
|
||||
<option value="4" <?= isset($_GET['min_rating']) && $_GET['min_rating'] == 4 ? 'selected' : '' ?>>4 stars & up</option>
|
||||
<option value="3" <?= isset($_GET['min_rating']) && $_GET['min_rating'] == 3 ? 'selected' : '' ?>>3 stars & up</option>
|
||||
<option value="2" <?= isset($_GET['min_rating']) && $_GET['min_rating'] == 2 ? 'selected' : '' ?>>2 stars & up</option>
|
||||
<option value="1" <?= isset($_GET['min_rating']) && $_GET['min_rating'] == 1 ? 'selected' : '' ?>>1 star & up</option>
|
||||
<label for="min_rating">Rating</label>
|
||||
<select name="min_rating" id="min_rating" class="form-control" onchange="this.form.submit()">
|
||||
<option value="">Any</option>
|
||||
<option value="4" <?= isset($_GET['min_rating']) && $_GET['min_rating'] == 4 ? 'selected' : '' ?>>4+ stars</option>
|
||||
<option value="3" <?= isset($_GET['min_rating']) && $_GET['min_rating'] == 3 ? 'selected' : '' ?>>3+ stars</option>
|
||||
<option value="2" <?= isset($_GET['min_rating']) && $_GET['min_rating'] == 2 ? 'selected' : '' ?>>2+ stars</option>
|
||||
<option value="1" <?= isset($_GET['min_rating']) && $_GET['min_rating'] == 1 ? 'selected' : '' ?>>1+ star</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary mt-3">Apply Filters</button>
|
||||
<a href="index.php" class="btn btn-secondary mt-3">Clear Filters</a>
|
||||
</form>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="open_now" value="1" id="open-now" <?= isset($_GET['open_now']) ? 'checked' : '' ?> onchange="this.form.submit()">
|
||||
<label class="form-check-label" for="open-now">
|
||||
Open Now
|
||||
</label>
|
||||
</div>
|
||||
<a href="index.php" class="btn btn-secondary">Clear Filters</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Restaurant Listing -->
|
||||
<div class="col-md-9">
|
||||
<h2 class="page-title">All Restaurants</h2>
|
||||
<h2 class="page-title mt-4">All Restaurants</h2>
|
||||
<section class="restaurant-list">
|
||||
<div class="restaurant-grid" id="restaurant-grid">
|
||||
<?php
|
||||
// Base query
|
||||
$sql = "SELECT DISTINCT r.id, r.name, r.image_url, AVG(rt.rating) as average_rating, COUNT(rt.id) as rating_count
|
||||
$sql = "SELECT DISTINCT r.id, r.name, r.image_url, r.opening_time, r.closing_time, r.days_open, AVG(rt.rating) as average_rating, COUNT(rt.id) as rating_count
|
||||
FROM restaurants r
|
||||
LEFT JOIN ratings rt ON r.id = rt.restaurant_id";
|
||||
$params = [];
|
||||
@ -89,11 +74,22 @@
|
||||
$params = array_merge($params, $selected_cuisines);
|
||||
}
|
||||
|
||||
// Append "Open Now" filter
|
||||
if (isset($_GET['open_now'])) {
|
||||
$current_time = date('H:i:s');
|
||||
$current_day = date('D'); // Mon, Tue, etc.
|
||||
$where_clauses[] = "r.opening_time <= ? AND r.closing_time >= ? AND r.days_open LIKE ?";
|
||||
$params[] = $current_time;
|
||||
$params[] = $current_time;
|
||||
$params[] = '%' . $current_day . '%';
|
||||
}
|
||||
|
||||
|
||||
if (!empty($where_clauses)) {
|
||||
$sql .= " WHERE " . implode(' AND ', $where_clauses);
|
||||
}
|
||||
|
||||
$sql .= " GROUP BY r.id, r.name, r.image_url";
|
||||
$sql .= " GROUP BY r.id, r.name, r.image_url, r.opening_time, r.closing_time, r.days_open";
|
||||
|
||||
// Add rating filter (HAVING clause)
|
||||
$selected_min_rating = isset($_GET['min_rating']) && is_numeric($_GET['min_rating']) ? (int)$_GET['min_rating'] : 0;
|
||||
@ -123,16 +119,17 @@
|
||||
echo '<div class="restaurant-card-content">';
|
||||
echo '<h3>' . htmlspecialchars($restaurant['name']) . '</h3>';
|
||||
echo '<p>' . htmlspecialchars(implode(', ', $restaurant_cuisines_list)) . '</p>';
|
||||
echo '<div class="restaurant-info">';
|
||||
if ($restaurant['rating_count'] > 0) {
|
||||
echo '<div class="rating-display">';
|
||||
echo '<span class="star">★</span>';
|
||||
echo '<span>' . htmlspecialchars(number_format($restaurant['average_rating'], 1)) . '</span>';
|
||||
echo '<span class="rating-count">(' . htmlspecialchars($restaurant['rating_count']) . ' ratings)</span>';
|
||||
echo '</div>';
|
||||
} else {
|
||||
echo '<div class="rating-display"><span class="rating-count">No ratings yet</span></div>';
|
||||
}
|
||||
echo '</div>';
|
||||
echo '</div>';
|
||||
echo '</a>';
|
||||
}
|
||||
}
|
||||
@ -140,8 +137,6 @@
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
|
||||
|
||||
11
migrations/20251015_add_hours_to_restaurants.sql
Normal file
@ -0,0 +1,11 @@
|
||||
|
||||
ALTER TABLE restaurants
|
||||
ADD COLUMN opening_time TIME,
|
||||
ADD COLUMN closing_time TIME,
|
||||
ADD COLUMN days_open VARCHAR(255);
|
||||
|
||||
-- Update existing restaurants with some default hours (e.g., 9 AM to 9 PM, open all week)
|
||||
UPDATE restaurants SET
|
||||
opening_time = '09:00:00',
|
||||
closing_time = '21:00:00',
|
||||
days_open = 'Mon,Tue,Wed,Thu,Fri,Sat,Sun';
|
||||