Add multilingual support with Ukrainian flag and phrases

- Created Ukrainian flag SVG file.
- Developed index.html to include structure for language switching.
- Implemented script.js for managing language phrases and automatic switching.
- Styled the application with style.css for a cohesive design and responsive layout.
This commit is contained in:
sHa
2025-05-20 12:47:55 +03:00
commit 636a563659
10 changed files with 4952 additions and 0 deletions

36
README.md Normal file
View File

@@ -0,0 +1,36 @@
# Omnia Possibilia
A minimalist single-page site featuring the timeless phrase "Omnia possibilia tempore et opibus" (All things are possible with time and resources) displayed in multiple languages.
## Features
- **Multi-language Support**: The phrase is displayed in multiple languages including Latin, English, Ukrainian, German, Spanish, and French.
- **Automatic Language Rotation**: The language automatically changes every 3 minutes, with visual progress indicator.
- **Background Flag Visualization**: Each language is accompanied by a subtle background featuring the corresponding country/region flag.
- **Elegant Typography**: Uses Google's "Great Vibes" font with proper support for both Latin and Cyrillic characters.
- **Responsive Design**: Adapts smoothly to different screen sizes.
- **Interactive Controls**: Users can manually switch to the next language using the provided button.
## Technical Implementation
- Pure HTML, CSS, and JavaScript with no external dependencies
- SVG flags stored locally for better performance
- Smooth transitions between languages and backgrounds
- Cross-browser compatibility with special attention to Safari rendering
- Preloaded assets for seamless experience
## Quote Translations
| Language | Translation |
|----------|-------------|
| Latin | Omnia possibilia tempore et opibus |
| English | All things are possible with time and resources |
| Ukrainian| Можливо все за наявності ресурсів та часу |
| German | Alles ist mit Zeit und Ressourcen möglich |
| Spanish | Todo es posible con tiempo y recursos |
| French | Tout est possible avec du temps et des ressources |
## Development
Developed by sHa as a simple, elegant display of a powerful philosophical concept across different cultures and languages.

11
flags/english.svg Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="400" viewBox="0 0 60 30">
<clipPath id="t">
<path d="M30,15 h30 v15 z v15 h-30 z h-30 v-15 z v-15 h30 z"/>
</clipPath>
<path d="M0,0 v30 h60 v-30 z" fill="#00247d"/>
<path d="M0,0 L60,30 M60,0 L0,30" stroke="#fff" stroke-width="6"/>
<path d="M0,0 L60,30 M60,0 L0,30" clip-path="url(#t)" stroke="#cf142b" stroke-width="4"/>
<path d="M30,0 v30 M0,15 h60" stroke="#fff" stroke-width="10"/>
<path d="M30,0 v30 M0,15 h60" stroke="#cf142b" stroke-width="6"/>
</svg>

After

Width:  |  Height:  |  Size: 577 B

6
flags/french.svg Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="533" viewBox="0 0 900 600">
<rect width="300" height="600" fill="#002654"/>
<rect width="300" height="600" x="300" fill="#FFFFFF"/>
<rect width="300" height="600" x="600" fill="#ED2939"/>
</svg>

After

Width:  |  Height:  |  Size: 300 B

6
flags/german.svg Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="480" viewBox="0 0 5 3">
<rect width="5" height="3" fill="#000"/>
<rect width="5" height="2" y="1" fill="#D00"/>
<rect width="5" height="1" y="2" fill="#FFCE00"/>
</svg>

After

Width:  |  Height:  |  Size: 274 B

5
flags/latin.svg Normal file
View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="800" viewBox="0 0 6 4">
<rect width="6" height="4" fill="#8e001c"/>
<rect width="3" height="4" x="3" fill="#ffb300"/>
</svg>

After

Width:  |  Height:  |  Size: 228 B

4498
flags/spanish.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 606 KiB

5
flags/ukrainian.svg Normal file
View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="533" viewBox="0 0 1200 800">
<rect width="1200" height="400" fill="#0057B7"/>
<rect width="1200" height="400" y="400" fill="#FFD700"/>
</svg>

After

Width:  |  Height:  |  Size: 245 B

36
index.html Normal file
View File

@@ -0,0 +1,36 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Omnia Possibilia</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Great+Vibes&subset=cyrillic,latin&display=swap" rel="stylesheet">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="progress-bar">
<div class="progress-line"></div>
</div>
<div class="flag-container">
<div id="flag-background-1" class="flag-background active"></div>
<div id="flag-background-2" class="flag-background"></div>
</div>
<div class="container">
<div class="quote">
<p id="phrase"></p>
</div>
</div>
<footer>
<p>Developed by sHa</p>
<div class="lang-controls">
<span id="current-lang-name">Latin</span>
<button id="next-lang-btn" aria-label="Next language">⏭︎</button>
</div>
</footer>
<script src="script.js"></script>
</body>
</html>

160
script.js Normal file
View File

@@ -0,0 +1,160 @@
document.addEventListener('DOMContentLoaded', function() {
const phrases = {
'la': { // Latin
text: 'Omnia possibilia tempore et opibus',
flag: 'flags/latin.svg',
name: 'Latin'
},
'en': { // English
text: 'All things are possible with time and resources',
flag: 'flags/english.svg',
name: 'English'
},
'uk': { // Ukrainian
text: 'Можливо все за наявності ресурсів та часу',
flag: 'flags/ukrainian.svg',
name: 'Ukrainian'
},
'de': { // German
text: 'Alles ist mit Zeit und Ressourcen möglich',
flag: 'flags/german.svg',
name: 'German'
},
'es': { // Spanish
text: 'Todo es posible con tiempo y recursos',
flag: 'flags/spanish.svg',
name: 'Spanish'
},
'fr': { // French
text: 'Tout est possible avec du temps et des ressources',
flag: 'flags/french.svg',
name: 'French'
}
};
// Preload all flag images
const preloadedFlags = {};
Object.keys(phrases).forEach(lang => {
const img = new Image();
img.src = phrases[lang].flag;
preloadedFlags[lang] = img;
});
const phraseElement = document.getElementById('phrase');
const flagBackground1 = document.getElementById('flag-background-1');
const flagBackground2 = document.getElementById('flag-background-2');
const quoteContainer = document.querySelector('.quote');
const progressLine = document.querySelector('.progress-line');
const nextLangBtn = document.getElementById('next-lang-btn');
const currentLangNameElement = document.getElementById('current-lang-name');
// Initialize the first background
flagBackground1.style.backgroundImage = `url('${phrases['la'].flag}')`;
let currentLang = 'la'; // Start with Latin
let languages = Object.keys(phrases);
let switchInterval = 3 * 60 * 1000; // 3 minutes
let progressInterval;
// Get browser language if available
const browserLang = navigator.language.substring(0, 2);
// Function to animate progress bar
function startProgressBar() {
// Reset progress bar
if (progressInterval) {
clearInterval(progressInterval);
}
progressLine.style.width = '0%';
// Animate progress bar over switchInterval duration
const startTime = Date.now();
progressInterval = setInterval(() => {
const elapsedTime = Date.now() - startTime;
const progress = Math.min((elapsedTime / switchInterval) * 100, 100);
progressLine.style.width = progress + '%';
if (progress >= 100) {
clearInterval(progressInterval);
}
}, 50); // Update every 50ms for smooth animation
}
// Function to change the phrase and flag
function changeLanguage(lang) {
quoteContainer.classList.add('fade-out');
// Get current and next background elements
const currentFlag = flagBackground1.classList.contains('active') ? flagBackground1 : flagBackground2;
const nextFlag = flagBackground1.classList.contains('active') ? flagBackground2 : flagBackground1;
// Prepare the next flag before showing it
const flagUrl = phrases[lang].flag;
nextFlag.style.backgroundImage = `url('${flagUrl}')`;
setTimeout(() => {
// Change the text
phraseElement.textContent = phrases[lang].text;
currentLangNameElement.textContent = phrases[lang].name;
// Swap the flags
currentFlag.classList.remove('active');
nextFlag.classList.add('active');
// Reset animation
quoteContainer.classList.remove('fade-out');
quoteContainer.classList.remove('visible');
// Trigger new animation after a short delay
setTimeout(() => {
quoteContainer.classList.add('visible');
}, 50);
currentLang = lang;
// Restart progress bar
startProgressBar();
}, 1000);
}
// Function to get next language
function getNextLanguage() {
// Filter out current language
const availableLangs = languages.filter(lang => lang !== currentLang);
// Choose random language
return availableLangs[Math.floor(Math.random() * availableLangs.length)];
}
// Add click event to next button
nextLangBtn.addEventListener('click', function() {
const nextLang = getNextLanguage();
changeLanguage(nextLang);
// Reset the automatic timer when manually changing
if (window.nextLangTimeout) {
clearTimeout(window.nextLangTimeout);
}
// Set up the next automatic change
window.nextLangTimeout = setTimeout(automaticChange, switchInterval);
});
// Function for automatic language change
function automaticChange() {
const nextLang = getNextLanguage();
changeLanguage(nextLang);
window.nextLangTimeout = setTimeout(automaticChange, switchInterval);
}
// Initial display - Latin
changeLanguage('la');
// After 3 minutes, switch to browser language if available, or English
window.nextLangTimeout = setTimeout(() => {
const nextLang = phrases[browserLang] ? browserLang : 'en';
changeLanguage(nextLang);
// Start random language rotation after first change
window.nextLangTimeout = setTimeout(automaticChange, switchInterval);
}, switchInterval);
});

189
style.css Normal file
View File

@@ -0,0 +1,189 @@
* {
margin: 0;
padding: 0;
}
body {
font-family: 'Great Vibes', cursive;
height: 100vh;
width: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: #fff;
position: relative;
background-color: #000;
}
.progress-bar {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 1px;
background-color: rgba(255, 255, 255, 0.1);
z-index: 10;
}
.progress-line {
height: 100%;
width: 0%;
background-color: rgba(255, 255, 255, 0.7);
}
.lang-controls {
position: absolute;
bottom: 0;
right: 10px;
display: flex;
align-items: center;
gap: 10px;
z-index: 100;
margin: 10px;
}
#current-lang-name {
color: rgba(255, 255, 255, 0.3);
font-family: Arial, Helvetica, sans-serif;
font-size: 14px;
letter-spacing: 1px;
text-transform: lowercase;
}
#next-lang-btn {
background: none;
border: none;
color: rgba(255, 255, 255, 0.2);
font-size: 20px;
cursor: pointer;
transition: all 0.3s ease;
outline: none;
display: flex;
justify-content: center;
align-items: center;
z-index: 100;
}
#next-lang-btn:hover {
color: rgba(255, 255, 255, 0.9);
}
#next-lang-btn:active {
transform: scale(0.95);
}
.flag-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
overflow: hidden;
}
.flag-background {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-size: cover !important;
background-position: center;
background-repeat: no-repeat;
opacity: 0;
transition: opacity 1s ease-in-out;
will-change: opacity;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
-webkit-transform: translateZ(0);
transform: translateZ(0);
}
.flag-background.active {
opacity: 0.15;
}
.container {
flex-grow: 1;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
max-width: 800px;
padding: 20px;
text-align: center;
}
.quote {
font-size: 3.5rem;
line-height: 1.4;
opacity: 0;
transform: translateY(20px);
word-wrap: break-word;
overflow-wrap: break-word;
font-family: 'Great Vibes', cursive;
font-weight: normal;
font-display: swap;
}
.quote.visible {
animation: fadeInUp 1.5s forwards;
}
.quote.fade-out {
animation: fadeOut 1s forwards;
}
footer {
width: 100%;
padding: 15px;
text-align: center;
font-size: 0.9rem;
background-color: rgba(0, 0, 0, 0.5);
position: absolute;
bottom: 0;
font-family: Arial, Helvetica, sans-serif;
color: #888;
}
@keyframes typeWriter {
from {
width: 0
}
to {
width: 100%
}
}
@keyframes fadeInUp {
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeOut {
to {
opacity: 0;
transform: translateY(-20px);
}
}
/* Media query for responsive design */
@media (max-width: 768px) {
.quote {
padding: 0 20px;
}
}
@media (max-width: 480px) {
.quote {
padding: 0 15px;
}
}