- Enhance maintenance page: add dynamic theme support, multilingual support, and improved 3D text rendering logic.

- Simplify URL configuration by replacing i18n patterns with direct path handling.
This commit is contained in:
Egor Pavlovich Gorbunov 2025-12-28 17:05:20 +03:00
parent 646fc5e1d7
commit 28f6e4d78f
2 changed files with 124 additions and 26 deletions

View file

@ -1,5 +1,4 @@
<!DOCTYPE html> <!DOCTYPE html>
<!--suppress JSSuspiciousNameCombination, JSUnresolvedReference -->
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"/> <meta charset="UTF-8"/>
@ -16,14 +15,26 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
font-family: 'Source Code Pro', monospace; font-family: 'Source Code Pro', 'Courier New', Courier, monospace;
background: #2a2a3a;
color: #fff;
} }
canvas { canvas {
display: block; display: block;
} }
/* Dark theme (default) */
body {
background: #2a2a3a;
color: #fff;
}
/* Light theme */
@media (prefers-color-scheme: light) {
body {
background: #f5f5f7;
color: #1d1d1f;
}
}
</style> </style>
</head> </head>
<body> <body>
@ -31,11 +42,62 @@
<script> <script>
(function () { (function () {
let scene, camera, renderer; let scene, camera, renderer;
let particleSystem, textGroup, fontLoader; let particleSystem, textGroup, fontLoader, loadedFont;
const clock = new THREE.Clock(); const clock = new THREE.Clock();
let targetRotX = 0, targetRotY = 0; let targetRotX = 0, targetRotY = 0;
let currentRotX = 0, currentRotY = 0; let currentRotX = 0, currentRotY = 0;
let isMobile = 'ontouchstart' in window; let isMobile = 'ontouchstart' in window;
let isDarkTheme = !window.matchMedia('(prefers-color-scheme: light)').matches;
const translations = {
'ar-ar': {title: 'سنعود قريباً', subtitle: 'جارٍ التحديث. يرجى التحقق مرة أخرى لاحقاً.'},
'cs-cz': {title: 'Brzy se vrátíme', subtitle: 'Probíhá aktualizace. Zkuste to prosím později.'},
'da-dk': {title: 'Vi er snart tilbage', subtitle: 'En opdatering er i gang. Prøv igen senere.'},
'de-de': {title: 'Wir sind bald zurück', subtitle: 'Ein Update wird durchgeführt. Bitte später erneut versuchen.'},
'en-gb': {title: 'We\'ll Be Back Soon', subtitle: 'An update is in progress. Please check back later.'},
'en-us': {title: 'We\'ll Be Back Soon', subtitle: 'An update is in progress. Please check back later.'},
'es-es': {title: 'Volveremos pronto', subtitle: 'Actualización en curso. Por favor, vuelva más tarde.'},
'fa-ir': {title: 'به زودی برمی‌گردیم', subtitle: 'به‌روزرسانی در حال انجام است. لطفاً بعداً بررسی کنید.'},
'fr-fr': {title: 'Nous revenons bientôt', subtitle: 'Une mise à jour est en cours. Revenez plus tard.'},
'he-il': {title: 'נחזור בקרוב', subtitle: 'עדכון מתבצע. אנא בדוק שוב מאוחר יותר.'},
'hi-in': {title: 'हम जल्द वापस आएंगे', subtitle: 'एक अपडेट प्रगति पर है। कृपया बाद में जांचें।'},
'hr-hr': {title: 'Uskoro se vraćamo', subtitle: 'Ažuriranje je u tijeku. Molimo provjerite kasnije.'},
'id-id': {title: 'Kami akan segera kembali', subtitle: 'Pembaruan sedang berlangsung. Silakan periksa lagi nanti.'},
'it-it': {title: 'Torneremo presto', subtitle: 'È in corso un aggiornamento. Si prega di tornare più tardi.'},
'ja-jp': {title: 'すぐに戻ります', subtitle: 'アップデート中です。後でもう一度お試しください。'},
'kk-kz': {title: 'Біз жақында қайтамыз', subtitle: 'Жаңарту жүріп жатыр. Кейінірек тексеріңіз.'},
'ko-kr': {title: '곧 돌아오겠습니다', subtitle: '업데이트가 진행 중입니다. 나중에 다시 확인해주세요.'},
'nl-nl': {title: 'We zijn zo terug', subtitle: 'Er wordt een update uitgevoerd. Kom later terug.'},
'no-no': {title: 'Vi er snart tilbake', subtitle: 'En oppdatering pågår. Vennligst sjekk igjen senere.'},
'pl-pl': {title: 'Wrócimy wkrótce', subtitle: 'Trwa aktualizacja. Sprawdź ponownie później.'},
'pt-br': {title: 'Voltaremos em breve', subtitle: 'Uma atualização está em andamento. Volte mais tarde.'},
'ro-ro': {title: 'Revenim în curând', subtitle: 'O actualizare este în curs. Vă rugăm să reveniți mai târziu.'},
'ru-ru': {title: 'Скоро вернёмся', subtitle: 'Идёт обновление. Пожалуйста, проверьте позже.'},
'sv-se': {title: 'Vi är snart tillbaka', subtitle: 'En uppdatering pågår. Kontrollera igen senare.'},
'th-th': {title: 'เราจะกลับมาเร็วๆ นี้', subtitle: 'กำลังอัปเดต โปรดตรวจสอบอีกครั้งในภายหลัง'},
'tr-tr': {title: 'Yakında geri döneceğiz', subtitle: 'Güncelleme devam ediyor. Lütfen daha sonra tekrar kontrol edin.'},
'vi-vn': {title: 'Chúng tôi sẽ sớm trở lại', subtitle: 'Đang cập nhật. Vui lòng quay lại sau.'},
'zh-hans': {title: '我们很快就会回来', subtitle: '正在更新中。请稍后再查看。'}
};
function detectUserLanguage() {
const browserLang = (navigator.language || navigator.userLanguage || 'en-gb').toLowerCase();
if (translations[browserLang]) {
return browserLang;
}
const langCode = browserLang.split('-')[0];
for (let key in translations) {
if (key.startsWith(langCode)) {
return key;
}
}
return 'en-gb';
}
const userLanguage = detectUserLanguage();
function init() { function init() {
scene = new THREE.Scene(); scene = new THREE.Scene();
@ -50,8 +112,9 @@
createParticleField(); createParticleField();
fontLoader = new THREE.FontLoader(); fontLoader = new THREE.FontLoader();
fontLoader.load('https://api.evibes.com/static/Source%20Code%20Pro%20ExtraLight_Regular.json', f => { fontLoader.load('/static/Source%20Code%20Pro%20ExtraLight_Regular.json', f => {
create3DText(f); loadedFont = f;
create3DText();
fitCameraToText(); fitCameraToText();
}); });
@ -71,8 +134,15 @@
c.height = 2; c.height = 2;
const ctx = c.getContext('2d'); const ctx = c.getContext('2d');
const gradient = ctx.createLinearGradient(0, 0, 0, 2); const gradient = ctx.createLinearGradient(0, 0, 0, 2);
gradient.addColorStop(0, '#1E1E2A');
gradient.addColorStop(1, '#4C4C6A'); if (isDarkTheme) {
gradient.addColorStop(0, '#1E1E2A');
gradient.addColorStop(1, '#4C4C6A');
} else {
gradient.addColorStop(0, '#E8E8ED');
gradient.addColorStop(1, '#F5F5F7');
}
ctx.fillStyle = gradient; ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 2, 2); ctx.fillRect(0, 0, 2, 2);
const texture = new THREE.Texture(c); const texture = new THREE.Texture(c);
@ -85,8 +155,15 @@
const geometry = new THREE.BufferGeometry(); const geometry = new THREE.BufferGeometry();
const positions = new Float32Array(particleCount * 3); const positions = new Float32Array(particleCount * 3);
const colors = new Float32Array(particleCount * 3); const colors = new Float32Array(particleCount * 3);
const colorStart = new THREE.Color(0x34000d);
const colorEnd = new THREE.Color(0x02066F); let colorStart, colorEnd;
if (isDarkTheme) {
colorStart = new THREE.Color(0x34000d);
colorEnd = new THREE.Color(0x02066F);
} else {
colorStart = new THREE.Color(0x7FB3D5);
colorEnd = new THREE.Color(0xC9ADA7);
}
for (let i = 0; i < particleCount; i++) { for (let i = 0; i < particleCount; i++) {
positions[i * 3] = (Math.random() - 0.5) * 400; positions[i * 3] = (Math.random() - 0.5) * 400;
@ -105,7 +182,7 @@
size: 0.7, size: 0.7,
vertexColors: true, vertexColors: true,
transparent: true, transparent: true,
opacity: 0.7, opacity: isDarkTheme ? 0.7 : 0.5,
blending: THREE.AdditiveBlending blending: THREE.AdditiveBlending
}); });
@ -113,11 +190,23 @@
scene.add(particleSystem); scene.add(particleSystem);
} }
function create3DText(font) { function create3DText() {
textGroup = new THREE.Group(); if (textGroup) {
scene.remove(textGroup);
textGroup.traverse(obj => {
if (obj.geometry) obj.geometry.dispose();
if (obj.material) obj.material.dispose();
});
}
const text1 = new THREE.TextGeometry("Well Be Back Soon", { textGroup = new THREE.Group();
font, const currentLang = translations[userLanguage];
const textColor = isDarkTheme ? 0xffffff : 0x1d1d1f;
const emissiveColor = isDarkTheme ? 0x111111 : 0x555555;
const text1 = new THREE.TextGeometry(currentLang.title, {
font: loadedFont,
size: 5, size: 5,
height: 1, height: 1,
curveSegments: 12, curveSegments: 12,
@ -126,20 +215,20 @@
bevelSize: 0.1, bevelSize: 0.1,
bevelSegments: 5 bevelSegments: 5
}); });
const material1 = new THREE.MeshPhongMaterial({color: 0xffffff, emissive: 0x111111}); const material1 = new THREE.MeshPhongMaterial({color: textColor, emissive: emissiveColor});
const mesh1 = new THREE.Mesh(text1, material1); const mesh1 = new THREE.Mesh(text1, material1);
mesh1.geometry.center(); mesh1.geometry.center();
mesh1.position.y = 5; mesh1.position.y = 5;
textGroup.add(mesh1); textGroup.add(mesh1);
const text2 = new THREE.TextGeometry("An update is in progress. Please check back later.", { const text2 = new THREE.TextGeometry(currentLang.subtitle, {
font, font: loadedFont,
size: 2, size: 2,
height: 0.5, height: 0.5,
curveSegments: 12, curveSegments: 12,
bevelEnabled: false bevelEnabled: false
}); });
const material2 = new THREE.MeshPhongMaterial({color: 0xffffff, emissive: 0x111111}); const material2 = new THREE.MeshPhongMaterial({color: textColor, emissive: emissiveColor});
const mesh2 = new THREE.Mesh(text2, material2); const mesh2 = new THREE.Mesh(text2, material2);
mesh2.geometry.center(); mesh2.geometry.center();
mesh2.position.y = -5; mesh2.position.y = -5;
@ -147,12 +236,18 @@
scene.add(textGroup); scene.add(textGroup);
const ambientLight = new THREE.AmbientLight(0x404040, 2); if (!scene.getObjectByName('ambientLight')) {
scene.add(ambientLight); const ambientLight = new THREE.AmbientLight(isDarkTheme ? 0x404040 : 0x808080, 2);
ambientLight.name = 'ambientLight';
scene.add(ambientLight);
}
const spotLight = new THREE.SpotLight(0xffffff, 1); if (!scene.getObjectByName('spotLight')) {
spotLight.position.set(100, 100, 100); const spotLight = new THREE.SpotLight(isDarkTheme ? 0xffffff : 0xf0f0f0, 1);
scene.add(spotLight); spotLight.position.set(100, 100, 100);
spotLight.name = 'spotLight';
scene.add(spotLight);
}
} }
function fitCameraToText() { function fitCameraToText() {

View file

@ -97,5 +97,8 @@ urlpatterns = [
"admin/doc/", "admin/doc/",
include("django.contrib.admindocs.urls"), include("django.contrib.admindocs.urls"),
), ),
*i18n_patterns(path("admin/", admin.site.urls)), path(
"admin/",
admin.site.urls,
),
] ]