<!DOCTYPE html>
<html lang="{{ locale|default('en') }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="csrf-token" content="{{ csrf_token }}">
<title>{% block title %}{{ system_name }}{% endblock %}</title>
{% block meta_description_tags %}
{% if appearance.seo.description %}
<meta name="description" content="{{ appearance.seo.description | e }}">
{% endif %}
{% endblock %}
{% block meta_tags %}{% endblock %}
<!-- Favicons -->
<link rel="icon" type="image/svg+xml" href="{{ favicon_svg }}">
<link rel="icon" type="image/x-icon" href="{{ favicon_ico }}">
<link rel="apple-touch-icon" href="{{ favicon_png }}">
<link rel="manifest" href="/appmanifestjson">
<link href="/vendor/bootstrap-5.3.0/css/bootstrap.min.css" rel="stylesheet">
<link href="/vendor/fontawesome-6.4.0/css/all.min.css" rel="stylesheet">
<link id="theme-stylesheet" href="{{ stylesheet }}" rel="stylesheet">
<link href="/css/ansisys.css" rel="stylesheet">
{% block head %}{% endblock %}
<style>
body.bbs-menu-shell {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.bbs-shell-header {
background: var(--bs-primary, #0d6efd);
color: #fff;
padding: 0.5rem 1rem;
display: flex;
align-items: center;
gap: 1rem;
flex-wrap: wrap;
position: relative;
z-index: 1030; /* Match Bootstrap's fixed navbar ? ensures dropdowns paint above footer */
}
.bbs-shell-system-name {
font-weight: bold;
font-size: 1.1rem;
}
.bbs-shell-section {
flex: 1;
opacity: 0.85;
font-size: 0.95rem;
}
.bbs-shell-home {
color: #fff;
text-decoration: none;
font-size: 0.9rem;
opacity: 0.9;
}
.bbs-shell-home:hover {
opacity: 1;
color: #fff;
text-decoration: underline;
}
.bbs-shell-main {
flex: 1;
}
/* Admin dropdown button in bbs-menu shell header ? scope tightly so the
rule does NOT cascade into .dropdown-item.dropdown-toggle submenu links */
.bbs-shell-header > .dropdown > .dropdown-toggle {
color: #fff;
text-decoration: none;
font-size: 0.9rem;
opacity: 0.9;
background: none;
border: none;
padding: 0;
cursor: pointer;
}
.bbs-shell-header > .dropdown > .dropdown-toggle:hover { opacity: 1; }
.bbs-shell-header .dropdown-submenu { position: relative; }
/* Submenus open to the LEFT because the dropdown is right-aligned in the
header; opening right would take them off-screen. */
.bbs-shell-header .dropdown-submenu > .dropdown-menu {
top: 0; left: auto; right: 100%; margin-top: -1px; margin-right: -1px;
}
.bbs-shell-header .dropdown-submenu:hover > .dropdown-menu { display: block; }
/* Caret points left (rotate -90°) to indicate the left-opening submenu */
.bbs-shell-header .dropdown-submenu > .dropdown-toggle::after {
transform: rotate(-90deg); position: absolute;
right: 10px; top: 50%; margin-top: -3px;
}
@media (max-width: 768px) {
.bbs-shell-header .dropdown-submenu > .dropdown-menu {
position: static !important; margin-left: 1rem;
border-left: 2px solid rgba(0,0,0,0.1);
}
}
{% if appearance.branding.accent_color %}
:root { --fidonet-blue: {{ appearance.branding.accent_color | e }}; }
.bbs-shell-header { background: {{ appearance.branding.accent_color | e }} !important; }
.bg-primary { background-color: {{ appearance.branding.accent_color | e }} !important; }
.btn-fidonet { background-color: {{ appearance.branding.accent_color | e }}; border-color: {{ appearance.branding.accent_color | e }}; }
{% endif %}
.file-menu-icon.unread,
.file-menu-link.unread {
color: #ffc107 !important;
}
</style>
<script src="/vendor/bootstrap-5.3.0/js/bootstrap.bundle.min.js"></script>
<script src="/vendor/jquery-3.7.1/jquery-3.7.1.min.js"></script>
<script>
window.appLocale = {{ locale|json_encode|raw }};
window.appDefaultLocale = {{ default_locale|json_encode|raw }};
window.appI18nNamespaces = {{ i18n_namespaces|json_encode|raw }};
window.siteConfig = {
ansiRendererMode: {{ ansi_renderer_mode|json_encode|raw }},
sseTransportMode: {{ effective_sse_transport_mode|json_encode|raw }},
configuredRealtimeTransportMode: {{ configured_realtime_transport_mode|json_encode|raw }},
realtimeTransportMode: {{ effective_realtime_transport_mode|json_encode|raw }},
realtimeWsUrl: {{ realtime_ws_url|json_encode|raw }}
};
</script>
<script src="/js/app.js"></script>
<script src="/js/ansisys.js"></script>
{% if current_user %}
<script src="/js/binkstream-client.js"></script>
{% endif %}
{% if current_user %}
<script src="/js/notifier.js"></script>
{% endif %}
{% include 'custom/header.insert.twig' ignore missing %}
{% if debug_ansi_not_perfect %}
<style>.ansi-art-container pre, .ansi-art { font-family: inherit !important; }</style>
{% elseif debug_ansi_use_consolas %}
<style>.ansi-art-container pre, .ansi-art { font-family: Consolas, monospace !important; }</style>
{% endif %}
</head>
<body class="bbs-menu-shell">
<header class="bbs-shell-header">
<span class="bbs-shell-system-name">
{% if appearance.branding.logo_url %}
<img src="{{ appearance.branding.logo_url | e }}" alt="{{ system_name | e }}" style="max-height:32px;" class="me-1">
{% else %}
{{ system_name }}
{% endif %}
</span>
<span class="bbs-shell-section">{% block section_title %}{% endblock %}</span>
{% if current_user %}
{% if credits_enabled %}
<span class="me-2 small" id="headerCreditBalance">{{ credits_symbol }}{{ credit_balance }}</span>
{% endif %}
<div class="dropdown me-2">
<button class="dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fas fa-user"></i> {{ current_user.username }}
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item" href="/profile"><i class="fas fa-id-card"></i> {{ t('ui.base.profile', {}, 'common') }}</a></li>
<li><a class="dropdown-item" href="/settings"><i class="fas fa-cog"></i> {{ t('ui.base.settings', {}, 'common') }}</a></li>
<li><a class="dropdown-item" href="/subscriptions"><i class="fas fa-bookmark"></i> {{ t('ui.base.subscriptions', {}, 'common') }}</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="logout(); return false;"><i class="fas fa-sign-out-alt"></i> {{ t('ui.base.logout', {}, 'common') }}</a></li>
</ul>
</div>
{% if current_user.is_admin %}
<div class="dropdown">
<button class="dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fas fa-cog"></i> {{ t('ui.base.admin', {}, 'common') }}
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item" href="/whosonline"><i class="fas fa-user-friends"></i> {{ t('ui.base.admin.whos_online', {}, 'common') }}</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="/admin/"><i class="fas fa-tachometer-alt"></i> {{ t('ui.base.admin.dashboard', {}, 'common') }}</a></li>
<li><a class="dropdown-item" href="/binkp"><i class="fas fa-network-wired"></i> {{ t('ui.base.admin.binkp_status', {}, 'common') }}</a></li>
<li><a class="dropdown-item" href="/admin/users"><i class="fas fa-users"></i> {{ t('ui.base.admin.manage_users', {}, 'common') }}</a></li>
<li><hr class="dropdown-divider"></li>
<li class="dropdown-submenu">
<a class="dropdown-item dropdown-toggle" href="#"><i class="fas fa-folder-tree"></i> {{ t('ui.base.admin.area_management', {}, 'common') }}</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="/echoareas"><i class="fas fa-comments"></i> {{ t('ui.base.admin.echo_areas', {}, 'common') }}</a></li>
<li><a class="dropdown-item" href="/fileareas"><i class="fas fa-folder-open"></i> {{ t('ui.base.admin.file_areas', {}, 'common') }}</a></li>
<li><a class="dropdown-item" href="/admin/file-approvals"><i class="fas fa-user-check"></i> {{ t('ui.base.admin.file_approvals', {}, 'common') }}</a></li>
<li><a class="dropdown-item" href="/admin/filearea-rules"><i class="fas fa-filter"></i> {{ t('ui.base.admin.file_area_rules', {}, 'common') }}</a></li>
<li><a class="dropdown-item" href="/admin/subscriptions"><i class="fas fa-bookmark"></i> {{ t('ui.base.admin.subscriptions', {}, 'common') }}</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="/admin/auto-feed"><i class="fas fa-rss"></i> {{ t('ui.base.admin.auto_feed', {}, 'common') }}</a></li>
</ul>
</li>
<li class="dropdown-submenu">
<a class="dropdown-item dropdown-toggle" href="#"><i class="fas fa-users"></i> {{ t('ui.base.admin.community', {}, 'common') }}</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="/admin/bbs-directory"><i class="fas fa-server"></i> {{ t('ui.base.admin.bbs_directory', {}, 'common') }}</a></li>
<li class="dropdown-submenu">
<a class="dropdown-item dropdown-toggle" href="#"><i class="fas fa-comments"></i> {{ t('ui.base.admin.chat', {}, 'common') }}</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="/admin/chat-rooms"><i class="fas fa-comment-dots"></i> {{ t('ui.base.admin.chat_rooms', {}, 'common') }}</a></li>
<li><a class="dropdown-item" href="/admin/mrc-settings"><i class="fas fa-satellite-dish"></i> {{ t('ui.base.admin.mrc_settings', {}, 'common') }}</a></li>
</ul>
</li>
<li><a class="dropdown-item" href="/admin/polls"><i class="fas fa-vote-yea"></i> {{ t('ui.base.admin.polls', {}, 'common') }}</a></li>
<li><a class="dropdown-item" href="/admin/shoutbox"><i class="fas fa-bullhorn"></i> {{ t('ui.base.admin.shoutbox', {}, 'common') }}</a></li>
<li><a class="dropdown-item" href="/admin/weather-config"><i class="fas fa-cloud-sun"></i> {{ t('ui.base.admin.weather_config', {}, 'common') }}</a></li>
</ul>
</li>
<li class="dropdown-submenu">
<a class="dropdown-item dropdown-toggle" href="#"><i class="fas fa-ad"></i> {{ t('ui.base.admin.ads_menu', {}, 'common') }}</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="/admin/ads"><i class="fas fa-image"></i> {{ t('ui.base.admin.ads', {}, 'common') }}</a></li>
<li><a class="dropdown-item" href="/admin/ad-campaigns"><i class="fas fa-bullhorn"></i> {{ t('ui.base.admin.ad_campaigns', {}, 'common') }}</a></li>
<li><a class="dropdown-item" href="/admin/bulletins"><i class="fas fa-clipboard-list"></i> {{ t('ui.base.admin.bulletins', {}, 'common') }}</a></li>
</ul>
</li>
<li class="dropdown-submenu">
<a class="dropdown-item dropdown-toggle" href="#"><i class="fas fa-gamepad"></i> {{ t('ui.base.admin.doors', {}, 'common') }}</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="/admin/dosdoors"><i class="fas fa-terminal"></i> {{ t('ui.base.admin.doors_dos', {}, 'common') }}</a></li>
<li><a class="dropdown-item" href="/admin/native-doors"><i class="fas fa-door-open"></i> {{ t('ui.base.admin.doors_native', {}, 'common') }}</a></li>
<li><a class="dropdown-item" href="/admin/webdoors"><i class="fas fa-globe"></i> {{ t('ui.base.admin.doors_web', {}, 'common') }}</a></li>
<li><a class="dropdown-item" href="/admin/jsdosdoors"><i class="fas fa-floppy-disk"></i> {{ t('ui.base.admin.doors_jsdos', {}, 'common') }}</a></li>
</ul>
</li>
<li><hr class="dropdown-divider"></li>
<li class="dropdown-submenu">
<a class="dropdown-item dropdown-toggle" href="#"><i class="fas fa-chart-line"></i> {{ t('ui.base.admin.analytics', {}, 'common') }}</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="/admin/activity-stats"><i class="fas fa-chart-bar"></i> {{ t('ui.base.admin.activity_stats', {}, 'common') }}</a></li>
<li><a class="dropdown-item" href="/admin/sharing"><i class="fas fa-share-alt"></i> {{ t('ui.base.admin.sharing', {}, 'common') }}</a></li>
<li><a class="dropdown-item{% if not license_valid %} dropdown-item-locked{% endif %}" href="{{ license_valid ? '/admin/economy' : '#' }}"{% if not license_valid %} title="{{ t('ui.base.admin.registered_feature', {}, 'common') }}"{% endif %}><i class="fas fa-coins"></i> {{ t('ui.base.admin.economy_viewer', {}, 'common') }}{% if not license_valid %} <i class="fas fa-lock ms-1 small"></i>{% endif %}</a></li>
<li><a class="dropdown-item{% if not license_valid %} dropdown-item-locked{% endif %}" href="{{ license_valid ? '/admin/referrals' : '#' }}"{% if not license_valid %} title="{{ t('ui.base.admin.registered_feature', {}, 'common') }}"{% endif %}><i class="fas fa-user-plus"></i> {{ t('ui.base.admin.referrals', {}, 'common') }}{% if not license_valid %} <i class="fas fa-lock ms-1 small"></i>{% endif %}</a></li>
</ul>
</li>
<li class="dropdown-submenu">
<a class="dropdown-item dropdown-toggle" href="/admin/bbs-settings"><i class="fas fa-sliders-h"></i> {{ t('ui.base.admin.bbs_settings', {}, 'common') }}</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="/admin/appearance"><i class="fas fa-palette"></i> {{ t('ui.base.admin.appearance', {}, 'common') }}</a></li>
<li><a class="dropdown-item" href="/admin/bbs-settings"><i class="fas fa-sliders-h"></i> {{ t('ui.base.admin.bbs_settings', {}, 'common') }}</a></li>
<li><a class="dropdown-item" href="/admin/binkp-config"><i class="fas fa-network-wired"></i> {{ t('ui.base.admin.binkp_configuration', {}, 'common') }}</a></li>
<li><a class="dropdown-item" href="/admin/networks"><i class="fas fa-globe"></i> {{ t('ui.base.admin.networks', {}, 'common') }}</a></li>
<li><a class="dropdown-item" href="/admin/i18n-overrides"><i class="fas fa-language"></i> {{ t('ui.base.admin.i18n_overrides', {}, 'common') }}</a></li>
<li><a class="dropdown-item" href="/admin/template-editor"><i class="fas fa-file-code"></i> {{ t('ui.base.admin.template_editor', {}, 'common') }}</a></li>
</ul>
</li>
<li><hr class="dropdown-divider"></li>
<li class="dropdown-submenu">
<a class="dropdown-item dropdown-toggle" href="#"><i class="fas fa-question-circle"></i> {{ t('ui.base.admin.help', {}, 'common') }}</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="/admin/docs/view/README"><i class="fas fa-book"></i> {{ t('ui.base.admin.readme', {}, 'common') }}</a></li>
<li><a class="dropdown-item" href="/admin/docs"><i class="fas fa-book-open"></i> {{ t('ui.base.admin.docs', {}, 'common') }}</a></li>
<li><a class="dropdown-item" href="/admin/docs/view/FAQ"><i class="fas fa-question-circle"></i> {{ t('ui.base.admin.faq', {}, 'common') }}</a></li>
{% if has_upgrading_doc %}<li><a class="dropdown-item" href="/admin/upgrade-notes"><i class="fas fa-arrow-up-from-bracket"></i> {{ t('ui.base.admin.upgrade_notes', {'version': app_version}, 'common') }}</a></li>{% endif %}
<li><a class="dropdown-item" href="https://github.com/awehttam/binkterm-php/issues" target="_blank"><i class="fas fa-bug"></i> {{ t('ui.base.admin.report_issue', {}, 'common') }}</a></li>
</ul>
</li>
</ul>
</div>
{% endif %}
{% else %}
{% if bbs_feature_enabled('guest_doors_page') %}
<a href="/guest-doors" class="bbs-shell-home me-2">
<i class="fas fa-terminal"></i> {{ t('ui.base.guest_doors', {}, 'common') }}
</a>
{% endif %}
<a href="/login" class="bbs-shell-home me-2">{{ t('ui.base.login', {}, 'common') }}</a>
{% endif %}
<a href="/" class="bbs-shell-home" title="{{ t('ui.shell.menu_main_q_title', {}, 'common') }}">
<i class="fas fa-home"></i> {{ t('ui.shell.menu', {}, 'common') }}
</a>
</header>
<main class="bbs-shell-main container-fluid py-3">
{% block content %}{% endblock %}
</main>
<footer class="bg-light py-2 border-top">
<div class="container text-center">
<small class="text-muted">
{% if license_valid and appearance.branding.footer_text %}
{{ appearance.branding.footer_text | e }}
{% else %}
{{ system_name }} - Node {{ fidonet_origin }} - Sysop: {{ sysop_name }}
{% endif %}
| <a href="#" data-bs-toggle="modal" data-bs-target="#houseRulesModal">{{ t('ui.about.house_rules', {}, 'common') }}</a>
{% if not (license_valid and appearance.branding.hide_powered_by) %}
| {{ t('ui.footer.powered_by', {}, 'common') }} <a href="https://lovelybits.org/binktermphp">BinktermPHP</a> <a href="https://github.com/awehttam/binkterm-php">{{ app_version }}</a>{% if license_valid and license_system_name and appearance.branding.show_registration_badge != false %} - {{ t('ui.footer.registered_to', {'name': license_system_name}, 'common') }}{% endif %}
{% endif %}
</small>
</div>
</footer>
<div class="modal fade" id="houseRulesModal" tabindex="-1" aria-labelledby="houseRulesModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="houseRulesModalLabel">{{ t('ui.about.house_rules', {}, 'common') }}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
{% if appearance_houserules_html %}
{{ appearance_houserules_html | raw }}
{% else %}
{% include 'rules.twig' %}
{% endif %}
</div>
</div>
</div>
</div>
<script>
{% if current_user %}
window.currentUserId = {{ current_user.user_id ?? current_user.id }};
window.currentUserIsAdmin = {{ current_user.is_admin ? 'true' : 'false' }};
{% endif %}
window.isDevMode = {{ is_dev ? 'true' : 'false' }};
// Nested admin dropdown submenus
$(document).ready(function() {
$('.bbs-shell-header .dropdown-submenu').hover(
function() { $(this).find('> .dropdown-menu').show(); },
function() { $(this).find('> .dropdown-menu').hide(); }
);
$('.bbs-shell-header .dropdown-submenu > .dropdown-toggle').on('click', function(e) {
e.preventDefault(); e.stopPropagation();
const $sub = $(this).next('.dropdown-menu');
$('.bbs-shell-header .dropdown-submenu .dropdown-menu').not($sub).hide();
$sub.toggle();
});
$('.bbs-shell-header .dropdown').on('hide.bs.dropdown', function() {
$(this).find('.dropdown-submenu .dropdown-menu').hide();
});
});
// Render ANSI art if present on this page
$(document).ready(function() {
const pre = document.getElementById('shellMenuAnsi');
if (pre && (typeof renderAnsiBuffer === 'function' || typeof renderAnsiTerminal === 'function')) {
const ansiSize = {{ appearance.shell.bbs_menu.ansi_size|default('80x25')|json_encode|raw }};
const dims = (function resolveAnsiMenuDimensions(size) {
switch (size) {
case '132x24':
return { cols: 132, rows: 24 };
case '132x43':
return { cols: 132, rows: 43 };
case '132x50':
return { cols: 132, rows: 50 };
case 'full':
return {
cols: 132,
rows: Math.max(25, Math.floor((window.innerHeight - 140) / 16))
};
case '80x25':
default:
return { cols: 80, rows: 25 };
}
})(ansiSize);
if (typeof renderAnsiBuffer === 'function') {
pre.innerHTML = renderAnsiBuffer(pre.textContent, dims.cols, dims.rows, null, {
preserveFullWidth: true,
preserveFullHeight: true,
rowCount: dims.rows
});
} else {
pre.innerHTML = renderAnsiTerminal(pre.textContent, dims.cols, dims.rows);
}
}
});
// BBS-style keyboard navigation
const bbsKeymap = {{ bbs_menu_items | json_encode | raw }};
function bbsNavigate(key) {
if (key === 'q' || key === 'Q' || key === 'Escape') {
window.location.href = '/';
return true;
}
const item = bbsKeymap.find(i => i.key.toUpperCase() === key.toUpperCase());
if (item) { window.location.href = item.url; return true; }
return false;
}
// Desktop keyboard navigation
document.addEventListener('keydown', function(e) {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.tagName === 'SELECT') return;
if (e.ctrlKey || e.altKey || e.metaKey) return;
bbsNavigate(e.key);
});
</script>
{% block scripts %}{% endblock %}
</body>
</html>
|