<!DOCTYPE html>
<html lang="{{ locale | default('en') }}" x-data="{ darkMode: false }" :class="{ 'dark': darkMode }" class="bg-slate-100 text-sm">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="{{site_description|default('RustPBX Admin Console')}}" />
<meta name="author" content="RustPBX" />
<meta name="locale" content="{{ locale | default('en') }}" />
<link rel="icon" href="{{favicon_url|default('/static/favicon.ico')}}" />
<title>{% block title %}{{page_title|default('RustPBX Admin')}}{% endblock %}</title>
<script type="application/json" id="__authLocales">{{ available_locales | json | safe }}</script>
{% block js_head %}
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('localeSwitcher', () => ({
open: false,
current: (document.querySelector('meta[name="locale"]') || {}).content || 'en',
locales: JSON.parse(document.getElementById('__authLocales').textContent || '[]'),
flags: {
'en': '๐บ๐ธ', 'en-US': '๐บ๐ธ', 'en-GB': '๐ฌ๐ง',
'zh': '๐จ๐ณ', 'zh-CN': '๐จ๐ณ', 'zh-TW': '๐น๐ผ', 'zh-HK': '๐ญ๐ฐ',
'ja': '๐ฏ๐ต', 'ko': '๐ฐ๐ท',
'fr': '๐ซ๐ท', 'de': '๐ฉ๐ช', 'es': '๐ช๐ธ',
'pt': '๐ง๐ท', 'pt-BR': '๐ง๐ท', 'pt-PT': '๐ต๐น',
'it': '๐ฎ๐น', 'ru': '๐ท๐บ', 'ar': '๐ธ๐ฆ',
'tr': '๐น๐ท', 'pl': '๐ต๐ฑ', 'nl': '๐ณ๐ฑ',
'sv': '๐ธ๐ช', 'da': '๐ฉ๐ฐ', 'fi': '๐ซ๐ฎ', 'nb': '๐ณ๐ด',
'cs': '๐จ๐ฟ', 'sk': '๐ธ๐ฐ', 'hu': '๐ญ๐บ', 'ro': '๐ท๐ด',
'uk': '๐บ๐ฆ', 'vi': '๐ป๐ณ', 'th': '๐น๐ญ', 'id': '๐ฎ๐ฉ',
'ms': '๐ฒ๐พ', 'he': '๐ฎ๐ฑ', 'el': '๐ฌ๐ท', 'hr': '๐ญ๐ท',
},
flag(code) { return this.flags[code] || '๐'; },
label(locale) { return locale.native_name || locale.name || locale.code; },
select(code) {
document.cookie = `locale=${code}; path=/; max-age=31536000; SameSite=Lax`;
window.location.reload();
},
}));
});
</script>
<script defer
src="{{alpine_js|default('https://cdnjs.cloudflare.com/ajax/libs/alpinejs/3.15.0/cdn.min.js')}}"></script>
<script
src="{{tailwind_js|default('https://cdnjs.cloudflare.com/ajax/libs/tailwindcss-browser/4.1.13/index.global.min.js')}}"></script>
{% for js in js_files %}
<script src="{{ js }}" defer></script>
{% endfor %}
{% endblock %}
<style>
[x-cloak] {
display: none !important;
}
input::placeholder,
textarea::placeholder {
color: rgba(148, 163, 184, 0.7);
opacity: 1;
}
.dark input::placeholder,
.dark textarea::placeholder {
color: rgba(148, 163, 184, 0.6);
}
</style>
</head>
<body class="min-h-screen bg-slate-100 text-slate-900">
<div class="min-h-screen">
{% if available_locales and available_locales | length > 1 %}
<div class="flex justify-end px-4 pt-4">
<div x-data="localeSwitcher()" class="relative" @keydown.escape.window="open=false">
<button @click="open=!open" @click.outside="open=false" type="button"
class="flex items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-sm bg-white/70 hover:bg-white shadow-sm ring-1 ring-black/5 transition-colors"
:aria-expanded="open" aria-haspopup="listbox">
<span x-text="flag(current)" class="text-base leading-none"></span>
<span x-text="current.toUpperCase()" class="hidden sm:inline text-xs font-medium text-slate-600 uppercase tracking-wide"></span>
<svg class="h-3.5 w-3.5 text-slate-400 transition-transform duration-150" :class="{'rotate-180': open}"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd"
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
clip-rule="evenodd" />
</svg>
</button>
<div x-show="open" x-cloak
x-transition:enter="transition ease-out duration-100"
x-transition:enter-start="opacity-0 scale-95"
x-transition:enter-end="opacity-100 scale-100"
x-transition:leave="transition ease-in duration-75"
x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 scale-95"
class="absolute right-0 mt-1 w-44 origin-top-right rounded-xl bg-white py-1 shadow-lg ring-1 ring-black/8 z-50"
role="listbox">
<template x-for="loc in locales" :key="loc.code">
<button @click="select(loc.code)" type="button" role="option"
:aria-selected="loc.code === current"
class="flex w-full items-center gap-2.5 px-3 py-2 text-sm transition-colors"
:class="loc.code === current ? 'bg-sky-50 text-sky-700 font-medium' : 'text-slate-700 hover:bg-slate-50'">
<span x-text="flag(loc.code)" class="text-base leading-none w-6 text-center"></span>
<span x-text="label(loc)"></span>
<svg x-show="loc.code === current" class="ml-auto h-4 w-4 text-sky-600 shrink-0"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd"
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
clip-rule="evenodd" />
</svg>
</button>
</template>
</div>
</div>
</div>
{% endif %}
<div class="transition-all duration-300 ease-in-out">
<main class="min-h-[calc(100vh-64px)] bg-slate-50">
{% block content %}{% endblock %}
</main>
<footer class="py-6 text-center text-xs text-slate-500">
{{site_name|default('RustPBX')}} ยท {{site_footer}}
</footer>
</div>
</div>
</body>
</html>