diff --git a/Panel/modules/website/README.md b/Panel/modules/website/README.md
new file mode 100644
index 00000000..b587a199
--- /dev/null
+++ b/Panel/modules/website/README.md
@@ -0,0 +1,152 @@
+# Gameservers.World Website Module
+
+This module is the public Gameservers.World sales and documentation website.
+
+## Purpose
+
+- Keep the public site inside the GSP repository at `Panel/modules/website/`
+- Remove location-dependent include logic from the old standalone `Website/` tree
+- Keep marketing pages working even when billing configuration is missing
+- Centralize URL and filesystem path handling so the site is portable
+
+## Structure
+
+```text
+Panel/modules/website/
+ index.php
+ serverlist.php
+ docs.php
+ login.php
+ pricing.php
+ locations.php
+ support.php
+ doc_asset.php
+ includes/
+ bootstrap.php
+ footer.php
+ header.php
+ navigation.php
+ paths.php
+ assets/
+ css/
+ js/
+ images/
+ pages/
+ home.php
+ game_servers.php
+ documentation.php
+ pricing.php
+ locations.php
+ support.php
+ config/
+ config.example.php
+```
+
+## URL helpers
+
+The website uses a central bootstrap instead of scattered relative paths.
+
+- `website_url('serverlist.php')`
+- `website_asset('css/site.css')`
+- `panel_url()`
+- `login_url()`
+- `billing_url('order.php?service_id=1')`
+- `documentation_url('minecraft')`
+
+## Billing and database behavior
+
+The public site does not include `Panel/modules/billing/includes/config.inc.php` directly.
+
+Instead it:
+
+1. Parses `Panel/includes/config.inc.php` when present
+2. Parses `Panel/modules/billing/includes/config.inc.php` when present
+3. Uses the discovered database values only for pages that need catalog data
+4. Falls back cleanly when no usable billing or panel DB config is available
+
+Effects:
+
+- `index.php`, `docs.php`, `locations.php`, `pricing.php`, and `support.php` still load
+- `serverlist.php` shows a clean fallback message instead of a fatal include error
+- shared navigation never crashes because billing config is missing
+
+## Documentation source
+
+Customer documentation is read from the existing billing docs directory:
+
+- `Panel/modules/billing/docs/`
+
+This keeps the website portable without duplicating the documentation tree.
+
+## Deployment
+
+Preferred Apache approach:
+
+1. Point the Gameservers.World vhost `DocumentRoot` to `Panel/modules/website`
+2. Expose billing separately under `/billing/` with an Apache `Alias`
+3. Set `panel_url`, `login_url`, and `billing_base_url` in `config/config.php` or `config/config.local.php`
+
+Example:
+
+```apacheconf
+
+ ServerName gameservers.world
+ ServerAlias www.gameservers.world
+ DocumentRoot /var/www/html/GSP/Panel/modules/website
+
+
+ AllowOverride All
+ Require all granted
+ DirectoryIndex index.php
+
+
+ Alias /billing /var/www/html/GSP/Panel/modules/billing
+
+ AllowOverride All
+ Require all granted
+ DirectoryIndex index.php
+
+
+```
+
+Alternate front-controller approach:
+
+- Keep a tiny public `index.php` outside the repo tree that requires `Panel/modules/website/index.php`
+- Do not duplicate the full website into a second location
+
+## Configuration
+
+1. Copy `config/config.example.php` to `config/config.php` or `config/config.local.php`
+2. Set:
+ - `public_base_url`
+ - `billing_base_url`
+ - `panel_url`
+ - `login_url`
+ - support links
+3. Keep any environment-specific config out of Git
+
+## Manual link checks
+
+Verify:
+
+1. `index.php`
+2. `serverlist.php`
+3. `docs.php`
+4. `pricing.php`
+5. `locations.php`
+6. `support.php`
+7. `login.php`
+8. header navigation
+9. footer links
+10. mobile navigation
+11. asset URLs
+12. `billing_url()` destinations
+
+## Portability notes
+
+- No `/var/www/html/...` filesystem assumptions
+- No `gameservers.world/panel` assumptions
+- No repeated `../../../` path climbing
+- Internal page links and assets route through helpers
+- Database config is optional for non-catalog pages
+
diff --git a/Panel/modules/website/assets/css/site.css b/Panel/modules/website/assets/css/site.css
new file mode 100644
index 00000000..22b71467
--- /dev/null
+++ b/Panel/modules/website/assets/css/site.css
@@ -0,0 +1,743 @@
+:root {
+ color-scheme: dark;
+ --bg: #06111f;
+ --bg-alt: #0b1729;
+ --panel: rgba(9, 19, 35, 0.9);
+ --panel-strong: #0f1d33;
+ --line: rgba(143, 174, 214, 0.18);
+ --line-strong: rgba(143, 174, 214, 0.28);
+ --text: #e8f0ff;
+ --muted: #93a8cb;
+ --accent: #54a6ff;
+ --accent-strong: #7bc0ff;
+ --accent-soft: rgba(84, 166, 255, 0.12);
+ --success: #89d77b;
+ --danger: #ff8c7a;
+ --shadow: 0 18px 50px rgba(0, 0, 0, 0.35);
+ --radius: 8px;
+ --content-width: 1180px;
+}
+
+*,
+*::before,
+*::after {
+ box-sizing: border-box;
+}
+
+html,
+body {
+ margin: 0;
+ padding: 0;
+}
+
+body {
+ min-height: 100vh;
+ font-family: Inter, "Segoe UI", Arial, sans-serif;
+ background:
+ linear-gradient(180deg, rgba(4, 10, 18, 0.72) 0%, rgba(4, 10, 18, 0.92) 55%, rgba(4, 10, 18, 1) 100%),
+ url("../images/dark.jpg") center/cover fixed no-repeat,
+ var(--bg);
+ color: var(--text);
+ line-height: 1.5;
+}
+
+a {
+ color: inherit;
+ text-decoration: none;
+}
+
+img {
+ max-width: 100%;
+ display: block;
+}
+
+button,
+input,
+select,
+textarea {
+ font: inherit;
+}
+
+.sr-only {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ border: 0;
+}
+
+.container {
+ width: min(calc(100% - 32px), var(--content-width));
+ margin: 0 auto;
+}
+
+.site-main {
+ padding-bottom: 72px;
+}
+
+.site-header {
+ position: sticky;
+ top: 0;
+ z-index: 20;
+ backdrop-filter: blur(16px);
+ background: rgba(5, 12, 23, 0.88);
+ border-bottom: 1px solid var(--line);
+}
+
+.header-shell {
+ display: grid;
+ grid-template-columns: auto 1fr auto;
+ align-items: center;
+ gap: 24px;
+ min-height: 76px;
+}
+
+.brand,
+.footer-brand {
+ display: inline-flex;
+ align-items: center;
+ gap: 14px;
+}
+
+.brand-logo,
+.footer-logo {
+ width: 42px;
+ height: 42px;
+}
+
+.brand-copy {
+ display: grid;
+ gap: 4px;
+}
+
+.brand-name {
+ font-size: 1rem;
+ font-weight: 700;
+}
+
+.brand-tagline {
+ color: var(--muted);
+ font-size: 0.8rem;
+}
+
+.primary-nav {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ gap: 8px;
+}
+
+.nav-link {
+ padding: 10px 12px;
+ border-radius: 6px;
+ color: var(--muted);
+}
+
+.nav-link:hover,
+.nav-link.is-active {
+ color: var(--text);
+ background: var(--accent-soft);
+}
+
+.header-actions {
+ display: flex;
+ gap: 12px;
+ align-items: center;
+}
+
+.button {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-height: 44px;
+ padding: 0 18px;
+ border-radius: 6px;
+ border: 1px solid transparent;
+ font-weight: 600;
+ transition: 0.18s ease;
+}
+
+.button:hover {
+ transform: translateY(-1px);
+}
+
+.button-primary {
+ background: linear-gradient(180deg, #58abff 0%, #3589e8 100%);
+ color: #051120;
+}
+
+.button-secondary {
+ background: transparent;
+ color: var(--text);
+ border-color: var(--line-strong);
+}
+
+.button-ghost {
+ background: rgba(255, 255, 255, 0.03);
+ border-color: var(--line);
+ color: var(--text);
+}
+
+.nav-toggle {
+ display: none;
+ width: 44px;
+ height: 44px;
+ padding: 0;
+ border: 1px solid var(--line);
+ border-radius: 6px;
+ background: transparent;
+ color: var(--text);
+}
+
+.nav-toggle span {
+ display: block;
+ width: 18px;
+ height: 2px;
+ background: currentColor;
+ margin: 4px auto;
+}
+
+.hero {
+ padding: 72px 0 40px;
+}
+
+.hero-layout {
+ display: grid;
+ grid-template-columns: minmax(0, 1.1fr) minmax(320px, 0.9fr);
+ gap: 32px;
+ align-items: center;
+}
+
+.eyebrow {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ padding: 8px 12px;
+ margin-bottom: 18px;
+ border-radius: 999px;
+ background: rgba(84, 166, 255, 0.12);
+ border: 1px solid rgba(84, 166, 255, 0.22);
+ color: #b6d8ff;
+ font-size: 0.88rem;
+ font-weight: 600;
+}
+
+.hero h1 {
+ margin: 0 0 16px;
+ font-size: clamp(2.5rem, 4vw, 4.4rem);
+ line-height: 1.02;
+}
+
+.hero p {
+ margin: 0;
+ max-width: 62ch;
+ color: #c3d2ea;
+ font-size: 1.05rem;
+}
+
+.hero-actions,
+.stack-actions {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 12px;
+ margin-top: 24px;
+}
+
+.hero-points {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 12px;
+ margin-top: 26px;
+}
+
+.hero-point,
+.info-card,
+.stat-card,
+.feature-card,
+.location-card,
+.service-card,
+.support-card,
+.doc-card,
+.empty-state,
+.panel-preview,
+.flow-step,
+.summary-card {
+ background: linear-gradient(180deg, rgba(13, 24, 43, 0.92) 0%, rgba(10, 18, 32, 0.92) 100%);
+ border: 1px solid var(--line);
+ border-radius: var(--radius);
+ box-shadow: var(--shadow);
+}
+
+.hero-point {
+ padding: 16px;
+}
+
+.hero-point strong,
+.section-heading h2,
+.service-card h3,
+.feature-card h3,
+.location-card h3,
+.doc-card h3,
+.support-card h3,
+.flow-step h3,
+.summary-card h3,
+.panel-preview h3 {
+ display: block;
+ margin: 0 0 8px;
+}
+
+.hero-visual {
+ position: relative;
+ overflow: hidden;
+}
+
+.hero-visual img {
+ width: 100%;
+ min-height: 420px;
+ object-fit: cover;
+ border-radius: var(--radius);
+ border: 1px solid var(--line);
+ box-shadow: var(--shadow);
+}
+
+.hero-visual-card {
+ position: absolute;
+ left: 24px;
+ right: 24px;
+ bottom: 24px;
+ padding: 18px;
+ background: rgba(5, 12, 23, 0.82);
+ border: 1px solid var(--line);
+ border-radius: 6px;
+ backdrop-filter: blur(18px);
+}
+
+.hero-visual-card strong {
+ font-size: 1rem;
+}
+
+.hero-visual-card p {
+ color: var(--muted);
+ margin-top: 8px;
+}
+
+.section {
+ padding: 28px 0;
+}
+
+.section-heading {
+ display: grid;
+ gap: 10px;
+ margin-bottom: 22px;
+}
+
+.section-heading h2 {
+ margin: 0;
+ font-size: clamp(1.6rem, 2vw, 2.2rem);
+}
+
+.section-heading p {
+ margin: 0;
+ color: var(--muted);
+ max-width: 72ch;
+}
+
+.stats-grid,
+.feature-grid,
+.location-grid,
+.service-grid,
+.support-grid,
+.doc-grid,
+.flow-grid,
+.summary-grid {
+ display: grid;
+ gap: 18px;
+}
+
+.stats-grid {
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+}
+
+.feature-grid {
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+}
+
+.location-grid,
+.service-grid,
+.support-grid,
+.doc-grid {
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+}
+
+.flow-grid {
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+}
+
+.summary-grid {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+}
+
+.stat-card,
+.feature-card,
+.location-card,
+.service-card,
+.support-card,
+.doc-card,
+.flow-step,
+.summary-card,
+.panel-preview,
+.empty-state {
+ padding: 22px;
+}
+
+.stat-card strong {
+ font-size: 1.65rem;
+}
+
+.stat-card span,
+.feature-card p,
+.location-card p,
+.service-card p,
+.doc-card p,
+.support-card p,
+.panel-preview p,
+.flow-step p,
+.summary-card p,
+.muted {
+ color: var(--muted);
+}
+
+.section-divider {
+ height: 1px;
+ margin: 16px 0 8px;
+ background: linear-gradient(90deg, rgba(84, 166, 255, 0) 0%, rgba(84, 166, 255, 0.35) 50%, rgba(84, 166, 255, 0) 100%);
+}
+
+.location-card small,
+.service-meta,
+.doc-meta {
+ color: #b8cae5;
+}
+
+.service-card img {
+ width: 100%;
+ aspect-ratio: 16 / 9;
+ object-fit: cover;
+ border-radius: 6px;
+ border: 1px solid var(--line);
+ margin-bottom: 16px;
+ background: #08111f;
+}
+
+.service-card header,
+.doc-card header {
+ margin-bottom: 10px;
+}
+
+.service-price {
+ font-size: 1.2rem;
+ font-weight: 700;
+ color: var(--accent-strong);
+}
+
+.card-actions {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px;
+ margin-top: 16px;
+}
+
+.status-badge {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ padding: 8px 12px;
+ border-radius: 999px;
+ background: rgba(137, 215, 123, 0.12);
+ border: 1px solid rgba(137, 215, 123, 0.22);
+ color: #b6efad;
+ font-size: 0.85rem;
+ font-weight: 600;
+}
+
+.alert {
+ padding: 16px 18px;
+ border-radius: 6px;
+ border: 1px solid rgba(255, 140, 122, 0.22);
+ background: rgba(255, 140, 122, 0.08);
+ color: #ffd1c8;
+}
+
+.alert.info {
+ border-color: rgba(84, 166, 255, 0.2);
+ background: rgba(84, 166, 255, 0.08);
+ color: #cbe1ff;
+}
+
+.empty-state {
+ text-align: center;
+}
+
+.page-heading {
+ padding: 54px 0 18px;
+}
+
+.page-heading h1 {
+ margin: 0 0 12px;
+ font-size: clamp(2rem, 3vw, 3rem);
+}
+
+.page-heading p {
+ margin: 0;
+ max-width: 74ch;
+ color: var(--muted);
+}
+
+.panel-preview {
+ display: grid;
+ grid-template-columns: minmax(0, 1.15fr) minmax(260px, 0.85fr);
+ gap: 24px;
+ align-items: center;
+}
+
+.panel-preview-shell {
+ border: 1px solid var(--line);
+ border-radius: 6px;
+ overflow: hidden;
+}
+
+.panel-preview-shell img {
+ width: 100%;
+ aspect-ratio: 16 / 10;
+ object-fit: cover;
+}
+
+.panel-preview-stats {
+ display: grid;
+ gap: 12px;
+}
+
+.flow-step-number {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 32px;
+ height: 32px;
+ border-radius: 999px;
+ background: rgba(84, 166, 255, 0.16);
+ color: var(--accent-strong);
+ font-weight: 700;
+ margin-bottom: 12px;
+}
+
+.doc-list-group {
+ display: grid;
+ gap: 18px;
+}
+
+.doc-list-group h2 {
+ margin: 0;
+ font-size: 1.25rem;
+}
+
+.doc-view {
+ padding: 28px;
+}
+
+.doc-view h1,
+.doc-view h2,
+.doc-view h3,
+.doc-view h4 {
+ color: var(--text);
+}
+
+.doc-view a {
+ color: var(--accent-strong);
+}
+
+.doc-view pre,
+.doc-view code {
+ font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
+}
+
+.doc-view pre {
+ overflow-x: auto;
+ padding: 16px;
+ border-radius: 6px;
+ background: rgba(0, 0, 0, 0.28);
+ border: 1px solid var(--line);
+}
+
+.doc-view table {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+.doc-view th,
+.doc-view td {
+ padding: 10px 12px;
+ border: 1px solid var(--line);
+ text-align: left;
+}
+
+.cta-band {
+ padding: 28px 0 0;
+}
+
+.cta-panel {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ gap: 16px;
+ align-items: center;
+ padding: 24px 26px;
+ background: linear-gradient(135deg, rgba(29, 72, 138, 0.55) 0%, rgba(12, 25, 45, 0.95) 100%);
+ border: 1px solid rgba(113, 176, 255, 0.28);
+ border-radius: var(--radius);
+ box-shadow: var(--shadow);
+}
+
+.site-footer {
+ border-top: 1px solid var(--line);
+ background: rgba(4, 10, 18, 0.78);
+ padding: 40px 0;
+}
+
+.footer-grid {
+ display: grid;
+ grid-template-columns: 1.2fr 1fr 1fr 1fr;
+ gap: 24px;
+}
+
+.footer-grid h2 {
+ font-size: 0.95rem;
+ margin: 0 0 14px;
+}
+
+.footer-copy {
+ color: var(--muted);
+ max-width: 32ch;
+}
+
+.footer-links {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ display: grid;
+ gap: 10px;
+ color: var(--muted);
+}
+
+.footer-links a:hover {
+ color: var(--text);
+}
+
+@media (max-width: 1080px) {
+ .hero-layout,
+ .panel-preview,
+ .footer-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .stats-grid,
+ .feature-grid {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+
+ .location-grid,
+ .service-grid,
+ .support-grid,
+ .doc-grid,
+ .flow-grid,
+ .summary-grid {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+}
+
+@media (max-width: 820px) {
+ .header-shell {
+ grid-template-columns: auto auto;
+ gap: 16px;
+ }
+
+ .nav-toggle {
+ display: inline-block;
+ justify-self: end;
+ }
+
+ .primary-nav,
+ .header-actions {
+ display: none;
+ }
+
+ .primary-nav.is-open,
+ .header-actions.is-open {
+ display: flex;
+ }
+
+ .header-shell {
+ padding: 14px 0;
+ }
+
+ .site-header .container {
+ display: grid;
+ }
+
+ .primary-nav.is-open {
+ grid-column: 1 / -1;
+ flex-direction: column;
+ align-items: stretch;
+ padding-top: 12px;
+ }
+
+ .header-actions {
+ grid-column: 1 / -1;
+ display: none;
+ flex-wrap: wrap;
+ margin-top: 6px;
+ }
+
+ .header-actions.is-open {
+ display: flex;
+ }
+
+ .header-actions .button {
+ flex: 1 1 180px;
+ }
+
+ .hero,
+ .page-heading {
+ padding-top: 42px;
+ }
+}
+
+@media (max-width: 640px) {
+ .container {
+ width: min(calc(100% - 24px), var(--content-width));
+ }
+
+ .hero-points,
+ .stats-grid,
+ .feature-grid,
+ .location-grid,
+ .service-grid,
+ .support-grid,
+ .doc-grid,
+ .flow-grid,
+ .summary-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .hero h1 {
+ font-size: 2.2rem;
+ }
+
+ .cta-panel,
+ .site-footer {
+ padding-left: 0;
+ padding-right: 0;
+ }
+}
diff --git a/Panel/modules/website/assets/images/banner.png b/Panel/modules/website/assets/images/banner.png
new file mode 100644
index 00000000..545e3c21
Binary files /dev/null and b/Panel/modules/website/assets/images/banner.png differ
diff --git a/Panel/modules/website/assets/images/dark.jpg b/Panel/modules/website/assets/images/dark.jpg
new file mode 100644
index 00000000..61daaa82
Binary files /dev/null and b/Panel/modules/website/assets/images/dark.jpg differ
diff --git a/Panel/modules/website/assets/images/games/7dtd.jpg b/Panel/modules/website/assets/images/games/7dtd.jpg
new file mode 100644
index 00000000..923a1562
Binary files /dev/null and b/Panel/modules/website/assets/images/games/7dtd.jpg differ
diff --git a/Panel/modules/website/assets/images/games/arkse.jpg b/Panel/modules/website/assets/images/games/arkse.jpg
new file mode 100644
index 00000000..3b5a0088
Binary files /dev/null and b/Panel/modules/website/assets/images/games/arkse.jpg differ
diff --git a/Panel/modules/website/assets/images/games/arma2.jpg b/Panel/modules/website/assets/images/games/arma2.jpg
new file mode 100644
index 00000000..2827f469
Binary files /dev/null and b/Panel/modules/website/assets/images/games/arma2.jpg differ
diff --git a/Panel/modules/website/assets/images/games/arma2_operation_arrowhead.jpg b/Panel/modules/website/assets/images/games/arma2_operation_arrowhead.jpg
new file mode 100644
index 00000000..cb5ae239
Binary files /dev/null and b/Panel/modules/website/assets/images/games/arma2_operation_arrowhead.jpg differ
diff --git a/Panel/modules/website/assets/images/games/arma_3.jpg b/Panel/modules/website/assets/images/games/arma_3.jpg
new file mode 100644
index 00000000..d25b250f
Binary files /dev/null and b/Panel/modules/website/assets/images/games/arma_3.jpg differ
diff --git a/Panel/modules/website/assets/images/games/asseto.jpg b/Panel/modules/website/assets/images/games/asseto.jpg
new file mode 100644
index 00000000..10dce008
Binary files /dev/null and b/Panel/modules/website/assets/images/games/asseto.jpg differ
diff --git a/Panel/modules/website/assets/images/games/avorion.jpg b/Panel/modules/website/assets/images/games/avorion.jpg
new file mode 100644
index 00000000..6b739af1
Binary files /dev/null and b/Panel/modules/website/assets/images/games/avorion.jpg differ
diff --git a/Panel/modules/website/assets/images/games/brainbread_2.jpg b/Panel/modules/website/assets/images/games/brainbread_2.jpg
new file mode 100644
index 00000000..ced82945
Binary files /dev/null and b/Panel/modules/website/assets/images/games/brainbread_2.jpg differ
diff --git a/Panel/modules/website/assets/images/games/chivalry.jpg b/Panel/modules/website/assets/images/games/chivalry.jpg
new file mode 100644
index 00000000..81f83ffa
Binary files /dev/null and b/Panel/modules/website/assets/images/games/chivalry.jpg differ
diff --git a/Panel/modules/website/assets/images/games/citadel.jpg b/Panel/modules/website/assets/images/games/citadel.jpg
new file mode 100644
index 00000000..b64ad647
Binary files /dev/null and b/Panel/modules/website/assets/images/games/citadel.jpg differ
diff --git a/Panel/modules/website/assets/images/games/colonysurvival.jpg b/Panel/modules/website/assets/images/games/colonysurvival.jpg
new file mode 100644
index 00000000..c922fc85
Binary files /dev/null and b/Panel/modules/website/assets/images/games/colonysurvival.jpg differ
diff --git a/Panel/modules/website/assets/images/games/conanexiles.jpg b/Panel/modules/website/assets/images/games/conanexiles.jpg
new file mode 100644
index 00000000..55c41e59
Binary files /dev/null and b/Panel/modules/website/assets/images/games/conanexiles.jpg differ
diff --git a/Panel/modules/website/assets/images/games/cs_go.jpg b/Panel/modules/website/assets/images/games/cs_go.jpg
new file mode 100644
index 00000000..941ee8c5
Binary files /dev/null and b/Panel/modules/website/assets/images/games/cs_go.jpg differ
diff --git a/Panel/modules/website/assets/images/games/cstrike.jpg b/Panel/modules/website/assets/images/games/cstrike.jpg
new file mode 100644
index 00000000..33418d19
Binary files /dev/null and b/Panel/modules/website/assets/images/games/cstrike.jpg differ
diff --git a/Panel/modules/website/assets/images/games/cstrikesource.jpg b/Panel/modules/website/assets/images/games/cstrikesource.jpg
new file mode 100644
index 00000000..a777d4a3
Binary files /dev/null and b/Panel/modules/website/assets/images/games/cstrikesource.jpg differ
diff --git a/Panel/modules/website/assets/images/games/day_of_defeat_source.jpg b/Panel/modules/website/assets/images/games/day_of_defeat_source.jpg
new file mode 100644
index 00000000..f8ee6857
Binary files /dev/null and b/Panel/modules/website/assets/images/games/day_of_defeat_source.jpg differ
diff --git a/Panel/modules/website/assets/images/games/day_z.jpg b/Panel/modules/website/assets/images/games/day_z.jpg
new file mode 100644
index 00000000..02b10a60
Binary files /dev/null and b/Panel/modules/website/assets/images/games/day_z.jpg differ
diff --git a/Panel/modules/website/assets/images/games/dayz_epochmod.jpg b/Panel/modules/website/assets/images/games/dayz_epochmod.jpg
new file mode 100644
index 00000000..ea7bc463
Binary files /dev/null and b/Panel/modules/website/assets/images/games/dayz_epochmod.jpg differ
diff --git a/Panel/modules/website/assets/images/games/dayz_mod.jpg b/Panel/modules/website/assets/images/games/dayz_mod.jpg
new file mode 100644
index 00000000..97577109
Binary files /dev/null and b/Panel/modules/website/assets/images/games/dayz_mod.jpg differ
diff --git a/Panel/modules/website/assets/images/games/deathmatch_classic.jpg b/Panel/modules/website/assets/images/games/deathmatch_classic.jpg
new file mode 100644
index 00000000..5128593c
Binary files /dev/null and b/Panel/modules/website/assets/images/games/deathmatch_classic.jpg differ
diff --git a/Panel/modules/website/assets/images/games/dst.jpg b/Panel/modules/website/assets/images/games/dst.jpg
new file mode 100644
index 00000000..f87782c8
Binary files /dev/null and b/Panel/modules/website/assets/images/games/dst.jpg differ
diff --git a/Panel/modules/website/assets/images/games/eco.jpg b/Panel/modules/website/assets/images/games/eco.jpg
new file mode 100644
index 00000000..8f0b813e
Binary files /dev/null and b/Panel/modules/website/assets/images/games/eco.jpg differ
diff --git a/Panel/modules/website/assets/images/games/eurotruck2.jpg b/Panel/modules/website/assets/images/games/eurotruck2.jpg
new file mode 100644
index 00000000..0e0e5fc2
Binary files /dev/null and b/Panel/modules/website/assets/images/games/eurotruck2.jpg differ
diff --git a/Panel/modules/website/assets/images/games/fistful_of_frags.jpg b/Panel/modules/website/assets/images/games/fistful_of_frags.jpg
new file mode 100644
index 00000000..4569650f
Binary files /dev/null and b/Panel/modules/website/assets/images/games/fistful_of_frags.jpg differ
diff --git a/Panel/modules/website/assets/images/games/garrys_mod.jpg b/Panel/modules/website/assets/images/games/garrys_mod.jpg
new file mode 100644
index 00000000..81416a5a
Binary files /dev/null and b/Panel/modules/website/assets/images/games/garrys_mod.jpg differ
diff --git a/Panel/modules/website/assets/images/games/half-life2_deathmatch.jpg b/Panel/modules/website/assets/images/games/half-life2_deathmatch.jpg
new file mode 100644
index 00000000..fc57e7d1
Binary files /dev/null and b/Panel/modules/website/assets/images/games/half-life2_deathmatch.jpg differ
diff --git a/Panel/modules/website/assets/images/games/harsh.jpg b/Panel/modules/website/assets/images/games/harsh.jpg
new file mode 100644
index 00000000..00d3f95e
Binary files /dev/null and b/Panel/modules/website/assets/images/games/harsh.jpg differ
diff --git a/Panel/modules/website/assets/images/games/hurt_world.jpg b/Panel/modules/website/assets/images/games/hurt_world.jpg
new file mode 100644
index 00000000..5d742175
Binary files /dev/null and b/Panel/modules/website/assets/images/games/hurt_world.jpg differ
diff --git a/Panel/modules/website/assets/images/games/insurgency.jpg b/Panel/modules/website/assets/images/games/insurgency.jpg
new file mode 100644
index 00000000..51f754bc
Binary files /dev/null and b/Panel/modules/website/assets/images/games/insurgency.jpg differ
diff --git a/Panel/modules/website/assets/images/games/insurgency_sandstorm.jpg b/Panel/modules/website/assets/images/games/insurgency_sandstorm.jpg
new file mode 100644
index 00000000..69afb048
Binary files /dev/null and b/Panel/modules/website/assets/images/games/insurgency_sandstorm.jpg differ
diff --git a/Panel/modules/website/assets/images/games/killing_floor.jpg b/Panel/modules/website/assets/images/games/killing_floor.jpg
new file mode 100644
index 00000000..31cf5ab8
Binary files /dev/null and b/Panel/modules/website/assets/images/games/killing_floor.jpg differ
diff --git a/Panel/modules/website/assets/images/games/killing_floor_2.jpg b/Panel/modules/website/assets/images/games/killing_floor_2.jpg
new file mode 100644
index 00000000..dc1bac04
Binary files /dev/null and b/Panel/modules/website/assets/images/games/killing_floor_2.jpg differ
diff --git a/Panel/modules/website/assets/images/games/left_4_dead.jpg b/Panel/modules/website/assets/images/games/left_4_dead.jpg
new file mode 100644
index 00000000..5a32cdd8
Binary files /dev/null and b/Panel/modules/website/assets/images/games/left_4_dead.jpg differ
diff --git a/Panel/modules/website/assets/images/games/left_4_dead_2.jpg b/Panel/modules/website/assets/images/games/left_4_dead_2.jpg
new file mode 100644
index 00000000..64cc4cb6
Binary files /dev/null and b/Panel/modules/website/assets/images/games/left_4_dead_2.jpg differ
diff --git a/Panel/modules/website/assets/images/games/minecraft.jpg b/Panel/modules/website/assets/images/games/minecraft.jpg
new file mode 100644
index 00000000..3b17a34a
Binary files /dev/null and b/Panel/modules/website/assets/images/games/minecraft.jpg differ
diff --git a/Panel/modules/website/assets/images/games/miscreated_server.jpg b/Panel/modules/website/assets/images/games/miscreated_server.jpg
new file mode 100644
index 00000000..faf040d9
Binary files /dev/null and b/Panel/modules/website/assets/images/games/miscreated_server.jpg differ
diff --git a/Panel/modules/website/assets/images/games/mordhau.jpg b/Panel/modules/website/assets/images/games/mordhau.jpg
new file mode 100644
index 00000000..a8534cb9
Binary files /dev/null and b/Panel/modules/website/assets/images/games/mordhau.jpg differ
diff --git a/Panel/modules/website/assets/images/games/nomoreroominhell.jpg b/Panel/modules/website/assets/images/games/nomoreroominhell.jpg
new file mode 100644
index 00000000..abe38073
Binary files /dev/null and b/Panel/modules/website/assets/images/games/nomoreroominhell.jpg differ
diff --git a/Panel/modules/website/assets/images/games/ootow.jpg b/Panel/modules/website/assets/images/games/ootow.jpg
new file mode 100644
index 00000000..cf12a4c9
Binary files /dev/null and b/Panel/modules/website/assets/images/games/ootow.jpg differ
diff --git a/Panel/modules/website/assets/images/games/rust_header.jpg b/Panel/modules/website/assets/images/games/rust_header.jpg
new file mode 100644
index 00000000..80c12acb
Binary files /dev/null and b/Panel/modules/website/assets/images/games/rust_header.jpg differ
diff --git a/Panel/modules/website/assets/images/games/scp.jpg b/Panel/modules/website/assets/images/games/scp.jpg
new file mode 100644
index 00000000..729e3973
Binary files /dev/null and b/Panel/modules/website/assets/images/games/scp.jpg differ
diff --git a/Panel/modules/website/assets/images/games/squad.jpg b/Panel/modules/website/assets/images/games/squad.jpg
new file mode 100644
index 00000000..116eb492
Binary files /dev/null and b/Panel/modules/website/assets/images/games/squad.jpg differ
diff --git a/Panel/modules/website/assets/images/games/starbound.jpg b/Panel/modules/website/assets/images/games/starbound.jpg
new file mode 100644
index 00000000..8281b054
Binary files /dev/null and b/Panel/modules/website/assets/images/games/starbound.jpg differ
diff --git a/Panel/modules/website/assets/images/games/stationeers.jpg b/Panel/modules/website/assets/images/games/stationeers.jpg
new file mode 100644
index 00000000..ab60bdbc
Binary files /dev/null and b/Panel/modules/website/assets/images/games/stationeers.jpg differ
diff --git a/Panel/modules/website/assets/images/games/team_fortress_2.jpg b/Panel/modules/website/assets/images/games/team_fortress_2.jpg
new file mode 100644
index 00000000..3efdc493
Binary files /dev/null and b/Panel/modules/website/assets/images/games/team_fortress_2.jpg differ
diff --git a/Panel/modules/website/assets/images/games/terraria.jpg b/Panel/modules/website/assets/images/games/terraria.jpg
new file mode 100644
index 00000000..51a10028
Binary files /dev/null and b/Panel/modules/website/assets/images/games/terraria.jpg differ
diff --git a/Panel/modules/website/assets/images/games/urt.jpg b/Panel/modules/website/assets/images/games/urt.jpg
new file mode 100644
index 00000000..539859f3
Binary files /dev/null and b/Panel/modules/website/assets/images/games/urt.jpg differ
diff --git a/Panel/modules/website/assets/images/games/valheim.jpg b/Panel/modules/website/assets/images/games/valheim.jpg
new file mode 100644
index 00000000..794b69b2
Binary files /dev/null and b/Panel/modules/website/assets/images/games/valheim.jpg differ
diff --git a/Panel/modules/website/assets/images/games/wurmu.jpg b/Panel/modules/website/assets/images/games/wurmu.jpg
new file mode 100644
index 00000000..03780ba7
Binary files /dev/null and b/Panel/modules/website/assets/images/games/wurmu.jpg differ
diff --git a/Panel/modules/website/assets/images/logo-sm.png b/Panel/modules/website/assets/images/logo-sm.png
new file mode 100644
index 00000000..55f05e00
Binary files /dev/null and b/Panel/modules/website/assets/images/logo-sm.png differ
diff --git a/Panel/modules/website/assets/images/logo.png b/Panel/modules/website/assets/images/logo.png
new file mode 100644
index 00000000..15491ac0
Binary files /dev/null and b/Panel/modules/website/assets/images/logo.png differ
diff --git a/Panel/modules/website/assets/js/site.js b/Panel/modules/website/assets/js/site.js
new file mode 100644
index 00000000..37123253
--- /dev/null
+++ b/Panel/modules/website/assets/js/site.js
@@ -0,0 +1,18 @@
+document.addEventListener('DOMContentLoaded', () => {
+ const toggle = document.querySelector('[data-nav-toggle]');
+ const menu = document.querySelector('[data-nav-menu]');
+ const actions = document.querySelector('[data-header-actions]');
+
+ if (!toggle || !menu) {
+ return;
+ }
+
+ toggle.addEventListener('click', () => {
+ const expanded = toggle.getAttribute('aria-expanded') === 'true';
+ toggle.setAttribute('aria-expanded', expanded ? 'false' : 'true');
+ menu.classList.toggle('is-open', !expanded);
+ if (actions) {
+ actions.classList.toggle('is-open', !expanded);
+ }
+ });
+});
diff --git a/Panel/modules/website/config/.gitignore b/Panel/modules/website/config/.gitignore
new file mode 100644
index 00000000..fb6b11e7
--- /dev/null
+++ b/Panel/modules/website/config/.gitignore
@@ -0,0 +1,3 @@
+config.php
+config.local.php
+
diff --git a/Panel/modules/website/config/config.example.php b/Panel/modules/website/config/config.example.php
new file mode 100644
index 00000000..03d1bc45
--- /dev/null
+++ b/Panel/modules/website/config/config.example.php
@@ -0,0 +1,39 @@
+ 'Gameservers.World',
+ 'site_tagline' => 'Virtual private game servers with dedicated resources and full configuration access.',
+ 'meta_description' => 'Virtual private game servers with dedicated resources, predictable performance, full configuration access, mod support, and real human support.',
+
+ // Leave null to derive the base path from the current request.
+ // Example: '/sales'
+ 'base_path' => null,
+
+ // Optional absolute public base URL without trailing slash.
+ // Example: 'https://gameservers.world'
+ 'public_base_url' => 'https://gameservers.world',
+
+ // Public billing/catalog/order surface. Recommended Apache alias: /billing
+ 'billing_base_url' => 'https://gameservers.world/billing',
+
+ // Active panel URL. Do not point the public site at /panel/ unless that route is real.
+ 'panel_url' => 'https://panel.iaregamer.com/',
+ 'login_url' => 'https://panel.iaregamer.com/',
+
+ // Optional support links.
+ 'discord_url' => 'https://discord.gg/replace-me',
+ 'support_url' => 'https://gameservers.world/support',
+ 'support_email' => 'support@gameservers.world',
+
+ // Public fallback copy for catalog outages or missing billing config.
+ 'admin_notice' => 'Server catalog is currently unavailable. Please contact support.',
+
+ 'locations' => [
+ ['name' => 'Los Angeles, USA', 'region' => 'West Coast coverage', 'host' => 'la-game-1.iaregamer.com'],
+ ['name' => 'Kansas City, USA', 'region' => 'Central US coverage', 'host' => 'kc-game-2.iaregamer.com'],
+ ['name' => 'Dallas, USA', 'region' => 'Southern US coverage', 'host' => 'dal-game-1.iaregamer.com'],
+ ['name' => 'New York City, USA', 'region' => 'East Coast coverage', 'host' => 'nyc-game-1.iaregamer.com'],
+ ['name' => 'Dublin, Ireland', 'region' => 'EU coverage', 'host' => 'dub-game-1.iaregamer.com'],
+ ],
+];
+
diff --git a/Panel/modules/website/doc_asset.php b/Panel/modules/website/doc_asset.php
new file mode 100644
index 00000000..3d30544e
--- /dev/null
+++ b/Panel/modules/website/doc_asset.php
@@ -0,0 +1,31 @@
+ 'image/png',
+ 'jpg', 'jpeg' => 'image/jpeg',
+ 'webp' => 'image/webp',
+ default => 'application/octet-stream',
+};
+
+header('Content-Type: ' . $mimeType);
+header('Content-Length: ' . (string)filesize($assetPath));
+readfile($assetPath);
+
diff --git a/Panel/modules/website/docs.php b/Panel/modules/website/docs.php
new file mode 100644
index 00000000..5fb399a1
--- /dev/null
+++ b/Panel/modules/website/docs.php
@@ -0,0 +1,42 @@
+ 'docs',
+ 'pageTitle' => $docEntry !== null ? ($docEntry['name'] . ' - Documentation - Gameservers.World') : 'Documentation - Gameservers.World',
+ 'metaDescription' => $docEntry !== null ? $docEntry['description'] : 'Browse server setup, panel usage, and troubleshooting guides for Gameservers.World customers.',
+ 'canonicalPath' => $docEntry !== null ? ('docs.php?doc=' . rawurlencode($docEntry['slug'])) : 'docs.php',
+ 'docIndex' => $docIndex,
+ 'docEntry' => $docEntry,
+ 'docContent' => $docContent,
+ ]
+);
+
diff --git a/Panel/modules/website/includes/bootstrap.php b/Panel/modules/website/includes/bootstrap.php
new file mode 100644
index 00000000..717f502f
--- /dev/null
+++ b/Panel/modules/website/includes/bootstrap.php
@@ -0,0 +1,506 @@
+ 'Gameservers.World',
+ 'site_tagline' => 'Virtual private game servers with dedicated resources and full configuration access.',
+ 'meta_description' => 'Virtual private game servers with dedicated resources, predictable performance, full configuration access, mod support, and real human support.',
+ 'base_path' => null,
+ 'public_base_url' => null,
+ 'billing_base_url' => '/billing',
+ 'panel_url' => 'https://panel.iaregamer.com/',
+ 'login_url' => 'https://panel.iaregamer.com/',
+ 'discord_url' => null,
+ 'support_url' => null,
+ 'support_email' => null,
+ 'admin_notice' => 'Server catalog is currently unavailable. Please contact support.',
+ 'locations' => [
+ ['name' => 'Los Angeles, USA', 'region' => 'West Coast coverage', 'host' => 'la-game-1.iaregamer.com'],
+ ['name' => 'Kansas City, USA', 'region' => 'Central US coverage', 'host' => 'kc-game-2.iaregamer.com'],
+ ['name' => 'Dallas, USA', 'region' => 'Southern US coverage', 'host' => 'dal-game-1.iaregamer.com'],
+ ['name' => 'New York City, USA', 'region' => 'East Coast coverage', 'host' => 'nyc-game-1.iaregamer.com'],
+ ['name' => 'Dublin, Ireland', 'region' => 'EU coverage', 'host' => 'dub-game-1.iaregamer.com'],
+ ],
+];
+
+$websiteConfig = array_replace_recursive($websiteDefaults, $websiteConfig);
+
+function website_config(?string $key = null, mixed $default = null): mixed
+{
+ global $websiteConfig;
+
+ if ($key === null) {
+ return $websiteConfig;
+ }
+
+ return $websiteConfig[$key] ?? $default;
+}
+
+function website_log(string $message): void
+{
+ error_log('[website] ' . $message);
+}
+
+function website_escape(mixed $value): string
+{
+ return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
+}
+
+function website_normalize_base_path(?string $path): string
+{
+ $path = trim((string)$path);
+ if ($path === '' || $path === '/') {
+ return '';
+ }
+
+ return '/' . trim($path, '/');
+}
+
+function website_request_scheme(): string
+{
+ $https = $_SERVER['HTTPS'] ?? '';
+ if ($https !== '' && strtolower((string)$https) !== 'off') {
+ return 'https';
+ }
+
+ $forwarded = $_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '';
+ if ($forwarded !== '') {
+ return strtolower((string)$forwarded) === 'https' ? 'https' : 'http';
+ }
+
+ return 'http';
+}
+
+function website_base_path(): string
+{
+ static $basePath = null;
+ if ($basePath !== null) {
+ return $basePath;
+ }
+
+ $configured = website_config('base_path');
+ if (is_string($configured) && $configured !== '') {
+ $basePath = website_normalize_base_path($configured);
+ return $basePath;
+ }
+
+ $scriptName = (string)($_SERVER['SCRIPT_NAME'] ?? '');
+ if ($scriptName === '') {
+ $basePath = '';
+ return $basePath;
+ }
+
+ $dir = str_replace('\\', '/', dirname($scriptName));
+ $basePath = ($dir === '/' || $dir === '.' || $dir === '') ? '' : website_normalize_base_path($dir);
+ return $basePath;
+}
+
+function website_public_base_url(): string
+{
+ static $baseUrl = null;
+ if ($baseUrl !== null) {
+ return $baseUrl;
+ }
+
+ $configured = trim((string)website_config('public_base_url', ''));
+ if ($configured !== '') {
+ $baseUrl = rtrim($configured, '/');
+ return $baseUrl;
+ }
+
+ $host = trim((string)($_SERVER['HTTP_HOST'] ?? ''));
+ if ($host === '') {
+ $baseUrl = '';
+ return $baseUrl;
+ }
+
+ $baseUrl = website_request_scheme() . '://' . $host . website_base_path();
+ return $baseUrl;
+}
+
+function website_url(string $path = ''): string
+{
+ $basePath = rtrim(website_base_path(), '/');
+ $path = ltrim($path, '/');
+ if ($path === '') {
+ return $basePath === '' ? '/' : $basePath . '/';
+ }
+
+ return ($basePath === '' ? '' : $basePath) . '/' . $path;
+}
+
+function website_asset(string $path): string
+{
+ return website_url('assets/' . ltrim($path, '/'));
+}
+
+function website_join_external_url(string $base, string $path = ''): string
+{
+ $base = trim($base);
+ if ($base === '') {
+ return website_url($path);
+ }
+
+ $base = rtrim($base, '/');
+ $path = ltrim($path, '/');
+ if ($path === '') {
+ return $base . '/';
+ }
+
+ return $base . '/' . $path;
+}
+
+function panel_url(string $path = ''): string
+{
+ return website_join_external_url((string)website_config('panel_url', ''), $path);
+}
+
+function login_url(string $path = ''): string
+{
+ return website_join_external_url((string)website_config('login_url', website_config('panel_url', '')), $path);
+}
+
+function billing_url(string $path = ''): string
+{
+ return website_join_external_url((string)website_config('billing_base_url', ''), $path);
+}
+
+function documentation_url(?string $docSlug = null): string
+{
+ if ($docSlug === null || $docSlug === '') {
+ return website_url('docs.php');
+ }
+
+ return website_url('docs.php?doc=' . rawurlencode($docSlug));
+}
+
+function website_canonical_url(string $path = ''): string
+{
+ $base = website_public_base_url();
+ if ($base === '') {
+ return website_url($path);
+ }
+
+ $path = ltrim($path, '/');
+ if ($path === '') {
+ return $base . '/';
+ }
+
+ return rtrim($base, '/') . '/' . $path;
+}
+
+function website_read_php_assignments(string $filePath, array $variableNames): array
+{
+ if (!is_readable($filePath)) {
+ return [];
+ }
+
+ $content = @file_get_contents($filePath);
+ if ($content === false) {
+ return [];
+ }
+
+ $result = [];
+ foreach ($variableNames as $variableName) {
+ $patternDouble = '/^\s*\$' . preg_quote($variableName, '/') . '\s*=\s*"([^"]*)"/m';
+ $patternSingle = '/^\s*\$' . preg_quote($variableName, '/') . "\s*=\s*'([^']*)'/m";
+ if (preg_match($patternDouble, $content, $match) === 1 || preg_match($patternSingle, $content, $match) === 1) {
+ $result[$variableName] = $match[1];
+ }
+ }
+
+ return $result;
+}
+
+function website_database_settings(): ?array
+{
+ static $settings = null;
+ static $resolved = false;
+
+ if ($resolved) {
+ return $settings;
+ }
+
+ $resolved = true;
+ $keys = ['db_host', 'db_port', 'db_user', 'db_pass', 'db_name', 'table_prefix', 'db_type'];
+ $merged = [];
+
+ $panelConfig = WEBSITE_PANEL_INCLUDE_DIR . '/config.inc.php';
+ if (is_readable($panelConfig)) {
+ $merged = array_replace($merged, website_read_php_assignments($panelConfig, $keys));
+ }
+
+ $billingConfig = WEBSITE_BILLING_ROOT . '/includes/config.inc.php';
+ if (is_readable($billingConfig)) {
+ $merged = array_replace($merged, website_read_php_assignments($billingConfig, $keys));
+ }
+
+ foreach (['db_host', 'db_user', 'db_name', 'table_prefix'] as $requiredKey) {
+ if (empty($merged[$requiredKey])) {
+ $settings = null;
+ return $settings;
+ }
+ }
+
+ $settings = $merged;
+ return $settings;
+}
+
+function website_billing_config_present(): bool
+{
+ return is_readable(WEBSITE_BILLING_ROOT . '/includes/config.inc.php');
+}
+
+function website_db(): ?mysqli
+{
+ static $connection = false;
+ if ($connection instanceof mysqli) {
+ return $connection;
+ }
+ if ($connection === null) {
+ return null;
+ }
+
+ $settings = website_database_settings();
+ if ($settings === null) {
+ $connection = null;
+ return null;
+ }
+
+ $port = isset($settings['db_port']) && $settings['db_port'] !== '' ? (int)$settings['db_port'] : null;
+ $mysqli = @mysqli_connect(
+ (string)$settings['db_host'],
+ (string)($settings['db_user'] ?? ''),
+ (string)($settings['db_pass'] ?? ''),
+ (string)$settings['db_name'],
+ $port
+ );
+
+ if (!$mysqli instanceof mysqli) {
+ website_log('Database connection failed for public website.');
+ $connection = null;
+ return null;
+ }
+
+ @mysqli_set_charset($mysqli, 'utf8mb4');
+ $connection = $mysqli;
+ return $connection;
+}
+
+function website_table_prefix(): string
+{
+ $settings = website_database_settings();
+ return (string)($settings['table_prefix'] ?? '');
+}
+
+function website_billing_available(): bool
+{
+ return website_db() instanceof mysqli;
+}
+
+function website_billing_docs_root(): ?string
+{
+ if (is_dir(WEBSITE_BILLING_DOCS_DIR)) {
+ return WEBSITE_BILLING_DOCS_DIR;
+ }
+
+ $legacyDocs = WEBSITE_LEGACY_SITE_ROOT . '/docs';
+ if (is_dir($legacyDocs)) {
+ return $legacyDocs;
+ }
+
+ return null;
+}
+
+function website_is_valid_doc_slug(string $slug): bool
+{
+ return (bool)preg_match('/^[a-z0-9][a-z0-9_-]*$/i', $slug);
+}
+
+function website_doc_path(string $slug, string $fileName = 'index.php'): ?string
+{
+ if (!website_is_valid_doc_slug($slug)) {
+ return null;
+ }
+
+ $docsRoot = website_billing_docs_root();
+ if ($docsRoot === null) {
+ return null;
+ }
+
+ $candidate = realpath($docsRoot . '/' . $slug . '/' . $fileName);
+ if ($candidate === false || strpos($candidate, realpath($docsRoot) ?: $docsRoot) !== 0) {
+ return null;
+ }
+
+ return $candidate;
+}
+
+function website_doc_icon_url(string $slug): ?string
+{
+ foreach (['icon.png', 'icon.jpg', 'icon.jpeg', 'icon.webp'] as $fileName) {
+ if (website_doc_path($slug, $fileName) !== null) {
+ return website_url('doc_asset.php?doc=' . rawurlencode($slug) . '&file=' . rawurlencode($fileName));
+ }
+ }
+
+ return null;
+}
+
+function website_service_image_url(string $imageValue): string
+{
+ $imageValue = trim($imageValue);
+ if ($imageValue === '') {
+ return website_asset('images/banner.png');
+ }
+
+ if (preg_match('#^https?://#i', $imageValue) === 1) {
+ return $imageValue;
+ }
+
+ $fileName = basename($imageValue);
+ if ($fileName === '') {
+ return website_asset('images/banner.png');
+ }
+
+ return website_asset('images/games/' . $fileName);
+}
+
+function website_fetch_services(int $limit = 0): array
+{
+ $db = website_db();
+ if (!$db instanceof mysqli) {
+ return [];
+ }
+
+ $prefix = website_table_prefix();
+ if ($prefix === '') {
+ return [];
+ }
+
+ $sql = "SELECT bs.service_id,
+ bs.service_name,
+ bs.description,
+ bs.img_url,
+ bs.price_monthly,
+ bs.remote_server_id,
+ ch.game_name AS cfg_game_name,
+ ch.game_key AS cfg_game_key,
+ ch.home_cfg_file AS cfg_file
+ FROM `{$prefix}billing_services` bs
+ LEFT JOIN `{$prefix}config_homes` ch ON ch.home_cfg_id = bs.home_cfg_id
+ WHERE bs.enabled = 1
+ AND bs.remote_server_id <> ''
+ AND bs.remote_server_id IS NOT NULL
+ ORDER BY bs.service_name ASC";
+
+ if ($limit > 0) {
+ $sql .= ' LIMIT ' . max(1, $limit);
+ }
+
+ $result = @$db->query($sql);
+ if (!$result instanceof mysqli_result) {
+ website_log('Failed to query billing services for website catalog.');
+ return [];
+ }
+
+ $rows = [];
+ while ($row = $result->fetch_assoc()) {
+ $rows[] = $row;
+ }
+ $result->free();
+
+ return $rows;
+}
+
+function website_fetch_doc_index(): array
+{
+ $docsRoot = website_billing_docs_root();
+ if ($docsRoot === null || !is_dir($docsRoot)) {
+ return [];
+ }
+
+ $entries = [];
+ foreach (array_diff(scandir($docsRoot) ?: [], ['.', '..']) as $folder) {
+ $docFolder = $docsRoot . '/' . $folder;
+ if (!is_dir($docFolder)) {
+ continue;
+ }
+
+ $indexPath = website_doc_path($folder, 'index.php');
+ $metadataPath = website_doc_path($folder, 'metadata.json');
+ if ($indexPath === null || $metadataPath === null) {
+ continue;
+ }
+
+ $metadataContent = @file_get_contents($metadataPath);
+ $metadataContent = $metadataContent === false ? '' : preg_replace('/^\xEF\xBB\xBF/', '', $metadataContent);
+ $metadata = json_decode((string)$metadataContent, true);
+ if (!is_array($metadata)) {
+ $metadata = [];
+ }
+
+ $entries[] = [
+ 'slug' => $folder,
+ 'name' => (string)($metadata['name'] ?? ucwords(str_replace(['-', '_'], ' ', $folder))),
+ 'description' => (string)($metadata['description'] ?? ''),
+ 'category' => (string)($metadata['category'] ?? 'other'),
+ 'order' => (int)($metadata['order'] ?? 999),
+ 'complete' => (bool)($metadata['complete'] ?? true),
+ 'icon_url' => website_doc_icon_url($folder),
+ ];
+ }
+
+ usort(
+ $entries,
+ static function (array $left, array $right): int {
+ if ($left['category'] !== $right['category']) {
+ return strcmp($left['category'], $right['category']);
+ }
+ if ($left['order'] !== $right['order']) {
+ return $left['order'] <=> $right['order'];
+ }
+ return strcasecmp($left['name'], $right['name']);
+ }
+ );
+
+ return $entries;
+}
+
+function website_render(string $pageTemplate, array $context = []): void
+{
+ extract($context, EXTR_SKIP);
+ require WEBSITE_INCLUDE_DIR . '/header.php';
+ require WEBSITE_ROOT_DIR . '/pages/' . $pageTemplate;
+ require WEBSITE_INCLUDE_DIR . '/footer.php';
+}
+
diff --git a/Panel/modules/website/includes/footer.php b/Panel/modules/website/includes/footer.php
new file mode 100644
index 00000000..d5d1e624
--- /dev/null
+++ b/Panel/modules/website/includes/footer.php
@@ -0,0 +1,57 @@
+
+
+
+
+