mirror of
https://github.com/shadoll/p9o.git
synced 2025-12-20 01:25:51 +00:00
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:
36
README.md
Normal file
36
README.md
Normal 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
11
flags/english.svg
Normal 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
6
flags/french.svg
Normal 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
6
flags/german.svg
Normal 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
5
flags/latin.svg
Normal 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
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
5
flags/ukrainian.svg
Normal 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
36
index.html
Normal 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
160
script.js
Normal 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
189
style.css
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user