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.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 = `
+
+
+
+ ${title} ${message}
+
+
+
+
+ `;
+ 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…
-
-
= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWiZZy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.
-
This page will update automatically as the plan is implemented.
-
Runtime: PHP = htmlspecialchars($phpVersion) ?> — UTC = htmlspecialchars($now) ?>
-
-
-
- Page updated: = htmlspecialchars($now) ?> (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.
+
+
+
+
+
+
+
+
+
+
+
+
+
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):
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+