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 @@ + + + + + + + diff --git a/Panel/modules/website/includes/header.php b/Panel/modules/website/includes/header.php new file mode 100644 index 00000000..61456687 --- /dev/null +++ b/Panel/modules/website/includes/header.php @@ -0,0 +1,34 @@ + + + + + + + <?= website_escape($pageTitle) ?> + + + + + + + + + + + + + + + + +
+ diff --git a/Panel/modules/website/includes/navigation.php b/Panel/modules/website/includes/navigation.php new file mode 100644 index 00000000..6a1b8755 --- /dev/null +++ b/Panel/modules/website/includes/navigation.php @@ -0,0 +1,45 @@ + 'home', 'label' => 'Home', 'href' => website_url('index.php')], + ['key' => 'servers', 'label' => 'Game Servers', 'href' => website_url('serverlist.php')], + ['key' => 'docs', 'label' => 'Documentation', 'href' => website_url('docs.php')], + ['key' => 'pricing', 'label' => 'Pricing', 'href' => website_url('pricing.php')], + ['key' => 'locations', 'label' => 'Locations', 'href' => website_url('locations.php')], + ['key' => 'support', 'label' => 'Support', 'href' => website_url('support.php')], +]; +?> + diff --git a/Panel/modules/website/includes/paths.php b/Panel/modules/website/includes/paths.php new file mode 100644 index 00000000..1fc456ae --- /dev/null +++ b/Panel/modules/website/includes/paths.php @@ -0,0 +1,20 @@ + 'home', + 'pageTitle' => 'Gameservers.World - Virtual private game servers', + 'metaDescription' => website_config('meta_description'), + 'canonicalPath' => 'index.php', + 'services' => $services, + 'locations' => is_array($locations) ? $locations : [], + ] +); + diff --git a/Panel/modules/website/locations.php b/Panel/modules/website/locations.php new file mode 100644 index 00000000..15e5566a --- /dev/null +++ b/Panel/modules/website/locations.php @@ -0,0 +1,17 @@ + 'locations', + 'pageTitle' => 'Locations - Gameservers.World', + 'metaDescription' => 'Current Gameservers.World hosting regions and deployment guidance.', + 'canonicalPath' => 'locations.php', + 'locations' => website_config('locations', []), + ] +); + diff --git a/Panel/modules/website/login.php b/Panel/modules/website/login.php new file mode 100644 index 00000000..be5e860f --- /dev/null +++ b/Panel/modules/website/login.php @@ -0,0 +1,23 @@ + 'support', + 'pageTitle' => 'Login - Gameservers.World', + 'metaDescription' => 'Customer login routing for Gameservers.World.', + 'canonicalPath' => 'login.php', + 'loginUnavailable' => true, + ] +); + diff --git a/Panel/modules/website/pages/documentation.php b/Panel/modules/website/pages/documentation.php new file mode 100644 index 00000000..bac3b0d3 --- /dev/null +++ b/Panel/modules/website/pages/documentation.php @@ -0,0 +1,71 @@ + 'Panel Documentation', + 'game' => 'Game Servers', + 'mods' => 'Mods and Plugins', + 'troubleshooting' => 'Troubleshooting', + 'other' => 'Other', +]; + +$groupedDocs = []; +foreach ($docIndex as $entry) { + $groupedDocs[$entry['category']][] = $entry; +} +?> +
+
+

+

+
+
+ +
+
+ + +
+ +
+ +
+ $entries): ?> +
+
+

+

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

Documentation is unavailable

+

No documentation source directory was found. Check that the billing module docs directory is present alongside the website module.

+
+ +
+
+ diff --git a/Panel/modules/website/pages/game_servers.php b/Panel/modules/website/pages/game_servers.php new file mode 100644 index 00000000..c9041c92 --- /dev/null +++ b/Panel/modules/website/pages/game_servers.php @@ -0,0 +1,61 @@ + +
+
+

Game Servers

+

Available services are pulled from the existing billing catalog when the database is configured. This page no longer reaches into billing includes with hardcoded filesystem paths, so it stays up even when billing config is missing.

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

Server catalog unavailable

+

+ + +

Billing configuration is missing or unreadable. Public pages still load, but service data cannot be queried until billing or panel database credentials are configured.

+ +
+ +
+
+ diff --git a/Panel/modules/website/pages/home.php b/Panel/modules/website/pages/home.php new file mode 100644 index 00000000..498f533f --- /dev/null +++ b/Panel/modules/website/pages/home.php @@ -0,0 +1,245 @@ + +
+
+
+
Dedicated resources. Full control. Real support.
+

Virtual private game servers with predictable resources and full configuration access.

+

Gameservers.World focuses on stable capacity, older community-favorite titles, mod support, and a control panel that gives customers practical access instead of a stripped-down toy interface.

+ +
+
+ Never oversold + Dedicated resources stay dedicated. Predictable performance is the product, not an upgrade. +
+
+ Mod and Workshop support + Common modding workflows, Workshop content, and file access stay available where the game allows it. +
+
+ Control and customization + Startup parameters, configs, backups, and updates are managed through the GSP panel without hiding the important controls. +
+
+ Real people + Documentation exists, but you can still talk to someone when an old modpack or legacy title gets awkward. +
+
+
+
+ Gameservers.World supported games collage +
+ Built for hosted communities that want actual control +

Use the GSP panel for server start/stop, config editing, Workshop or mod workflows, logs, file access, and routine maintenance without depending on fragile manual shell work.

+
+
+
+
+ +
+
+
+
+ Dedicated + No noisy-neighbor overselling. +
+
+ Full access + Configs, startup parameters, files, backups, and updates. +
+
+ Legacy-friendly + Older and community-favorite games stay part of the catalog. +
+
+ Operational help + Human support when the docs are not enough. +
+
+
+
+ +
+
+
+

Why Gameservers.World

+

The website now makes the actual sales proposition explicit: dedicated capacity, access to the knobs that matter, and hosting for both current and older multiplayer titles without burying customers behind fragile shared-hosting abstractions.

+
+
+
+

Dedicated resources

+

Capacity is reserved for the server you pay for. CPU and memory planning are aimed at stable behavior, not squeezing one more tenant onto the node.

+
+
+

Mod support

+

Workshop and addon workflows are supported where the underlying game tooling allows it, with file access and update paths that customers can actually use.

+
+
+

Panel control

+

Customers manage their service through GSP rather than opening tickets for every config tweak, reboot, backup, or startup parameter change.

+
+
+

Real support

+

When an old title, community mod, or provisioning edge case gets weird, support is available through documentation plus direct assistance.

+
+
+
+
+ +
+
+
+

Current locations

+

Locations are configured data, not marketing filler. These reflect the currently documented host regions from the existing Gameservers.World site content.

+
+
+ +
+

+

+ +
+ +
+
+
+ +
+
+
+

Popular and supported game servers

+

These cards are driven from the existing billing catalog when configuration is available. The site does not invent fake availability if the billing catalog is offline.

+
+ +
+ + +
+ <?= website_escape($serviceName) ?> +
+

+
0 ? '$' . number_format($price, 2) . ' / month' : 'Contact for pricing' ?>
+
+

+
+ Order + Catalog +
+
+ +
+ +
+

Catalog visibility depends on billing data

+

+ +
+ +
+
+ +
+
+
+

How it works

+

The public site and the control plane are now separated cleanly: the website explains the service, the billing catalog handles order-specific data, and GSP runs the operational lifecycle.

+
+
+
+ 1 +

Choose a server

+

Browse the supported game catalog, confirm the available plan, and select a region that matches your community.

+
+
+ 2 +

Configure it

+

Use the GSP panel to set startup parameters, edit configs, install supported content, and keep backups or updates under control.

+
+
+ 3 +

Manage through GSP

+

Operate the service through the panel for lifecycle, monitoring, logs, file access, and routine administrative work.

+
+
+
+
+ +
+
+
+
+ GSP control panel preview background +
+
+
+

GSP control panel

+

Customers do not get a fake storefront-only experience. They manage hosted servers through the same GSP platform that handles starts, stops, files, logs, addons, scheduling, and provisioning workflows.

+
+
+

Operational access

+

Start and stop servers, manage files, inspect logs, update Workshop content, and adjust configuration safely from the panel.

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

Documentation and support

+

Game-specific docs, panel guidance, and troubleshooting live in the same repo and can be served without depending on billing configuration.

+ +
+
+

Panel and login routing

+

The Control Panel and Login actions are configuration-driven. The public site no longer assumes a fragile `/panel/` path under the marketing domain.

+ +
+
+
+
+ +
+
+
+
+

Ready to deploy a server?

+

Browse the current game catalog, confirm the right location, and manage the result through GSP.

+
+ +
+
+
+ diff --git a/Panel/modules/website/pages/locations.php b/Panel/modules/website/pages/locations.php new file mode 100644 index 00000000..018b8438 --- /dev/null +++ b/Panel/modules/website/pages/locations.php @@ -0,0 +1,42 @@ + +
+
+

Locations

+

Deployment regions are configured data. Large or latency-sensitive communities should choose a region that matches where the players actually are, not just where the cheapest machine happened to be.

+
+
+ +
+
+
+ +
+

+

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

Regional fit matters

+

Pick a region based on player distribution and the specific game. Older titles and mod-heavy servers can be more sensitive to latency and disk performance than the average commodity hosting page admits.

+
+
+

Need a different region?

+

If the catalog does not list the region you need yet, contact support. The site copy now makes this explicit instead of hiding the conversation behind a dead link.

+
+
+
+
+ diff --git a/Panel/modules/website/pages/pricing.php b/Panel/modules/website/pages/pricing.php new file mode 100644 index 00000000..cffc41c3 --- /dev/null +++ b/Panel/modules/website/pages/pricing.php @@ -0,0 +1,55 @@ + +
+
+

Pricing

+

Gameservers.World pricing is catalog-driven. The public site does not hardcode fake plan availability; it shows live service rows when the billing catalog is available and falls back to guidance when it is not.

+
+
+ +
+
+
+
+

What you are paying for

+

Dedicated resources, full configuration access, mod and Workshop support where available, control through GSP, backups, and support from people who know the game hosting side of the product.

+
+
+

How pricing is presented

+

Plan data comes from the billing catalog. That keeps the marketing site aligned with the actual services table instead of drifting into stale hardcoded prices.

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

+

+
+
0 ? '$' . number_format($price, 2) . ' / month' : 'Contact for pricing' ?>
+
+ Order + Catalog +
+
+ +
+ +
+ +
+ +
+
+ diff --git a/Panel/modules/website/pages/support.php b/Panel/modules/website/pages/support.php new file mode 100644 index 00000000..c389d776 --- /dev/null +++ b/Panel/modules/website/pages/support.php @@ -0,0 +1,58 @@ + +
+
+

Support

+

Documentation should handle the routine cases. Support exists for the ones that are still operationally messy: mods, legacy engines, provisioning issues, and the inevitable edge cases around community-maintained titles.

+
+
+ +
+
+ +
+ Login URL is not configured for this deployment. Set login_url in the website config to route customers to the active panel login. +
+ +
+
+

Customer login

+

Access existing servers, files, logs, and administrative workflows through the configured GSP panel login.

+
+ Login + Panel +
+
+
+

Documentation

+

Server guides, panel references, and troubleshooting content are available even if billing configuration is missing.

+
+ Open Docs +
+
+
+

Direct help

+

Use Discord, a support portal, or email depending on what is configured for this deployment.

+
+ + Discord + + + Support Portal + + + Email + +
+
+
+
+
+ diff --git a/Panel/modules/website/pricing.php b/Panel/modules/website/pricing.php new file mode 100644 index 00000000..f412b88f --- /dev/null +++ b/Panel/modules/website/pricing.php @@ -0,0 +1,17 @@ + 'pricing', + 'pageTitle' => 'Pricing - Gameservers.World', + 'metaDescription' => 'Pricing guidance and available game server plans for Gameservers.World.', + 'canonicalPath' => 'pricing.php', + 'services' => website_fetch_services(), + ] +); + diff --git a/Panel/modules/website/serverlist.php b/Panel/modules/website/serverlist.php new file mode 100644 index 00000000..ac90d7f6 --- /dev/null +++ b/Panel/modules/website/serverlist.php @@ -0,0 +1,19 @@ + 'servers', + 'pageTitle' => 'Game Servers - Gameservers.World', + 'metaDescription' => 'Browse supported game server packages, locations, and ordering options for Gameservers.World.', + 'canonicalPath' => 'serverlist.php', + 'services' => $services, + ] +); + diff --git a/Panel/modules/website/support.php b/Panel/modules/website/support.php new file mode 100644 index 00000000..398791d0 --- /dev/null +++ b/Panel/modules/website/support.php @@ -0,0 +1,16 @@ + 'support', + 'pageTitle' => 'Support - Gameservers.World', + 'metaDescription' => 'Support, Discord, documentation, and control panel access for Gameservers.World customers.', + 'canonicalPath' => 'support.php', + ] +); + diff --git a/docs/modules/MODULE_INDEX.md b/docs/modules/MODULE_INDEX.md index 9bdaa4c3..c0f7c3a6 100644 --- a/docs/modules/MODULE_INDEX.md +++ b/docs/modules/MODULE_INDEX.md @@ -42,6 +42,7 @@ This is the master module inventory for the Panel. Use it as the first stop befo | [`user_admin`](user_admin.md) | User management | Production | Keep / Improve | | [`user_games`](user_games.md) | Server provisioning and assignment | Production / functional | Keep / Improve | | [`util`](util.md) | Miscellaneous utility tools | Functional / mixed | Keep / Rewrite selectively | +| [`website`](website.md) | Public Gameservers.World sales and documentation website | Functional / newly modularized | Keep / Improve | ## Shared Dependencies diff --git a/docs/modules/website.md b/docs/modules/website.md new file mode 100644 index 00000000..c6ace874 --- /dev/null +++ b/docs/modules/website.md @@ -0,0 +1,59 @@ +# Website + +Workspace reference: [`GSP-WORKSPACE.md`](../../../GSP-WORKSPACE.md) + +## Purpose + +Public Gameservers.World sales, documentation, and customer-entry website. + +## Runtime Location + +- Canonical public site: `Panel/modules/website/` +- Billing catalog and order flow: `Panel/modules/billing/` +- Legacy compatibility site: `Website/` + +## Design Rules + +- Public pages must load without fatal errors when billing config is missing +- Path and URL handling must be centralized +- Panel and login URLs must be configuration-driven +- Documentation must be readable without depending on billing database access +- Catalog pages may degrade gracefully when billing data is unavailable + +## Helpers + +The website module centralizes these helpers in `includes/bootstrap.php`: + +- `website_url()` +- `website_asset()` +- `panel_url()` +- `login_url()` +- `billing_url()` +- `documentation_url()` + +## Billing Interaction + +The website does not include the billing config loader directly. It reads panel or billing DB values safely, uses them only when needed, and avoids public fatal errors tied to missing config files. + +## Deployment + +Recommended: + +1. `DocumentRoot` -> `Panel/modules/website` +2. Apache `Alias /billing` -> `Panel/modules/billing` +3. Configure: + - `public_base_url` + - `billing_base_url` + - `panel_url` + - `login_url` + +## Key Public Pages + +- `index.php` +- `serverlist.php` +- `docs.php` +- `pricing.php` +- `locations.php` +- `support.php` +- `login.php` +