diff --git a/about.php b/about.php
new file mode 100644
index 0000000..bc7c982
--- /dev/null
+++ b/about.php
@@ -0,0 +1,118 @@
+ 'Institutional trust platform', 'summary' => 'A calm, premium first impression that feels credible to partners, institutions, and high-value guests.'],
+ ['title' => 'Curated service commerce', 'summary' => 'Packages are presented with editorial clarity and routed into a structured consultation path.'],
+ ['title' => 'Partnership integration system', 'summary' => 'Multiple partner categories can be invited into one operating ecosystem without clutter.'],
+ ['title' => 'Publication authority layer', 'summary' => 'Insight and updates reinforce operational maturity, not just marketing claims.'],
+ ['title' => 'Governance foundation', 'summary' => 'The site prepares future reporting, admin processes, and ecosystem accountability.'],
+];
+
+$operatingModel = [
+ ['title' => 'Discovery', 'summary' => 'Brief collection, stakeholder mapping, and intent clarification.'],
+ ['title' => 'Curation', 'summary' => 'Program architecture, partner matching, and experience design.'],
+ ['title' => 'Delivery', 'summary' => 'On-ground hospitality, logistics, facilitation, and documentation.'],
+ ['title' => 'Governance', 'summary' => 'Reporting discipline, partner oversight, and institutional trust signals.'],
+];
+
+require __DIR__ . '/includes/header.php';
+?>
+
+
+
+
+
+
About GBP
+
An integrated ecosystem designed for trust, transformation, and operational clarity.
+
PT. Genitri Bregas Persada positions Genitri Ecco Wellness as a premium nature-based experience ecosystem—bridging eco wellness, hospitality, retreat design, MICE support, and partnership collaboration.
+
+
+
+
Tagline
+
Nature-Based Experiential Tourism & Eco Wellness Services
+
The experience direction is intentionally calm, institutional, and editorial rather than marketplace-like or generic tourism-led.
+
+
+
+
+
+
+
+
+
+
+
Strategic positioning
+
What makes the ecosystem distinct.
+
The site is designed to show how multiple disciplines can feel integrated without looking crowded.
+
+
+
+
+ = h($pillar['title']) ?>
+ = h($pillar['summary']) ?>
+
+
+
+
+
+
+
+
+
+
+
+
Core objectives
+
A website built to do real institutional work.
+
+
+ = h($objective) ?>
+
+
+
+
+
+
+
Operating model
+
A lightweight structure that can scale.
+
+
+
+ = h($step['title']) ?>
+ = h($step['summary']) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Values
+
A premium tone supported by ethical grounding.
+
+
+
+
+ = h($value['title']) ?>
+ = h($value['summary']) ?>
+
+
+
+
+
+
+
diff --git a/admin.php b/admin.php
new file mode 100644
index 0000000..3c9e199
--- /dev/null
+++ b/admin.php
@@ -0,0 +1,169 @@
+ 0)) {
+ $selectedInquiry = find_inquiry($selectedId, $selectedReference !== '' ? $selectedReference : null);
+}
+
+if (!$selectedInquiry && $inquiries !== []) {
+ $selectedInquiry = $inquiries[0];
+}
+
+require __DIR__ . '/includes/header.php';
+?>
+
+
+
+
+
+
Preview desk
+
Qualified inquiry queue and detail view.
+
This admin-style page gives the first delivery an internal operational surface: a list of saved inquiries and a focused detail panel for review.
+
+
+
+
Queue snapshot
+
+
+ Entries
+ = h((string) count($inquiries)) ?>
+
+
+ Storage mode
+ = using_preview_storage() ? 'Preview session' : 'Database' ?>
+
+
+
+
+
+
+
+
+
+ Preview mode: the queue is reading from the current browser session because the database connection is rejecting access right now.
+
+
+
+
+
+
+
+ Inquiry list
+
Current queue
+
+
New inquiry
+
+
+
+
Empty state
+
No inquiries yet
+
Submit the first strategic brief to see the list and detail layout in action.
+
Open inquiry desk
+
+
+
+
+
+
+ Reference
+ Contact
+ Interest
+ Status
+
+
+
+
+
+
+ = h($inquiry['reference_code']) ?>
+ = h(format_date_label($inquiry['created_at'] ?? '')) ?>
+
+
+ = h($inquiry['contact_name']) ?>
+ = h($inquiry['organization_name']) ?>
+
+
+ = h($inquiry['interest_area']) ?>
+ = h($inquiry['inquiry_type']) ?>
+
+ = h($inquiry['status'] ?? 'New') ?>
+
+
+
+
+
+
+
+
+
+
+
+
No selection
+
Select an inquiry
+
Pick a queue item to inspect the detailed context for review.
+
+
+
+
+
Detail view
+
= h($selectedInquiry['organization_name']) ?>
+
= h($selectedInquiry['contact_name']) ?> · = h($selectedInquiry['email']) ?>
+
+
= h($selectedInquiry['status'] ?? 'New') ?>
+
+
+ = h($selectedInquiry['reference_code']) ?>
+ Copy
+
+
+
+
+
+
+
+
diff --git a/assets/css/custom.css b/assets/css/custom.css
index 789132e..4f7eed4 100644
--- a/assets/css/custom.css
+++ b/assets/css/custom.css
@@ -1,403 +1,770 @@
+:root {
+ --bg: #f4f6f8;
+ --surface: #ffffff;
+ --surface-muted: #f8fafb;
+ --surface-dark: #1c2733;
+ --text: #1c2733;
+ --muted: #5f6b76;
+ --border: #dde4ec;
+ --border-strong: #c7d0db;
+ --primary: #0066cc;
+ --primary-soft: #e8f1fb;
+ --accent: #00acc1;
+ --accent-soft: #e7f7fa;
+ --gold: #bfa100;
+ --gold-soft: #faf7e8;
+ --success-soft: #edf8f2;
+ --shadow-sm: 0 10px 24px rgba(28, 39, 51, 0.04);
+ --shadow-lg: 0 24px 60px rgba(28, 39, 51, 0.08);
+ --radius-sm: 10px;
+ --radius-md: 14px;
+ --radius-lg: 18px;
+ --spacing-xs: 0.375rem;
+ --spacing-sm: 0.75rem;
+ --spacing-md: 1rem;
+ --spacing-lg: 1.5rem;
+ --spacing-xl: 2rem;
+ --spacing-2xl: 3.5rem;
+}
+
+html {
+ scroll-behavior: smooth;
+}
+
body {
- background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab);
- background-size: 400% 400%;
- animation: gradient 15s ease infinite;
- color: #212529;
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
- font-size: 14px;
margin: 0;
- min-height: 100vh;
+ background: var(--bg);
+ color: var(--text);
+ font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
+ line-height: 1.6;
+ -webkit-font-smoothing: antialiased;
}
-.main-wrapper {
- display: flex;
- align-items: center;
- justify-content: center;
- min-height: 100vh;
- width: 100%;
- padding: 20px;
- box-sizing: border-box;
- position: relative;
- z-index: 1;
+body.modal-open {
+ padding-right: 0 !important;
}
-@keyframes gradient {
- 0% {
- background-position: 0% 50%;
- }
- 50% {
- background-position: 100% 50%;
- }
- 100% {
- background-position: 0% 50%;
- }
-}
-
-.chat-container {
- width: 100%;
- max-width: 600px;
- background: rgba(255, 255, 255, 0.85);
- border: 1px solid rgba(255, 255, 255, 0.3);
- border-radius: 20px;
- display: flex;
- flex-direction: column;
- height: 85vh;
- box-shadow: 0 20px 40px rgba(0,0,0,0.2);
- backdrop-filter: blur(15px);
- -webkit-backdrop-filter: blur(15px);
- overflow: hidden;
-}
-
-.chat-header {
- padding: 1.5rem;
- border-bottom: 1px solid rgba(0, 0, 0, 0.05);
- background: rgba(255, 255, 255, 0.5);
- font-weight: 700;
- font-size: 1.1rem;
- display: flex;
- justify-content: space-between;
- align-items: center;
-}
-
-.chat-messages {
- flex: 1;
- overflow-y: auto;
- padding: 1.5rem;
- display: flex;
- flex-direction: column;
- gap: 1.25rem;
-}
-
-/* Custom Scrollbar */
-::-webkit-scrollbar {
- width: 6px;
-}
-
-::-webkit-scrollbar-track {
- background: transparent;
-}
-
-::-webkit-scrollbar-thumb {
- background: rgba(255, 255, 255, 0.3);
- border-radius: 10px;
-}
-
-::-webkit-scrollbar-thumb:hover {
- background: rgba(255, 255, 255, 0.5);
-}
-
-.message {
- max-width: 85%;
- padding: 0.85rem 1.1rem;
- border-radius: 16px;
- line-height: 1.5;
- font-size: 0.95rem;
- box-shadow: 0 4px 15px rgba(0,0,0,0.05);
- animation: fadeIn 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
-}
-
-@keyframes fadeIn {
- from { opacity: 0; transform: translateY(20px) scale(0.95); }
- to { opacity: 1; transform: translateY(0) scale(1); }
-}
-
-.message.visitor {
- align-self: flex-end;
- background: linear-gradient(135deg, #212529 0%, #343a40 100%);
- color: #fff;
- border-bottom-right-radius: 4px;
-}
-
-.message.bot {
- align-self: flex-start;
- background: #ffffff;
- color: #212529;
- border-bottom-left-radius: 4px;
-}
-
-.chat-input-area {
- padding: 1.25rem;
- background: rgba(255, 255, 255, 0.5);
- border-top: 1px solid rgba(0, 0, 0, 0.05);
-}
-
-.chat-input-area form {
- display: flex;
- gap: 0.75rem;
-}
-
-.chat-input-area input {
- flex: 1;
- border: 1px solid rgba(0, 0, 0, 0.1);
- border-radius: 12px;
- padding: 0.75rem 1rem;
- outline: none;
- background: rgba(255, 255, 255, 0.9);
- transition: all 0.3s ease;
-}
-
-.chat-input-area input:focus {
- border-color: #23a6d5;
- box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.2);
-}
-
-.chat-input-area button {
- background: #212529;
- color: #fff;
- border: none;
- padding: 0.75rem 1.5rem;
- border-radius: 12px;
- cursor: pointer;
- font-weight: 600;
- transition: all 0.3s ease;
-}
-
-.chat-input-area button:hover {
- background: #000;
- transform: translateY(-2px);
- box-shadow: 0 5px 15px rgba(0,0,0,0.2);
-}
-
-/* Background Animations */
-.bg-animations {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- z-index: 0;
- overflow: hidden;
- pointer-events: none;
-}
-
-.blob {
- position: absolute;
- width: 500px;
- height: 500px;
- background: rgba(255, 255, 255, 0.2);
- border-radius: 50%;
- filter: blur(80px);
- animation: move 20s infinite alternate cubic-bezier(0.45, 0, 0.55, 1);
-}
-
-.blob-1 {
- top: -10%;
- left: -10%;
- background: rgba(238, 119, 82, 0.4);
-}
-
-.blob-2 {
- bottom: -10%;
- right: -10%;
- background: rgba(35, 166, 213, 0.4);
- animation-delay: -7s;
- width: 600px;
- height: 600px;
-}
-
-.blob-3 {
- top: 40%;
- left: 30%;
- background: rgba(231, 60, 126, 0.3);
- animation-delay: -14s;
- width: 450px;
- height: 450px;
-}
-
-@keyframes move {
- 0% { transform: translate(0, 0) rotate(0deg) scale(1); }
- 33% { transform: translate(150px, 100px) rotate(120deg) scale(1.1); }
- 66% { transform: translate(-50px, 200px) rotate(240deg) scale(0.9); }
- 100% { transform: translate(0, 0) rotate(360deg) scale(1); }
-}
-
-.header-link {
- font-size: 14px;
- color: #fff;
- text-decoration: none;
- background: rgba(0, 0, 0, 0.2);
- padding: 0.5rem 1rem;
- border-radius: 8px;
- transition: all 0.3s ease;
-}
-
-.header-link:hover {
- background: rgba(0, 0, 0, 0.4);
+a {
+ color: inherit;
text-decoration: none;
}
-/* Admin Styles */
-.admin-container {
- max-width: 900px;
- margin: 3rem auto;
- padding: 2.5rem;
- background: rgba(255, 255, 255, 0.85);
- backdrop-filter: blur(20px);
- -webkit-backdrop-filter: blur(20px);
- border-radius: 24px;
- box-shadow: 0 20px 50px rgba(0,0,0,0.15);
- border: 1px solid rgba(255, 255, 255, 0.4);
- position: relative;
- z-index: 1;
+a:hover {
+ color: inherit;
}
-.admin-container h1 {
- margin-top: 0;
- color: #212529;
- font-weight: 800;
-}
-
-.table {
- width: 100%;
- border-collapse: separate;
- border-spacing: 0 8px;
- margin-top: 1.5rem;
-}
-
-.table th {
- background: transparent;
- border: none;
- padding: 1rem;
- color: #6c757d;
- font-weight: 600;
- text-transform: uppercase;
- font-size: 0.75rem;
- letter-spacing: 1px;
-}
-
-.table td {
- background: #fff;
- padding: 1rem;
- border: none;
-}
-
-.table tr td:first-child { border-radius: 12px 0 0 12px; }
-.table tr td:last-child { border-radius: 0 12px 12px 0; }
-
-.form-group {
- margin-bottom: 1.25rem;
-}
-
-.form-group label {
+img {
+ max-width: 100%;
display: block;
- margin-bottom: 0.5rem;
- font-weight: 600;
+}
+
+.skip-link {
+ position: absolute;
+ left: 1rem;
+ top: -3rem;
+ z-index: 1100;
+ background: var(--surface-dark);
+ color: #fff;
+ padding: 0.625rem 0.9rem;
+ border-radius: var(--radius-sm);
+ transition: top 0.2s ease;
+}
+
+.skip-link:focus {
+ top: 1rem;
+}
+
+.page-main {
+ padding-top: 5.5rem;
+}
+
+.page-section {
+ padding: 3.75rem 0;
+}
+
+.section-head {
+ max-width: 760px;
+ margin-bottom: 1.5rem;
+}
+
+.eyebrow,
+.panel-label,
+.section-kicker,
+.footer-kicker {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+ margin-bottom: 0.75rem;
+ text-transform: uppercase;
+ letter-spacing: 0.14em;
+ font-size: 0.72rem;
+ font-weight: 700;
+ color: var(--primary);
+}
+
+.section-kicker--muted {
+ color: var(--muted);
+}
+
+.display-title,
+.section-title,
+.footer-title,
+.page-title {
+ letter-spacing: -0.03em;
+ line-height: 1.06;
+}
+
+.display-title {
+ font-size: clamp(2.2rem, 4vw, 3.6rem);
+ margin-bottom: 1rem;
+}
+
+.page-title {
+ font-size: clamp(2rem, 3vw, 3rem);
+ margin-bottom: 0.9rem;
+}
+
+.section-title,
+.footer-title {
+ font-size: clamp(1.6rem, 2.2vw, 2.4rem);
+ margin-bottom: 0.8rem;
+}
+
+.lead-copy,
+.section-copy,
+.footer-copy {
+ font-size: 1rem;
+ color: var(--muted);
+ max-width: 760px;
+}
+
+.hero-shell,
+.surface-card,
+.table-shell,
+.empty-panel,
+.form-panel,
+.contact-panel,
+.dark-panel {
+ border: 1px solid var(--border);
+ border-radius: var(--radius-lg);
+ background: var(--surface);
+ box-shadow: var(--shadow-sm);
+}
+
+.hero-shell {
+ padding: clamp(1.5rem, 3vw, 3rem);
+ box-shadow: var(--shadow-lg);
+}
+
+.hero-panel {
+ padding: 1.4rem;
+ background: var(--surface-muted);
+}
+
+.surface-card {
+ height: 100%;
+ padding: 1.35rem;
+}
+
+.surface-card--dark,
+.dark-panel {
+ background: var(--surface-dark);
+ border-color: rgba(255, 255, 255, 0.08);
+ color: rgba(255, 255, 255, 0.92);
+}
+
+.surface-card--dark .section-kicker,
+.surface-card--dark .meta-label,
+.surface-card--dark .card-copy,
+.dark-panel .section-copy,
+.dark-panel .meta-inline,
+.dark-panel .card-copy {
+ color: rgba(255, 255, 255, 0.74);
+}
+
+.metric-strip {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 0.85rem;
+}
+
+.metric-card {
+ padding: 1rem;
+ border: 1px solid var(--border);
+ border-radius: var(--radius-md);
+ background: var(--surface-muted);
+}
+
+.metric-value {
+ display: block;
+ font-size: 1.1rem;
+ font-weight: 700;
+ letter-spacing: -0.03em;
+}
+
+.metric-label {
+ display: block;
+ margin-top: 0.2rem;
+ color: var(--muted);
font-size: 0.9rem;
}
-.form-control {
- width: 100%;
- padding: 0.75rem 1rem;
- border: 1px solid rgba(0, 0, 0, 0.1);
- border-radius: 12px;
- background: #fff;
- transition: all 0.3s ease;
- box-sizing: border-box;
+.stack-list {
+ display: grid;
+ gap: 0.75rem;
}
-.form-control:focus {
- outline: none;
- border-color: #23a6d5;
- box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.1);
-}
-
-.header-container {
+.stack-item {
display: flex;
justify-content: space-between;
- align-items: center;
+ gap: 1rem;
+ align-items: flex-start;
+ padding: 0.9rem 1rem;
+ border: 1px solid var(--border);
+ border-radius: var(--radius-md);
+ background: var(--surface);
}
-.header-links {
- display: flex;
+.stack-item span,
+.card-title,
+.list-title,
+.queue-link,
+.detail-title {
+ font-weight: 700;
+ letter-spacing: -0.02em;
+}
+
+.stack-item small,
+.card-copy,
+.meta-inline,
+.meta-label,
+.note-muted,
+.empty-copy,
+.form-hint,
+.helper-text,
+.footer-note {
+ color: var(--muted);
+}
+
+.divider {
+ height: 1px;
+ background: var(--border);
+ margin: 1rem 0;
+}
+
+.signal-chip,
+.status-badge,
+.filter-chip,
+.info-pill {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0.3rem 0.72rem;
+ border-radius: 999px;
+ border: 1px solid var(--border);
+ background: var(--surface-muted);
+ color: var(--muted);
+ font-size: 0.78rem;
+ font-weight: 600;
+}
+
+.signal-chip--primary,
+.status-badge--new {
+ color: var(--primary);
+ background: var(--primary-soft);
+ border-color: rgba(0, 102, 204, 0.14);
+}
+
+.signal-chip--accent {
+ color: #0d7f8c;
+ background: var(--accent-soft);
+ border-color: rgba(0, 172, 193, 0.18);
+}
+
+.signal-chip--gold {
+ color: #8b7416;
+ background: var(--gold-soft);
+ border-color: rgba(191, 161, 0, 0.18);
+}
+
+.btn {
+ border-radius: 999px;
+ font-weight: 600;
+ letter-spacing: -0.01em;
+ padding: 0.7rem 1.1rem;
+}
+
+.btn-brand {
+ background: var(--primary);
+ border-color: var(--primary);
+ color: #fff;
+}
+
+.btn-brand:hover,
+.btn-brand:focus {
+ background: #0a57a8;
+ border-color: #0a57a8;
+ color: #fff;
+}
+
+.btn-outline-brand {
+ background: transparent;
+ border-color: var(--border-strong);
+ color: var(--text);
+}
+
+.btn-outline-brand:hover,
+.btn-outline-brand:focus {
+ border-color: var(--primary);
+ color: var(--primary);
+ background: var(--surface);
+}
+
+.btn-subtle {
+ background: var(--surface-muted);
+ border-color: var(--border);
+ color: var(--text);
+}
+
+.btn-subtle:hover,
+.btn-subtle:focus {
+ border-color: var(--border-strong);
+ background: var(--surface);
+}
+
+.button-link {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.4rem;
+ color: var(--primary);
+ font-weight: 600;
+}
+
+.button-link::after {
+ content: '→';
+ font-size: 0.95rem;
+}
+
+.page-intro {
+ margin-bottom: 1.75rem;
+}
+
+.feature-grid,
+.capability-grid,
+.preview-grid,
+.partner-grid,
+.stats-grid,
+.timeline-grid,
+.contact-grid {
+ display: grid;
gap: 1rem;
}
-.admin-card {
- background: rgba(255, 255, 255, 0.6);
- padding: 2rem;
- border-radius: 20px;
- border: 1px solid rgba(255, 255, 255, 0.5);
- margin-bottom: 2.5rem;
- box-shadow: 0 10px 30px rgba(0,0,0,0.05);
+.feature-grid {
+ grid-template-columns: repeat(3, minmax(0, 1fr));
}
-.admin-card h3 {
- margin-top: 0;
- margin-bottom: 1.5rem;
- font-weight: 700;
+.capability-grid {
+ grid-template-columns: repeat(4, minmax(0, 1fr));
}
-.btn-delete {
- background: #dc3545;
- color: white;
- border: none;
- padding: 0.25rem 0.5rem;
- border-radius: 4px;
- cursor: pointer;
+.preview-grid,
+.partner-grid,
+.stats-grid,
+.contact-grid {
+ grid-template-columns: repeat(3, minmax(0, 1fr));
}
-.btn-add {
- background: #212529;
- color: white;
- border: none;
- padding: 0.5rem 1rem;
- border-radius: 4px;
- cursor: pointer;
- margin-top: 1rem;
+.timeline-grid {
+ grid-template-columns: repeat(3, minmax(0, 1fr));
}
-.btn-save {
- background: #0088cc;
- color: white;
- border: none;
- padding: 0.8rem 1.5rem;
- border-radius: 12px;
- cursor: pointer;
+.card-title,
+.list-title,
+.detail-title {
+ font-size: 1.05rem;
+ margin-bottom: 0.5rem;
+}
+
+.card-copy,
+.list-copy,
+.detail-copy,
+.queue-summary {
+ font-size: 0.94rem;
+}
+
+.service-card {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+ min-height: 100%;
+}
+
+.service-points,
+.footer-links,
+.value-list,
+.detail-list,
+.objective-list,
+.tone-list {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+.service-points li,
+.value-list li,
+.detail-list li,
+.objective-list li,
+.tone-list li {
+ position: relative;
+ padding-left: 1rem;
+ margin-bottom: 0.55rem;
+ color: var(--muted);
+}
+
+.service-points li::before,
+.value-list li::before,
+.detail-list li::before,
+.objective-list li::before,
+.tone-list li::before {
+ content: '';
+ position: absolute;
+ left: 0;
+ top: 0.62rem;
+ width: 0.38rem;
+ height: 0.38rem;
+ border-radius: 999px;
+ background: var(--primary);
+}
+
+.meta-row,
+.meta-stack,
+.inline-actions {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.65rem;
+}
+
+.meta-stack {
+ flex-direction: column;
+ gap: 0.25rem;
+}
+
+.section-panel {
+ padding: 1.4rem;
+}
+
+.dark-block {
+ padding: clamp(1.4rem, 2vw, 2.4rem);
+ border-radius: var(--radius-lg);
+ background: var(--surface-dark);
+ color: #fff;
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ box-shadow: var(--shadow-lg);
+}
+
+.dark-grid {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 1rem;
+}
+
+.notice-banner,
+.inline-alert {
+ padding: 1rem 1.1rem;
+ border-radius: var(--radius-md);
+ border: 1px solid var(--border);
+ background: var(--surface);
+}
+
+.notice-banner--warning {
+ background: #fff8e7;
+ border-color: rgba(191, 161, 0, 0.28);
+}
+
+.notice-banner--info {
+ background: var(--primary-soft);
+ border-color: rgba(0, 102, 204, 0.2);
+}
+
+.form-panel {
+ padding: 1.5rem;
+}
+
+.form-label {
+ font-size: 0.88rem;
font-weight: 600;
- width: 100%;
- transition: all 0.3s ease;
+ color: var(--text);
+ margin-bottom: 0.45rem;
}
-.webhook-url {
- font-size: 0.85em;
- color: #555;
- margin-top: 0.5rem;
+.form-control,
+.form-select {
+ border-color: var(--border);
+ border-radius: var(--radius-sm);
+ padding: 0.8rem 0.9rem;
+ font-size: 0.95rem;
+ box-shadow: none !important;
}
-.history-table-container {
- overflow-x: auto;
- background: rgba(255, 255, 255, 0.4);
- padding: 1rem;
- border-radius: 12px;
- border: 1px solid rgba(255, 255, 255, 0.3);
+.form-control:focus,
+.form-select:focus {
+ border-color: rgba(0, 102, 204, 0.48);
}
-.history-table {
- width: 100%;
+.form-text,
+.invalid-feedback {
+ font-size: 0.82rem;
}
-.history-table-time {
- width: 15%;
- white-space: nowrap;
- font-size: 0.85em;
- color: #555;
+textarea.form-control {
+ min-height: 160px;
+ resize: vertical;
}
-.history-table-user {
- width: 35%;
- background: rgba(255, 255, 255, 0.3);
- border-radius: 8px;
- padding: 8px;
+.filter-bar {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.7rem;
+ margin-bottom: 1.2rem;
}
-.history-table-ai {
- width: 50%;
- background: rgba(255, 255, 255, 0.5);
- border-radius: 8px;
- padding: 8px;
+.filter-bar .btn.active {
+ background: var(--text);
+ color: #fff;
+ border-color: var(--text);
}
-.no-messages {
+.queue-table {
+ margin: 0;
+}
+
+.queue-table thead th {
+ border-bottom-color: var(--border);
+ color: var(--muted);
+ font-size: 0.82rem;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+}
+
+.queue-table tbody td {
+ vertical-align: top;
+ border-color: var(--border);
+ padding-top: 1rem;
+ padding-bottom: 1rem;
+}
+
+.queue-row--active {
+ background: var(--primary-soft);
+}
+
+.queue-link {
+ color: var(--primary);
+}
+
+.detail-grid {
+ display: grid;
+ gap: 1rem;
+}
+
+.detail-meta {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 0.75rem;
+}
+
+.meta-block {
+ padding: 0.9rem;
+ border: 1px solid var(--border);
+ border-radius: var(--radius-md);
+ background: var(--surface-muted);
+}
+
+.copy-inline {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+}
+
+.admin-layout {
+ display: grid;
+ grid-template-columns: 1.2fr 0.8fr;
+ gap: 1rem;
+}
+
+.empty-panel {
+ padding: 1.6rem;
text-align: center;
- color: #777;
-}
\ No newline at end of file
+}
+
+.site-navbar {
+ background: rgba(244, 246, 248, 0.92);
+ border-bottom: 1px solid rgba(221, 228, 236, 0.78);
+ backdrop-filter: blur(14px);
+}
+
+.site-navbar.scrolled {
+ box-shadow: 0 12px 24px rgba(28, 39, 51, 0.06);
+}
+
+.navbar-brand {
+ display: flex;
+ flex-direction: column;
+ gap: 0.05rem;
+}
+
+.brand-mark__eyebrow {
+ text-transform: uppercase;
+ letter-spacing: 0.12em;
+ font-size: 0.66rem;
+ color: var(--muted);
+}
+
+.brand-mark__title {
+ font-weight: 800;
+ letter-spacing: -0.03em;
+ color: var(--text);
+}
+
+.nav-link {
+ color: var(--muted);
+ font-weight: 500;
+ padding: 0.5rem 0.7rem !important;
+ border-radius: 999px;
+}
+
+.nav-link:hover,
+.nav-link:focus,
+.nav-link.active {
+ color: var(--text);
+ background: rgba(255, 255, 255, 0.92);
+}
+
+.footer-links li {
+ margin-bottom: 0.55rem;
+}
+
+.footer-links a {
+ color: var(--muted);
+}
+
+.footer-links a:hover,
+.footer-links a:focus {
+ color: var(--text);
+}
+
+.footer-links--muted li {
+ color: var(--muted);
+}
+
+.site-footer {
+ padding: 3rem 0 2rem;
+ border-top: 1px solid var(--border);
+ background: var(--surface);
+ margin-top: 2rem;
+}
+
+.footer-title {
+ font-size: 1.5rem;
+}
+
+.footer-label {
+ margin-bottom: 0.75rem;
+ text-transform: uppercase;
+ letter-spacing: 0.12em;
+ font-size: 0.74rem;
+ font-weight: 700;
+ color: var(--muted);
+}
+
+.footer-bar {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ gap: 0.75rem;
+ margin-top: 2rem;
+ padding-top: 1.25rem;
+ border-top: 1px solid var(--border);
+ color: var(--muted);
+ font-size: 0.88rem;
+}
+
+.form-counter {
+ font-size: 0.8rem;
+ color: var(--muted);
+}
+
+.anchor-offset {
+ scroll-margin-top: 6rem;
+}
+
+@media (max-width: 991.98px) {
+ .feature-grid,
+ .capability-grid,
+ .preview-grid,
+ .partner-grid,
+ .timeline-grid,
+ .contact-grid,
+ .dark-grid,
+ .admin-layout {
+ grid-template-columns: 1fr;
+ }
+
+ .metric-strip,
+ .detail-meta {
+ grid-template-columns: 1fr;
+ }
+
+ .page-main {
+ padding-top: 4.9rem;
+ }
+}
+
+@media (max-width: 767.98px) {
+ .page-section {
+ padding: 3rem 0;
+ }
+
+ .hero-shell,
+ .surface-card,
+ .form-panel,
+ .empty-panel,
+ .contact-panel {
+ padding: 1.15rem;
+ }
+
+ .btn {
+ width: 100%;
+ justify-content: center;
+ }
+
+ .inline-actions .btn,
+ .inline-actions .button-link {
+ width: auto;
+ }
+
+ .queue-table thead {
+ display: none;
+ }
+
+ .queue-table tbody,
+ .queue-table tr,
+ .queue-table td {
+ display: block;
+ width: 100%;
+ }
+
+ .queue-table tr {
+ border-bottom: 1px solid var(--border);
+ }
+
+ .queue-table td {
+ border: 0;
+ padding: 0.25rem 0;
+ }
+}
+
+*:focus-visible {
+ outline: 2px solid rgba(0, 102, 204, 0.42);
+ outline-offset: 2px;
+}
diff --git a/assets/js/main.js b/assets/js/main.js
index d349598..7e0a5ef 100644
--- a/assets/js/main.js
+++ b/assets/js/main.js
@@ -1,39 +1,68 @@
document.addEventListener('DOMContentLoaded', () => {
- const chatForm = document.getElementById('chat-form');
- const chatInput = document.getElementById('chat-input');
- const chatMessages = document.getElementById('chat-messages');
-
- const appendMessage = (text, sender) => {
- const msgDiv = document.createElement('div');
- msgDiv.classList.add('message', sender);
- msgDiv.textContent = text;
- chatMessages.appendChild(msgDiv);
- chatMessages.scrollTop = chatMessages.scrollHeight;
+ const navbar = document.querySelector('.site-navbar');
+ const onScroll = () => {
+ if (navbar) {
+ navbar.classList.toggle('scrolled', window.scrollY > 12);
+ }
};
- chatForm.addEventListener('submit', async (e) => {
- e.preventDefault();
- const message = chatInput.value.trim();
- if (!message) return;
+ document.addEventListener('scroll', onScroll, { passive: true });
+ onScroll();
- appendMessage(message, 'visitor');
- chatInput.value = '';
+ if (window.bootstrap && typeof bootstrap.Toast === 'function') {
+ document.querySelectorAll('.toast').forEach((toastElement) => {
+ const toast = new bootstrap.Toast(toastElement, { delay: 5200 });
+ toast.show();
+ });
+ }
- try {
- const response = await fetch('api/chat.php', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ message })
+ const filterButtons = document.querySelectorAll('[data-service-filter]');
+ const serviceItems = document.querySelectorAll('[data-service-track]');
+
+ if (filterButtons.length && serviceItems.length) {
+ filterButtons.forEach((button) => {
+ button.addEventListener('click', () => {
+ const filter = button.dataset.serviceFilter || 'all';
+ filterButtons.forEach((candidate) => candidate.classList.toggle('active', candidate === button));
+ serviceItems.forEach((item) => {
+ const track = item.dataset.serviceTrack || '';
+ const visible = filter === 'all' || track === filter;
+ item.classList.toggle('d-none', !visible);
+ });
});
- const data = await response.json();
-
- // Artificial delay for realism
- setTimeout(() => {
- appendMessage(data.reply, 'bot');
- }, 500);
- } catch (error) {
- console.error('Error:', error);
- appendMessage("Sorry, something went wrong. Please try again.", 'bot');
- }
+ });
+ }
+
+ document.querySelectorAll('[data-copy-target]').forEach((button) => {
+ button.addEventListener('click', async () => {
+ const target = document.querySelector(button.dataset.copyTarget || '');
+ if (!target) return;
+
+ const originalText = button.textContent;
+ try {
+ await navigator.clipboard.writeText(target.textContent.trim());
+ button.textContent = 'Copied';
+ window.setTimeout(() => {
+ button.textContent = originalText;
+ }, 1600);
+ } catch (error) {
+ button.textContent = 'Unavailable';
+ window.setTimeout(() => {
+ button.textContent = originalText;
+ }, 1600);
+ }
+ });
+ });
+
+ document.querySelectorAll('[data-char-count]').forEach((textarea) => {
+ const counter = document.querySelector(textarea.dataset.charCount || '');
+ if (!counter) return;
+
+ const updateCount = () => {
+ counter.textContent = `${textarea.value.length} / ${textarea.maxLength}`;
+ };
+
+ textarea.addEventListener('input', updateCount);
+ updateCount();
});
});
diff --git a/contact.php b/contact.php
new file mode 100644
index 0000000..6cc8bfd
--- /dev/null
+++ b/contact.php
@@ -0,0 +1,53 @@
+ 'Strategic discussion', 'summary' => 'Use the inquiry desk for partnership, package customization, or operational collaboration.'],
+ ['title' => 'Email-based follow-up', 'summary' => 'The intake form captures a work email so the next step can stay formal and documented.'],
+ ['title' => 'Location coordination', 'summary' => 'Share the preferred area or property context in the brief so site planning starts earlier.'],
+];
+
+require __DIR__ . '/includes/header.php';
+?>
+
+
+
+
+
+
Contact guidance
+
A clear route into strategic discussion.
+
Rather than scattering generic contact details, this MVP uses a structured intake path so every conversation starts with context, scale, and the right ecosystem lane.
+
+
+
+
+
+
+
+
+
Recommended next action
+
Use the partnership page as the main intake channel.
+
It already captures the package or partner route, preferred timing, location context, and a short narrative brief—everything needed for a credible first review.
+
+
+
+
+
diff --git a/database/gbp_ecosystem.sql b/database/gbp_ecosystem.sql
new file mode 100644
index 0000000..8f6725c
--- /dev/null
+++ b/database/gbp_ecosystem.sql
@@ -0,0 +1,18 @@
+-- Genitri Ecco Wellness inquiry desk
+CREATE TABLE IF NOT EXISTS ecosystem_inquiries (
+ id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
+ reference_code VARCHAR(32) NOT NULL UNIQUE,
+ inquiry_type VARCHAR(80) NOT NULL,
+ interest_area VARCHAR(120) NOT NULL,
+ contact_name VARCHAR(120) NOT NULL,
+ organization_name VARCHAR(160) NOT NULL,
+ email VARCHAR(160) NOT NULL,
+ phone VARCHAR(40) DEFAULT NULL,
+ target_date VARCHAR(40) DEFAULT NULL,
+ event_scale VARCHAR(80) DEFAULT NULL,
+ preferred_location VARCHAR(120) DEFAULT NULL,
+ notes TEXT NOT NULL,
+ status VARCHAR(30) NOT NULL DEFAULT 'New',
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ INDEX idx_status_created (status, created_at)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
diff --git a/healthz.php b/healthz.php
new file mode 100644
index 0000000..4fb9295
--- /dev/null
+++ b/healthz.php
@@ -0,0 +1,14 @@
+ 'ok',
+ 'timestamp' => gmdate(DATE_ATOM),
+ 'storage' => using_preview_storage() ? 'preview-session' : 'database',
+ 'site' => site_name(),
+], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
diff --git a/includes/app.php b/includes/app.php
new file mode 100644
index 0000000..c47be39
--- /dev/null
+++ b/includes/app.php
@@ -0,0 +1,709 @@
+ 'home', 'label' => 'Home', 'href' => 'index.php'],
+ ['key' => 'about', 'label' => 'About Us', 'href' => 'about.php'],
+ ['key' => 'services', 'label' => 'Services', 'href' => 'services.php'],
+ ['key' => 'portfolio', 'label' => 'Portfolio', 'href' => 'portfolio.php'],
+ ['key' => 'partnership', 'label' => 'Partnership', 'href' => 'partnership.php'],
+ ['key' => 'publications', 'label' => 'Publications', 'href' => 'publications.php'],
+ ['key' => 'contact', 'label' => 'Contact Us', 'href' => 'contact.php'],
+ ];
+}
+
+function default_service_catalog(): array
+{
+ return [
+ [
+ 'slug' => 'eco-wellness',
+ 'track' => 'immersive',
+ 'category' => 'Eco Wellness Package',
+ 'headline' => 'Preventive wellbeing journeys grounded in place.',
+ 'summary' => 'Curated wellness experiences that combine guided recovery, quiet hospitality, and structured facilitation for premium groups.',
+ 'audience' => 'Clinics, communities, executive wellness groups',
+ 'duration' => 'Half day to 3 days',
+ 'outcome' => 'Participants leave with a calmer baseline, curated routines, and professionally coordinated aftercare recommendations.',
+ 'includes' => [
+ 'Guided breathing, mobility, and reflection sessions',
+ 'Nature immersion flow with on-site facilitator',
+ 'Healthy hospitality setup and light nourishment',
+ 'Operational coordinator, safety briefing, and guest support',
+ ],
+ ],
+ [
+ 'slug' => 'retreat',
+ 'track' => 'immersive',
+ 'category' => 'Retreat Package',
+ 'headline' => 'Immersive retreats with quiet luxury and operational discipline.',
+ 'summary' => 'Multi-day retreat programs for restorative groups, leadership circles, or curated community cohorts seeking a trusted delivery partner.',
+ 'audience' => 'Leadership teams, private communities, destination hosts',
+ 'duration' => '2 to 4 days',
+ 'outcome' => 'Guests receive a coherent retreat arc, documented experience moments, and a host-ready operating structure.',
+ 'includes' => [
+ 'Retreat flow design from arrival to closing circle',
+ 'Hospitality curation and accommodation coordination',
+ 'Experience documentation and facilitator scheduling',
+ 'Transportation, guest movement, and contingency planning',
+ ],
+ ],
+ [
+ 'slug' => 'corporate',
+ 'track' => 'corporate',
+ 'category' => 'Corporate Package',
+ 'headline' => 'Nature-based programs for teams that need focus, recovery, and alignment.',
+ 'summary' => 'Corporate offsites and culture journeys that balance executive standards, wellness objectives, and measurable group outcomes.',
+ 'audience' => 'Companies, HR teams, executive assistants, corporate communities',
+ 'duration' => '1 to 2 days',
+ 'outcome' => 'A more connected team, clean guest logistics, and a premium experience that avoids generic event energy.',
+ 'includes' => [
+ 'Team reset sessions and guided collaboration moments',
+ 'Hospitality coordination with venue and catering partners',
+ 'Brand-safe itineraries, agendas, and support crew',
+ 'Post-program recap for internal reporting',
+ ],
+ ],
+ [
+ 'slug' => 'hospitality',
+ 'track' => 'operational',
+ 'category' => 'Hospitality Package',
+ 'headline' => 'Hospitality collaborations that expand destination value.',
+ 'summary' => 'Program overlays for properties, destinations, or operators wanting to add curated wellness and experiential layers to their guest offering.',
+ 'audience' => 'Hotels, villas, destination operators, stay partners',
+ 'duration' => 'Modular activation',
+ 'outcome' => 'Partners gain a credible package layer, documented service standards, and a stronger experiential positioning.',
+ 'includes' => [
+ 'Experience packaging and guest-ready positioning',
+ 'Venue flow, staffing, and hospitality service mapping',
+ 'Wellness or retreat modules fitted to the property',
+ 'Partner coordination with clear operating notes',
+ ],
+ ],
+ [
+ 'slug' => 'event-mice',
+ 'track' => 'corporate',
+ 'category' => 'Event & MICE Package',
+ 'headline' => 'Operationally credible events with experiential depth.',
+ 'summary' => 'MICE and event support for organizations that need premium guest handling, destination texture, and dependable execution.',
+ 'audience' => 'Associations, brands, institutions, MICE organizers',
+ 'duration' => 'Single activation to multi-day agenda',
+ 'outcome' => 'An event environment that feels curated, calm, and professionally managed from transport to closing session.',
+ 'includes' => [
+ 'Run-of-show design and ecosystem partner alignment',
+ 'Guest logistics, transport, and venue operations support',
+ 'Curated destination layers for delegates or VIP groups',
+ 'Documentation for post-event portfolio use',
+ ],
+ ],
+ [
+ 'slug' => 'custom-experience',
+ 'track' => 'operational',
+ 'category' => 'Custom Experience Package',
+ 'headline' => 'Flexible program architecture for bespoke briefs.',
+ 'summary' => 'A consultative route for institutions or partners who need tailored combinations of wellness, hospitality, logistics, and activation.',
+ 'audience' => 'Strategic partners, institutions, multi-stakeholder projects',
+ 'duration' => 'Scoped during consultation',
+ 'outcome' => 'A custom proposal with the right operating modules, partner map, and delivery assumptions for the brief.',
+ 'includes' => [
+ 'Discovery call and brief clarification',
+ 'Package architecture and ecosystem matchmaking',
+ 'Operational assumptions for location, scale, and timing',
+ 'Proposal-ready summary to support internal decision-making',
+ ],
+ ],
+ ];
+}
+
+function service_by_slug(string $slug): ?array
+{
+ foreach (service_catalog() as $service) {
+ if ($service['slug'] === $slug) {
+ return $service;
+ }
+ }
+
+ return null;
+}
+
+function default_portfolio_catalog(): array
+{
+ return [
+ [
+ 'category' => 'Retreat Documentation',
+ 'title' => 'Immersive reset for healthcare leaders',
+ 'summary' => 'Three-day private retreat with guided wellness, quiet hospitality, and destination movement planning.',
+ 'detail' => 'Built around trusted facilitation, transport sequencing, curated dining, and premium guest care.',
+ 'metric' => '48 delegates · 3 days · full service coordination',
+ ],
+ [
+ 'category' => 'Hospitality Activities',
+ 'title' => 'Property-based eco wellness activation',
+ 'summary' => 'Hospitality collaboration that layered preventive wellness modules into a destination stay offer.',
+ 'detail' => 'Designed to expand partner value without disrupting existing property operations.',
+ 'metric' => '6 touchpoints · 2 host teams · reusable package model',
+ ],
+ [
+ 'category' => 'Event Showcase',
+ 'title' => 'Institutional offsite with nature-led programming',
+ 'summary' => 'Corporate gathering that fused structured sessions, outdoor recovery windows, and logistics support.',
+ 'detail' => 'Focused on calm execution, executive-safe service, and documented experience moments.',
+ 'metric' => '120 participants · transport + venue + documentation',
+ ],
+ [
+ 'category' => 'Operational Support',
+ 'title' => 'Partner coordination for a multi-vendor destination brief',
+ 'summary' => 'Cross-functional partner map covering venue, mobility, hospitality, and merchandise support.',
+ 'detail' => 'Organized into clear workstreams to reduce friction and improve delivery certainty.',
+ 'metric' => '9 partner lanes · single briefing track · review-ready notes',
+ ],
+ ];
+}
+
+function default_partnership_categories(): array
+{
+ return [
+ ['title' => 'Medical Partner', 'summary' => 'Preventive health programs, professional credibility, and trusted wellness positioning.'],
+ ['title' => 'Food & Beverage Partner', 'summary' => 'Healthy dining curation, premium service alignment, and guest experience enhancement.'],
+ ['title' => 'Hospitality Partner', 'summary' => 'Hotels, villas, and destination stays that want elevated experiential packages.'],
+ ['title' => 'Transportation Partner', 'summary' => 'Ground movement, transfer planning, and reliable guest logistics.'],
+ ['title' => 'Vendor Partner', 'summary' => 'Operational vendors for events, staging, equipment, or curated supporting services.'],
+ ['title' => 'Venue Partner', 'summary' => 'Destination spaces that fit wellness, retreat, and premium gathering formats.'],
+ ['title' => 'Merchandise Partner', 'summary' => 'Thoughtful branded items, gifting, and supporting program materials.'],
+ ['title' => 'Talent Partner', 'summary' => 'Facilitators, wellness practitioners, speakers, and curated experience talent.'],
+ ['title' => 'Facilities Partner', 'summary' => 'Technical, utility, and infrastructure support for safe, smooth delivery.'],
+ ];
+}
+
+function default_publication_catalog(): array
+{
+ return [
+ [
+ 'slug' => 'governance-wellness',
+ 'category' => 'Whitepaper',
+ 'date' => '2026-05-12',
+ 'title' => 'Governance as a differentiator in eco wellness operations',
+ 'summary' => 'A concise look at why ethical operating systems increase trust in nature-based programs.',
+ 'body' => [
+ 'Nature-based experiences become institution-ready when governance is visible, practical, and embedded into the delivery model.',
+ 'For GBP, governance is not an afterthought. It shapes partner qualification, guest flow, documentation, safety considerations, and post-program reporting.',
+ 'Publishing this kind of operational insight helps the website function as both a service platform and a credibility engine for future collaborations.',
+ ],
+ ],
+ [
+ 'slug' => 'hospitality-ecosystem',
+ 'category' => 'Strategic Insight',
+ 'date' => '2026-04-28',
+ 'title' => 'Why hospitality ecosystems outperform isolated program offers',
+ 'summary' => 'Integrated destinations create stronger guest journeys when transport, venue, and wellness layers are coordinated early.',
+ 'body' => [
+ 'Standalone experiences can look attractive in a brochure but collapse under operational pressure when the surrounding ecosystem is weak.',
+ 'An integrated hospitality ecosystem aligns venue readiness, movement planning, service staffing, food design, and escalation paths before guests arrive.',
+ 'That alignment is what allows a premium minimalist brand like Genitri Ecco Wellness to stay calm, polished, and credible at scale.',
+ ],
+ ],
+ [
+ 'slug' => 'experience-customization',
+ 'category' => 'Ecosystem Update',
+ 'date' => '2026-03-30',
+ 'title' => 'A practical framework for customizing destination experiences',
+ 'summary' => 'How inquiry intake, partner matching, and brief translation can stay lightweight while still feeling premium.',
+ 'body' => [
+ 'Customization is most effective when the intake process captures the essentials: audience, timing, desired outcome, and operating constraints.',
+ 'From there, GBP can match the right service modules, partner types, and delivery assumptions without overcomplicating the first conversation.',
+ 'This is why the first MVP slice focuses on a strategic inquiry desk that feels both editorial and operational.',
+ ],
+ ],
+ ];
+}
+
+function publication_by_slug(string $slug): ?array
+{
+ foreach (publication_catalog() as $publication) {
+ if ($publication['slug'] === $slug) {
+ return $publication;
+ }
+ }
+
+ return null;
+}
+
+function governance_values(): array
+{
+ return [
+ ['title' => 'Excellence with Integrity', 'summary' => 'Premium execution grounded in transparent standards, safety, and reliable delivery.'],
+ ['title' => 'Shared Value Creation', 'summary' => 'Programs and partnerships designed to benefit guests, collaborators, and the wider ecosystem.'],
+ ['title' => 'Sustainable Equilibrium', 'summary' => 'Nature, hospitality, and operations balanced with long-term resilience in mind.'],
+ ];
+}
+
+function objective_list(): array
+{
+ return [
+ 'Build institutional trust',
+ 'Showcase operational capability',
+ 'Open strategic partnership routes',
+ 'Generate qualified inquiry',
+ 'Provide curated package services',
+ 'Enable experience customization',
+ 'Display ecosystem portfolio',
+ 'Build publication authority',
+ 'Prepare governance infrastructure',
+ ];
+}
+
+function inquiry_type_options(): array
+{
+ return [
+ 'Strategic Partnership',
+ 'Package Customization',
+ 'Operational Collaboration',
+ 'Publication / Media',
+ ];
+}
+
+function interest_groups(): array
+{
+ $services = [];
+ foreach (service_catalog() as $service) {
+ $services[] = $service['category'];
+ }
+
+ $partners = [];
+ foreach (partnership_categories() as $partner) {
+ $partners[] = $partner['title'];
+ }
+
+ return [
+ 'Curated Programs' => $services,
+ 'Partnership Routes' => $partners,
+ ];
+}
+
+function scale_options(): array
+{
+ return [
+ 'Private group (10–30)',
+ 'Team offsite (31–80)',
+ 'Corporate / MICE (81–200)',
+ 'Multi-site activation (200+)',
+ ];
+}
+
+function location_options(): array
+{
+ return [
+ 'Tegal / Bregas corridor',
+ 'Central Java destination',
+ 'Partner property or venue',
+ 'To be discussed during scoping',
+ ];
+}
+
+function active_class(string $activePage, string $key): string
+{
+ return $activePage === $key ? 'active' : '';
+}
+
+function current_year(): string
+{
+ return gmdate('Y');
+}
+
+function csrf_token(): string
+{
+ if (empty($_SESSION['csrf_token'])) {
+ $_SESSION['csrf_token'] = bin2hex(random_bytes(16));
+ }
+
+ return $_SESSION['csrf_token'];
+}
+
+function verify_csrf_token(?string $token): bool
+{
+ return is_string($token) && hash_equals($_SESSION['csrf_token'] ?? '', $token);
+}
+
+function flash_set(string $type, string $message): void
+{
+ $_SESSION['flash'] = [
+ 'type' => $type,
+ 'message' => $message,
+ ];
+}
+
+function flash_get(): ?array
+{
+ if (empty($_SESSION['flash']) || !is_array($_SESSION['flash'])) {
+ return null;
+ }
+
+ $flash = $_SESSION['flash'];
+ unset($_SESSION['flash']);
+
+ return $flash;
+}
+
+function old_value(array $source, string $key, string $fallback = ''): string
+{
+ $value = $source[$key] ?? $fallback;
+ return trim((string) $value);
+}
+
+function is_selected(?string $candidate, ?string $value): string
+{
+ return ((string) $candidate === (string) $value) ? 'selected' : '';
+}
+
+function app_db_connection(): ?PDO
+{
+ static $attempted = false;
+ static $pdo = null;
+ static $error = null;
+
+ if ($attempted) {
+ return $pdo;
+ }
+
+ $attempted = true;
+
+ try {
+ $pdo = db();
+ $error = null;
+ } catch (Throwable $exception) {
+ $pdo = null;
+ $error = $exception->getMessage();
+ }
+
+ $GLOBALS['APP_DB_ERROR'] = $error;
+
+ return $pdo;
+}
+
+function app_db_error(): ?string
+{
+ app_db_connection();
+ $error = $GLOBALS['APP_DB_ERROR'] ?? null;
+ return is_string($error) && $error !== '' ? $error : null;
+}
+
+function using_preview_storage(): bool
+{
+ return app_db_connection() === null;
+}
+
+function inquiry_table_sql(): string
+{
+ return <<<'SQL'
+CREATE TABLE IF NOT EXISTS ecosystem_inquiries (
+ id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
+ reference_code VARCHAR(32) NOT NULL UNIQUE,
+ inquiry_type VARCHAR(80) NOT NULL,
+ interest_area VARCHAR(120) NOT NULL,
+ contact_name VARCHAR(120) NOT NULL,
+ organization_name VARCHAR(160) NOT NULL,
+ email VARCHAR(160) NOT NULL,
+ phone VARCHAR(40) DEFAULT NULL,
+ target_date VARCHAR(40) DEFAULT NULL,
+ event_scale VARCHAR(80) DEFAULT NULL,
+ preferred_location VARCHAR(120) DEFAULT NULL,
+ notes TEXT NOT NULL,
+ status VARCHAR(30) NOT NULL DEFAULT 'New',
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ INDEX idx_status_created (status, created_at)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+SQL;
+}
+
+function ensure_inquiry_table(): bool
+{
+ static $ensured = false;
+
+ if ($ensured) {
+ return true;
+ }
+
+ $pdo = app_db_connection();
+ if (!$pdo) {
+ return false;
+ }
+
+ try {
+ $pdo->exec(inquiry_table_sql());
+ $ensured = true;
+ return true;
+ } catch (Throwable $exception) {
+ $GLOBALS['APP_DB_ERROR'] = $exception->getMessage();
+ return false;
+ }
+}
+
+function normalize_multiline(string $value): string
+{
+ $normalized = preg_replace("/\r\n?|\r/", "\n", $value);
+ $normalized = preg_replace("/\n{3,}/", "\n\n", (string) $normalized);
+ return trim((string) $normalized);
+}
+
+function validate_inquiry(array $input): array
+{
+ $data = [
+ 'inquiry_type' => trim((string) ($input['inquiry_type'] ?? '')),
+ 'interest_area' => trim((string) ($input['interest_area'] ?? '')),
+ 'contact_name' => trim((string) ($input['contact_name'] ?? '')),
+ 'organization_name' => trim((string) ($input['organization_name'] ?? '')),
+ 'email' => trim((string) ($input['email'] ?? '')),
+ 'phone' => trim((string) ($input['phone'] ?? '')),
+ 'target_date' => trim((string) ($input['target_date'] ?? '')),
+ 'event_scale' => trim((string) ($input['event_scale'] ?? '')),
+ 'preferred_location' => trim((string) ($input['preferred_location'] ?? '')),
+ 'notes' => normalize_multiline((string) ($input['notes'] ?? '')),
+ ];
+
+ $errors = [];
+
+ if ($data['inquiry_type'] === '') {
+ $errors['inquiry_type'] = 'Choose the type of collaboration you want to discuss.';
+ }
+
+ if ($data['interest_area'] === '') {
+ $errors['interest_area'] = 'Select the package or partnership route that best matches your brief.';
+ }
+
+ if ($data['contact_name'] === '') {
+ $errors['contact_name'] = 'Add the primary contact name.';
+ }
+
+ if ($data['organization_name'] === '') {
+ $errors['organization_name'] = 'Add the company or organization name.';
+ }
+
+ if ($data['email'] === '' || !filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
+ $errors['email'] = 'Use a valid email address so the team can respond.';
+ }
+
+ if ($data['notes'] === '') {
+ $errors['notes'] = 'Describe the program scope, partner intent, or operational need.';
+ } elseif (text_length($data['notes']) < 24) {
+ $errors['notes'] = 'Add a little more detail so the brief is useful for review.';
+ } elseif (text_length($data['notes']) > 900) {
+ $errors['notes'] = 'Keep the brief under 900 characters for this first intake step.';
+ }
+
+ if ($data['phone'] !== '' && text_length($data['phone']) > 40) {
+ $errors['phone'] = 'Phone number is too long.';
+ }
+
+ if ($data['target_date'] !== '' && !preg_match('/^\d{4}-\d{2}-\d{2}$/', $data['target_date'])) {
+ $errors['target_date'] = 'Use a valid preferred date.';
+ }
+
+ return [
+ 'data' => $data,
+ 'errors' => $errors,
+ ];
+}
+
+function generate_reference_code(): string
+{
+ return 'GBP-' . gmdate('ymd') . '-' . strtoupper(bin2hex(random_bytes(2)));
+}
+
+function save_inquiry(array $input): array
+{
+ $validated = validate_inquiry($input);
+ $data = $validated['data'];
+ $errors = $validated['errors'];
+
+ if ($errors !== []) {
+ return [
+ 'success' => false,
+ 'errors' => $errors,
+ 'data' => $data,
+ ];
+ }
+
+ $inquiry = $data;
+ $inquiry['reference_code'] = generate_reference_code();
+ $inquiry['status'] = 'New';
+ $inquiry['created_at'] = gmdate('Y-m-d H:i:s');
+
+ if (ensure_inquiry_table()) {
+ try {
+ $pdo = app_db_connection();
+ if ($pdo instanceof PDO) {
+ $statement = $pdo->prepare(
+ 'INSERT INTO ecosystem_inquiries
+ (reference_code, inquiry_type, interest_area, contact_name, organization_name, email, phone, target_date, event_scale, preferred_location, notes, status)
+ VALUES
+ (:reference_code, :inquiry_type, :interest_area, :contact_name, :organization_name, :email, :phone, :target_date, :event_scale, :preferred_location, :notes, :status)'
+ );
+
+ $statement->bindValue(':reference_code', $inquiry['reference_code']);
+ $statement->bindValue(':inquiry_type', $inquiry['inquiry_type']);
+ $statement->bindValue(':interest_area', $inquiry['interest_area']);
+ $statement->bindValue(':contact_name', $inquiry['contact_name']);
+ $statement->bindValue(':organization_name', $inquiry['organization_name']);
+ $statement->bindValue(':email', $inquiry['email']);
+ $statement->bindValue(':phone', $inquiry['phone'] ?: null, $inquiry['phone'] === '' ? PDO::PARAM_NULL : PDO::PARAM_STR);
+ $statement->bindValue(':target_date', $inquiry['target_date'] ?: null, $inquiry['target_date'] === '' ? PDO::PARAM_NULL : PDO::PARAM_STR);
+ $statement->bindValue(':event_scale', $inquiry['event_scale'] ?: null, $inquiry['event_scale'] === '' ? PDO::PARAM_NULL : PDO::PARAM_STR);
+ $statement->bindValue(':preferred_location', $inquiry['preferred_location'] ?: null, $inquiry['preferred_location'] === '' ? PDO::PARAM_NULL : PDO::PARAM_STR);
+ $statement->bindValue(':notes', $inquiry['notes']);
+ $statement->bindValue(':status', $inquiry['status']);
+ $statement->execute();
+
+ $inquiry['id'] = (int) $pdo->lastInsertId();
+
+ return [
+ 'success' => true,
+ 'inquiry' => $inquiry,
+ 'storage' => 'database',
+ ];
+ }
+ } catch (Throwable $exception) {
+ $GLOBALS['APP_DB_ERROR'] = $exception->getMessage();
+ }
+ }
+
+ $_SESSION['preview_inquiries'] = $_SESSION['preview_inquiries'] ?? [];
+ $_SESSION['preview_inquiry_counter'] = (int) ($_SESSION['preview_inquiry_counter'] ?? 0) + 1;
+ $inquiry['id'] = (int) $_SESSION['preview_inquiry_counter'];
+ $_SESSION['preview_inquiries'][$inquiry['reference_code']] = $inquiry;
+
+ return [
+ 'success' => true,
+ 'inquiry' => $inquiry,
+ 'storage' => 'session',
+ 'warning' => 'Database connectivity is not available, so this submission is stored only for the current preview session.',
+ ];
+}
+
+function session_inquiries(): array
+{
+ $items = array_values($_SESSION['preview_inquiries'] ?? []);
+ usort($items, static fn (array $left, array $right): int => strcmp($right['created_at'], $left['created_at']));
+ return $items;
+}
+
+function list_inquiries(): array
+{
+ if (ensure_inquiry_table()) {
+ try {
+ $pdo = app_db_connection();
+ if ($pdo instanceof PDO) {
+ $statement = $pdo->query('SELECT * FROM ecosystem_inquiries ORDER BY created_at DESC, id DESC LIMIT 50');
+ return $statement->fetchAll() ?: [];
+ }
+ } catch (Throwable $exception) {
+ $GLOBALS['APP_DB_ERROR'] = $exception->getMessage();
+ }
+ }
+
+ return session_inquiries();
+}
+
+function find_inquiry(?int $id = null, ?string $reference = null): ?array
+{
+ if (ensure_inquiry_table()) {
+ try {
+ $pdo = app_db_connection();
+ if ($pdo instanceof PDO) {
+ if ($reference !== null && $reference !== '') {
+ $statement = $pdo->prepare('SELECT * FROM ecosystem_inquiries WHERE reference_code = :reference LIMIT 1');
+ $statement->bindValue(':reference', $reference);
+ $statement->execute();
+ $found = $statement->fetch();
+ return is_array($found) ? $found : null;
+ }
+
+ if ($id !== null && $id > 0) {
+ $statement = $pdo->prepare('SELECT * FROM ecosystem_inquiries WHERE id = :id LIMIT 1');
+ $statement->bindValue(':id', $id, PDO::PARAM_INT);
+ $statement->execute();
+ $found = $statement->fetch();
+ return is_array($found) ? $found : null;
+ }
+ }
+ } catch (Throwable $exception) {
+ $GLOBALS['APP_DB_ERROR'] = $exception->getMessage();
+ }
+ }
+
+ foreach (session_inquiries() as $item) {
+ if (($reference !== null && $reference !== '' && $item['reference_code'] === $reference) || ($id !== null && (int) $item['id'] === $id)) {
+ return $item;
+ }
+ }
+
+ return null;
+}
+
+function format_date_label(?string $date): string
+{
+ $value = trim((string) $date);
+ if ($value === '') {
+ return 'To be coordinated';
+ }
+
+ try {
+ return (new DateTimeImmutable($value))->format('j M Y');
+ } catch (Throwable) {
+ return $value;
+ }
+}
diff --git a/includes/footer.php b/includes/footer.php
new file mode 100644
index 0000000..c567ee4
--- /dev/null
+++ b/includes/footer.php
@@ -0,0 +1,69 @@
+
+
+
+ 'text-bg-success',
+ 'warning' => 'text-bg-warning',
+ 'danger' => 'text-bg-danger',
+ default => 'text-bg-primary',
+ };
+?>
+
+
+
+
= h($flash['message']) ?>
+
+
+
+
+
+
+
+