365 feature request add a thank you section to the about page (#404)

This commit is contained in:
Daniel Graf
2025-11-05 14:25:44 +01:00
committed by GitHub
parent 33d0cee9e9
commit f6a3acc41d
15 changed files with 1617 additions and 530 deletions

View File

@@ -19,6 +19,12 @@ jobs:
java-version: '24'
distribution: 'temurin'
cache: maven
- name: Generate acknowledgments data
run: |
chmod +x scripts/generate-acknowledgments.sh
./scripts/generate-acknowledgments.sh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build
run: mvn verify -DskipTests
- name: Login to Docker Hub

View File

@@ -19,6 +19,16 @@ jobs:
java-version: '24'
distribution: 'temurin'
cache: maven
- name: Install dependencies for acknowledgments script
run: |
sudo apt-get update
sudo apt-get install -y jq curl
- name: Generate acknowledgments data
run: |
chmod +x scripts/generate-acknowledgments.sh
./scripts/generate-acknowledgments.sh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build
run: mvn verify -DskipTests
- name: Create bundle
@@ -66,4 +76,4 @@ jobs:
staging/*.jar
LICENSE
- name: Update dependency graph
uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6
uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6

View File

@@ -0,0 +1,132 @@
#!/bin/bash
# Script to generate contributors.json and translators.json for the acknowledgments page
# This script should be run before building the application
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
RESOURCES_DIR="$SCRIPT_DIR/../src/main/resources"
# GitHub API settings
GITHUB_REPO="${GITHUB_REPOSITORY:-dedicatedcode/reitti}"
GITHUB_TOKEN="${GITHUB_TOKEN:-}"
echo "Generating acknowledgments data..."
# Function to fetch GitHub contributors
fetch_contributors() {
echo "Fetching contributors from GitHub..."
local api_url="https://api.github.com/repos/$GITHUB_REPO/contributors"
local headers=""
if [ -n "$GITHUB_TOKEN" ]; then
headers="-H \"Authorization: token $GITHUB_TOKEN\""
fi
# Fetch contributors and format as JSON
local contributors_data=$(eval "curl -s $headers \"$api_url\"" | jq '[
.[] |
select(.type == "User") |
select(.login != "dependabot[bot]") |
select(.login != "github-actions[bot]") |
select(.login != "weblate") |
select(.login != "dgraf-gh") |
{
name: (.name // .login),
role: "Contributor",
avatar: .avatar_url,
github: .login
}
]')
# Create contributors.json
echo "{\"contributors\": $contributors_data}" > "$RESOURCES_DIR/contributors.json"
echo "✓ Contributors data saved to contributors.json"
}
# Function to create projects.json with open source dependencies
create_projects_data() {
echo "Creating projects acknowledgments..."
local projects_json='[
{
"name": "Spring Boot",
"description": "Java-based framework for building production-ready applications",
"url": "https://spring.io/projects/spring-boot",
"license": "Apache 2.0",
"category": "Framework"
},
{
"name": "PostgreSQL",
"description": "Advanced open source relational database",
"url": "https://www.postgresql.org/",
"license": "PostgreSQL License",
"category": "Database"
},
{
"name": "Leaflet",
"description": "Open-source JavaScript library for mobile-friendly interactive maps",
"url": "https://leafletjs.com/",
"license": "BSD-2-Clause",
"category": "Frontend"
},
{
"name": "HTMX",
"description": "High power tools for HTML",
"url": "https://htmx.org/",
"license": "BSD-2-Clause",
"category": "Frontend"
},
{
"name": "Thymeleaf",
"description": "Modern server-side Java template engine",
"url": "https://www.thymeleaf.org/",
"license": "Apache 2.0",
"category": "Template Engine"
},
{
"name": "Chart.js",
"description": "Simple yet flexible JavaScript charting library",
"url": "https://www.chartjs.org/",
"license": "MIT",
"category": "Frontend"
}
]'
echo "{\"projects\": $projects_json}" > "$RESOURCES_DIR/projects.json"
echo "✓ Projects data saved to projects.json"
}
# Main execution
main() {
# Check if jq is available
if ! command -v jq &> /dev/null; then
echo "Error: jq is required but not installed."
exit 1
fi
# Check if curl is available
if ! command -v curl &> /dev/null; then
echo "Error: curl is required but not installed."
exit 1
fi
# Create resources directory if it doesn't exist
mkdir -p "$RESOURCES_DIR"
# Generate all acknowledgment files
fetch_contributors
create_projects_data
echo "✅ Acknowledgments data generation completed!"
echo "Generated files:"
echo " - $RESOURCES_DIR/contributors.json"
echo " - $RESOURCES_DIR/projects.json"
}
# Run main function
main "$@"

View File

@@ -4,23 +4,33 @@ package com.dedicatedcode.reitti.controller.settings;
import com.dedicatedcode.reitti.model.Role;
import com.dedicatedcode.reitti.model.security.User;
import com.dedicatedcode.reitti.service.VersionService;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import java.io.IOException;
import java.util.List;
import java.util.Map;
@Controller
@RequestMapping("/settings")
public class AboutController {
private final VersionService versionService;
private final boolean dataManagementEnabled;
private final ObjectMapper objectMapper;
public AboutController(VersionService versionService,
@Value("${reitti.data-management.enabled:false}") boolean dataManagementEnabled) {
@Value("${reitti.data-management.enabled:false}") boolean dataManagementEnabled,
ObjectMapper objectMapper) {
this.versionService = versionService;
this.dataManagementEnabled = dataManagementEnabled;
this.objectMapper = objectMapper;
}
@GetMapping("/about")
@@ -31,7 +41,31 @@ public class AboutController {
model.addAttribute("buildVersion", this.versionService.getVersion());
model.addAttribute("gitCommitDetails", this.versionService.getCommitDetails());
model.addAttribute("buildTime", this.versionService.getBuildTime());
// Load acknowledgments data
try {
model.addAttribute("contributors", loadContributors());
model.addAttribute("projects", loadProjects());
} catch (IOException e) {
// Log error and continue without acknowledgments
model.addAttribute("contributors", List.of());
model.addAttribute("projects", List.of());
}
return "settings/about";
}
private List<Map<String, Object>> loadContributors() throws IOException {
var resource = new ClassPathResource("contributors.json");
var data = objectMapper.readValue(resource.getInputStream(), new TypeReference<Map<String, Object>>() {});
return (List<Map<String, Object>>) data.get("contributors");
}
private List<Map<String, Object>> loadProjects() throws IOException {
var resource = new ClassPathResource("projects.json");
var data = objectMapper.readValue(resource.getInputStream(), new TypeReference<Map<String, Object>>() {});
return (List<Map<String, Object>>) data.get("projects");
}
}

View File

@@ -0,0 +1,3 @@
{
"contributors": []
}

View File

@@ -1280,3 +1280,12 @@ transportation.modes.reclassify.button=Reclassify All Trips
transportation.modes.reclassify.processing=Processing...
transportation.modes.reclassify.started=Reclassification started successfully. This process will run in the background.
transportation.modes.reclassify.error=Failed to start reclassification. Please try again.
about.acknowledgments.title=Acknowledgments
about.acknowledgments.subtitle=Reitti wouldn't be possible without the amazing contributions from our community and the incredible open-source projects we build upon.
about.contributors.title=Contributors
about.translators.title=Translators
about.projects.title=Open Source Projects
about.projects.visit=Visit Project
about.thankyou.title=Thank You!
about.thankyou.message=Every contribution, no matter how small, helps make Reitti better for everyone. We're grateful for your support and dedication to the open-source community.

View File

@@ -1210,5 +1210,15 @@ memory.processing.step.accommodation=Unterkunft wird bestimmt...
memory.processing.step.texts=Texte werden generiert...
memory.processing.step.images=Bilder werden kopiert...
memory.processing.step.counter=Schritt {0} von {1}
memory.block.trip.empty=Es sind keine Reisen für diesen Block ausgewählt.
memory.block.visit.empty=Es sind keine Besuche für diesen Block ausgewählt.
memory.block.trip.empty=Es sind keine Reisen f\u00FCr diesen Block ausgew\u00E4hlt.
memory.block.visit.empty=Es sind keine Besuche f\u00FCr diesen Block ausgew\u00E4hlt.
about.acknowledgments.title=Danksagungen
about.acknowledgments.subtitle=Reitti w\u00E4re ohne die gro\u00DFartigen Beitr\u00E4ge unserer Community und die unglaublichen Open-Source-Projekte, auf denen wir aufbauen, nicht m\u00F6glich.
about.contributors.title=Mitwirkende
about.translators.title=\u00DCbersetzer
about.projects.title=Open-Source-Projekte
about.projects.visit=Projekt besuchen
about.thankyou.title=Vielen Dank!
about.thankyou.message=Jeder Beitrag, egal wie klein, hilft dabei, Reitti f\u00FCr alle besser zu machen. Wir sind dankbar f\u00FCr Ihre Unterst\u00FCtzung und Ihr Engagement f\u00FCr die Open-Source-Community.

View File

@@ -606,4 +606,14 @@ memory.processing.step.visits=Luodaan vierailutietueita...
memory.processing.step.accommodation=M\u00E4\u00E4ritet\u00E4\u00E4n majoitusta...
memory.processing.step.texts=Generoidaan tekstej\u00E4...
memory.processing.step.images=Kopioidaan kuvia...
memory.processing.step.counter=Vaihe {0}/{1}
memory.processing.step.counter=Vaihe {0}/{1}
about.acknowledgments.title=Kiitokset
about.acknowledgments.subtitle=Reitti ei olisi mahdollinen ilman yhteis\u00F6mme upeita panoksia ja uskomattomia avoimen l\u00E4hdekoodin projekteja, joiden varaan rakennamme.
about.contributors.title=Avustajat
about.translators.title=K\u00E4\u00E4nt\u00E4j\u00E4t
about.projects.title=Avoimen L\u00E4hdekoodin Projektit
about.projects.visit=Vieraile Projektissa
about.thankyou.title=Kiitos!
about.thankyou.message=Jokainen panos, oli se kuinka pieni tahansa, auttaa tekem\u00E4\u00E4n Reitist\u00E4 paremman kaikille. Olemme kiitollisia tuestanne ja omistautumisestanne avoimen l\u00E4hdekoodin yhteis\u00F6lle.

View File

@@ -269,7 +269,7 @@ integrations.owntracks.recorder.device.id.placeholder=Entrez l'ID de l'appareil
integrations.owntracks.recorder.enabled=Activer l'int\u00E9gration
integrations.owntracks.recorder.save=Sauvegarder la configuration
integrations.owntracks.recorder.test.connection=Tester la connexion
integrations.owntracks.recorder.connection.success=Connexion réussie
integrations.owntracks.recorder.connection.success=Connexion r\u00E9ussie
integrations.owntracks.recorder.connection.failed=Connexion \u00E9chou\u00E9e : {0}
integrations.owntracks.recorder.config.saved=Configuration OwnTracks Recorder sauvegard\u00E9e avec succ\u00E8s
integrations.owntracks.recorder.config.error=Erreur lors de la sauvegarde de la configuration : {0}
@@ -608,4 +608,14 @@ memory.processing.step.visits=Cr\u00E9ation des enregistrements de visite...
memory.processing.step.accommodation=D\u00E9termination de l'h\u00E9bergement...
memory.processing.step.texts=G\u00E9n\u00E9ration des textes...
memory.processing.step.images=Copie des images...
memory.processing.step.counter=\u00C9tape {0} sur {1}
memory.processing.step.counter=\u00C9tape {0} sur {1}
about.acknowledgments.title=Remerciements
about.acknowledgments.subtitle=Reitti ne serait pas possible sans les contributions extraordinaires de notre communaut\u00E9 et les incroyables projets open-source sur lesquels nous nous appuyons.
about.contributors.title=Contributeurs
about.translators.title=Traducteurs
about.projects.title=Projets Open Source
about.projects.visit=Visiter le Projet
about.thankyou.title=Merci !
about.thankyou.message=Chaque contribution, aussi petite soit-elle, aide \u00E0 am\u00E9liorer Reitti pour tout le monde. Nous sommes reconnaissants pour votre soutien et votre d\u00E9vouement \u00E0 la communaut\u00E9 open-source.

File diff suppressed because it is too large Load Diff

View File

@@ -1,114 +1,123 @@
nav.statistics=Статистика
nav.settings=Настройки
nav.logout=Выход
nav.logout.tooltip=Выход
timeline.loading=Загрузка…
timeline.distance=Дистанция
timeline.duration=Продолжительность
datepicker.today=Сегодня
datepicker.days.sun=Вс
datepicker.days.mon=Пон
datepicker.days.tue=Вт
datepicker.days.wed=Ср
datepicker.days.thu=Чт
datepicker.days.fri=Пт
datepicker.days.sat=Сб
datepicker.months.jan=Янв
datepicker.months.feb=Фев
datepicker.months.mar=Мар
datepicker.months.apr=Апр
datepicker.months.may=Май
datepicker.months.jun=Июнь
datepicker.months.jul=Июль
datepicker.months.aug=Авг
datepicker.months.sep=Сен
datepicker.months.oct=Окт
datepicker.months.nov=Нояб
datepicker.months.dec=Дек
settings.title=Настройки
settings.api.tokens=API токены
settings.user.management=Управление пользователем
tokens.title=API токены
tokens.create.title=Создать новый токен
map.auto-update.latest-location=Последнее местоположение
export.title=Экспорт данных
export.date.range=Диапазон дат
export.start.date=Начальная дата
export.end.date=Конечная дата
export.gpx.button=Экспорт как GPX
export.raw.data.title=Raw данные местоположения
export.raw.data.table.latitude=Широта
export.raw.data.table.longitude=Долгота
export.raw.data.table.accuracy=Точность (м)
export.raw.data.no.data=Не найдено мест в выбранном диапазоне дат
export.gpx.success=GPX файл экспортирован удачно
export.gpx.error=Ошибка экспорта GPX: {0}
error.page.title=Ошибка - Reitti
error.title=Упс! Что то пошло не так
error.generic.message=Неожиданная ошибка. Попробуйте еще раз позже.
error.technical.details=Технические детали
error.action.home=Домой
error.action.back=Назад
error.action.retry=Еще раз
share-access.title=Поделиться доступом
magic.links.new.token.value=Только токен:
magic.links.info.security.title=Вопросы безопасности
magic.links.info.security.point1=Любой, у кого есть ссылка, может получить доступ к вашим данным, относитесь к ним как к паролю
magic.links.info.security.point2=Ссылки не могут быть восстановлены, если потеряете, надо будет создать новые
magic.links.info.security.point3=Установите даты истечения срока действия временного общего доступа, чтобы ограничить продолжительность доступа
magic.links.info.security.point4=Немедленно ссылки удалите, когда они станут не нужны
magic.links.info.security.point5=Мониторинг столбца 'Последний используемый' для отслеживания доступа
magic.links.info.access.levels.title=Уровни доступа
magic.links.info.access.full.description=Полный доступ ко всем данным и истории вашего местоположения
magic.links.info.access.live.description=Доступ только к данным о текущем/недавнем местоположении
form.cancel=Отмена
form.previous=Предыдущий
form.next=Следующий
form.refresh=Обновить
message.success.token.created=Токен успешно создан
message.success.token.deleted=Токен успешно удален
message.success.user.created=Пользователь успешно создан
message.success.user.updated=Пользователь успешно обновлен
message.success.user.deleted=Пользователь успешно удален
message.success.place.updated=Место успешно обновлено
message.error.token.creation=Ошибка создания токена: {0}
message.error.token.deletion=Ошибка удаления токена:{0}
message.error.user.creation=Ошибка при создании пользователя: {0}
message.error.user.update=Ошибка обновления пользователя: {0}
message.error.user.deletion=Ошибка удаления пользователя: {0}
message.error.user.self.delete=Вы не можете удалить свой аккаунт
message.error.place.update=Ошибка обновления места: {0}
message.relogin.required=Ваше имя пользователя было изменено на : {0}. Вам необходимо выйти и зайти снова с вашим новым именем пользователя.
message.error.access.denied=Доступ запрещен. У вас нет разрешения на выполнение этого действия.
upload.title=Импорт данных локаций
nav.statistics=\u0421\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043A\u0430
nav.settings=\u041D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438
nav.logout=\u0412\u044B\u0445\u043E\u0434
nav.logout.tooltip=\u0412\u044B\u0445\u043E\u0434
timeline.loading=\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430\u2026
timeline.distance=\u0414\u0438\u0441\u0442\u0430\u043D\u0446\u0438\u044F
timeline.duration=\u041F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u0435\u043B\u044C\u043D\u043E\u0441\u0442\u044C
datepicker.today=\u0421\u0435\u0433\u043E\u0434\u043D\u044F
datepicker.days.sun=\u0412\u0441
datepicker.days.mon=\u041F\u043E\u043D
datepicker.days.tue=\u0412\u0442
datepicker.days.wed=\u0421\u0440
datepicker.days.thu=\u0427\u0442
datepicker.days.fri=\u041F\u0442
datepicker.days.sat=\u0421\u0431
datepicker.months.jan=\u042F\u043D\u0432
datepicker.months.feb=\u0424\u0435\u0432
datepicker.months.mar=\u041C\u0430\u0440
datepicker.months.apr=\u0410\u043F\u0440
datepicker.months.may=\u041C\u0430\u0439
datepicker.months.jun=\u0418\u044E\u043D\u044C
datepicker.months.jul=\u0418\u044E\u043B\u044C
datepicker.months.aug=\u0410\u0432\u0433
datepicker.months.sep=\u0421\u0435\u043D
datepicker.months.oct=\u041E\u043A\u0442
datepicker.months.nov=\u041D\u043E\u044F\u0431
datepicker.months.dec=\u0414\u0435\u043A
settings.title=\u041D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438
settings.api.tokens=API \u0442\u043E\u043A\u0435\u043D\u044B
settings.user.management=\u0423\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435 \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u0435\u043C
tokens.title=API \u0442\u043E\u043A\u0435\u043D\u044B
tokens.create.title=\u0421\u043E\u0437\u0434\u0430\u0442\u044C \u043D\u043E\u0432\u044B\u0439 \u0442\u043E\u043A\u0435\u043D
map.auto-update.latest-location=\u041F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u0435 \u043C\u0435\u0441\u0442\u043E\u043F\u043E\u043B\u043E\u0436\u0435\u043D\u0438\u0435
export.title=\u042D\u043A\u0441\u043F\u043E\u0440\u0442 \u0434\u0430\u043D\u043D\u044B\u0445
export.date.range=\u0414\u0438\u0430\u043F\u0430\u0437\u043E\u043D \u0434\u0430\u0442
export.start.date=\u041D\u0430\u0447\u0430\u043B\u044C\u043D\u0430\u044F \u0434\u0430\u0442\u0430
export.end.date=\u041A\u043E\u043D\u0435\u0447\u043D\u0430\u044F \u0434\u0430\u0442\u0430
export.gpx.button=\u042D\u043A\u0441\u043F\u043E\u0440\u0442 \u043A\u0430\u043A GPX
export.raw.data.title=Raw \u0434\u0430\u043D\u043D\u044B\u0435 \u043C\u0435\u0441\u0442\u043E\u043F\u043E\u043B\u043E\u0436\u0435\u043D\u0438\u044F
export.raw.data.table.latitude=\u0428\u0438\u0440\u043E\u0442\u0430
export.raw.data.table.longitude=\u0414\u043E\u043B\u0433\u043E\u0442\u0430
export.raw.data.table.accuracy=\u0422\u043E\u0447\u043D\u043E\u0441\u0442\u044C (\u043C)
export.raw.data.no.data=\u041D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E \u043C\u0435\u0441\u0442 \u0432 \u0432\u044B\u0431\u0440\u0430\u043D\u043D\u043E\u043C \u0434\u0438\u0430\u043F\u0430\u0437\u043E\u043D\u0435 \u0434\u0430\u0442
export.gpx.success=GPX \u0444\u0430\u0439\u043B \u044D\u043A\u0441\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u043D \u0443\u0434\u0430\u0447\u043D\u043E
export.gpx.error=\u041E\u0448\u0438\u0431\u043A\u0430 \u044D\u043A\u0441\u043F\u043E\u0440\u0442\u0430 GPX: {0}
error.page.title=\u041E\u0448\u0438\u0431\u043A\u0430 - Reitti
error.title=\u0423\u043F\u0441! \u0427\u0442\u043E \u0442\u043E \u043F\u043E\u0448\u043B\u043E \u043D\u0435 \u0442\u0430\u043A
error.generic.message=\u041D\u0435\u043E\u0436\u0438\u0434\u0430\u043D\u043D\u0430\u044F \u043E\u0448\u0438\u0431\u043A\u0430. \u041F\u043E\u043F\u0440\u043E\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437 \u043F\u043E\u0437\u0436\u0435.
error.technical.details=\u0422\u0435\u0445\u043D\u0438\u0447\u0435\u0441\u043A\u0438\u0435 \u0434\u0435\u0442\u0430\u043B\u0438
error.action.home=\u0414\u043E\u043C\u043E\u0439
error.action.back=\u041D\u0430\u0437\u0430\u0434
error.action.retry=\u0415\u0449\u0435 \u0440\u0430\u0437
share-access.title=\u041F\u043E\u0434\u0435\u043B\u0438\u0442\u044C\u0441\u044F \u0434\u043E\u0441\u0442\u0443\u043F\u043E\u043C
magic.links.new.token.value=\u0422\u043E\u043B\u044C\u043A\u043E \u0442\u043E\u043A\u0435\u043D:
magic.links.info.security.title=\u0412\u043E\u043F\u0440\u043E\u0441\u044B \u0431\u0435\u0437\u043E\u043F\u0430\u0441\u043D\u043E\u0441\u0442\u0438
magic.links.info.security.point1=\u041B\u044E\u0431\u043E\u0439, \u0443 \u043A\u043E\u0433\u043E \u0435\u0441\u0442\u044C \u0441\u0441\u044B\u043B\u043A\u0430, \u043C\u043E\u0436\u0435\u0442 \u043F\u043E\u043B\u0443\u0447\u0438\u0442\u044C \u0434\u043E\u0441\u0442\u0443\u043F \u043A \u0432\u0430\u0448\u0438\u043C \u0434\u0430\u043D\u043D\u044B\u043C, \u043E\u0442\u043D\u043E\u0441\u0438\u0442\u0435\u0441\u044C \u043A \u043D\u0438\u043C \u043A\u0430\u043A \u043A \u043F\u0430\u0440\u043E\u043B\u044E
magic.links.info.security.point2=\u0421\u0441\u044B\u043B\u043A\u0438 \u043D\u0435 \u043C\u043E\u0433\u0443\u0442 \u0431\u044B\u0442\u044C \u0432\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u044B, \u0435\u0441\u043B\u0438 \u043F\u043E\u0442\u0435\u0440\u044F\u0435\u0442\u0435, \u043D\u0430\u0434\u043E \u0431\u0443\u0434\u0435\u0442 \u0441\u043E\u0437\u0434\u0430\u0442\u044C \u043D\u043E\u0432\u044B\u0435
magic.links.info.security.point3=\u0423\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u0435 \u0434\u0430\u0442\u044B \u0438\u0441\u0442\u0435\u0447\u0435\u043D\u0438\u044F \u0441\u0440\u043E\u043A\u0430 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044F \u0432\u0440\u0435\u043C\u0435\u043D\u043D\u043E\u0433\u043E \u043E\u0431\u0449\u0435\u0433\u043E \u0434\u043E\u0441\u0442\u0443\u043F\u0430, \u0447\u0442\u043E\u0431\u044B \u043E\u0433\u0440\u0430\u043D\u0438\u0447\u0438\u0442\u044C \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u0435\u043B\u044C\u043D\u043E\u0441\u0442\u044C \u0434\u043E\u0441\u0442\u0443\u043F\u0430
magic.links.info.security.point4=\u041D\u0435\u043C\u0435\u0434\u043B\u0435\u043D\u043D\u043E \u0441\u0441\u044B\u043B\u043A\u0438 \u0443\u0434\u0430\u043B\u0438\u0442\u0435, \u043A\u043E\u0433\u0434\u0430 \u043E\u043D\u0438 \u0441\u0442\u0430\u043D\u0443\u0442 \u043D\u0435 \u043D\u0443\u0436\u043D\u044B
magic.links.info.security.point5=\u041C\u043E\u043D\u0438\u0442\u043E\u0440\u0438\u043D\u0433 \u0441\u0442\u043E\u043B\u0431\u0446\u0430 '\u041F\u043E\u0441\u043B\u0435\u0434\u043D\u0438\u0439 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0435\u043C\u044B\u0439' \u0434\u043B\u044F \u043E\u0442\u0441\u043B\u0435\u0436\u0438\u0432\u0430\u043D\u0438\u044F \u0434\u043E\u0441\u0442\u0443\u043F\u0430
magic.links.info.access.levels.title=\u0423\u0440\u043E\u0432\u043D\u0438 \u0434\u043E\u0441\u0442\u0443\u043F\u0430
magic.links.info.access.full.description=\u041F\u043E\u043B\u043D\u044B\u0439 \u0434\u043E\u0441\u0442\u0443\u043F \u043A\u043E \u0432\u0441\u0435\u043C \u0434\u0430\u043D\u043D\u044B\u043C \u0438 \u0438\u0441\u0442\u043E\u0440\u0438\u0438 \u0432\u0430\u0448\u0435\u0433\u043E \u043C\u0435\u0441\u0442\u043E\u043F\u043E\u043B\u043E\u0436\u0435\u043D\u0438\u044F
magic.links.info.access.live.description=\u0414\u043E\u0441\u0442\u0443\u043F \u0442\u043E\u043B\u044C\u043A\u043E \u043A \u0434\u0430\u043D\u043D\u044B\u043C \u043E \u0442\u0435\u043A\u0443\u0449\u0435\u043C/\u043D\u0435\u0434\u0430\u0432\u043D\u0435\u043C \u043C\u0435\u0441\u0442\u043E\u043F\u043E\u043B\u043E\u0436\u0435\u043D\u0438\u0438
form.cancel=\u041E\u0442\u043C\u0435\u043D\u0430
form.previous=\u041F\u0440\u0435\u0434\u044B\u0434\u0443\u0449\u0438\u0439
form.next=\u0421\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u0439
form.refresh=\u041E\u0431\u043D\u043E\u0432\u0438\u0442\u044C
message.success.token.created=\u0422\u043E\u043A\u0435\u043D \u0443\u0441\u043F\u0435\u0448\u043D\u043E \u0441\u043E\u0437\u0434\u0430\u043D
message.success.token.deleted=\u0422\u043E\u043A\u0435\u043D \u0443\u0441\u043F\u0435\u0448\u043D\u043E \u0443\u0434\u0430\u043B\u0435\u043D
message.success.user.created=\u041F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044C \u0443\u0441\u043F\u0435\u0448\u043D\u043E \u0441\u043E\u0437\u0434\u0430\u043D
message.success.user.updated=\u041F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044C \u0443\u0441\u043F\u0435\u0448\u043D\u043E \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D
message.success.user.deleted=\u041F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044C \u0443\u0441\u043F\u0435\u0448\u043D\u043E \u0443\u0434\u0430\u043B\u0435\u043D
message.success.place.updated=\u041C\u0435\u0441\u0442\u043E \u0443\u0441\u043F\u0435\u0448\u043D\u043E \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u043E
message.error.token.creation=\u041E\u0448\u0438\u0431\u043A\u0430 \u0441\u043E\u0437\u0434\u0430\u043D\u0438\u044F \u0442\u043E\u043A\u0435\u043D\u0430: {0}
message.error.token.deletion=\u041E\u0448\u0438\u0431\u043A\u0430 \u0443\u0434\u0430\u043B\u0435\u043D\u0438\u044F \u0442\u043E\u043A\u0435\u043D\u0430:{0}
message.error.user.creation=\u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u0438 \u0441\u043E\u0437\u0434\u0430\u043D\u0438\u0438 \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F: {0}
message.error.user.update=\u041E\u0448\u0438\u0431\u043A\u0430 \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u044F \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F: {0}
message.error.user.deletion=\u041E\u0448\u0438\u0431\u043A\u0430 \u0443\u0434\u0430\u043B\u0435\u043D\u0438\u044F \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F: {0}
message.error.user.self.delete=\u0412\u044B \u043D\u0435 \u043C\u043E\u0436\u0435\u0442\u0435 \u0443\u0434\u0430\u043B\u0438\u0442\u044C \u0441\u0432\u043E\u0439 \u0430\u043A\u043A\u0430\u0443\u043D\u0442
message.error.place.update=\u041E\u0448\u0438\u0431\u043A\u0430 \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u044F \u043C\u0435\u0441\u0442\u0430: {0}
message.relogin.required=\u0412\u0430\u0448\u0435 \u0438\u043C\u044F \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F \u0431\u044B\u043B\u043E \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u043E \u043D\u0430 : {0}. \u0412\u0430\u043C \u043D\u0435\u043E\u0431\u0445\u043E\u0434\u0438\u043C\u043E \u0432\u044B\u0439\u0442\u0438 \u0438 \u0437\u0430\u0439\u0442\u0438 \u0441\u043D\u043E\u0432\u0430 \u0441 \u0432\u0430\u0448\u0438\u043C \u043D\u043E\u0432\u044B\u043C \u0438\u043C\u0435\u043D\u0435\u043C \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F.
message.error.access.denied=\u0414\u043E\u0441\u0442\u0443\u043F \u0437\u0430\u043F\u0440\u0435\u0449\u0435\u043D. \u0423 \u0432\u0430\u0441 \u043D\u0435\u0442 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043D\u0438\u044F \u043D\u0430 \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u0435 \u044D\u0442\u043E\u0433\u043E \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044F.
upload.title=\u0418\u043C\u043F\u043E\u0440\u0442 \u0434\u0430\u043D\u043D\u044B\u0445 \u043B\u043E\u043A\u0430\u0446\u0438\u0439
upload.gpx.title=GPX Files
upload.gpx.description=Загрузите файлы GPX с устройств GPS или приложения для отслеживания. Файлы GPX содержат путевые точки, треки и маршруты с временными метками, которые могут быть обработаны в историю вашего местоположения.
upload.gpx.description=\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u0435 \u0444\u0430\u0439\u043B\u044B GPX \u0441 \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432 GPS \u0438\u043B\u0438 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F \u0434\u043B\u044F \u043E\u0442\u0441\u043B\u0435\u0436\u0438\u0432\u0430\u043D\u0438\u044F. \u0424\u0430\u0439\u043B\u044B GPX \u0441\u043E\u0434\u0435\u0440\u0436\u0430\u0442 \u043F\u0443\u0442\u0435\u0432\u044B\u0435 \u0442\u043E\u0447\u043A\u0438, \u0442\u0440\u0435\u043A\u0438 \u0438 \u043C\u0430\u0440\u0448\u0440\u0443\u0442\u044B \u0441 \u0432\u0440\u0435\u043C\u0435\u043D\u043D\u044B\u043C\u0438 \u043C\u0435\u0442\u043A\u0430\u043C\u0438, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u043C\u043E\u0433\u0443\u0442 \u0431\u044B\u0442\u044C \u043E\u0431\u0440\u0430\u0431\u043E\u0442\u0430\u043D\u044B \u0432 \u0438\u0441\u0442\u043E\u0440\u0438\u044E \u0432\u0430\u0448\u0435\u0433\u043E \u043C\u0435\u0441\u0442\u043E\u043F\u043E\u043B\u043E\u0436\u0435\u043D\u0438\u044F.
upload.google.title=Google Takeout
units.metric.description=(км, м)
units.imperial=Имперская
units.imperial.description=(ми, фт)
places.title=Значительные места
places.no.places=Никаких значительных мест.
places.page.info=Страница {0} из {1}
places.name.label=Имя
places.address.label=Адрес
places.category.label=Категория
places.coordinates.label=Координаты
places.address.not.available=Недоступен
places.category.not.categorized=Не категоризирован
units.metric.description=(\u043A\u043C, \u043C)
units.imperial=\u0418\u043C\u043F\u0435\u0440\u0441\u043A\u0430\u044F
units.imperial.description=(\u043C\u0438, \u0444\u0442)
places.title=\u0417\u043D\u0430\u0447\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u0435 \u043C\u0435\u0441\u0442\u0430
places.no.places=\u041D\u0438\u043A\u0430\u043A\u0438\u0445 \u0437\u043D\u0430\u0447\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u0445 \u043C\u0435\u0441\u0442.
places.page.info=\u0421\u0442\u0440\u0430\u043D\u0438\u0446\u0430 {0} \u0438\u0437 {1}
places.name.label=\u0418\u043C\u044F
places.address.label=\u0410\u0434\u0440\u0435\u0441
places.category.label=\u041A\u0430\u0442\u0435\u0433\u043E\u0440\u0438\u044F
places.coordinates.label=\u041A\u043E\u043E\u0440\u0434\u0438\u043D\u0430\u0442\u044B
places.address.not.available=\u041D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D
places.category.not.categorized=\u041D\u0435 \u043A\u0430\u0442\u0435\u0433\u043E\u0440\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u043D
places.geocode.button=Geocode
places.geocode.confirm=Вы уверены, что хотите перекодировать это место? Это позволит очистить текущий адрес и запросить новый.
places.geocode.success=Место, запланированное для геокодирования
places.address.placeholder=Введите адрес
places.geocoding.response.button=Вид Geocoding
places.geocoding.response.back=Назад в Места
places.geocoding.response.provider=Поставщик
places.geocoding.response.status=Статус
places.geocoding.response.fetched.at=На
places.geocode.confirm=\u0412\u044B \u0443\u0432\u0435\u0440\u0435\u043D\u044B, \u0447\u0442\u043E \u0445\u043E\u0442\u0438\u0442\u0435 \u043F\u0435\u0440\u0435\u043A\u043E\u0434\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u044D\u0442\u043E \u043C\u0435\u0441\u0442\u043E? \u042D\u0442\u043E \u043F\u043E\u0437\u0432\u043E\u043B\u0438\u0442 \u043E\u0447\u0438\u0441\u0442\u0438\u0442\u044C \u0442\u0435\u043A\u0443\u0449\u0438\u0439 \u0430\u0434\u0440\u0435\u0441 \u0438 \u0437\u0430\u043F\u0440\u043E\u0441\u0438\u0442\u044C \u043D\u043E\u0432\u044B\u0439.
places.geocode.success=\u041C\u0435\u0441\u0442\u043E, \u0437\u0430\u043F\u043B\u0430\u043D\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u043E\u0435 \u0434\u043B\u044F \u0433\u0435\u043E\u043A\u043E\u0434\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u044F
places.address.placeholder=\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0430\u0434\u0440\u0435\u0441
places.geocoding.response.button=\u0412\u0438\u0434 Geocoding
places.geocoding.response.back=\u041D\u0430\u0437\u0430\u0434 \u0432 \u041C\u0435\u0441\u0442\u0430
places.geocoding.response.provider=\u041F\u043E\u0441\u0442\u0430\u0432\u0449\u0438\u043A
places.geocoding.response.status=\u0421\u0442\u0430\u0442\u0443\u0441
places.geocoding.response.fetched.at=\u041D\u0430
places.geocoding.response.raw.data=Raw Data
places.geocoding.response.error.details=Детали ошибки
visit.sensitivity.level.help=Низкая чувствительность обнаруживает меньше, более длительные посещения. Высокая чувствительность обнаруживает больше, более короткие визиты. Регулируйте на основе ваших потребностей отслеживания: используйте низкий уровень для отслеживания общего местоположения, высокий для подробного анализа движения. Это сильно зависит от того, как часто ваша интеграция отправляет данные в Reitti. Чем меньше интервал входящих данных, тем выше должен быть уровень чувствительности.
visit.detection.max.merge.time.help=Максимальный временной интервал между посещениями одного и того же места, прежде чем они будут считаться отдельными посещениями. Если вы покинете одно и то же место и вернетесь в течение этого времени, это будет рассматриваться как одно непрерывное посещение. Типичные значения: 1800 с (30 минут) для коротких поручений, 3600 с (1 час) для длительных перерывов.
visit.merging.max.merge.time.help=Максимальное время между посещением того же места до того, как они будут считаться отдельными событиями. Это помогает объединить посещения, которые были неправильно разделены из-за неточностей GPS или кратких вылетов. Типичные значения: 3600S (1 час) для строгого разделения, 7200S (2 часа) для более мягкого слияния.
visit.detection.minimum.stay.help=Минимальная продолжительность, чтобы считать это место посещением, а не просто проездом. Более низкие значения (60300 с) определяют короткие остановки, более высокие значения (6001800 с) обнаруживают только значительные остановки. Типичные значения: 300 с (5 минут) для подробного отслеживания, 900 с (15 минут) только для основных местоположений.
share-with.info.description=Когда вы поделитесь своими данными с другими пользователями, они смогут просматривать ваше время и историю местоположения вместе со своими собственными данными. Это полезно для семей или команд, которые хотят координировать и делиться информацией о местоположении.
places.geocoding.response.error.details=\u0414\u0435\u0442\u0430\u043B\u0438 \u043E\u0448\u0438\u0431\u043A\u0438
visit.sensitivity.level.help=\u041D\u0438\u0437\u043A\u0430\u044F \u0447\u0443\u0432\u0441\u0442\u0432\u0438\u0442\u0435\u043B\u044C\u043D\u043E\u0441\u0442\u044C \u043E\u0431\u043D\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u0442 \u043C\u0435\u043D\u044C\u0448\u0435, \u0431\u043E\u043B\u0435\u0435 \u0434\u043B\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u0435 \u043F\u043E\u0441\u0435\u0449\u0435\u043D\u0438\u044F. \u0412\u044B\u0441\u043E\u043A\u0430\u044F \u0447\u0443\u0432\u0441\u0442\u0432\u0438\u0442\u0435\u043B\u044C\u043D\u043E\u0441\u0442\u044C \u043E\u0431\u043D\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u0435\u0442 \u0431\u043E\u043B\u044C\u0448\u0435, \u0431\u043E\u043B\u0435\u0435 \u043A\u043E\u0440\u043E\u0442\u043A\u0438\u0435 \u0432\u0438\u0437\u0438\u0442\u044B. \u0420\u0435\u0433\u0443\u043B\u0438\u0440\u0443\u0439\u0442\u0435 \u043D\u0430 \u043E\u0441\u043D\u043E\u0432\u0435 \u0432\u0430\u0448\u0438\u0445 \u043F\u043E\u0442\u0440\u0435\u0431\u043D\u043E\u0441\u0442\u0435\u0439 \u043E\u0442\u0441\u043B\u0435\u0436\u0438\u0432\u0430\u043D\u0438\u044F: \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439\u0442\u0435 \u043D\u0438\u0437\u043A\u0438\u0439 \u0443\u0440\u043E\u0432\u0435\u043D\u044C \u0434\u043B\u044F \u043E\u0442\u0441\u043B\u0435\u0436\u0438\u0432\u0430\u043D\u0438\u044F \u043E\u0431\u0449\u0435\u0433\u043E \u043C\u0435\u0441\u0442\u043E\u043F\u043E\u043B\u043E\u0436\u0435\u043D\u0438\u044F, \u0432\u044B\u0441\u043E\u043A\u0438\u0439 \u0434\u043B\u044F \u043F\u043E\u0434\u0440\u043E\u0431\u043D\u043E\u0433\u043E \u0430\u043D\u0430\u043B\u0438\u0437\u0430 \u0434\u0432\u0438\u0436\u0435\u043D\u0438\u044F. \u042D\u0442\u043E \u0441\u0438\u043B\u044C\u043D\u043E \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043E\u0442 \u0442\u043E\u0433\u043E, \u043A\u0430\u043A \u0447\u0430\u0441\u0442\u043E \u0432\u0430\u0448\u0430 \u0438\u043D\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044F \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u044F\u0435\u0442 \u0434\u0430\u043D\u043D\u044B\u0435 \u0432 Reitti. \u0427\u0435\u043C \u043C\u0435\u043D\u044C\u0448\u0435 \u0438\u043D\u0442\u0435\u0440\u0432\u0430\u043B \u0432\u0445\u043E\u0434\u044F\u0449\u0438\u0445 \u0434\u0430\u043D\u043D\u044B\u0445, \u0442\u0435\u043C \u0432\u044B\u0448\u0435 \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u0443\u0440\u043E\u0432\u0435\u043D\u044C \u0447\u0443\u0432\u0441\u0442\u0432\u0438\u0442\u0435\u043B\u044C\u043D\u043E\u0441\u0442\u0438.
visit.detection.max.merge.time.help=\u041C\u0430\u043A\u0441\u0438\u043C\u0430\u043B\u044C\u043D\u044B\u0439 \u0432\u0440\u0435\u043C\u0435\u043D\u043D\u043E\u0439 \u0438\u043D\u0442\u0435\u0440\u0432\u0430\u043B \u043C\u0435\u0436\u0434\u0443 \u043F\u043E\u0441\u0435\u0449\u0435\u043D\u0438\u044F\u043C\u0438 \u043E\u0434\u043D\u043E\u0433\u043E \u0438 \u0442\u043E\u0433\u043E \u0436\u0435 \u043C\u0435\u0441\u0442\u0430, \u043F\u0440\u0435\u0436\u0434\u0435 \u0447\u0435\u043C \u043E\u043D\u0438 \u0431\u0443\u0434\u0443\u0442 \u0441\u0447\u0438\u0442\u0430\u0442\u044C\u0441\u044F \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u044B\u043C\u0438 \u043F\u043E\u0441\u0435\u0449\u0435\u043D\u0438\u044F\u043C\u0438. \u0415\u0441\u043B\u0438 \u0432\u044B \u043F\u043E\u043A\u0438\u043D\u0435\u0442\u0435 \u043E\u0434\u043D\u043E \u0438 \u0442\u043E \u0436\u0435 \u043C\u0435\u0441\u0442\u043E \u0438 \u0432\u0435\u0440\u043D\u0435\u0442\u0435\u0441\u044C \u0432 \u0442\u0435\u0447\u0435\u043D\u0438\u0435 \u044D\u0442\u043E\u0433\u043E \u0432\u0440\u0435\u043C\u0435\u043D\u0438, \u044D\u0442\u043E \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0441\u0441\u043C\u0430\u0442\u0440\u0438\u0432\u0430\u0442\u044C\u0441\u044F \u043A\u0430\u043A \u043E\u0434\u043D\u043E \u043D\u0435\u043F\u0440\u0435\u0440\u044B\u0432\u043D\u043E\u0435 \u043F\u043E\u0441\u0435\u0449\u0435\u043D\u0438\u0435. \u0422\u0438\u043F\u0438\u0447\u043D\u044B\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044F: 1800 \u0441 (30 \u043C\u0438\u043D\u0443\u0442) \u0434\u043B\u044F \u043A\u043E\u0440\u043E\u0442\u043A\u0438\u0445 \u043F\u043E\u0440\u0443\u0447\u0435\u043D\u0438\u0439, 3600 \u0441 (1 \u0447\u0430\u0441) \u0434\u043B\u044F \u0434\u043B\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u0445 \u043F\u0435\u0440\u0435\u0440\u044B\u0432\u043E\u0432.
visit.merging.max.merge.time.help=\u041C\u0430\u043A\u0441\u0438\u043C\u0430\u043B\u044C\u043D\u043E\u0435 \u0432\u0440\u0435\u043C\u044F \u043C\u0435\u0436\u0434\u0443 \u043F\u043E\u0441\u0435\u0449\u0435\u043D\u0438\u0435\u043C \u0442\u043E\u0433\u043E \u0436\u0435 \u043C\u0435\u0441\u0442\u0430 \u0434\u043E \u0442\u043E\u0433\u043E, \u043A\u0430\u043A \u043E\u043D\u0438 \u0431\u0443\u0434\u0443\u0442 \u0441\u0447\u0438\u0442\u0430\u0442\u044C\u0441\u044F \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u044B\u043C\u0438 \u0441\u043E\u0431\u044B\u0442\u0438\u044F\u043C\u0438. \u042D\u0442\u043E \u043F\u043E\u043C\u043E\u0433\u0430\u0435\u0442 \u043E\u0431\u044A\u0435\u0434\u0438\u043D\u0438\u0442\u044C \u043F\u043E\u0441\u0435\u0449\u0435\u043D\u0438\u044F, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u0431\u044B\u043B\u0438 \u043D\u0435\u043F\u0440\u0430\u0432\u0438\u043B\u044C\u043D\u043E \u0440\u0430\u0437\u0434\u0435\u043B\u0435\u043D\u044B \u0438\u0437-\u0437\u0430 \u043D\u0435\u0442\u043E\u0447\u043D\u043E\u0441\u0442\u0435\u0439 GPS \u0438\u043B\u0438 \u043A\u0440\u0430\u0442\u043A\u0438\u0445 \u0432\u044B\u043B\u0435\u0442\u043E\u0432. \u0422\u0438\u043F\u0438\u0447\u043D\u044B\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044F: 3600S (1 \u0447\u0430\u0441) \u0434\u043B\u044F \u0441\u0442\u0440\u043E\u0433\u043E\u0433\u043E \u0440\u0430\u0437\u0434\u0435\u043B\u0435\u043D\u0438\u044F, 7200S (2 \u0447\u0430\u0441\u0430) \u0434\u043B\u044F \u0431\u043E\u043B\u0435\u0435 \u043C\u044F\u0433\u043A\u043E\u0433\u043E \u0441\u043B\u0438\u044F\u043D\u0438\u044F.
visit.detection.minimum.stay.help=\u041C\u0438\u043D\u0438\u043C\u0430\u043B\u044C\u043D\u0430\u044F \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u0435\u043B\u044C\u043D\u043E\u0441\u0442\u044C, \u0447\u0442\u043E\u0431\u044B \u0441\u0447\u0438\u0442\u0430\u0442\u044C \u044D\u0442\u043E \u043C\u0435\u0441\u0442\u043E \u043F\u043E\u0441\u0435\u0449\u0435\u043D\u0438\u0435\u043C, \u0430 \u043D\u0435 \u043F\u0440\u043E\u0441\u0442\u043E \u043F\u0440\u043E\u0435\u0437\u0434\u043E\u043C. \u0411\u043E\u043B\u0435\u0435 \u043D\u0438\u0437\u043A\u0438\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044F (60\u2013300 \u0441) \u043E\u043F\u0440\u0435\u0434\u0435\u043B\u044F\u044E\u0442 \u043A\u043E\u0440\u043E\u0442\u043A\u0438\u0435 \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0438, \u0431\u043E\u043B\u0435\u0435 \u0432\u044B\u0441\u043E\u043A\u0438\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044F (600\u20131800 \u0441) \u043E\u0431\u043D\u0430\u0440\u0443\u0436\u0438\u0432\u0430\u044E\u0442 \u0442\u043E\u043B\u044C\u043A\u043E \u0437\u043D\u0430\u0447\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u0435 \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0438. \u0422\u0438\u043F\u0438\u0447\u043D\u044B\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044F: 300\u00A0\u0441 (5\u00A0\u043C\u0438\u043D\u0443\u0442) \u0434\u043B\u044F \u043F\u043E\u0434\u0440\u043E\u0431\u043D\u043E\u0433\u043E \u043E\u0442\u0441\u043B\u0435\u0436\u0438\u0432\u0430\u043D\u0438\u044F, 900\u00A0\u0441 (15\u00A0\u043C\u0438\u043D\u0443\u0442) \u0442\u043E\u043B\u044C\u043A\u043E \u0434\u043B\u044F \u043E\u0441\u043D\u043E\u0432\u043D\u044B\u0445 \u043C\u0435\u0441\u0442\u043E\u043F\u043E\u043B\u043E\u0436\u0435\u043D\u0438\u0439.
share-with.info.description=\u041A\u043E\u0433\u0434\u0430 \u0432\u044B \u043F\u043E\u0434\u0435\u043B\u0438\u0442\u0435\u0441\u044C \u0441\u0432\u043E\u0438\u043C\u0438 \u0434\u0430\u043D\u043D\u044B\u043C\u0438 \u0441 \u0434\u0440\u0443\u0433\u0438\u043C\u0438 \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F\u043C\u0438, \u043E\u043D\u0438 \u0441\u043C\u043E\u0433\u0443\u0442 \u043F\u0440\u043E\u0441\u043C\u0430\u0442\u0440\u0438\u0432\u0430\u0442\u044C \u0432\u0430\u0448\u0435 \u0432\u0440\u0435\u043C\u044F \u0438 \u0438\u0441\u0442\u043E\u0440\u0438\u044E \u043C\u0435\u0441\u0442\u043E\u043F\u043E\u043B\u043E\u0436\u0435\u043D\u0438\u044F \u0432\u043C\u0435\u0441\u0442\u0435 \u0441\u043E \u0441\u0432\u043E\u0438\u043C\u0438 \u0441\u043E\u0431\u0441\u0442\u0432\u0435\u043D\u043D\u044B\u043C\u0438 \u0434\u0430\u043D\u043D\u044B\u043C\u0438. \u042D\u0442\u043E \u043F\u043E\u043B\u0435\u0437\u043D\u043E \u0434\u043B\u044F \u0441\u0435\u043C\u0435\u0439 \u0438\u043B\u0438 \u043A\u043E\u043C\u0430\u043D\u0434, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u0445\u043E\u0442\u044F\u0442 \u043A\u043E\u043E\u0440\u0434\u0438\u043D\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0438 \u0434\u0435\u043B\u0438\u0442\u044C\u0441\u044F \u0438\u043D\u0444\u043E\u0440\u043C\u0430\u0446\u0438\u0435\u0439 \u043E \u043C\u0435\u0441\u0442\u043E\u043F\u043E\u043B\u043E\u0436\u0435\u043D\u0438\u0438.
about.acknowledgments.title=\u0411\u043B\u0430\u0433\u043E\u0434\u0430\u0440\u043D\u043E\u0441\u0442\u0438
about.acknowledgments.subtitle=Reitti \u0431\u044B\u043B \u0431\u044B \u043D\u0435\u0432\u043E\u0437\u043C\u043E\u0436\u0435\u043D \u0431\u0435\u0437 \u0443\u0434\u0438\u0432\u0438\u0442\u0435\u043B\u044C\u043D\u043E\u0433\u043E \u0432\u043A\u043B\u0430\u0434\u0430 \u043D\u0430\u0448\u0435\u0433\u043E \u0441\u043E\u043E\u0431\u0449\u0435\u0441\u0442\u0432\u0430 \u0438 \u043D\u0435\u0432\u0435\u0440\u043E\u044F\u0442\u043D\u044B\u0445 \u043F\u0440\u043E\u0435\u043A\u0442\u043E\u0432 \u0441 \u043E\u0442\u043A\u0440\u044B\u0442\u044B\u043C \u0438\u0441\u0445\u043E\u0434\u043D\u044B\u043C \u043A\u043E\u0434\u043E\u043C, \u043D\u0430 \u043A\u043E\u0442\u043E\u0440\u044B\u0445 \u043C\u044B \u0441\u0442\u0440\u043E\u0438\u043C.
about.contributors.title=\u0423\u0447\u0430\u0441\u0442\u043D\u0438\u043A\u0438
about.translators.title=\u041F\u0435\u0440\u0435\u0432\u043E\u0434\u0447\u0438\u043A\u0438
about.projects.title=\u041F\u0440\u043E\u0435\u043A\u0442\u044B \u0441 \u041E\u0442\u043A\u0440\u044B\u0442\u044B\u043C \u0418\u0441\u0445\u043E\u0434\u043D\u044B\u043C \u041A\u043E\u0434\u043E\u043C
about.projects.visit=\u041F\u043E\u0441\u0435\u0442\u0438\u0442\u044C \u041F\u0440\u043E\u0435\u043A\u0442
about.thankyou.title=\u0421\u043F\u0430\u0441\u0438\u0431\u043E!
about.thankyou.message=\u041A\u0430\u0436\u0434\u044B\u0439 \u0432\u043A\u043B\u0430\u0434, \u043A\u0430\u043A\u0438\u043C \u0431\u044B \u043C\u0430\u043B\u0435\u043D\u044C\u043A\u0438\u043C \u043E\u043D \u043D\u0438 \u0431\u044B\u043B, \u043F\u043E\u043C\u043E\u0433\u0430\u0435\u0442 \u0441\u0434\u0435\u043B\u0430\u0442\u044C Reitti \u043B\u0443\u0447\u0448\u0435 \u0434\u043B\u044F \u0432\u0441\u0435\u0445. \u041C\u044B \u0431\u043B\u0430\u0433\u043E\u0434\u0430\u0440\u043D\u044B \u0437\u0430 \u0432\u0430\u0448\u0443 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0443 \u0438 \u043F\u0440\u0435\u0434\u0430\u043D\u043D\u043E\u0441\u0442\u044C \u0441\u043E\u043E\u0431\u0449\u0435\u0441\u0442\u0432\u0443 \u043E\u0442\u043A\u0440\u044B\u0442\u043E\u0433\u043E \u0438\u0441\u0445\u043E\u0434\u043D\u043E\u0433\u043E \u043A\u043E\u0434\u0430.

View File

@@ -0,0 +1,44 @@
{"projects": [
{
"name": "Spring Boot",
"description": "Java-based framework for building production-ready applications",
"url": "https://spring.io/projects/spring-boot",
"license": "Apache 2.0",
"category": "Framework"
},
{
"name": "PostgreSQL",
"description": "Advanced open source relational database",
"url": "https://www.postgresql.org/",
"license": "PostgreSQL License",
"category": "Database"
},
{
"name": "Leaflet",
"description": "Open-source JavaScript library for mobile-friendly interactive maps",
"url": "https://leafletjs.com/",
"license": "BSD-2-Clause",
"category": "Frontend"
},
{
"name": "HTMX",
"description": "High power tools for HTML",
"url": "https://htmx.org/",
"license": "BSD-2-Clause",
"category": "Frontend"
},
{
"name": "Thymeleaf",
"description": "Modern server-side Java template engine",
"url": "https://www.thymeleaf.org/",
"license": "Apache 2.0",
"category": "Template Engine"
},
{
"name": "Chart.js",
"description": "Simple yet flexible JavaScript charting library",
"url": "https://www.chartjs.org/",
"license": "MIT",
"category": "Frontend"
}
]}

View File

@@ -0,0 +1,697 @@
/* Acknowledgments Section Styles */
:root {
--trail-color: #ffffff;
}
.acknowledgments-section {
margin-top: 2rem;
background: linear-gradient(135deg, var(--color-background-dark) 0%, var(--color-background-dark-light) 100%);
border-radius: 8px;
padding: 2rem;
position: relative;
overflow: hidden;
border: 1px solid var(--color-highlight);
}
.acknowledgments-section::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background:
radial-gradient(circle at 20% 20%, rgba(245, 222, 179, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 80%, rgba(245, 222, 179, 0.05) 0%, transparent 50%);
pointer-events: none;
}
.acknowledgments-title {
display: flex;
align-items: center;
gap: 0.75rem;
font-size: 2rem;
font-weight: 700;
color: var(--color-highlight);
margin-bottom: 0.5rem;
font-family: var(--serif-font), serif;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
position: relative;
z-index: 1;
}
.acknowledgments-title i {
color: var(--color-highlight);
font-size: 1.5rem;
animation: gentle-pulse 3s ease-in-out infinite;
filter: drop-shadow(0 2px 4px rgba(245, 222, 179, 0.3));
}
.acknowledgments-subtitle {
color: var(--color-text-white);
margin-bottom: 2rem;
line-height: 1.6;
font-size: 1.1rem;
opacity: 0.9;
position: relative;
z-index: 1;
}
.section-title {
display: flex;
align-items: center;
gap: 0.75rem;
font-size: 1.5rem;
font-weight: 600;
margin-bottom: 2rem;
color: var(--color-highlight);
font-family: var(--serif-font), serif;
position: relative;
z-index: 1;
}
.section-title i {
color: var(--color-highlight);
font-size: 1.25rem;
filter: drop-shadow(0 2px 4px rgba(245, 222, 179, 0.3));
animation: gentle-glow 4s ease-in-out infinite;
}
/* Contributors Section */
.contributors-section {
margin-bottom: 4rem;
position: relative;
z-index: 1;
}
.contributors-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1.5rem;
}
.contributor-card {
background: linear-gradient(145deg, var(--color-background-dark-light), var(--color-background-dark));
border: 2px solid rgba(245, 222, 179, 0.2);
border-radius: 15px;
padding: 1.5rem;
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
position: relative;
overflow: hidden;
backdrop-filter: blur(10px);
}
.contributor-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 6px;
background: linear-gradient(90deg, var(--color-highlight), rgba(245, 222, 179, 0.8), var(--color-highlight));
background-size: 300% 100%;
animation: shimmer 3s ease-in-out infinite;
}
.contributor-card::after {
content: '✨';
position: absolute;
top: 1rem;
right: 1rem;
font-size: 1.5rem;
opacity: 0;
transition: all 0.3s ease;
color: var(--color-highlight);
}
.contributor-card:hover {
transform: translateY(-8px) scale(1.02);
box-shadow:
0 20px 40px rgba(0, 0, 0, 0.3),
0 0 30px rgba(245, 222, 179, 0.2);
border-color: var(--color-highlight);
}
.contributor-card:hover::after {
opacity: 1;
transform: scale(1.2);
}
.contributor-avatar {
position: relative;
width: 70px;
height: 70px;
margin: 0 auto 1rem;
}
.contributor-avatar img {
width: 100%;
height: 100%;
border-radius: 50%;
object-fit: cover;
border: 4px solid var(--color-highlight);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3);
transition: all 0.3s ease;
}
.contributor-card:hover .contributor-avatar img {
transform: scale(1.1);
box-shadow: 0 12px 30px rgba(245, 222, 179, 0.4);
}
.contributor-badge {
position: absolute;
bottom: -3px;
right: -3px;
background: linear-gradient(135deg, var(--color-highlight), #ffd700);
color: var(--color-background-dark);
border-radius: 50%;
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.8rem;
font-weight: bold;
border: 2px solid var(--color-background-dark-light);
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.3);
}
.contributor-info {
text-align: center;
}
.contributor-name {
font-size: 1rem;
font-weight: 700;
margin-bottom: 1rem;
color: var(--color-highlight);
font-family: var(--serif-font), serif;
}
.contributor-link {
display: inline-flex;
align-items: center;
gap: 0.5rem;
color: var(--color-text-white);
text-decoration: none;
font-size: 0.8rem;
transition: all 0.3s ease;
padding: 0.4rem 0.8rem;
border-radius: 12px;
background: rgba(245, 222, 179, 0.1);
border: 1px solid rgba(245, 222, 179, 0.2);
}
.contributor-link:hover {
color: var(--color-highlight);
background: rgba(245, 222, 179, 0.2);
transform: translateY(-2px);
box-shadow: 0 3px 8px rgba(245, 222, 179, 0.2);
}
/* Translators Section */
.translators-section {
margin-bottom: 4rem;
position: relative;
z-index: 1;
}
.translators-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
}
.translator-card {
background: linear-gradient(145deg, var(--color-background-dark-light), var(--color-background-dark));
border: 2px solid rgba(245, 222, 179, 0.2);
border-radius: 20px;
padding: 2rem;
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
position: relative;
overflow: hidden;
backdrop-filter: blur(10px);
}
.translator-card::before {
content: '🌍';
position: absolute;
top: 1rem;
right: 1rem;
font-size: 1.5rem;
opacity: 0.3;
transition: all 0.3s ease;
}
.translator-card:hover {
transform: translateY(-6px) scale(1.02);
box-shadow:
0 15px 35px rgba(0, 0, 0, 0.3),
0 0 25px rgba(245, 222, 179, 0.15);
border-color: var(--color-highlight);
}
.translator-card:hover::before {
opacity: 1;
transform: scale(1.2);
}
.translator-avatar {
position: relative;
width: 90px;
height: 90px;
margin: 0 auto 1.5rem;
}
.translator-avatar img {
width: 100%;
height: 100%;
border-radius: 50%;
object-fit: cover;
transition: all 0.3s ease;
}
.translator-card:hover .translator-avatar img {
transform: scale(1.1);
}
.completion-badge {
position: absolute;
bottom: -6px;
right: -6px;
background: linear-gradient(135deg, #10b981, #059669);
color: white;
border-radius: 15px;
padding: 0.4rem 0.8rem;
font-size: 0.8rem;
font-weight: 700;
border: 3px solid var(--color-background-dark-light);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
min-width: 45px;
text-align: center;
}
.completion-badge.incomplete {
background: linear-gradient(135deg, #f59e0b, #d97706);
}
.translator-info {
text-align: center;
}
.translator-name {
font-size: 1.1rem;
font-weight: 700;
margin-bottom: 1rem;
color: var(--color-highlight);
font-family: var(--serif-font), serif;
}
.translator-languages {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
justify-content: center;
margin-bottom: 1.5rem;
}
.language-tag {
background: linear-gradient(135deg, rgba(245, 222, 179, 0.2), rgba(245, 222, 179, 0.1));
color: var(--color-highlight);
padding: 0.4rem 0.8rem;
border-radius: 20px;
font-size: 0.8rem;
font-weight: 600;
border: 1px solid rgba(245, 222, 179, 0.3);
transition: all 0.3s ease;
backdrop-filter: blur(5px);
}
.language-tag:hover {
background: var(--color-highlight);
color: var(--color-background-dark);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(245, 222, 179, 0.3);
}
.completion-bar {
background: rgba(245, 222, 179, 0.2);
height: 8px;
border-radius: 10px;
margin-bottom: 1.5rem;
overflow: hidden;
border: 1px solid rgba(245, 222, 179, 0.3);
position: relative;
}
.completion-bar::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent);
animation: shimmer 2s ease-in-out infinite;
}
.completion-fill {
height: 100%;
background: linear-gradient(90deg, var(--color-highlight), rgba(245, 222, 179, 0.8), var(--color-highlight));
border-radius: 10px;
transition: width 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94);
position: relative;
overflow: hidden;
}
.completion-fill::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
animation: progress-shine 3s ease-in-out infinite;
}
.translator-link {
display: inline-flex;
align-items: center;
gap: 0.75rem;
color: var(--color-text-white);
text-decoration: none;
font-size: 0.9rem;
transition: all 0.3s ease;
padding: 0.5rem 1rem;
border-radius: 15px;
background: rgba(245, 222, 179, 0.1);
border: 1px solid rgba(245, 222, 179, 0.2);
}
.translator-link:hover {
color: var(--color-highlight);
background: rgba(245, 222, 179, 0.2);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(245, 222, 179, 0.2);
}
/* Projects Section */
.projects-section {
margin-bottom: 4rem;
position: relative;
z-index: 1;
}
.projects-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 2rem;
}
.project-card {
background: linear-gradient(145deg, var(--color-background-dark-light), var(--color-background-dark));
border: 2px solid rgba(245, 222, 179, 0.2);
border-radius: 20px;
overflow: hidden;
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
position: relative;
backdrop-filter: blur(10px);
}
.project-card::before {
content: '⚡';
position: absolute;
top: 1rem;
right: 1rem;
font-size: 1.5rem;
opacity: 0.3;
transition: all 0.3s ease;
z-index: 2;
color: var(--color-highlight);
}
.project-card:hover {
transform: translateY(-6px) scale(1.02);
box-shadow:
0 15px 35px rgba(0, 0, 0, 0.3),
0 0 25px rgba(245, 222, 179, 0.15);
border-color: var(--color-highlight);
}
.project-card:hover::before {
opacity: 1;
transform: scale(1.2);
}
.project-header {
background: linear-gradient(135deg, rgba(245, 222, 179, 0.15), rgba(245, 222, 179, 0.05));
padding: 1.5rem;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 2px solid rgba(245, 222, 179, 0.2);
position: relative;
}
.project-category {
background: linear-gradient(135deg, var(--color-highlight), #ffd700);
color: var(--color-background-dark);
padding: 0.5rem 1rem;
border-radius: 25px;
font-size: 0.8rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.5px;
box-shadow: 0 4px 12px rgba(245, 222, 179, 0.3);
}
.project-license {
color: var(--color-text-white);
font-size: 0.8rem;
font-weight: 600;
opacity: 0.8;
background: rgba(245, 222, 179, 0.1);
padding: 0.3rem 0.8rem;
border-radius: 15px;
border: 1px solid rgba(245, 222, 179, 0.2);
}
.project-content {
padding: 2rem;
}
.project-name {
font-size: 1.3rem;
font-weight: 700;
margin-bottom: 1rem;
color: var(--color-highlight);
font-family: var(--serif-font), serif;
}
.project-description {
color: var(--color-text-white);
line-height: 1.6;
margin-bottom: 1.5rem;
font-size: 0.95rem;
opacity: 0.9;
}
.project-link {
display: inline-flex;
align-items: center;
gap: 0.75rem;
color: var(--color-text-white);
text-decoration: none;
font-weight: 600;
font-size: 0.9rem;
transition: all 0.3s ease;
padding: 0.75rem 1.5rem;
border-radius: 15px;
background: linear-gradient(135deg, rgba(245, 222, 179, 0.2), rgba(245, 222, 179, 0.1));
border: 1px solid rgba(245, 222, 179, 0.3);
backdrop-filter: blur(5px);
}
.project-link:hover {
color: var(--color-background-dark);
background: var(--color-highlight);
transform: translateY(-2px);
box-shadow: 0 6px 18px rgba(245, 222, 179, 0.3);
gap: 1rem;
}
/* Thank You Message */
.thank-you-message {
background: linear-gradient(135deg,
rgba(245, 222, 179, 0.3) 0%,
rgba(245, 222, 179, 0.1) 50%,
rgba(255, 107, 107, 0.1) 100%);
border: 3px solid rgba(245, 222, 179, 0.4);
border-radius: 25px;
padding: 3rem;
text-align: center;
margin-top: 3rem;
position: relative;
overflow: hidden;
backdrop-filter: blur(15px);
cursor: pointer;
}
.thank-you-message::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background:
radial-gradient(circle at 30% 30%, rgba(255, 107, 107, 0.1) 0%, transparent 50%),
radial-gradient(circle at 70% 70%, rgba(245, 222, 179, 0.1) 0%, transparent 50%);
pointer-events: none;
}
.thank-you-content {
max-width: 700px;
margin: 0 auto;
position: relative;
z-index: 1;
}
.thank-you-icon {
font-size: 4rem;
color: var(--color-highlight);
margin-bottom: 1.5rem;
animation: gentle-pulse 3s ease-in-out infinite;
filter: drop-shadow(0 4px 8px rgba(245, 222, 179, 0.3));
}
@keyframes gentle-pulse {
0% { transform: scale(1); opacity: 0.8; }
50% { transform: scale(1.05); opacity: 1; }
100% { transform: scale(1); opacity: 0.8; }
}
@keyframes gentle-glow {
0% { filter: drop-shadow(0 2px 4px rgba(245, 222, 179, 0.3)); }
50% { filter: drop-shadow(0 2px 8px rgba(245, 222, 179, 0.5)); }
100% { filter: drop-shadow(0 2px 4px rgba(245, 222, 179, 0.3)); }
}
@keyframes shimmer {
0% { background-position: -300% 0; }
100% { background-position: 300% 0; }
}
@keyframes progress-shine {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
.thank-you-message h4 {
font-size: 2rem;
font-weight: 700;
color: var(--color-highlight);
margin-bottom: 1.5rem;
font-family: var(--serif-font), serif;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
.thank-you-message p {
color: var(--color-text-white);
line-height: 1.8;
font-size: 1.1rem;
opacity: 0.95;
}
/* Fireworks Container */
.fireworks-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 9999;
}
/* Responsive Design */
@media (max-width: 768px) {
.contributors-grid,
.translators-grid,
.projects-grid {
grid-template-columns: 1fr;
}
.acknowledgments-section {
padding: 1.5rem;
margin-top: 1rem;
}
.acknowledgments-title {
font-size: 1.5rem;
}
.section-title {
font-size: 1.25rem;
}
.project-header {
flex-direction: column;
gap: 1rem;
align-items: flex-start;
}
.contributor-card,
.translator-card,
.project-card {
padding: 1.5rem;
}
.thank-you-message {
padding: 2rem;
margin-top: 2rem;
}
.thank-you-icon {
font-size: 3rem;
}
.thank-you-message h4 {
font-size: 1.5rem;
}
.thank-you-message p {
font-size: 1rem;
}
}
@media (max-width: 480px) {
.acknowledgments-section {
padding: 1rem;
}
.contributor-card,
.translator-card,
.project-card {
padding: 1rem;
}
.contributors-grid {
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 1rem;
}
.contributor-avatar,
.translator-avatar {
width: 60px;
height: 60px;
}
.acknowledgments-title {
font-size: 1.25rem;
flex-direction: column;
text-align: center;
}
}

File diff suppressed because one or more lines are too long

View File

@@ -9,8 +9,10 @@
<link rel="shortcut icon" type="image/x-icon" th:href="@{/img/logo.svg}">
<link rel="stylesheet" href="/css/main.css">
<link rel="stylesheet" href="/css/lineicons.css">
<link rel="stylesheet" href="/css/acknowledgments.css">
<script src="/js/htmx.min.js"></script>
<script src="/js/util.js"></script>
<script src="/js/fireworks.min.js"></script>
</head>
<body class="settings-page">
<div class="settings-container">
@@ -32,10 +34,104 @@
<span th:text="${buildTime}">N/A</span>
</p>
</div>
<!-- Acknowledgments Section -->
<div class="acknowledgments-section">
<h3 class="acknowledgments-title">
<i class="lni lni-heart"></i>
<span th:text="#{about.acknowledgments.title}">Acknowledgments</span>
</h3>
<p class="acknowledgments-subtitle" th:text="#{about.acknowledgments.subtitle}">
Reitti wouldn't be possible without the amazing contributions from our community and the incredible open-source projects we build upon.
</p>
<!-- Contributors Section -->
<div class="contributors-section" th:if="${!contributors.isEmpty()}">
<h4 class="section-title">
<i class="lni lni-user-4"></i>
<span th:text="#{about.contributors.title}">Contributors</span>
</h4>
<div class="contributors-grid">
<div class="contributor-card" th:each="contributor : ${contributors}">
<div class="contributor-avatar">
<img th:src="${contributor.avatar}" th:alt="${contributor.name}" />
<div class="contributor-badge">
<i class="lni lni-github"></i>
</div>
</div>
<div class="contributor-info">
<h5 class="contributor-name" th:text="${contributor.name}">Name</h5>
<a th:href="'https://github.com/' + ${contributor.github}" class="contributor-link" target="_blank">
<i class="lni lni-github"></i>
<span th:text="${contributor.github}">GitHub</span>
</a>
</div>
</div>
</div>
</div>
<!-- Open Source Projects Section -->
<div class="projects-section" th:if="${!projects.isEmpty()}">
<h4 class="section-title">
<i class="lni lni-code-1"></i>
<span th:text="#{about.projects.title}">Open Source Projects</span>
</h4>
<div class="projects-grid">
<div class="project-card" th:each="project : ${projects}">
<div class="project-header">
<div class="project-category" th:text="${project.category}">Category</div>
<div class="project-license" th:text="${project.license}">License</div>
</div>
<div class="project-content">
<h5 class="project-name" th:text="${project.name}">Project Name</h5>
<p class="project-description" th:text="${project.description}">Description</p>
<a th:href="${project.url}" class="project-link" target="_blank">
<i class="lni lni-eye"></i>
<span th:text="#{about.projects.visit}">Visit Project</span>
</a>
</div>
</div>
</div>
</div>
<!-- Thank You Message -->
<div class="thank-you-message" onclick="startFireworks()">
<div class="thank-you-content">
<i class="lni lni-heart thank-you-icon"></i>
<h4 th:text="#{about.thankyou.title}">Thank You!</h4>
<p th:text="#{about.thankyou.message}">
Every contribution, no matter how small, helps make Reitti better for everyone.
We're grateful for your support and dedication to the open-source community.
</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Fireworks Container -->
<div id="fireworks-container" class="fireworks-container"></div>
<script th:inline="javascript">
window.userSettings = /*[[${userSettings}]]*/ {}
const container = document.getElementById('fireworks-container');
let fireworks = new Fireworks.default(container, {
particles: 100,
explosion: 15,
});
let running = false;
function startFireworks() {
if (running) {
fireworks.stop();
running = false;
} else {
fireworks.start();
running = true;
}
}
</script>
</body>