mirror of
https://github.com/godotengine/godot-website.git
synced 2025-12-31 09:48:43 +03:00
338 lines
12 KiB
HTML
338 lines
12 KiB
HTML
<input type="checkbox" id="nav_toggle_cb">
|
||
<header class="flex column">
|
||
<div class="container flex align-center">
|
||
<div id="nav_head">
|
||
<a href="/" id="logo-link">
|
||
<img class="nav-logo" src="/assets/logo.svg" width="136" height="48" alt="Godot Engine">
|
||
<img class="nav-logo dark-logo" src="/assets/logo_dark.svg" width="136" height="48" alt="Godot Engine">
|
||
</a>
|
||
<div class="mobile-links">
|
||
<span class="fund mobile"><a href="https://fund.godotengine.org"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style="width: 13px; fill: white; margin-right: 4px;"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M47.6 300.4L228.3 469.1c7.5 7 17.4 10.9 27.7 10.9s20.2-3.9 27.7-10.9L464.4 300.4c30.4-28.3 47.6-68 47.6-109.5v-5.8c0-69.9-50.5-129.5-119.4-141C347 36.5 300.6 51.4 268 84L256 96 244 84c-32.6-32.6-79-47.5-124.6-39.9C50.5 55.6 0 115.2 0 185.1v5.8c0 41.5 17.2 81.2 47.6 109.5z"/></svg> Donate</a></span>
|
||
<label for="nav_toggle_cb" id="nav_toggle_btn">
|
||
<img src="/assets/icons/hamburger.svg" width="24" height="24" alt="Main menu">
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<nav id="nav">
|
||
<ul class="left">
|
||
<!-- On hover, opens a dropdown containing the link to console support page -->
|
||
<li><a href="/features/" data-dropdown="features-dropdown">{% t header.features %}</a></li>
|
||
<li><a href="/showcase/">{% t header.showcase %}</a></li>
|
||
<li><a href="/blog/">{% t header.blog %}</a></li>
|
||
<li><a href="/community/" data-dropdown="community-dropdown">{% t header.community %}</a></li>
|
||
<li><a href="https://godotengine.org/asset-library/asset">{% t header.assets %}</a></li>
|
||
<!-- Show console support link in the mobile hamburger menu -->
|
||
<li class="mobile-only"><a href="/consoles/">{% t header.console_support %}</a></li>
|
||
</ul>
|
||
|
||
<ul class="right">
|
||
<li><a href="/download/windows/" class="set-os-download-url">{% t header.download %}</a></li>
|
||
<li><a href="https://docs.godotengine.org">{% t header.docs %}</a></li>
|
||
<li><a href="https://contributing.godotengine.org/en/latest/organization/how_to_contribute.html">{% t header.contribute %}</a></li>
|
||
{% if page.localize %}
|
||
<li class="language-selector" onclick="this.classList.toggle('open')">
|
||
<a class="mobile-language-selector" href="#">{% t header.language %}: </a>
|
||
<div class="language-dropdown">
|
||
{% for lang in page.localize %}
|
||
<div class="language-option"><a href="#" data-lang-path="{% if lang == 'en' %}/{% else %}/{{ lang }}/{% endif %}" data-lang="{{ lang }}" onclick="onSetLanguagePreference(event, this)"><span class="localize-language-label">{{ lang }}</span></a></div>
|
||
{% endfor %}
|
||
</div>
|
||
</li>
|
||
{% endif %}
|
||
<li class="fund desktop"><a href="https://fund.godotengine.org"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style="width: 13px; fill: white; margin-right: 4px; top: 1px;
|
||
position: relative;"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M47.6 300.4L228.3 469.1c7.5 7 17.4 10.9 27.7 10.9s20.2-3.9 27.7-10.9L464.4 300.4c30.4-28.3 47.6-68 47.6-109.5v-5.8c0-69.9-50.5-129.5-119.4-141C347 36.5 300.6 51.4 268 84L256 96 244 84c-32.6-32.6-79-47.5-124.6-39.9C50.5 55.6 0 115.2 0 185.1v5.8c0 41.5 17.2 81.2 47.6 109.5z"/></svg> {% t header.donate %}</a></li>
|
||
</ul>
|
||
</nav>
|
||
</div>
|
||
{% comment %}
|
||
{% if page.path == "pages/home.html" %}
|
||
<div class="banner-container" style="background-color: #fb99ff;">
|
||
<div class="container">
|
||
<div class="banner">
|
||
<div class="banner-text">
|
||
Godot 4.5 just released!
|
||
</div>
|
||
<div class="banner-text">
|
||
Check out the <a href="/releases/4.5/">release page!</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
{% endcomment %}
|
||
</header>
|
||
|
||
<!-- Dropdown menu positioned outside header to avoid backdrop-filter nesting issues -->
|
||
<div class="nav-dropdown-menu" id="features-dropdown">
|
||
<!-- The features link is only visible on high-resolution tablets -->
|
||
<!-- On desktop we show the dropdown on hover, and the user can navigate to the features page by clicking the link in main menu-->
|
||
<!-- And on small screens (width < 1200px) we switch to mobile hamburger menu -->
|
||
<!-- So we need this only to cover the edge case of high-resolution tablets -->
|
||
<a href="/features/" class="touch-only">{% t header.features %}</a>
|
||
<a href="/consoles/">{% t header.console_support %}</a>
|
||
</div>
|
||
|
||
<div class="nav-dropdown-menu" id="community-dropdown">
|
||
<a href="/community/" class="touch-only">{% t header.community %}</a>
|
||
<a href="https://forum.godotengine.org">{% t header.forum %}</a>
|
||
<a href="/events/">{% t header.events %}</a>
|
||
</div>
|
||
|
||
{% if page.localize %}
|
||
<script type="module">
|
||
let _languageSelector = null;
|
||
|
||
function isLanguageMatch(langA, langB, lax = false) {
|
||
const langALower = (langA ?? "").toLowerCase();
|
||
const langBLower = (langB ?? "").toLowerCase();
|
||
if (langALower === langBLower) {
|
||
return true;
|
||
}
|
||
if (!lax) {
|
||
return false;
|
||
}
|
||
return langALower.slice(0, 2) === langBLower.slice(0, 2);
|
||
}
|
||
|
||
function redirectTo(path) {
|
||
if (path == null) {
|
||
return;
|
||
}
|
||
window.location.href = path;
|
||
}
|
||
|
||
function getLanguageSelector() {
|
||
if (_languageSelector != null) {
|
||
return _languageSelector;
|
||
}
|
||
_languageSelector = document.querySelector(".language-selector");
|
||
if (_languageSelector == null) {
|
||
throw new Error("Could not find `.language-selector`");
|
||
}
|
||
return _languageSelector;
|
||
}
|
||
|
||
function setPreferredLanguage(lang) {
|
||
window.localStorage.setItem("preferred_language", lang);
|
||
}
|
||
|
||
function getPreferredLanguage() {
|
||
return window.localStorage.getItem("preferred_language");
|
||
}
|
||
|
||
function setLanguagePreference(path, lang) {
|
||
setPreferredLanguage(lang);
|
||
redirectTo(path);
|
||
}
|
||
|
||
function getLanguagePath(lang) {
|
||
const languageSelector = getLanguageSelector();
|
||
const languageAnchors = Array.from(
|
||
languageSelector.querySelectorAll(".language-option > a[data-lang]"),
|
||
);
|
||
for (const languageAnchor of languageAnchors) {
|
||
if (isLanguageMatch(lang, languageAnchor.dataset.lang)) {
|
||
return languageAnchor.dataset.langPath;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
function getBrowserPreferredLanguage() {
|
||
const languageSelector = getLanguageSelector();
|
||
const languageAnchors = Array.from(
|
||
languageSelector.querySelectorAll(".language-option > a[data-lang]"),
|
||
);
|
||
const languages = languageAnchors.map(
|
||
(languageAnchor) => languageAnchor.dataset.lang,
|
||
);
|
||
|
||
const matchLanguage = (currentLanguage) => {
|
||
for (const language of languages) {
|
||
if (isLanguageMatch(navigator.language, language)) {
|
||
return language;
|
||
}
|
||
}
|
||
for (const language of languages) {
|
||
if (isLanguageMatch(navigator.language, language, true)) {
|
||
return language;
|
||
}
|
||
}
|
||
return null;
|
||
};
|
||
|
||
let matchedLanguage = null;
|
||
|
||
if (navigator.language != null) {
|
||
matchedLanguage = matchLanguage(navigator.language);
|
||
if (matchedLanguage != null) {
|
||
return matchedLanguage;
|
||
}
|
||
}
|
||
|
||
for (const navigatorLanguage of navigator.languages ?? []) {
|
||
matchedLanguage = matchLanguage(navigatorLanguage);
|
||
if (matchedLanguage != null) {
|
||
return matchedLanguage;
|
||
}
|
||
}
|
||
|
||
return matchedLanguage;
|
||
}
|
||
|
||
function main() {
|
||
document.addEventListener("click", function (event) {
|
||
const languageSelector = getLanguageSelector();
|
||
// Check if the click is outside the language selector
|
||
if (!languageSelector.contains(event.target)) {
|
||
languageSelector.classList.remove("open");
|
||
}
|
||
});
|
||
|
||
const currentLanguage = document.documentElement.lang;
|
||
const preferredLanguage = getPreferredLanguage();
|
||
if (preferredLanguage == null) {
|
||
const browserPreferredLanguage = getBrowserPreferredLanguage();
|
||
if (browserPreferredLanguage == null) {
|
||
setPreferredLanguage("en");
|
||
const languagePath = getLanguagePath("en");
|
||
if (!window.location.href.startsWith(languagePath)) {
|
||
redirectTo(languagePath);
|
||
}
|
||
} else {
|
||
setPreferredLanguage(browserPreferredLanguage);
|
||
const languagePath = getLanguagePath(browserPreferredLanguage);
|
||
if (!window.location.href.startsWith(languagePath)) {
|
||
redirectTo(languagePath);
|
||
}
|
||
}
|
||
} else if (preferredLanguage !== currentLanguage && currentLanguage === "en") {
|
||
const languagePath = getLanguagePath(preferredLanguage);
|
||
if (!window.location.href.startsWith(languagePath)) {
|
||
redirectTo(languagePath);
|
||
}
|
||
} else {
|
||
setPreferredLanguage(currentLanguage);
|
||
}
|
||
|
||
// Language select event listener.
|
||
window.onSetLanguagePreference = (event, element) => {
|
||
event.preventDefault();
|
||
const path = element.dataset.langPath;
|
||
const lang = element.dataset.lang;
|
||
setLanguagePreference(path, lang);
|
||
};
|
||
}
|
||
|
||
main();
|
||
</script>
|
||
{% endif %}
|
||
|
||
<script>
|
||
// Open dropdown on hover (desktop) or tap (touch devices)
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
// Find all dropdown triggers
|
||
const dropdownTriggers = document.querySelectorAll('[data-dropdown]');
|
||
|
||
dropdownTriggers.forEach(trigger => {
|
||
const dropdownId = trigger.getAttribute('data-dropdown');
|
||
const dropdownMenu = document.getElementById(dropdownId);
|
||
|
||
if (dropdownMenu) {
|
||
let hideTimeout = -1;
|
||
const clearHideTimeout = () => {
|
||
if (hideTimeout === -1) {
|
||
return;
|
||
}
|
||
clearTimeout(hideTimeout);
|
||
hideTimeout = -1;
|
||
};
|
||
|
||
const isDropdownVisible = () => {
|
||
return dropdownMenu.style.display === 'block';
|
||
};
|
||
|
||
const showDropdown = () => {
|
||
if (isDropdownVisible()) {
|
||
return;
|
||
}
|
||
|
||
// Don't show dropdown on small screens (width < 1200px)
|
||
if (window.innerWidth < 1200) {
|
||
return;
|
||
}
|
||
clearHideTimeout();
|
||
const rect = trigger.getBoundingClientRect();
|
||
dropdownMenu.style.top = (rect.bottom) + 'px';
|
||
dropdownMenu.style.left = (rect.left) + 'px';
|
||
dropdownMenu.style.display = 'block';
|
||
trigger.classList.add('dropdown-open');
|
||
};
|
||
|
||
// Hide dropdown after a delay
|
||
const hideDropdown = ({ instant = false } = {}) => {
|
||
if (!isDropdownVisible()) {
|
||
return;
|
||
}
|
||
|
||
const hideDropdownTrigger = () => {
|
||
clearHideTimeout();
|
||
dropdownMenu.style.display = 'none';
|
||
trigger.classList.remove('dropdown-open');
|
||
};
|
||
|
||
if (instant) {
|
||
hideDropdownTrigger();
|
||
return;
|
||
}
|
||
hideTimeout = setTimeout(hideDropdownTrigger, 100);
|
||
}
|
||
|
||
// Toggle dropdown on high-resolution tablets
|
||
const toggleDropdown = (event) => {
|
||
if (window.innerWidth < 1200) {
|
||
return;
|
||
}
|
||
event.preventDefault();
|
||
if (isDropdownVisible()) {
|
||
hideDropdown({ instant: true })
|
||
} else {
|
||
showDropdown();
|
||
}
|
||
}
|
||
|
||
const onlyOnTouch = (callback) => (event) => {
|
||
if (event.pointerType === "touch") {
|
||
callback(event);
|
||
}
|
||
};
|
||
const notOnTouch = (callback) => (event) => {
|
||
if (event.pointerType !== "touch") {
|
||
callback(event);
|
||
}
|
||
}
|
||
|
||
// Touch device: use click/tap to toggle dropdown
|
||
trigger.addEventListener('pointerup', onlyOnTouch((event) => toggleDropdown(event)));
|
||
// Close dropdown when clicking outside (for touch devices)
|
||
document.documentElement.addEventListener('pointerup', onlyOnTouch((event) => {
|
||
if (!trigger.contains(event.target) && !dropdownMenu.contains(event.target)) {
|
||
hideDropdown({ instant: true });
|
||
}
|
||
}));
|
||
|
||
// Desktop: use hover
|
||
trigger.addEventListener('pointerenter', notOnTouch((_event) => showDropdown()));
|
||
trigger.addEventListener('pointerleave', notOnTouch((_event) => hideDropdown()));
|
||
|
||
// Keep dropdown visible when hovering over it
|
||
dropdownMenu.addEventListener('pointerenter', notOnTouch((_event) => clearHideTimeout()));
|
||
dropdownMenu.addEventListener('pointerleave', notOnTouch((_event) => hideDropdown()));
|
||
}
|
||
});
|
||
});
|
||
</script>
|
||
|
||
<main>
|