diff --git a/static/css/style.css b/static/css/style.css index 850cde9..6e65ffc 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -303,6 +303,27 @@ a:hover { text-transform: uppercase; } +.lang-code { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 2.2rem; + height: 1.6rem; + padding: 0 6px; + border-radius: 6px; + background: rgba(139, 92, 246, 0.12); + color: var(--accent-light); + font-size: 0.7rem; + font-weight: 700; + letter-spacing: 0.04em; +} + +.toast .ui-icon { + width: 16px; + height: 16px; + flex-shrink: 0; +} + /* ===== Buttons ===== */ .btn { display: inline-flex; @@ -391,6 +412,110 @@ a:hover { font-size: 1.1rem; } +.btn-icon .ui-icon { + width: 16px; + height: 16px; +} + +.btn-sm .ui-icon { + width: 15px; + height: 15px; +} + +/* ===== UI Icons (SVG sprite) ===== */ +.ui-icon { + width: 1.15em; + height: 1.15em; + display: inline-block; + flex-shrink: 0; + vertical-align: -0.15em; + stroke: currentColor; + fill: none; + color: inherit; + pointer-events: none; +} + +.ui-icon-lg { + width: 1.5rem; + height: 1.5rem; +} + +.ui-icon-xl { + width: 2rem; + height: 2rem; +} + +.ui-icon-2xl { + width: 2.75rem; + height: 2.75rem; +} + +.nav-link .ui-icon { + width: 15px; + height: 15px; + opacity: 0.9; +} + +.nav-link.active .ui-icon { + opacity: 1; +} + +.theme-toggle .ui-icon { + width: 18px; + height: 18px; +} + +.section-title .icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 2rem; + height: 2rem; + border-radius: var(--radius-sm); + background: rgba(139, 92, 246, 0.12); + color: var(--accent-light); + font-size: 0; +} + +.section-title .icon .ui-icon { + width: 1.1rem; + height: 1.1rem; +} + +.logo-icon .ui-icon { + width: 22px; + height: 22px; + color: #fff; +} + +.server-icon .ui-icon, +.protocol-icon .ui-icon, +.promo-icon .ui-icon { + width: 1.35em; + height: 1.35em; +} + +.empty-state .empty-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 4.5rem; + height: 4.5rem; + margin: 0 auto var(--space-lg); + border-radius: var(--radius-xl); + background: rgba(139, 92, 246, 0.08); + border: 1px solid rgba(139, 92, 246, 0.12); + color: var(--accent-light); + font-size: 0; + opacity: 1; +} + +.empty-state .empty-icon .ui-icon { + width: 2rem; + height: 2rem; + opacity: 0.85; +} + .btn:disabled { opacity: 0.5; cursor: not-allowed; @@ -450,10 +575,6 @@ a:hover { gap: var(--space-md); } -.section-title .icon { - font-size: 1.3rem; -} - /* ===== Server Card ===== */ .server-grid { display: grid; @@ -1190,6 +1311,12 @@ a:hover { flex-shrink: 0; } +.client-avatar .ui-icon { + width: 18px; + height: 18px; + color: #fff; +} + .client-name { font-weight: 600; font-size: 0.92rem; @@ -1396,12 +1523,6 @@ a:hover { color: var(--text-muted); } -.empty-state .empty-icon { - font-size: 3.5rem; - margin-bottom: var(--space-lg); - opacity: 0.5; -} - .empty-state .empty-title { font-size: 1.2rem; font-weight: 600; diff --git a/static/icons.svg b/static/icons.svg new file mode 100644 index 0000000..3cc7afd --- /dev/null +++ b/static/icons.svg @@ -0,0 +1,111 @@ +๏ปฟ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/templates/base.html b/templates/base.html index e256479..4e44204 100644 --- a/templates/base.html +++ b/templates/base.html @@ -28,12 +28,13 @@ + {% from "macros/icons.html" import icon %}
{% endif %}
@@ -96,19 +95,19 @@
{% set langs = [ - ('en', '๐Ÿ‡บ๐Ÿ‡ธ', 'lang_en'), - ('ru', '๐Ÿ‡ท๐Ÿ‡บ', 'lang_ru'), - ('fr', '๐Ÿ‡ซ๐Ÿ‡ท', 'lang_fr'), - ('zh', '๐Ÿ‡จ๐Ÿ‡ณ', 'lang_zh'), - ('fa', '๐Ÿ‡ฎ๐Ÿ‡ท', 'lang_fa') + ('en', 'EN', 'lang_en'), + ('ru', 'RU', 'lang_ru'), + ('fr', 'FR', 'lang_fr'), + ('zh', 'ZH', 'lang_zh'), + ('fa', 'FA', 'lang_fa') ] %} {% for l_code, l_flag, l_key in langs %} - {{ l_flag }} + {{ l_flag }} {{ _(l_key) }} {% if lang == l_code %} - โœ“ + {{ icon('check') }} {% endif %} {% endfor %} @@ -120,6 +119,12 @@ window.I18N = {{ translations_json | safe }}; window._ = function (key) { return window.I18N[key] || key; }; + /* ===== SVG icons (same sprite as templates) ===== */ + window.uiIcon = function (name, className) { + const cls = className ? `ui-icon ${className}` : 'ui-icon'; + return ``; + }; + /* ===== Theme Toggle ===== */ (function () { const saved = localStorage.getItem('theme') || 'dark'; @@ -128,10 +133,9 @@ const updateUI = () => { const isLight = document.documentElement.getAttribute('data-theme') === 'light'; document.querySelectorAll('.theme-toggle').forEach(btn => { - const isLang = btn.getAttribute('onclick').includes('langModal'); - if (!isLang) { - btn.textContent = isLight ? 'โ˜€๏ธ' : '๐ŸŒ™'; - } + const onclick = btn.getAttribute('onclick') || ''; + if (onclick.includes('langModal')) return; + btn.innerHTML = uiIcon(isLight ? 'sun' : 'moon'); }); }; @@ -166,8 +170,8 @@ const toast = document.createElement('div'); toast.className = `toast toast-${type}`; - const icons = { success: 'โœ“', error: 'โœ•', info: 'โ„น' }; - toast.innerHTML = `${icons[type] || 'โ„น'} ${message}`; + const icons = { success: 'check', error: 'x', info: 'info' }; + toast.innerHTML = `${uiIcon(icons[type] || 'info')} ${message}`; container.appendChild(toast); setTimeout(() => { @@ -273,4 +277,4 @@ {% block scripts %}{% endblock %} - \ No newline at end of file + diff --git a/templates/guest.html b/templates/guest.html index 2c7594d..9cc282f 100644 --- a/templates/guest.html +++ b/templates/guest.html @@ -1,4 +1,5 @@ {% extends "base.html" %} +{% from "macros/icons.html" import icon %} {% block title_extra %} โ€” {{ _('guest_title') }}{% endblock %} @@ -12,7 +13,7 @@ {% if need_password %}
-
๐Ÿ”
+
{{ icon('lock') }}

{{ _('guest_protected_desc') }}

- ๐Ÿ–ฅ + {{ icon('server') }} {{ _('nav_servers') }}

@@ -20,7 +21,7 @@ data-username="{{ server.username }}" data-auth="{{ 'key' if server.private_key else 'password' }}" draggable="true">
-
๐Ÿ–ฅ
+
{{ icon('server') }}
@@ -69,13 +70,13 @@
{% else %}
-
๐Ÿ›ก
+
{{ icon('shield') }}
{{ _('no_servers') }}
{{ _('add_server_desc') }}
{% endif %} diff --git a/templates/invites.html b/templates/invites.html index 221b222..e2e5dcc 100644 --- a/templates/invites.html +++ b/templates/invites.html @@ -1,4 +1,5 @@ {% extends "base.html" %} +{% from "macros/icons.html" import icon %} {% block title_extra %} โ€” {{ _('invites_title') }}{% endblock %} @@ -7,18 +8,18 @@

- ๐Ÿ”— + {{ icon('link') }} {{ _('invites_title') }}

{{ _('invites_hint') }}

-
๐Ÿ”—
+
{{ icon('link') }}
{{ _('invites_empty') }}
{{ _('invites_empty_desc') }}
@@ -58,12 +59,12 @@
- - - + + - +
{% endfor %} diff --git a/templates/login.html b/templates/login.html index 8de7e60..4c0d8a0 100644 --- a/templates/login.html +++ b/templates/login.html @@ -76,20 +76,20 @@ + {% from "macros/icons.html" import icon %} + title="{{ _('toggle_theme') }}" type="button">{{ icon('moon') }}
@@ -153,19 +153,19 @@
{% set langs = [ - ('en', '๐Ÿ‡บ๐Ÿ‡ธ', 'lang_en'), - ('ru', '๐Ÿ‡ท๐Ÿ‡บ', 'lang_ru'), - ('fr', '๐Ÿ‡ซ๐Ÿ‡ท', 'lang_fr'), - ('zh', '๐Ÿ‡จ๐Ÿ‡ณ', 'lang_zh'), - ('fa', '๐Ÿ‡ฎ๐Ÿ‡ท', 'lang_fa') + ('en', 'EN', 'lang_en'), + ('ru', 'RU', 'lang_ru'), + ('fr', 'FR', 'lang_fr'), + ('zh', 'ZH', 'lang_zh'), + ('fa', 'FA', 'lang_fa') ] %} {% for l_code, l_flag, l_key in langs %} - {{ l_flag }} + {{ l_flag }} {{ _(l_key) }} {% if lang == l_code %} - โœ“ + {{ icon('check') }} {% endif %} {% endfor %} @@ -177,6 +177,11 @@ window.I18N = {{ translations_json | safe }}; window._ = function (key) { return window.I18N[key] || key; }; + window.uiIcon = function (name, className) { + const cls = className ? `ui-icon ${className}` : 'ui-icon'; + return ``; + }; + /* Theme toggle */ (function () { const saved = localStorage.getItem('theme') || 'dark'; @@ -184,11 +189,8 @@ const updateUI = () => { const isLight = document.documentElement.getAttribute('data-theme') === 'light'; - document.querySelectorAll('.theme-toggle').forEach(btn => { - if (btn.id === 'themeToggle') { - btn.textContent = isLight ? 'โ˜€๏ธ' : '๐ŸŒ™'; - } - }); + const btn = document.getElementById('themeToggle'); + if (btn) btn.innerHTML = uiIcon(isLight ? 'sun' : 'moon'); }; if (document.readyState === 'loading') { diff --git a/templates/macros/icons.html b/templates/macros/icons.html new file mode 100644 index 0000000..de57334 --- /dev/null +++ b/templates/macros/icons.html @@ -0,0 +1,4 @@ +{# SVG icon via sprite โ€” usage: {% from "macros/icons.html" import icon %} {{ icon('server') }} #} +{% macro icon(name, class='', size=None) -%} + +{%- endmacro %} diff --git a/templates/my_connections.html b/templates/my_connections.html index 0bd34d8..61cf909 100644 --- a/templates/my_connections.html +++ b/templates/my_connections.html @@ -1,11 +1,12 @@ {% extends "base.html" %} +{% from "macros/icons.html" import icon %} {% block title_extra %} โ€” {{ _('my_connections_title') }}{% endblock %} {% block content %}

- ๐Ÿ”— + {{ icon('layers') }} {{ _('my_connections_title') }}

@@ -14,11 +15,11 @@ {% for c in connections %}
-
๐Ÿ”—
+
{{ icon('link') }}
{{ c.name or 'VPN Connection' }}
- ๐Ÿ–ฅ {{ c.server_name }} + {{ icon('server') }} {{ c.server_name }} {{ 'AmneziaWG' if c.protocol == 'awg' else ('AmneziaWG 2.0' if c.protocol == 'awg2' else ('AWG Legacy' if c.protocol == 'awg_legacy' else ('Xray' if c.protocol == 'xray' else c.protocol | upper))) }} @@ -40,7 +41,7 @@
{% else %}
-
๐Ÿ”—
+
{{ icon('link') }}
{{ _('no_connections') }}
{{ _('no_connections_user_desc') }}
diff --git a/templates/server.html b/templates/server.html index c82dabb..01fcb71 100644 --- a/templates/server.html +++ b/templates/server.html @@ -1,4 +1,5 @@ {% extends "base.html" %} +{% from "macros/icons.html" import icon %} {% block title_extra %} โ€” {{ server.name }}{% endblock %} @@ -125,7 +126,7 @@
-
๐Ÿ–ฅ
+
{{ icon('server') }}

{{ server.name }}

{{ server.host }}:{{ server.ssh_port }} โ€ข {{ server.username @@ -146,10 +147,10 @@
@@ -163,7 +164,7 @@ {{ _('checking_server') }}
@@ -505,7 +506,7 @@ {{ _('loading_connections') }}
@@ -518,7 +519,7 @@