This commit is contained in:
Frank Harris 2026-06-18 15:16:45 -05:00
parent 67022a3846
commit 26fd3364ac
9 changed files with 302 additions and 73 deletions

View file

@ -45,6 +45,7 @@ Panel/modules/website/
webhook.php
pricing.php
locations.php
panel_features.php
support.php
doc_asset.php
api/
@ -68,6 +69,7 @@ Panel/modules/website/
documentation.php
pricing.php
locations.php
panel_features.php
support.php
config/
config.example.php
@ -88,19 +90,47 @@ The website uses a central bootstrap instead of scattered relative paths.
## Navigation and homepage
The shared header keeps the main navigation short:
The shared header separates public navigation, account access, and action links.
Primary navigation:
- Home
- Game Servers
- Pricing
- Locations
- Panel Features
- Documentation
- Support
- Account/Login
Account area:
- logged out: Login, Create Account, Cart
- logged in: My Account, My Orders, My Servers, Cart, Logout
- staff-only: Staff Dashboard
Action links:
- Custom Projects
- Control Panel
Pricing and locations pages can remain available through the footer or direct
links, but the public homepage carries concise pricing and location summaries so
the desktop and mobile menus stay focused.
`Panel Features` is an informational website page. `Control Panel` is the
external link that opens the operational GSP Panel at the configured Panel URL.
The homepage should not duplicate the header Control Panel action with extra
promotional Control Panel blocks. Pricing and location summaries on the homepage
must use customer-facing sales language rather than internal planning notes.
Footer placement:
- one modest `Control Panel` link under account/services
- one `Panel Features` informational link in the explore links
## Public copy policy
Public website copy must never repeat developer instructions, prompt wording,
acceptance criteria, task rationale, or statements explaining where content
belongs. Internal requirements must be translated into polished customer-facing
language before they appear on public pages.
## Billing, database, and staff behavior

View file

@ -133,10 +133,48 @@ textarea {
flex-wrap: nowrap;
align-items: center;
justify-content: flex-end;
gap: 12px;
min-width: 0;
}
.nav-section {
display: flex;
align-items: center;
gap: 10px;
min-width: 0;
}
.nav-inline-group {
display: flex;
align-items: center;
gap: 6px;
min-width: 0;
}
.nav-section-public {
flex: 1 1 auto;
min-width: 0;
}
.nav-section-account,
.nav-section-actions {
flex: 0 0 auto;
}
.nav-account-inline,
.nav-actions-inline {
justify-content: flex-end;
}
.nav-section-label {
display: none;
color: #b8cae5;
font-size: 0.76rem;
font-weight: 800;
letter-spacing: 0.06em;
text-transform: uppercase;
}
.nav-link {
padding: 9px 10px;
border-radius: 6px;
@ -1066,6 +1104,21 @@ textarea {
flex-direction: column;
align-items: stretch;
padding-top: 12px;
gap: 16px;
}
.nav-section {
display: grid;
gap: 8px;
}
.nav-inline-group {
display: grid;
gap: 6px;
}
.nav-section-label {
display: block;
}
.nav-link {
@ -1075,8 +1128,7 @@ textarea {
}
.account-menu {
border-top: 1px solid var(--line);
padding-top: 8px;
padding-top: 0;
}
.account-menu summary {
@ -1092,12 +1144,23 @@ textarea {
border-radius: 0;
background: transparent;
box-shadow: none;
gap: 2px;
}
.account-menu-link {
min-height: 42px;
}
.nav-section-account .nav-inline-group,
.nav-section-actions .nav-inline-group {
justify-content: stretch;
}
.nav-section-actions {
padding-top: 4px;
border-top: 1px solid var(--line);
}
.nav-action-secondary,
.nav-action-primary {
justify-content: center;

View file

@ -11,8 +11,22 @@ document.addEventListener('DOMContentLoaded', () => {
const expanded = toggle.getAttribute('aria-expanded') === 'true';
toggle.setAttribute('aria-expanded', expanded ? 'false' : 'true');
menu.classList.toggle('is-open', !expanded);
if (accountMenu && window.matchMedia('(max-width: 820px)').matches && expanded) {
accountMenu.open = false;
}
});
document.addEventListener('click', (event) => {
if (!menu.classList.contains('is-open')) {
return;
}
if (toggle.contains(event.target) || menu.contains(event.target)) {
return;
}
toggle.setAttribute('aria-expanded', 'false');
menu.classList.remove('is-open');
if (accountMenu && window.matchMedia('(max-width: 820px)').matches) {
accountMenu.open = !expanded;
accountMenu.open = false;
}
});
});

View file

@ -27,22 +27,23 @@ $currentUser = website_current_user();
<h2>Explore</h2>
<ul class="footer-links">
<li><a href="<?= website_escape(website_url('serverlist.php')) ?>">Game Servers</a></li>
<li><a href="<?= website_escape(website_url('docs.php')) ?>">Documentation</a></li>
<li><a href="<?= website_escape(website_url('pricing.php')) ?>">Pricing</a></li>
<li><a href="<?= website_escape(website_url('locations.php')) ?>">Locations</a></li>
<li><a href="<?= website_escape(website_url('panel_features.php')) ?>">Panel Features</a></li>
<li><a href="<?= website_escape(website_url('docs.php')) ?>">Documentation</a></li>
<li><a href="<?= website_escape(website_url('support.php')) ?>">Support</a></li>
</ul>
</div>
<div>
<h2>Customer Access</h2>
<h2>Account and Services</h2>
<ul class="footer-links">
<?php if ($currentUser): ?>
<li><a href="<?= website_escape(website_url('account.php')) ?>">My Account</a></li>
<li><a href="<?= website_escape(website_url('orders.php')) ?>">My Orders</a></li>
<li><a href="<?= website_escape(website_url('invoices.php')) ?>">My Invoices</a></li>
<li><a href="<?= website_escape(website_url('my_servers.php')) ?>">My Servers</a></li>
<li><a href="<?= website_escape(website_url('serverlist.php')) ?>">Order a Server</a></li>
<li><a href="<?= website_escape(website_control_panel_url()) ?>">Control Panel</a></li>
<li><a href="<?= website_escape(website_url('my_servers.php')) ?>">My Servers</a></li>
<li><a href="<?= website_escape(website_cart_url()) ?>">Cart</a></li>
<li><a href="<?= website_escape(website_url('logout.php')) ?>">Log Out</a></li>
<?php else: ?>
@ -52,19 +53,19 @@ $currentUser = website_current_user();
<li><a href="<?= website_escape(website_control_panel_url()) ?>">Control Panel</a></li>
<li><a href="<?= website_escape(website_cart_url()) ?>">Cart</a></li>
<?php endif; ?>
<li><a href="<?= website_escape(website_url('docs.php')) ?>">Server Guides</a></li>
<li><a href="<?= website_escape(website_custom_project_url()) ?>">Request Custom Work</a></li>
<?php if ($currentUser && website_current_user_is_staff()): ?>
<li><a href="<?= website_escape(website_url('staff.php')) ?>">Staff Dashboard</a></li>
<?php endif; ?>
<?php if ($discordUrl !== ''): ?>
<li><a href="<?= website_escape($discordUrl) ?>" target="_blank" rel="noopener noreferrer">Discord</a></li>
<?php endif; ?>
</ul>
</div>
<div>
<h2>Need Help?</h2>
<h2>Help and Projects</h2>
<ul class="footer-links">
<li><a href="<?= website_escape(website_url('docs.php')) ?>">Server Guides</a></li>
<li><a href="<?= website_escape(website_custom_project_url()) ?>">Request Custom Work</a></li>
<?php if ($discordUrl !== ''): ?>
<li><a href="<?= website_escape($discordUrl) ?>" target="_blank" rel="noopener noreferrer">Discord</a></li>
<?php endif; ?>
<?php if ($supportUrl !== ''): ?>
<li><a href="<?= website_escape($supportUrl) ?>" target="_blank" rel="noopener noreferrer">Discuss a Custom Project</a></li>
<?php endif; ?>

View file

@ -8,6 +8,9 @@ $cartCount = website_cart_count();
$primaryLinks = [
['key' => 'home', 'label' => 'Home', 'href' => website_url('index.php')],
['key' => 'servers', 'label' => 'Game Servers', 'href' => website_url('serverlist.php')],
['key' => 'pricing', 'label' => 'Pricing', 'href' => website_url('pricing.php')],
['key' => 'locations', 'label' => 'Locations', 'href' => website_url('locations.php')],
['key' => 'panel-features', 'label' => 'Panel Features', 'href' => website_url('panel_features.php')],
['key' => 'docs', 'label' => 'Documentation', 'href' => website_url('docs.php')],
['key' => 'support', 'label' => 'Support', 'href' => website_url('support.php')],
];
@ -16,20 +19,18 @@ if ($currentUser) {
['key' => 'account', 'label' => 'My Account', 'href' => website_url('my_account.php')],
['key' => 'orders', 'label' => 'My Orders', 'href' => website_url('my_orders.php')],
['key' => 'servers', 'label' => 'My Servers', 'href' => website_url('my_servers.php')],
['key' => 'cart', 'label' => 'Cart' . ($cartCount > 0 ? ' (' . $cartCount . ')' : ''), 'href' => website_cart_url()],
];
if (website_current_user_is_staff()) {
$accountLinks[] = ['key' => 'staff', 'label' => 'Staff Dashboard', 'href' => website_url('staff.php')];
}
$accountLinks[] = ['key' => 'logout', 'label' => 'Logout', 'href' => website_url('logout.php')];
$accountLabel = 'Account';
$accountLabel = 'My Account';
} else {
$accountLinks = [
['key' => 'account', 'label' => 'Login', 'href' => website_login_url()],
['key' => 'register', 'label' => 'Create Account', 'href' => website_register_url()],
['key' => 'cart', 'label' => 'Cart' . ($cartCount > 0 ? ' (' . $cartCount . ')' : ''), 'href' => website_cart_url()],
];
$accountLabel = 'Account';
$accountLabel = 'Login';
}
$accountActiveKeys = array_column($accountLinks, 'key');
$accountIsActive = in_array($activePage, $accountActiveKeys, true);
@ -52,23 +53,43 @@ $accountIsActive = in_array($activePage, $accountActiveKeys, true);
</button>
<nav class="primary-nav" id="primary-nav" data-nav-menu aria-label="Primary navigation">
<?php foreach ($primaryLinks as $link): ?>
<a class="nav-link<?= $activePage === $link['key'] ? ' is-active' : '' ?>" href="<?= website_escape($link['href']) ?>">
<?= website_escape($link['label']) ?>
</a>
<?php endforeach; ?>
<details class="account-menu" data-account-menu>
<summary class="nav-link<?= $accountIsActive ? ' is-active' : '' ?>"><?= website_escape($accountLabel) ?></summary>
<div class="account-menu-panel">
<?php foreach ($accountLinks as $link): ?>
<a class="account-menu-link<?= $activePage === $link['key'] ? ' is-active' : '' ?>" href="<?= website_escape($link['href']) ?>">
<div class="nav-section nav-section-public">
<span class="nav-section-label">Public</span>
<div class="nav-inline-group">
<?php foreach ($primaryLinks as $link): ?>
<a class="nav-link<?= $activePage === $link['key'] ? ' is-active' : '' ?>" href="<?= website_escape($link['href']) ?>">
<?= website_escape($link['label']) ?>
</a>
<?php endforeach; ?>
</div>
</details>
<a class="nav-link nav-action-secondary" href="<?= website_escape(website_custom_project_url()) ?>">Custom Projects</a>
<a class="nav-link nav-action-primary" href="<?= website_escape(website_control_panel_url()) ?>">Control Panel</a>
</div>
<div class="nav-section nav-section-account">
<span class="nav-section-label">Account</span>
<div class="nav-inline-group nav-account-inline">
<details class="account-menu" data-account-menu>
<summary class="nav-link<?= $accountIsActive ? ' is-active' : '' ?>"><?= website_escape($accountLabel) ?></summary>
<div class="account-menu-panel">
<?php foreach ($accountLinks as $link): ?>
<a class="account-menu-link<?= $activePage === $link['key'] ? ' is-active' : '' ?>" href="<?= website_escape($link['href']) ?>">
<?= website_escape($link['label']) ?>
</a>
<?php endforeach; ?>
</div>
</details>
<a class="nav-link<?= $activePage === 'cart' ? ' is-active' : '' ?>" href="<?= website_escape(website_cart_url()) ?>">
Cart<?= $cartCount > 0 ? ' (' . $cartCount . ')' : '' ?>
</a>
</div>
</div>
<div class="nav-section nav-section-actions">
<span class="nav-section-label">Actions</span>
<div class="nav-inline-group nav-actions-inline">
<a class="nav-link nav-action-secondary" href="<?= website_escape(website_custom_project_url()) ?>">Custom Projects</a>
<a class="nav-link nav-action-primary" href="<?= website_escape(website_control_panel_url()) ?>">Control Panel</a>
</div>
</div>
</nav>
</div>
</header>

View file

@ -25,7 +25,6 @@ $locationSummary = ['USA East', 'USA Central', 'USA West', 'Europe'];
<div class="hero-actions">
<a class="button button-primary" href="<?= website_escape(website_url('serverlist.php')) ?>">View Game Servers</a>
<a class="button button-secondary" href="<?= website_escape($customProjectUrl) ?>">Discuss a Custom Server</a>
<a class="button button-ghost" href="<?= website_escape(website_control_panel_url()) ?>">Open Control Panel</a>
</div>
</div>
<div class="hero-visual">
@ -106,50 +105,31 @@ $locationSummary = ['USA East', 'USA Central', 'USA West', 'Europe'];
<div class="summary-grid">
<article class="summary-card">
<div class="kicker">Pricing summary</div>
<h3>Prices belong on the order page.</h3>
<p>Actual prices are reflected on each server order page. Standard slot-based servers start at $<?= website_escape(number_format($standardSlotStart, 2)) ?> per slot, and most standard plans use a <?= website_escape((string)$standardMinSlots) ?>-slot minimum.</p>
<h3>Flexible hosting for different communities.</h3>
<p>Choose a game, player capacity, region, and available options for the server you want to run. Standard slot-based hosting starts at $<?= website_escape(number_format($standardSlotStart, 2)) ?> per slot, with most standard plans beginning at <?= website_escape((string)$standardMinSlots) ?> slots.</p>
<p>Selected lightweight legacy servers may start around $<?= website_escape(number_format($legacyMonthlyStart, 2)) ?> per month. <?= website_escape($fixedCapNote) ?></p>
<p><?= website_escape($pricingDisclaimer) ?> <?= website_escape($customWorkNote) ?></p>
<p><?= website_escape($customWorkNote) ?></p>
<div class="card-actions">
<a class="button button-primary" href="<?= website_escape(website_url('serverlist.php')) ?>">View Game Servers</a>
</div>
</article>
<article class="summary-card">
<div class="kicker">Locations summary</div>
<h3>Regional hosting options.</h3>
<p>Locations are summarized here so visitors can get oriented without another sales page.</p>
<div class="kicker">Locations</div>
<h3>Hosting locations for your players.</h3>
<p>Select from available regions in the United States and Europe when you configure a server. Availability can vary by game and current capacity.</p>
<div class="tag-list tag-list-small" aria-label="Hosting region summary">
<?php foreach ($locationSummary as $locationName): ?>
<span><?= website_escape($locationName) ?></span>
<?php endforeach; ?>
</div>
<p>Final availability depends on the selected game, resource needs, and current capacity.</p>
<div class="card-actions">
<a class="button button-secondary" href="<?= website_escape(website_url('locations.php')) ?>">View Locations</a>
</div>
</article>
</div>
</div>
</section>
<section class="section">
<div class="container">
<div class="panel-preview">
<div class="panel-preview-shell">
<img src="<?= website_escape(website_asset('images/dark.jpg')) ?>" alt="GSP control panel preview background">
</div>
<div class="panel-preview-stats">
<div class="section-heading">
<div class="kicker">GSP control</div>
<h2>Manage servers through the GSP control panel.</h2>
<p>Start, stop, and restart servers. Edit configuration files, review logs, run supported content workflows, schedule tasks, and manage operational work from the panel.</p>
</div>
<div class="stack-actions">
<a class="button button-primary" href="<?= website_escape(website_control_panel_url()) ?>">Open Control Panel</a>
<a class="button button-secondary" href="<?= website_escape(website_url('docs.php')) ?>">Read Documentation</a>
</div>
</div>
</div>
</div>
</section>
<section class="cta-band">
<div class="container">
<div class="cta-panel">

View file

@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
?>
<section class="page-heading">
<div class="container">
<h1>Server management without unnecessary complexity.</h1>
<p>Manage your hosted game servers from a browser with tools for everyday controls, configuration, content workflows, and practical maintenance. Available options can vary by game, but the goal stays the same: straightforward control without guesswork.</p>
</div>
</section>
<section class="section">
<div class="container">
<div class="feature-grid feature-grid-three">
<article class="feature-card">
<h3>Everyday Server Controls</h3>
<p>Start, stop, and restart servers from one account, with clear status feedback and direct access to the server homes assigned to you.</p>
</article>
<article class="feature-card">
<h3>Files and Configuration</h3>
<p>Edit common configuration files, work with available file-management tools, and use FTP access where your service and game setup support it.</p>
</article>
<article class="feature-card">
<h3>Monitoring and Status</h3>
<p>Review current server state, logs, and operational details without relying on a separate machine or remote desktop session just to check what happened.</p>
</article>
</div>
</div>
</section>
<section class="section">
<div class="container">
<div class="summary-grid">
<article class="summary-card">
<div class="kicker">Content workflows</div>
<h3>Mods and server content</h3>
<p>Install supported server content, run scripted content installers, and use dedicated Workshop workflows where the game configuration provides them.</p>
</article>
<article class="summary-card">
<div class="kicker">Maintenance</div>
<h3>Scheduling and routine tasks</h3>
<p>Use available scheduling tools for server actions and maintenance workflows, so restarts, updates, and recurring tasks do not have to be handled by hand every time.</p>
</article>
<article class="summary-card">
<div class="kicker">Support</div>
<h3>Help when you need it</h3>
<p>If a game needs custom configuration, legacy hosting knowledge, or a more unusual setup, support and development help are available instead of leaving you with a stock panel and no follow-through.</p>
</article>
<article class="summary-card">
<div class="kicker">Browser access</div>
<h3>Designed for day-to-day use</h3>
<p>Common controls stay available from desktop and mobile browsers, with one account linking your website orders and the servers assigned to you.</p>
</article>
</div>
</div>
</section>
<section class="cta-band">
<div class="container">
<div class="cta-panel">
<div>
<h2>Ready to see what you can run?</h2>
<p>Browse the current game-server catalog or open the control panel if you already have a hosted server with us.</p>
</div>
<div class="stack-actions">
<a class="button button-primary" href="<?= website_escape(website_url('serverlist.php')) ?>">Browse Game Servers</a>
<a class="button button-secondary" href="<?= website_escape(website_control_panel_url()) ?>">Open Control Panel</a>
</div>
</div>
</div>
</section>

View file

@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/includes/bootstrap.php';
website_render(
'panel_features.php',
[
'activePage' => 'panel-features',
'pageTitle' => 'Panel Features - Gameservers.World',
'metaDescription' => 'See what the Gameservers.World control panel lets you manage from your browser, including server controls, files, configuration, scheduling, content workflows, and status tools.',
'canonicalPath' => 'panel_features.php',
]
);

View file

@ -93,26 +93,59 @@ name, platform, price, slot range, image status, and location count visible.
## Navigation
Website footer account links are state-aware:
The shared header uses one responsive navigation implementation for every public
page.
- logged out: `Account Login`, `Order a Server`, `Control Panel`
- logged in: `My Account`, `Order a Server`, `Control Panel`, `My Servers`, `Log Out`
- staff-only links appear only for Panel admin users and still enforce website staff authorization server-side
Primary links:
The shared header uses one responsive navigation bar:
- Home
- Game Servers
- Pricing
- Locations
- Panel Features
- Documentation
- Support
- primary links: Home, Game Servers, Documentation, Support
- account menu: Login/Create Account/Cart or My Account/My Orders/My Servers/Cart/Staff Dashboard/Logout
- action links: Custom Projects and Control Panel
Account area:
Pricing and locations remain available as website pages and footer links, but they
are summarized on the homepage instead of being prominent main navigation items.
- logged out: Login, Create Account, Cart
- logged in: My Account, My Orders, My Servers, Cart, Logout
- staff-only: Staff Dashboard
Right-side actions:
- Custom Projects
- Control Panel
`Panel Features` is a website information page that explains the customer
benefits of the GSP Panel. `Control Panel` is the direct external action that
opens the operational Panel at the configured Panel URL.
The footer should contain only one modest `Control Panel` link. Public pages,
especially the homepage, must not repeat Control Panel promotions unnecessarily.
Control Panel links point directly to the configured Panel domain. `My Servers`
opens a website customer page that summarizes website orders and links to the
Panel for live server controls. Staff Dashboard opens the website sales/billing
staff area, not Panel activity logging.
## Public Copy Policy
Public website copy must never repeat developer instructions, prompt wording,
acceptance criteria, task rationale, or explanations about where content belongs.
Internal requirements must be translated into polished customer-facing language.
Avoid public wording such as:
- "belongs on"
- "summarized here"
- "visitors can get oriented"
- "another sales page"
- "as requested"
- "this section was moved"
Use direct customer-facing language instead.
## Deployment
Recommended:
@ -132,6 +165,7 @@ Recommended:
- `docs.php`
- `pricing.php`
- `locations.php`
- `panel_features.php`
- `support.php`
- `login.php`
- `register.php`