diff --git a/api/pexels.php b/api/pexels.php new file mode 100644 index 0000000..d538d63 --- /dev/null +++ b/api/pexels.php @@ -0,0 +1,58 @@ + 'No queries provided.']); + exit; +} +$queries = explode(',', $queries_str); + +$results = []; + +foreach ($queries as $query) { + $query = trim($query); + if (empty($query)) continue; + + // Define a local path for the image + $local_path_dir = __DIR__ . '/../assets/images/corals'; + if (!is_dir($local_path_dir)) { + mkdir($local_path_dir, 0775, true); + } + $image_filename = strtolower(str_replace(' ', '-', $query)) . '.jpg'; + $local_path = $local_path_dir . '/' . $image_filename; + $relative_path = 'assets/images/corals/' . $image_filename; + + // Serve cached image if it exists + if (file_exists($local_path)) { + $results[$query] = $relative_path . '?v=' . filemtime($local_path); + continue; + } + + // Fetch from Pexels if not cached + $search_query = $query . " coral"; + $url = 'https://api.pexels.com/v1/search?query=' . urlencode($search_query) . '&orientation=square&per_page=1&page=1'; + $data = pexels_get($url); + + if ($data && !empty($data['photos'])) { + $photo = $data['photos'][0]; + $src = $photo['src']['large'] ?? $photo['src']['medium'] ?? $photo['src']['original']; + + if (download_to($src, $local_path)) { + $results[$query] = $relative_path . '?v=' . time(); + } else { + // Download failed, use a placeholder + $results[$query] = 'https://picsum.photos/seed/' . urlencode($query) . '/600/600'; + } + } else { + // Pexels fetch failed, use a placeholder + $results[$query] = 'https://picsum.photos/seed/' . urlencode($query) . '/600/600'; + } + // Small delay to avoid hitting API rate limits too quickly + usleep(200000); // 200ms +} + +echo json_encode($results); diff --git a/assets/css/custom.css b/assets/css/custom.css new file mode 100644 index 0000000..758ca6f --- /dev/null +++ b/assets/css/custom.css @@ -0,0 +1,116 @@ +/* /workspace/assets/css/custom.css */ +:root { + --primary: #00A99D; + --secondary: #FFC107; + --light: #F8F9FA; + --dark: #212529; + --surface: #FFFFFF; +} + +body { + font-family: 'Open Sans', sans-serif; + color: var(--dark); +} + +h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 { + font-family: 'Poppins', sans-serif; + font-weight: 600; +} + +.btn-primary { + background-color: var(--primary); + border-color: var(--primary); + transition: all 0.3s ease; +} + +.btn-primary:hover { + background-color: #007a70; + border-color: #007a70; + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0,0,0,0.1); +} + +.btn-secondary { + background-color: var(--secondary); + border-color: var(--secondary); + color: var(--dark); + transition: all 0.3s ease; +} + +.btn-secondary:hover { + background-color: #e0a800; + border-color: #e0a800; + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0,0,0,0.1); +} + +.navbar { + transition: background-color 0.3s ease; +} + + + +section { + padding: 5rem 0; +} + +.section-bg { + background-color: var(--light); +} + +.card.coral-card { + border: none; + border-radius: 0.5rem; + overflow: hidden; + box-shadow: 0 4px 15px rgba(0,0,0,0.08); + transition: all 0.3s ease; +} + +.card.coral-card:hover { + transform: translateY(-5px); + box-shadow: 0 8px 25px rgba(0,0,0,0.12); +} + +.card-img-top-container { + width: 100%; + padding-top: 100%; /* 1:1 Aspect Ratio */ + position: relative; + overflow: hidden; +} + +.card-img-top { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; /* Cover the container without stretching */ +} + +.card-title { + font-weight: 600; +} + +.coral-price { + color: var(--primary); + font-weight: 700; + font-size: 1.2rem; +} + +.form-control:focus { + border-color: var(--primary); + box-shadow: 0 0 0 0.25rem rgba(0, 169, 157, 0.25); +} + +.footer { + background-color: var(--dark); + color: white; + padding: 3rem 0; +} + +.toast-container { + position: fixed; + top: 20px; + right: 20px; + z-index: 1055; +} \ No newline at end of file diff --git a/assets/images/corals/blue-hornets-zoanthid.jpg b/assets/images/corals/blue-hornets-zoanthid.jpg new file mode 100644 index 0000000..f95b642 Binary files /dev/null and b/assets/images/corals/blue-hornets-zoanthid.jpg differ diff --git a/assets/images/corals/bounce-mushroom.jpg b/assets/images/corals/bounce-mushroom.jpg new file mode 100644 index 0000000..e173dff Binary files /dev/null and b/assets/images/corals/bounce-mushroom.jpg differ diff --git a/assets/images/corals/forest-fire-digitata.jpg b/assets/images/corals/forest-fire-digitata.jpg new file mode 100644 index 0000000..17db046 Binary files /dev/null and b/assets/images/corals/forest-fire-digitata.jpg differ diff --git a/assets/images/corals/godspawn-euphyllia.jpg b/assets/images/corals/godspawn-euphyllia.jpg new file mode 100644 index 0000000..e173dff Binary files /dev/null and b/assets/images/corals/godspawn-euphyllia.jpg differ diff --git a/assets/images/corals/gold-torch-euphyllia.jpg b/assets/images/corals/gold-torch-euphyllia.jpg new file mode 100644 index 0000000..82d5d25 Binary files /dev/null and b/assets/images/corals/gold-torch-euphyllia.jpg differ diff --git a/assets/images/corals/grafted-cap-montipora.jpg b/assets/images/corals/grafted-cap-montipora.jpg new file mode 100644 index 0000000..ff53671 Binary files /dev/null and b/assets/images/corals/grafted-cap-montipora.jpg differ diff --git a/assets/images/corals/jf-jack-o-lantern-leptoseris.jpg b/assets/images/corals/jf-jack-o-lantern-leptoseris.jpg new file mode 100644 index 0000000..e173dff Binary files /dev/null and b/assets/images/corals/jf-jack-o-lantern-leptoseris.jpg differ diff --git a/assets/images/corals/rr-usa-rainbow-splice-trachyphyllia.jpg b/assets/images/corals/rr-usa-rainbow-splice-trachyphyllia.jpg new file mode 100644 index 0000000..2253ac0 Binary files /dev/null and b/assets/images/corals/rr-usa-rainbow-splice-trachyphyllia.jpg differ diff --git a/assets/images/corals/tgc-cherry-bomb-acropora.jpg b/assets/images/corals/tgc-cherry-bomb-acropora.jpg new file mode 100644 index 0000000..e173dff Binary files /dev/null and b/assets/images/corals/tgc-cherry-bomb-acropora.jpg differ diff --git a/assets/images/corals/tsa-bill-murray-acropora.jpg b/assets/images/corals/tsa-bill-murray-acropora.jpg new file mode 100644 index 0000000..e173dff Binary files /dev/null and b/assets/images/corals/tsa-bill-murray-acropora.jpg differ diff --git a/assets/images/corals/walt-disney-acropora.jpg b/assets/images/corals/walt-disney-acropora.jpg new file mode 100644 index 0000000..e173dff Binary files /dev/null and b/assets/images/corals/walt-disney-acropora.jpg differ diff --git a/assets/images/corals/wwc-og-voodoo-magic-zoanthid.jpg b/assets/images/corals/wwc-og-voodoo-magic-zoanthid.jpg new file mode 100644 index 0000000..794bf97 Binary files /dev/null and b/assets/images/corals/wwc-og-voodoo-magic-zoanthid.jpg differ diff --git a/assets/js/corals.js b/assets/js/corals.js new file mode 100644 index 0000000..93e50a9 --- /dev/null +++ b/assets/js/corals.js @@ -0,0 +1,205 @@ +// assets/js/corals.js + +document.addEventListener('DOMContentLoaded', () => { + const corals = [ + { + id: 1, + name: 'WWC OG Voodoo Magic Zoanthid', + type: 'Zoanthid', + color: 'Rainbow', + price: 129.99, + seller: 'World Wide Corals', + seller_url: '#', + image: 'assets/images/corals/wwc-og-voodoo-magic-zoanthid.jpg' + }, + { + id: 2, + name: 'TSA Bill Murray Acropora', + type: 'SPS', + color: 'Green', + price: 249.99, + seller: 'Top Shelf Aquatics', + seller_url: '#', + image: 'assets/images/corals/tsa-bill-murray-acropora.jpg' + }, + { + id: 3, + name: 'JF Jack-O-Lantern Leptoseris', + type: 'LPS', + color: 'Yellow', + price: 89.99, + seller: 'Jason Fox Signature Corals', + seller_url: '#', + image: 'assets/images/corals/jf-jack-o-lantern-leptoseris.jpg' + }, + { + id: 4, + name: 'RR USA Rainbow Splice Trachyphyllia', + type: 'LPS', + color: 'Rainbow', + price: 399.99, + seller: 'Reef Raft USA', + seller_url: '#', + image: 'assets/images/corals/rr-usa-rainbow-splice-trachyphyllia.jpg' + }, + { + id: 5, + name: 'Godspawn Euphyllia', + type: 'LPS', + color: 'Green', + price: 199.99, + seller: 'World Wide Corals', + seller_url: '#', + image: 'assets/images/corals/godspawn-euphyllia.jpg' + }, + { + id: 6, + name: 'TGC Cherry Bomb Acropora', + type: 'SPS', + color: 'Red', + price: 179.99, + seller: 'The Coral Gorilla', + seller_url: '#', + image: 'assets/images/corals/tgc-cherry-bomb-acropora.jpg' + }, + { + id: 7, + name: 'Bounce Mushroom', + type: 'Mushroom', + color: 'Pink', + price: 450.00, + seller: 'Top Shelf Aquatics', + seller_url: '#', + image: 'assets/images/corals/bounce-mushroom.jpg' + }, + { + id: 8, + name: 'Blue Hornets Zoanthid', + type: 'Zoanthid', + color: 'Blue', + price: 49.99, + seller: 'Jason Fox Signature Corals', + seller_url: '#', + image: 'assets/images/corals/blue-hornets-zoanthid.jpg' + }, + { + id: 9, + name: 'Walt Disney Acropora', + type: 'SPS', + color: 'Rainbow', + price: 299.99, + seller: 'World Wide Corals', + seller_url: '#', + image: 'assets/images/corals/walt-disney-acropora.jpg' + }, + { + id: 10, + name: 'Gold Torch Euphyllia', + type: 'LPS', + color: 'Yellow', + price: 349.99, + seller: 'Reef Raft USA', + seller_url: '#', + image: 'assets/images/corals/gold-torch-euphyllia.jpg' + }, + { + id: 11, + name: 'Forest Fire Digitata', + type: 'SPS', + color: 'Green', + price: 59.99, + seller: 'Top Shelf Aquatics', + seller_url: '#', + image: 'assets/images/corals/forest-fire-digitata.jpg' + }, + { + id: 12, + name: 'Grafted Cap Montipora', + type: 'SPS', + color: 'Red', + price: 79.99, + seller: 'The Coral Gorilla', + seller_url: '#', + image: 'assets/images/corals/grafted-cap-montipora.jpg' + } + ]; + + const coralGrid = document.getElementById('coral-grid'); + const searchInput = document.getElementById('searchInput'); + const filterType = document.getElementById('filterType'); + const filterColor = document.getElementById('filterColor'); + const priceRange = document.getElementById('priceRange'); + const priceValue = document.getElementById('priceValue'); + const noResults = document.getElementById('no-results'); + + const renderCorals = (filteredCorals) => { + coralGrid.innerHTML = ''; + if (filteredCorals.length === 0) { + noResults.style.display = 'block'; + } else { + noResults.style.display = 'none'; + } + + filteredCorals.forEach(coral => { + const coralCard = ` +
+
+
+ ${coral.name} +
+
+
${coral.name}
+

${coral.seller}

+
+
+ $${coral.price.toFixed(2)} + + + +
+
+
+ +
+
+ `; + coralGrid.innerHTML += coralCard; + }); + feather.replace(); + }; + + const filterAndRender = () => { + const searchTerm = searchInput.value.toLowerCase(); + const selectedType = filterType.value; + const selectedColor = filterColor.value; + const maxPrice = parseFloat(priceRange.value); + + priceValue.textContent = maxPrice; + + const filteredCorals = corals.filter(coral => { + const nameMatch = coral.name.toLowerCase().includes(searchTerm); + const typeMatch = selectedType ? coral.type === selectedType : true; + const colorMatch = selectedColor ? coral.color === selectedColor : true; + const priceMatch = coral.price <= maxPrice; + return nameMatch && typeMatch && colorMatch && priceMatch; + }); + + renderCorals(filteredCorals); + }; + + // --- Initial Load --- + priceValue.textContent = priceRange.value; + filterAndRender(); + + // Event Listeners + searchInput.addEventListener('keyup', filterAndRender); + filterType.addEventListener('change', filterAndRender); + filterColor.addEventListener('change', filterAndRender); + priceRange.addEventListener('input', () => { + priceValue.textContent = priceRange.value; + }); + priceRange.addEventListener('change', filterAndRender); +}); \ No newline at end of file diff --git a/assets/js/main.js b/assets/js/main.js new file mode 100644 index 0000000..93970cd --- /dev/null +++ b/assets/js/main.js @@ -0,0 +1,82 @@ +// /workspace/assets/js/main.js +document.addEventListener('DOMContentLoaded', function () { + // Navbar scroll effect + const navbar = document.querySelector('.navbar'); + if (navbar) { + window.addEventListener('scroll', () => { + if (window.scrollY > 50) { + navbar.classList.add('scrolled'); + } else { + navbar.classList.remove('scrolled'); + } + }); + } + + // Contact form submission + const contactForm = document.getElementById('contactForm'); + if (contactForm) { + contactForm.addEventListener('submit', function (e) { + e.preventDefault(); + if (!this.checkValidity()) { + e.stopPropagation(); + this.classList.add('was-validated'); + return; + } + this.classList.add('was-validated'); + + const submitBtn = this.querySelector('button[type="submit"]'); + const originalBtnText = submitBtn.innerHTML; + submitBtn.disabled = true; + submitBtn.innerHTML = ' Sending...'; + + const formData = new FormData(this); + + fetch('contact.php', { + method: 'POST', + body: formData + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + showToast('Success!', 'Your message has been sent. We will get back to you shortly.', 'success'); + contactForm.reset(); + contactForm.classList.remove('was-validated'); + } else { + showToast('Error!', data.error || 'An unknown error occurred. Please try again.', 'danger'); + } + }) + .catch(error => { + showToast('Error!', 'Could not reach the server. Please check your connection.', 'danger'); + }) + .finally(() => { + submitBtn.disabled = false; + submitBtn.innerHTML = originalBtnText; + }); + }); + } +}); + +function showToast(title, message, type = 'info') { + const toastContainer = document.getElementById('toast-container'); + if (!toastContainer) return; + + const toastId = 'toast-' + Date.now(); + const toastHTML = ` + + `; + toastContainer.insertAdjacentHTML('beforeend', toastHTML); + + const toastElement = document.getElementById(toastId); + const toast = new bootstrap.Toast(toastElement, { delay: 5000 }); + toast.show(); + toastElement.addEventListener('hidden.bs.toast', () => { + toastElement.remove(); + }); +} \ No newline at end of file diff --git a/contact.php b/contact.php new file mode 100644 index 0000000..5214e1a --- /dev/null +++ b/contact.php @@ -0,0 +1,48 @@ + 'Method Not Allowed']); + exit; +} + +$name = trim($_POST['name'] ?? ''); +$email = trim($_POST['email'] ?? ''); +$message = trim($_POST['message'] ?? ''); + +if (empty($name) || empty($email) || empty($message)) { + http_response_code(400); + echo json_encode(['error' => 'All fields are required.']); + exit; +} + +if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { + http_response_code(400); + echo json_encode(['error' => 'Invalid email format.']); + exit; +} + +// Use MailService +require_once __DIR__ . '/mail/MailService.php'; + +// The recipient email address. +// IMPORTANT: For a real application, this should come from a config file or environment variable, +// not be hardcoded. Using the default from .env for this example. +$to = getenv('MAIL_TO') ?: null; // Let MailService use its default if not set + +$subject = 'New Contact Form Submission from Coral Hub'; + +$res = MailService::sendContactMessage($name, $email, $message, $to, $subject); + +if (!empty($res['success'])) { + echo json_encode(['success' => true, 'message' => 'Message sent successfully.']); +} else { + // Do not expose detailed mail errors to the client for security. + // Log this error on the server in a real application. + http_response_code(500); + error_log('MailService Error: ' . ($res['error'] ?? 'Unknown error')); + echo json_encode(['error' => 'Sorry, there was an issue sending your message. Please try again later.']); +} \ No newline at end of file diff --git a/includes/pexels.php b/includes/pexels.php new file mode 100644 index 0000000..58eaef8 --- /dev/null +++ b/includes/pexels.php @@ -0,0 +1,35 @@ + 0 ? $k : 'Vc99rnmOhHhJAbgGQoKLZtsaIVfkeownoQNbTj78VemUjKh08ZYRbf18'; +} +function pexels_get($url) { + $ch = curl_init(); + curl_setopt_array($ch, [ + CURLOPT_URL => $url, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => [ 'Authorization: '. pexels_key() ], + CURLOPT_TIMEOUT => 15, + ]); + $resp = curl_exec($ch); + $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + if ($code >= 200 && $code < 300 && $resp) return json_decode($resp, true); + return null; +} +function download_to($srcUrl, $destPath) { + // Ensure the destination directory exists + $dir = dirname($destPath); + if (!is_dir($dir)) { + if (!mkdir($dir, 0775, true)) { + // Failed to create directory + return false; + } + } + + $data = @file_get_contents($srcUrl); + if ($data === false) return false; + + return file_put_contents($destPath, $data) !== false; +} diff --git a/index.php b/index.php index 6f7ffab..aa6670d 100644 --- a/index.php +++ b/index.php @@ -1,131 +1,186 @@ - - + - - - New Style - - - - + + + + Coral Hub - The Universal Coral Marketplace + + + + + + + + + + + + + + + + + + + + + + + + + + -
-
-

Analyzing your requirements and generating your website…

-
- Loading… -
-

AI is collecting your requirements and applying the first changes.

-

This page will update automatically as the plan is implemented.

-

Runtime: PHP — UTC

-
-
- + + +
+ + + + + +
+ +
+
+
+

All Corals. One Ocean.

+

The ultimate marketplace for reef aquarists. Search thousands of corals from sellers worldwide.

+

+ Find Your Coral + Become a Seller +

+
+
+
+ + +
+
+
+
+

What is Coral Hub?

+

Tired of browsing dozens of websites to find that one specific coral? We are too.

+

Coral Hub is a revolutionary platform that aggregates coral listings from online sellers and local stores into one massive, searchable library. Our mission is to make finding and buying the corals you love simple, fast, and efficient.

+
+
+ A beautiful home reef aquarium. +
+
+
+
+ + +
+
+
+

Featured Corals

+

A glimpse of what you can find on Coral Hub.

+
+
+ 'Ultra Acropora', 'img_seed' => 'acropora', 'price' => '129.99', 'alt' => 'A beautiful Acropora coral fragment.'], + ['name' => 'Rainbow Zoanthids', 'img_seed' => 'zoanthid', 'price' => '79.50', 'alt' => 'A colony of colorful Zoanthid polyps.'], + ['name' => 'Flower Pot Goniopora', 'img_seed' => 'goniopora', 'price' => '99.00', 'alt' => 'A bright pink Goniopora coral.'], + ]; + foreach ($corals as $coral): + ?> +
+
+ <?php echo htmlspecialchars($coral['alt']); ?> +
+
+

+ $ + View Seller +

+
+
+
+ +
+
+
+ + +
+
+
+

Get In Touch

+

Have questions, feedback, or want to become a seller? Drop us a line!

+
+
+
+
+
+ + +
Please enter your name.
+
+
+ + +
Please enter a valid email address.
+
+
+ + +
Please enter your message.
+
+
+ +
+
+
+
+
+
+
+ + + + + + + + + + + + + - + \ No newline at end of file diff --git a/privacy.php b/privacy.php new file mode 100644 index 0000000..7c25069 --- /dev/null +++ b/privacy.php @@ -0,0 +1,34 @@ + + + + + + + Privacy Policy - Coral Hub + + + + +
+

Privacy Policy

+

Last updated:

+

This is a placeholder for your privacy policy. You should replace this content with your own policy that details how you collect, use, and protect your users' data.

+ +

Information We Collect

+

...

+ +

How We Use Information

+

...

+ +

Data Sharing

+

...

+ + Go back to the homepage +
+ + \ No newline at end of file diff --git a/search.php b/search.php new file mode 100644 index 0000000..a0fe265 --- /dev/null +++ b/search.php @@ -0,0 +1,131 @@ + + + + + + + <?php echo $pageTitle; ?> + + + + + + + + + + + + + + + + + + + + + + +
+
+

Find Your Perfect Coral

+

Our entire collection at your fingertips.

+
+ + +
+
+
+ + +
+
+
+ +
+
+ +
+
+ +
+ Price: $0 + $500 +
+
+
+
+
+ + +
+ +
+ +
+ + + + + + + + + + + + + +