From 480a42481c0827f06c6bfd6d0931306943c9ee2d Mon Sep 17 00:00:00 2001 From: LUCA-PYTHON <87448287+LUCA-PYTHON@users.noreply.github.com> Date: Fri, 19 Jun 2026 20:29:59 +0200 Subject: [PATCH] Addet Services Page and updated full design minimal --- backend/app/auth/service.py | 2 +- backend/app/profile/badges.py | 15 +- backend/app/profile/service.py | 26 +- frontend/src/App.jsx | 2 + frontend/src/components/Footer.jsx | 1 + frontend/src/components/Navbar.jsx | 11 +- frontend/src/components/icons/svgs.jsx | 410 +++++--------- frontend/src/i18n/de.json | 74 ++- frontend/src/i18n/en.json | 72 ++- frontend/src/pages/Home.jsx | 150 ++--- frontend/src/pages/Imprint.jsx | 78 +-- frontend/src/pages/Login.jsx | 21 +- frontend/src/pages/Privacy.jsx | 738 +++++++++++++++---------- frontend/src/pages/services.jsx | 281 ++++++++++ frontend/src/styles/Footer.css | 24 +- frontend/src/styles/Github.css | 42 +- frontend/src/styles/Home.css | 296 +++++++--- frontend/src/styles/Legal.css | 35 +- frontend/src/styles/Login.css | 84 ++- frontend/src/styles/Me.css | 2 +- frontend/src/styles/Navbar.css | 131 +++-- frontend/src/styles/NotFound.css | 17 +- frontend/src/styles/ProfileNavbar.css | 50 +- frontend/src/styles/UserProfile.css | 4 +- frontend/src/styles/global.css | 116 ++-- frontend/src/styles/services.css | 410 ++++++++++++++ 26 files changed, 2127 insertions(+), 965 deletions(-) create mode 100644 frontend/src/pages/services.jsx create mode 100644 frontend/src/styles/services.css diff --git a/backend/app/auth/service.py b/backend/app/auth/service.py index 907ed3a..1b67ae2 100644 --- a/backend/app/auth/service.py +++ b/backend/app/auth/service.py @@ -176,7 +176,7 @@ def _verify_backup_code(cur, user_id, code): if not row: return False - cur.execute("UPDATE user_2fa_backup_codes SET used_at=NOW() WHERE id=%s", (row["id"],)) + cur.execute("DELETE FROM user_2fa_backup_codes WHERE id=%s", (row["id"],)) return True diff --git a/backend/app/profile/badges.py b/backend/app/profile/badges.py index aef197b..c32180a 100644 --- a/backend/app/profile/badges.py +++ b/backend/app/profile/badges.py @@ -149,7 +149,7 @@ def get_public_profile(username): try: cursor.execute( - "SELECT username, avatar, role, badges, created_at, bio, location, timezone, github_username " + "SELECT username, avatar, badges, created_at, bio, location, timezone, github_username " "FROM users WHERE username = %s AND verified = 1", (username,), ) @@ -170,20 +170,23 @@ def get_public_profile(username): badges = [] created_at = user.get("created_at") + avatar = user.get("avatar") + if not isinstance(avatar, str) or not avatar.startswith("/uploads/avatars/"): + avatar = None + member_since = ( - created_at.isoformat() if hasattr(created_at, "isoformat") - else str(created_at) if created_at + created_at.strftime("%Y-%m-01") if hasattr(created_at, "strftime") + else str(created_at)[:7] + "-01" if created_at else None ) return { "username": user["username"], - "avatar": user.get("avatar"), + "avatar": avatar, "bio": user.get("bio"), "location": user.get("location"), "timezone": user.get("timezone"), "github_username": user.get("github_username"), - "role": user.get("role"), "badges": badges, "member_since": member_since, - } \ No newline at end of file + } diff --git a/backend/app/profile/service.py b/backend/app/profile/service.py index 85232aa..6cca01a 100644 --- a/backend/app/profile/service.py +++ b/backend/app/profile/service.py @@ -64,7 +64,7 @@ def update_user(user_id, data): if avatar is not None: avatar = avatar.strip() - if avatar and not avatar.startswith(("http://", "https://", "/uploads/")): + if avatar and not avatar.startswith("/uploads/avatars/"): db.close() return False, "invalid_avatar" fields.append("avatar=%s") @@ -131,6 +131,12 @@ def update_user(user_id, data): logger = logging.getLogger(__name__) +def _local_avatar_url(value): + if isinstance(value, str) and value.startswith("/uploads/avatars/"): + return value + return None + + def _safe_unlink(path): try: if path and os.path.isfile(path): @@ -314,7 +320,7 @@ def serialize_user(user): "bio": user.get("bio"), "location": user.get("location"), "timezone": user.get("timezone"), - "avatar": user.get("avatar"), + "avatar": _local_avatar_url(user.get("avatar")), "role": user.get("role"), "badges": user.get("badges") or [], "achievements": user.get("achievements") or [], @@ -339,15 +345,29 @@ def get_user_and_refresh_token(user_id): def delete_user_account(user_id): db = get_db() cursor = db.cursor() + avatar_path = None try: + cursor.execute("SELECT avatar FROM users WHERE id=%s", (user_id,)) + user = cursor.fetchone() + if not user: + return False + + avatar = user.get("avatar") + if avatar and avatar.startswith("/uploads/avatars/"): + avatar_path = os.path.join( + current_app.config["UPLOAD_FOLDER"], + avatar.rsplit("/", 1)[-1], + ) + cursor.execute("DELETE FROM refresh_tokens WHERE user_id=%s", (user_id,)) cursor.execute("DELETE FROM password_resets WHERE user_id=%s", (user_id,)) cursor.execute("DELETE FROM email_verifications WHERE user_id=%s", (user_id,)) cursor.execute("DELETE FROM users WHERE id=%s", (user_id,)) db.commit() - return cursor.rowcount > 0 + _safe_unlink(avatar_path) + return True except Exception: db.rollback() return False diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 1a7a728..5669267 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -20,6 +20,7 @@ import VerifyEmail from "./pages/VerifyEmail"; import ResetPassword from "./pages/ResetPassword"; import UserProfile from "./pages/user/UserProfile"; import GitHubCallback from "./pages/GitHubCallback"; +import Services from "./pages/services"; function AppContent() { const { user } = useAuth(); @@ -33,6 +34,7 @@ function AppContent() { } /> } /> + } /> } /> } /> } /> diff --git a/frontend/src/components/Footer.jsx b/frontend/src/components/Footer.jsx index 188d6ac..1457ab3 100644 --- a/frontend/src/components/Footer.jsx +++ b/frontend/src/components/Footer.jsx @@ -22,6 +22,7 @@ export default function Footer() {

{t("footer.nav_heading")}

  • {t("nav.home")}
  • +
  • {t("nav.services")}
  • {t("nav.github")}
  • {t("nav.contact")}
diff --git a/frontend/src/components/Navbar.jsx b/frontend/src/components/Navbar.jsx index cfeaccc..eb033f1 100644 --- a/frontend/src/components/Navbar.jsx +++ b/frontend/src/components/Navbar.jsx @@ -103,6 +103,15 @@ export default function Navbar() { +
  • + + {t("nav.services")} + +
  • +
  • ); -} \ No newline at end of file +} diff --git a/frontend/src/components/icons/svgs.jsx b/frontend/src/components/icons/svgs.jsx index 31cea17..509f2b2 100644 --- a/frontend/src/components/icons/svgs.jsx +++ b/frontend/src/components/icons/svgs.jsx @@ -1,268 +1,158 @@ import { - StarIcon, - RepoForkedIcon, + GearIcon, IssueOpenedIcon, - RepoIcon, PersonIcon, - GearIcon, ProjectIcon, + RepoForkedIcon, + StarIcon, TrophyIcon, - CloudIcon, - QuestionIcon, - ShieldIcon, - DeviceMobileIcon -} from '@primer/octicons-react'; - -import { - FaDiscord, - FaHeadset, - FaPython, - FaReact - } from 'react-icons/fa'; - -import { - SiFlask, - SiDiscord, - SiThreedotjs, - SiMysql, - SiRedis, - SiDocker, - SiNginx, - SiGsap, - SiFramer, -} from "react-icons/si"; +} from "@primer/octicons-react"; const ICON_SIZE = 16; - -const STAR_COLOR = '#fbbf24'; -const FORK_COLOR = '#79c0ff'; -const ISSUE_COLOR = '#ef4444'; - -export function SvgStar(props) { - return ; -} - -export function SvgFork(props) { - return ; -} - -export function SvgOpenIssue(props) { - return ; -} - -export const SvgIssue = SvgOpenIssue; - -export function SvgRepo(props) { - return ; -} - -export function SvgProfile(props) { - return ; +const ICON_COLOR = "#9babbc"; + +export const SERVICE_ICON_PATHS = { + discord: [ + "M7.2 9.2c.4-2.6 2.2-4.1 4.8-4.1s4.4 1.5 4.8 4.1", + "M8.4 5.8 7.2 3.7", + "m15.6 5.8 1.2-2.1", + "M7.3 8.2h9.4a3.1 3.1 0 0 1 3.1 3.1v5.2a3.1 3.1 0 0 1-3.1 3.1H7.3a3.1 3.1 0 0 1-3.1-3.1v-5.2a3.1 3.1 0 0 1 3.1-3.1Z", + "M8.1 15.8c1.1.8 2.4 1.2 3.9 1.2s2.8-.4 3.9-1.2", + "M9.1 12.7h.01", + "M14.9 12.7h.01", + "M3 13.1H1.7", + "M22.3 13.1H21", + ], + web: [ + "M5.6 4.4h12.8a2.4 2.4 0 0 1 2.4 2.4v9.6a2.4 2.4 0 0 1-2.4 2.4H5.6a2.4 2.4 0 0 1-2.4-2.4V6.8a2.4 2.4 0 0 1 2.4-2.4Z", + "M3.2 8.2h17.6", + "m9.4 12-2.1 2.1 2.1 2.1", + "m14.6 12 2.1 2.1-2.1 2.1", + "m12.9 11.6-1.8 4.8", + "M6.4 6.35h.01", + "M8.8 6.35h.01", + ], + app: [ + "M9.3 2.8h5.4a2.2 2.2 0 0 1 2.2 2.2v14a2.2 2.2 0 0 1-2.2 2.2H9.3A2.2 2.2 0 0 1 7.1 19V5a2.2 2.2 0 0 1 2.2-2.2Z", + "M10.2 5h3.6", + "M11.2 18.4h1.6", + "M9.6 9.1h4.8", + "M9.6 12h4.8", + "M9.6 14.9h2.2", + ], + individual: [ + "M12 3.2v3", + "M12 17.8v3", + "M3.2 12h3", + "M17.8 12h3", + "M15.7 12a3.7 3.7 0 1 1-7.4 0 3.7 3.7 0 0 1 7.4 0Z", + "M6.3 6.3 8.4 8.4", + "m15.6 15.6 2.1 2.1", + "m17.7 6.3-2.1 2.1", + "m8.4 15.6-2.1 2.1", + "M12 10.4v3.2", + "M10.4 12h3.2", + ], +}; + +export const SvgStar = () => ; +export const SvgFork = () => ; +export const SvgOpenIssue = () => ; +export const SvgProfile = () => ; +export const SvgSettings = () => ; +export const SvgProducts = () => ; +export const SvgBadges = () => ; + +function ServiceSvg({ children, size = 24, ...props }) { + return ( + + ); +} + +export function SvgDiscordBot(props) { + return ( + + {SERVICE_ICON_PATHS.discord.map((path) => )} + + ); +} + +export function SvgWebDevelopment(props) { + return ( + + {SERVICE_ICON_PATHS.web.map((path) => )} + + ); +} + +export function SvgAppDevelopment(props) { + return ( + + {SERVICE_ICON_PATHS.app.map((path) => )} + + ); +} + +export function SvgIndividualSolutions(props) { + return ( + + {SERVICE_ICON_PATHS.individual.map((path) => )} + + ); +} + +export function IconEye(props) { + return ( + + ); +} + +export function IconEyeOff(props) { + return ( + + ); } - -export function SvgSettings(props) { - return ; -} - -export function SvgProducts(props) { - return ; -} - -export function SvgBadges(props) { - return ; -} - -export function IconSupport(props) { - return ; -} - -export function IconPhone(props) { - return ; -} - -export function IconCloud(props) { - return ; -} - -export function IconDiscord(props) { - return ; -} - -export function IconShield(props) { - return ; -} - -export function IconPython(props) { - return ; -} - -export function IconReact(props) { - return ; -} - -export function IconFlask(props) { - return ; -} - -export function IconDiscordPy(props) { - return ; -} - -export function IconThreeJS(props) { - return ; -} - -export function IconMySQL(props) { - return ; -} - -export function IconRedis(props) { - return ; -} - -export function IconDocker(props) { - return ; -} - -export function IconNginx(props) { - return ; -} - -export function IconGSAP(props) { - return ; -} - -export function IconFramerMotion(props) { - return ; -} - -export const IconWeb = () => ( - - - - -); - -export const IconApp = () => ( - - - - -); - -export const IconBot = () => ( - - - - - -); - -export const IconApi = () => ( - - - - -); - -export const IconAuto = () => ( - - - -); - -export const IconSaas = () => ( - - - -); - -export const IconStar = () => ( - - - -); - -export const IconChevron = () => ( - - - -); - -export const IconEye = () => ( - - - - -); - -export const IconEyeOff = () => ( - - - - -); - -export const IconGithub = () => ( - - - -); - -export const IconCheck = () => ( - - - -); - -export const IconTrash = () => ( - - - - - - -); - -export const IconUpload = () => ( - - - - - -); - -export const IconUser = () => ( - - - - -); - -export const IconSettingsOutline = () => ( - - - - -); - -export const IconLock = () => ( - - - - -); - -export const IconMail = () => ( - - - - -); - -export const IconCode = () => ( - - - - -); \ No newline at end of file diff --git a/frontend/src/i18n/de.json b/frontend/src/i18n/de.json index b662298..6a2062e 100644 --- a/frontend/src/i18n/de.json +++ b/frontend/src/i18n/de.json @@ -1,5 +1,6 @@ { "nav.home": "Startseite", + "nav.services": "Services", "nav.servies": "Services", "nav.contact": "Kontakt", "nav.policy": "Datenschutz", @@ -26,13 +27,74 @@ "home.section.web.title": "Webentwicklung", "home.section.web.desc": "Webanwendungen, die schnell laden, sicher betrieben werden können und mit deinem Projekt wachsen.", "home.section.app.title": "App-Entwicklung", - "home.section.app.desc": "Cross-Platform-Apps für iOS und Android mit klarer Bedienung und einer stabilen technischen Grundlage.", + "home.section.app.desc": "Wir starten aktuell in die App-Entwicklung und übernehmen zunächst kleine, klar abgegrenzte Projekte für iOS und Android.", "home.section.individual.title": "Lösungen nach Bedarf", "home.section.individual.desc": "Wenn Standardsoftware nicht passt, entwickeln wir die Funktionen und Integrationen, die dein Projekt tatsächlich braucht.", "home.section.learn_more": "Mehr erfahren", "home.cta.title": "Du hast ein Projekt im Kopf?", "home.cta.button": "Projekt besprechen", + "services.hero.eyebrow": "Leistungsübersicht", + "services.hero.title_1": "Services im Detail", + "services.hero.title_2": "", + "services.hero.description": "Hier findest du Leistungsumfang, mögliche Technologien, Projektablauf und künftig konkrete Beispiele zu jedem Bereich.", + "services.cta.project": "Projekt besprechen", + "services.cta.overview": "Services ansehen", + "services.overview.label": "Service-Navigation", + "services.overview.eyebrow": "Was wir entwickeln", + "services.overview.title": "Vier Bereiche. Eine saubere technische Basis.", + "services.overview.description": "Kein Baukasten und keine Lösung von der Stange. Jeder Service wird passend zum tatsächlichen Anwendungsfall geplant und umgesetzt.", + "services.detail.label": "Service", + "services.detail.cta": "Anforderungen besprechen", + "services.detail.scope": "Möglicher Leistungsumfang", + "services.detail.technologies": "Technologien und Plattformen", + "services.examples.eyebrow": "Referenz", + "services.examples.title": "Beispiel-Projekte werden bald hier zu sehen sein.", + "services.examples.description": "Kurzer Projektkontext folgt.", + "services.examples.status": "Noch nicht veröffentlicht", + "services.discord.title": "Discord Bots", + "services.discord.description": "Individuelle Bots, die Community-Arbeit vereinfachen und wiederkehrende Aufgaben zuverlässig automatisieren.", + "services.discord.result": "Das Ergebnis: weniger manuelle Moderation, schnellere Abläufe und ein System, das mit deiner Community wachsen kann.", + "services.discord.feature_1": "Moderation und Rollenverwaltung", + "services.discord.feature_2": "Ticket- und Supportsysteme", + "services.discord.feature_3": "Economy, Level und Belohnungen", + "services.discord.feature_4": "Dashboards und externe APIs", + "services.web.title": "Web Entwicklung", + "services.web.description": "Schnelle Websites, Plattformen und Webanwendungen mit klarer Nutzerführung und einer wartbaren technischen Grundlage.", + "services.web.result": "Das Ergebnis: eine Anwendung, die professionell wirkt, auf jedem Gerät funktioniert und später erweitert werden kann.", + "services.web.feature_1": "Responsive Frontends", + "services.web.feature_2": "Backends und REST APIs", + "services.web.feature_3": "Accounts und Dashboards", + "services.web.feature_4": "Performance und Sicherheit", + "services.app.title": "App Entwicklung", + "services.app.description": "Wir stehen in der App-Entwicklung noch am Anfang und nehmen hier bewusst kleine, klar abgegrenzte Projekte an.", + "services.app.result": "Wichtig: In diesem Bereich sammeln wir aktuell erste praktische Erfahrung und kommunizieren Umfang und Risiken transparent.", + "services.app.feature_1": "Cross-Platform Entwicklung", + "services.app.feature_2": "Push-Benachrichtigungen", + "services.app.feature_3": "Login, Chat und Karten", + "services.app.feature_4": "Store- und Release-Begleitung", + "services.individual.title": "Individuelle Lösungen", + "services.individual.description": "Interne Tools, Automatisierungen und Integrationen für Anforderungen, die nicht in ein fertiges Produkt passen.", + "services.individual.result": "Das Ergebnis: weniger Medienbrüche, weniger Handarbeit und Software, die deinen vorhandenen Prozess wirklich unterstützt.", + "services.individual.feature_1": "Prozessautomatisierung", + "services.individual.feature_2": "Drittanbieter-Integrationen", + "services.individual.feature_3": "Interne Verwaltungsoberflächen", + "services.individual.feature_4": "Datenverarbeitung und APIs", + "services.process.eyebrow": "Zusammenarbeit", + "services.process.title": "Vom Problem zur funktionierenden Lösung.", + "services.process.description": "Ein transparenter Ablauf hält Entscheidungen nachvollziehbar und verhindert Überraschungen kurz vor dem Launch.", + "services.process.discovery.title": "Verstehen", + "services.process.discovery.description": "Wir klären Ziel, Nutzer, Anforderungen und den tatsächlichen Umfang.", + "services.process.concept.title": "Konzipieren", + "services.process.concept.description": "Struktur, technische Basis und zentrale Nutzerwege werden festgelegt.", + "services.process.build.title": "Entwickeln", + "services.process.build.description": "Die Lösung entsteht in nachvollziehbaren Schritten mit regelmäßigen Abstimmungen.", + "services.process.launch.title": "Veröffentlichen", + "services.process.launch.description": "Nach Tests folgen Deployment, Übergabe und die weitere technische Begleitung.", + "services.final.eyebrow": "Der nächste Schritt", + "services.final.title": "Du hast eine Idee oder ein konkretes Problem?", + "services.final.description": "Beschreibe kurz, was du erreichen möchtest. Gemeinsam finden wir heraus, welcher technische Weg sinnvoll ist.", + "contact.label": "Information", "contact.title": "Kontakt", "contact.subtitle": "Kontaktformular", @@ -40,17 +102,17 @@ "imprint.label": "Rechtliches", "imprint.title": "Impressum", - "imprint.subtitle": "Angaben gemäß § 5 TMG", + "imprint.subtitle": "Angaben gemäß § 5 DDG", "imprint.responsible": "Verantwortlich", "imprint.disclaimer": "Haftungsausschluss", "imprint.liability_content": "Haftung für Inhalte", "imprint.liability_links": "Haftung für Links", "imprint.copyright": "Urheberrecht", - "imprint.last_updated": "Zuletzt aktualisiert: April 2026", + "imprint.last_updated": "Stand: 12. Juni 2026", "privacy.label": "Rechtliches", "privacy.title": "Datenschutzerklärung", - "privacy.subtitle": "Informationen zur Verarbeitung personenbezogener Daten auf NexoryDev-dev.de", + "privacy.subtitle": "Informationen zur Verarbeitung personenbezogener Daten auf nexory-dev.de", "github.stars": "Sterne", "github.forks": "Verzweigungen", @@ -101,6 +163,8 @@ "login.email_not_verified": "E-Mail noch nicht verifiziert", "login.check_email": "Überprüfe dein Postfach und klicke auf den Bestätigungslink", "login.register_failed": "Registrierung fehlgeschlagen", + "login.register_privacy_prefix": "Informationen zur Verarbeitung deiner Daten findest du in der", + "login.register_privacy_link": "Datenschutzerklärung", "login.forgot_password_header": "Passwort zurücksetzen", "login.forgot_password": "Passwort vergessen?", "login.send_reset": "Link senden", @@ -127,7 +191,7 @@ "account.settings.title": "Account-Einstellungen", "account.settings.subtitle": "Verwalte dein öffentliches Profil, den Zugriff auf dein Konto und sensible Aktionen.", "account.settings.profile.title": "Profil", - "account.settings.profile.help": "Diese Informationen werden in deinem Account-Bereich verwendet.", + "account.settings.profile.help": "Mit einem Benutzernamen wird dein verifiziertes Profil öffentlich abrufbar. Öffentliche Profilangaben kannst du jederzeit ändern oder entfernen.", "account.settings.profile.username_placeholder": "Benutzername", "account.settings.security.title": "Sicherheit", "account.settings.security.help": "Aktualisiere dein Passwort regelmässig, um dein Konto sicher zu halten.", diff --git a/frontend/src/i18n/en.json b/frontend/src/i18n/en.json index f6816e4..2cb5dd9 100644 --- a/frontend/src/i18n/en.json +++ b/frontend/src/i18n/en.json @@ -1,5 +1,6 @@ { "nav.home": "Home", + "nav.services": "Services", "nav.github": "GitHub", "nav.contact": "Contact", "nav.policy": "Privacy Policy", @@ -26,13 +27,74 @@ "home.section.web.title": "Web Development", "home.section.web.desc": "Web applications that load quickly, can be operated securely, and grow with your project.", "home.section.app.title": "App Development", - "home.section.app.desc": "Cross-platform apps for iOS and Android with clear navigation and a stable technical foundation.", + "home.section.app.desc": "We are currently starting out with app development and only take on small, clearly scoped iOS and Android projects.", "home.section.individual.title": "Solutions for specific needs", "home.section.individual.desc": "When standard software does not fit, we build the features and integrations your project actually needs.", "home.section.learn_more": "Learn more", "home.cta.title": "Have a project in mind?", "home.cta.button": "Start project", + "services.hero.eyebrow": "Service overview", + "services.hero.title_1": "Services in detail", + "services.hero.title_2": "", + "services.hero.description": "Find the scope, possible technologies, project process, and future real-world examples for every service area.", + "services.cta.project": "Discuss your project", + "services.cta.overview": "View services", + "services.overview.label": "Service navigation", + "services.overview.eyebrow": "What we build", + "services.overview.title": "Four disciplines. One solid technical foundation.", + "services.overview.description": "No generic templates or one-size-fits-all packages. Every service is planned and built around the actual use case.", + "services.detail.label": "Service", + "services.detail.cta": "Discuss requirements", + "services.detail.scope": "Possible scope", + "services.detail.technologies": "Technologies and platforms", + "services.examples.eyebrow": "Reference", + "services.examples.title": "Example projects will be shown here soon.", + "services.examples.description": "Brief project context coming.", + "services.examples.status": "Not published yet", + "services.discord.title": "Discord Bots", + "services.discord.description": "Custom bots that simplify community management and automate recurring tasks reliably.", + "services.discord.result": "The result: less manual moderation, faster workflows, and a system that can grow with your community.", + "services.discord.feature_1": "Moderation and role management", + "services.discord.feature_2": "Ticket and support systems", + "services.discord.feature_3": "Economy, levels, and rewards", + "services.discord.feature_4": "Dashboards and external APIs", + "services.web.title": "Web Development", + "services.web.description": "Fast websites, platforms, and web applications with clear user journeys and a maintainable technical foundation.", + "services.web.result": "The result: a professional application that works on every device and remains ready for future extensions.", + "services.web.feature_1": "Responsive frontends", + "services.web.feature_2": "Backends and REST APIs", + "services.web.feature_3": "Accounts and dashboards", + "services.web.feature_4": "Performance and security", + "services.app.title": "App Development", + "services.app.description": "We are still at the beginning of app development and deliberately take on small, clearly scoped projects here.", + "services.app.result": "Important: this is an area where we are currently building practical experience, so scope and risks are communicated transparently.", + "services.app.feature_1": "Cross-platform development", + "services.app.feature_2": "Push notifications", + "services.app.feature_3": "Authentication, chat, and maps", + "services.app.feature_4": "Store and release support", + "services.individual.title": "Individual Solutions", + "services.individual.description": "Internal tools, automation, and integrations for requirements that do not fit an off-the-shelf product.", + "services.individual.result": "The result: fewer disconnected tools, less manual work, and software that supports the way you actually operate.", + "services.individual.feature_1": "Process automation", + "services.individual.feature_2": "Third-party integrations", + "services.individual.feature_3": "Internal administration tools", + "services.individual.feature_4": "Data processing and APIs", + "services.process.eyebrow": "Collaboration", + "services.process.title": "From problem to working solution.", + "services.process.description": "A transparent process keeps decisions understandable and prevents surprises shortly before launch.", + "services.process.discovery.title": "Understand", + "services.process.discovery.description": "We clarify the goal, users, requirements, and realistic scope.", + "services.process.concept.title": "Design", + "services.process.concept.description": "We define the structure, technical foundation, and primary user journeys.", + "services.process.build.title": "Build", + "services.process.build.description": "The product is developed in clear steps with regular reviews and feedback.", + "services.process.launch.title": "Launch", + "services.process.launch.description": "After testing, we handle deployment, handover, and continued technical support.", + "services.final.eyebrow": "The next step", + "services.final.title": "Have an idea or a specific problem?", + "services.final.description": "Tell us briefly what you want to achieve. Together we will identify the technical approach that makes sense.", + "contact.label": "Information", "contact.title": "Contact", "contact.subtitle": "Contact form", @@ -40,13 +102,13 @@ "imprint.label": "Legal", "imprint.title": "Imprint", - "imprint.subtitle": "Information pursuant to § 5 TMG (German Telemedia Act)", + "imprint.subtitle": "Information pursuant to Section 5 DDG (German Digital Services Act)", "imprint.responsible": "Responsible Party", "imprint.disclaimer": "Disclaimer", "imprint.liability_content": "Liability for Content", "imprint.liability_links": "Liability for Links", "imprint.copyright": "Copyright", - "imprint.last_updated": "Last updated: April 2026", + "imprint.last_updated": "Last updated: 12 June 2026", "privacy.label": "Legal", "privacy.title": "Privacy Policy", @@ -102,6 +164,8 @@ "login.email_not_verified": "Email not verified yet", "login.check_email": "Check your inbox and click the confirmation link", "login.register_failed": "Registration failed", + "login.register_privacy_prefix": "Information about how we process your data is available in the", + "login.register_privacy_link": "privacy policy", "login.forgot_password_header": "Reset password", "login.forgot_password": "Forgot password?", "login.send_reset": "Send reset link", @@ -128,7 +192,7 @@ "account.settings.title": "Account settings", "account.settings.subtitle": "Manage your public profile, account access, and sensitive actions.", "account.settings.profile.title": "Profile", - "account.settings.profile.help": "This information is used across your account area.", + "account.settings.profile.help": "Setting a username makes your verified profile publicly accessible. You can change or remove public profile details at any time.", "account.settings.profile.username_placeholder": "Username", "account.settings.security.title": "Security", "account.settings.security.help": "Update your password regularly to keep your account secure.", diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index 5989256..aab9db7 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -4,28 +4,21 @@ import { Canvas, useFrame } from "@react-three/fiber"; import { Points, PointMaterial, RoundedBox } from "@react-three/drei"; import { motion, useReducedMotion } from "framer-motion"; import * as THREE from "three"; -import { ArrowRight, Mouse, MessageSquare, Code2, Smartphone, Users } from "lucide-react"; +import { ArrowRight, Mouse } from "lucide-react"; import { useLanguage } from "../context/LanguageContext"; +import { + SERVICE_ICON_PATHS, + SvgAppDevelopment, + SvgDiscordBot, + SvgIndividualSolutions, + SvgWebDevelopment, +} from "../components/icons/svgs"; import "../styles/Home.css"; -const BG = "#020409"; +const BG = "#03060b"; const fadeEase = [0.16, 1, 0.3, 1]; const SECTION_IDS = ["hero", "discord", "web", "mobile", "individual", "cta"]; -function rrect(ctx, x, y, w, h, r) { - ctx.beginPath(); - ctx.moveTo(x + r, y); - ctx.lineTo(x + w - r, y); - ctx.arcTo(x + w, y, x + w, y + r, r); - ctx.lineTo(x + w, y + h - r); - ctx.arcTo(x + w, y + h, x + w - r, y + h, r); - ctx.lineTo(x + r, y + h); - ctx.arcTo(x, y + h, x, y + h - r, r); - ctx.lineTo(x, y + r); - ctx.arcTo(x, y, x + r, y, r); - ctx.closePath(); -} - function fitText(ctx, text, maxWidth, startSize, minSize) { let size = startSize; ctx.font = `800 ${size}px 'Outfit','Inter',sans-serif`; @@ -38,7 +31,13 @@ function fitText(ctx, text, maxWidth, startSize, minSize) { return size; } -function drawFace(label, iconFn, color) { +function drawServiceIcon(ctx, paths) { + paths.forEach((path) => { + ctx.stroke(new Path2D(path)); + }); +} + +function drawFace(label, iconPaths, color) { const cv = document.createElement("canvas"); cv.width = 1024; cv.height = 1024; @@ -59,7 +58,7 @@ function drawFace(label, iconFn, color) { c.lineJoin = "round"; c.shadowBlur = 16; c.shadowColor = color; - iconFn(c); + drawServiceIcon(c, iconPaths); c.restore(); c.shadowBlur = 0; @@ -86,72 +85,6 @@ function drawFace(label, iconFn, color) { return texture; } -const ICONS = { - discord: (c) => { - c.beginPath(); - c.moveTo(5.5, 10); - c.bezierCurveTo(4.8, 7, 6.8, 4.5, 9.2, 4.5); - c.lineTo(14.8, 4.5); - c.bezierCurveTo(17.2, 4.5, 19.2, 7, 18.5, 10); - c.lineTo(18.2, 15.5); - c.bezierCurveTo(17.8, 18.2, 16, 19.5, 14.2, 19.5); - c.lineTo(13, 21.2); - c.lineTo(12, 19.5); - c.lineTo(9.8, 19.5); - c.bezierCurveTo(8, 19.5, 6.2, 18.2, 5.8, 15.5); - c.closePath(); - c.stroke(); - c.beginPath(); - c.arc(9.2, 12.5, 1.9, 0, Math.PI * 2); - c.fill(); - c.beginPath(); - c.arc(14.8, 12.5, 1.9, 0, Math.PI * 2); - c.fill(); - c.beginPath(); - c.moveTo(9.5, 16.2); - c.quadraticCurveTo(12, 18, 14.5, 16.2); - c.stroke(); - }, - web: (c) => { - c.lineWidth = 2.1; - c.beginPath(); - c.moveTo(8, 6.5); - c.lineTo(2.5, 12); - c.lineTo(8, 17.5); - c.stroke(); - c.beginPath(); - c.moveTo(14.5, 4.5); - c.lineTo(9.5, 19.5); - c.stroke(); - c.beginPath(); - c.moveTo(16, 6.5); - c.lineTo(21.5, 12); - c.lineTo(16, 17.5); - c.stroke(); - }, - app: (c) => { - rrect(c, 6.5, 1.5, 11, 21, 2.2); - c.stroke(); - c.beginPath(); - c.moveTo(10, 20); - c.lineTo(14, 20); - c.stroke(); - c.beginPath(); - c.arc(12, 4.2, 1, 0, Math.PI * 2); - c.fill(); - }, - individual: (c) => { - c.beginPath(); - c.arc(12, 7.5, 3.8, 0, Math.PI * 2); - c.stroke(); - c.beginPath(); - c.moveTo(3.5, 22); - c.bezierCurveTo(3.5, 16.5, 7.2, 13.5, 12, 13.5); - c.bezierCurveTo(16.8, 13.5, 20.5, 16.5, 20.5, 22); - c.stroke(); - }, -}; - function Particles({ count, progressRef }) { const ref = useRef(); @@ -177,7 +110,7 @@ function Particles({ count, progressRef }) { return ( - + ); } @@ -216,10 +149,10 @@ function AccentCubes({ mobile }) { {cubes.map((cube, index) => ( - + - + ))} @@ -514,10 +447,10 @@ function Scene({ progressRef, pointerRef, reducedMotion, mobile, cubeTexts = {} const textures = useMemo( () => ({ - discord: drawFace(cubeTexts.discord || "Discord Bot", ICONS.discord, "#5865f2"), - web: drawFace(cubeTexts.web || "Web Entwicklung", ICONS.web, "#0ea5e9"), - app: drawFace(cubeTexts.app || "Mobile App", ICONS.app, "#ec4899"), - individual: drawFace(cubeTexts.individual || "Individual", ICONS.individual, "#a855f7"), + discord: drawFace(cubeTexts.discord || "Discord Bot", SERVICE_ICON_PATHS.discord, "#5865f2"), + web: drawFace(cubeTexts.web || "Web Entwicklung", SERVICE_ICON_PATHS.web, "#0ea5e9"), + app: drawFace(cubeTexts.app || "Mobile App", SERVICE_ICON_PATHS.app, "#ec4899"), + individual: drawFace(cubeTexts.individual || "Individual", SERVICE_ICON_PATHS.individual, "#a855f7"), }), [cubeTexts] ); @@ -534,19 +467,20 @@ function Scene({ progressRef, pointerRef, reducedMotion, mobile, cubeTexts = {} - - + + + @@ -802,49 +736,49 @@ export default function Home() { } + icon={} align="left" title={t("home.section.discord.title")} desc={t("home.section.discord.desc")} learnMore={t("home.section.learn_more")} > - + } + icon={} align="right" title={t("home.section.web.title")} desc={t("home.section.web.desc")} learnMore={t("home.section.learn_more")} > - + } + icon={} align="left" title={t("home.section.app.title")} desc={t("home.section.app.desc")} learnMore={t("home.section.learn_more")} > - + } + icon={} align="right" title={t("home.section.individual.title")} desc={t("home.section.individual.desc")} learnMore={t("home.section.learn_more")} > - +
    diff --git a/frontend/src/pages/Imprint.jsx b/frontend/src/pages/Imprint.jsx index 4e9b9b4..c54b3f5 100644 --- a/frontend/src/pages/Imprint.jsx +++ b/frontend/src/pages/Imprint.jsx @@ -1,73 +1,81 @@ -import '../styles/Legal.css'; -import { useLanguage } from '../context/LanguageContext'; +import "../styles/Legal.css"; +import { useLanguage } from "../context/LanguageContext"; export default function Imprint() { - const { t } = useLanguage(); + const { t, language } = useLanguage(); + const german = language === "de"; return (
    -

    {t('imprint.label')}

    -

    {t('imprint.title')}

    -

    {t('imprint.subtitle')}

    +

    {t("imprint.label")}

    +

    {t("imprint.title")}

    +

    {t("imprint.subtitle")}

    -

    {t('imprint.responsible')}

    +

    {german ? "Angaben gemäß § 5 DDG" : "Information pursuant to Section 5 DDG"}

    Luca Bohnet
    Vogelsangweg 3
    72202 Nagold
    - Germany
    - Phone: +49 7452 8866722 + Deutschland

    - Email:{' '} - support@nexory-dev.de + {german ? "Telefon" : "Phone"}: +49 7452 8866722
    + E-Mail: support@nexory-dev.de

    -

    {t('imprint.disclaimer')}

    - -

    {t('imprint.liability_content')}

    -

    - The content of this website is created with highest possible care. However, we cannot - guarantee that the information is always complete, correct, or up to date. We are - therefore liable only according to applicable legal provisions. -

    +

    {german ? "Verantwortlich für redaktionelle Inhalte" : "Responsible for editorial content"}

    - This imprint is provided for legal compliance and informational purposes only and does - not constitute legal advice. + {german + ? "Verantwortlich gemäß § 18 Abs. 2 MStV, soweit journalistisch-redaktionelle Inhalte angeboten werden:" + : "Responsible under Section 18(2) of the German State Media Treaty where journalistic-editorial content is provided:"}

    +

    Luca Bohnet, Vogelsangweg 3, 72202 Nagold, Deutschland

    +
    -

    {t('imprint.liability_links')}

    +
    +

    {german ? "Verbraucherstreitbeilegung" : "Consumer dispute resolution"}

    - This website may contain links to third-party websites. We have no influence on the - content of external websites and therefore assume no liability for them. The respective - provider or operator of the linked pages is solely responsible for their content. + {german + ? "Wir sind nicht bereit oder verpflichtet, an Streitbeilegungsverfahren vor einer Verbraucherschlichtungsstelle teilzunehmen." + : "We are neither willing nor obliged to participate in dispute-resolution proceedings before a consumer arbitration board."}

    +
    + +
    +

    {german ? "Haftung für Inhalte" : "Liability for content"}

    - At the time of linking, no illegal content was apparent. A permanent content control of - the linked pages is not possible without concrete evidence of a violation of law. + {german + ? "Als Diensteanbieter sind wir für eigene Inhalte auf diesen Seiten nach den allgemeinen Gesetzen verantwortlich. Eine Verpflichtung zur Überwachung übermittelter oder gespeicherter fremder Informationen besteht nur im Rahmen der gesetzlichen Vorschriften. Verpflichtungen zur Entfernung oder Sperrung von Informationen nach den allgemeinen Gesetzen bleiben unberührt." + : "As a service provider, we are responsible for our own content under general law. Duties to monitor third-party information arise only where required by law. Statutory obligations to remove or block information remain unaffected."}

    +
    -

    {t('imprint.copyright')}

    +
    +

    {german ? "Haftung für Links" : "Liability for links"}

    - The content and works published on this website by the operator are subject to German - and international copyright law. Reproduction, editing, distribution, or any form of - commercialization of such material beyond the scope of copyright law requires the prior - written consent of the respective author or creator. + {german + ? "Unser Angebot enthält Links zu externen Websites, auf deren Inhalte wir keinen Einfluss haben. Für diese fremden Inhalte ist der jeweilige Anbieter verantwortlich. Bei Bekanntwerden konkreter Rechtsverletzungen werden entsprechende Links unverzüglich entfernt." + : "This service contains links to external websites whose content we cannot control. The respective provider is responsible for that content. Links will be removed promptly if a specific legal violation becomes known."}

    +
    + +
    +

    {german ? "Urheberrecht" : "Copyright"}

    - Downloads and copies of this website are permitted solely for private, non-commercial - use. + {german + ? "Die durch den Betreiber erstellten Inhalte und Werke unterliegen dem deutschen Urheberrecht. Vervielfältigung, Bearbeitung, Verbreitung und Verwertung außerhalb der gesetzlichen Grenzen bedürfen der vorherigen Zustimmung des jeweiligen Rechteinhabers. Rechte Dritter werden beachtet und entsprechende Inhalte als solche gekennzeichnet." + : "Content and works created by the operator are subject to German copyright law. Reproduction, adaptation, distribution, or exploitation beyond statutory limits requires prior permission from the respective rights holder. Third-party rights are respected and relevant content is identified accordingly."}

    -

    {t('imprint.last_updated')}

    +

    {german ? "Stand: 12. Juni 2026" : "Last updated: 12 June 2026"}

    diff --git a/frontend/src/pages/Login.jsx b/frontend/src/pages/Login.jsx index 1c782a0..1cb5c61 100644 --- a/frontend/src/pages/Login.jsx +++ b/frontend/src/pages/Login.jsx @@ -1,7 +1,7 @@ import { useState } from "react"; import { IconEye, IconEyeOff } from "../components/icons/svgs"; import { useLanguage } from "../context/LanguageContext"; -import { useNavigate } from "react-router-dom"; +import { Link, useNavigate } from "react-router-dom"; import { useAuth } from "../auth/AuthProvider"; import "../styles/Login.css"; @@ -282,18 +282,25 @@ export default function Login() { - {mode === "login" && ( - + )} + + {mode === "register" && ( +

    + {t("login.register_privacy_prefix")}{" "} + {t("login.register_privacy_link")}. +

    + )} + + {mode === "login" ? ( diff --git a/frontend/src/pages/Privacy.jsx b/frontend/src/pages/Privacy.jsx index 6e89cad..a40eb6b 100644 --- a/frontend/src/pages/Privacy.jsx +++ b/frontend/src/pages/Privacy.jsx @@ -1,8 +1,466 @@ import "../styles/Legal.css"; import { useLanguage } from "../context/LanguageContext"; +const SUPPORT_EMAIL = "support@nexory-dev.de"; + +function ContactAddress({ country = "Deutschland" }) { + return ( +

    + Luca Bohnet
    + Vogelsangweg 3
    + 72202 Nagold
    + {country} +

    + ); +} + +function GermanPrivacy() { + return ( + <> +
    +

    1. Verantwortlicher

    + +

    + Telefon: +49 7452 8866722
    + E-Mail: {SUPPORT_EMAIL} +

    +
    + +
    +

    2. Allgemeine Hinweise

    +

    + Diese Datenschutzerklärung informiert gemäß Art. 12 bis 14 DSGVO darüber, wie personenbezogene Daten beim + Besuch dieser Website, bei der Nutzung eines Benutzerkontos und bei freiwillig verbundenen Diensten + verarbeitet werden. Die DSGVO gilt für die Verarbeitung durch den in Deutschland ansässigen + Verantwortlichen unabhängig vom Aufenthaltsort der betroffenen Person. +

    +

    + Personenbezogene Daten werden nur verarbeitet, soweit dies für den Betrieb, die Sicherheit und die + angeforderten Funktionen erforderlich ist oder eine Einwilligung erteilt wurde. +

    +
    + +
    +

    3. Hosting, Auslieferung und Server-Protokolle

    +

    + Beim Aufruf der Website verarbeiten der Webserver und der eingesetzte Hostinganbieter insbesondere + IP-Adresse, Datum und Uhrzeit, aufgerufene Ressource, HTTP-Status, übertragene Datenmenge, Referrer sowie + Browser- und Betriebssysteminformationen. Die Verarbeitung ist für die Auslieferung der Website sowie zur + Erkennung und Abwehr von Angriffen erforderlich. +

    +

    + Rechtsgrundlage ist Art. 6 Abs. 1 lit. f DSGVO. Unser berechtigtes Interesse liegt im sicheren, stabilen und + fehlerfreien Betrieb des Angebots. Anwendungsbezogene Sicherheitsprotokolle werden in der + Produktionsumgebung grundsätzlich spätestens nach 90 Tagen gelöscht, sofern sie nicht zur Aufklärung eines + konkreten Sicherheitsvorfalls länger benötigt werden. Infrastrukturprotokolle des Hostinganbieters werden + nach dessen vertraglichen Löschfristen entfernt. +

    +

    + Empfänger können der Hostinganbieter und technische Dienstleister sein, die als Auftragsverarbeiter nach + Art. 28 DSGVO eingesetzt werden. Der konkret eingesetzte Hostinganbieter und dessen Serverstandort richten + sich nach der jeweiligen Bereitstellungskonfiguration. +

    +
    + +
    +

    4. Cookies und Speicher im Browser

    +

    + Es werden keine Analyse-, Werbe- oder Profiling-Cookies eingesetzt. Für Anmeldung und Einstellungen werden + ausschließlich technisch erforderliche Speichermechanismen verwendet. Rechtsgrundlage für den Zugriff auf + die Endeinrichtung ist § 25 Abs. 2 Nr. 2 TDDDG; die anschließende Verarbeitung erfolgt nach Art. 6 Abs. 1 + lit. b oder lit. f DSGVO. +

    +
      +
    • refresh_token: HttpOnly-Cookie für die Anmeldung; Sitzungsdauer oder höchstens 7 Tage bei „angemeldet bleiben“.
    • +
    • access_token: kurzlebiges Authentifizierungstoken im Session- oder Local Storage; technisch 15 Minuten gültig.
    • +
    • remember_me: speichert lokal, ob die dauerhafte Anmeldung gewählt wurde.
    • +
    • device_id: zufällige lokale Gerätekennung zur Verwaltung von Anmeldesitzungen.
    • +
    • language: speichert die gewählte Sprache bis zur Löschung durch den Nutzer.
    • +
    • gh_connect_token: vorübergehendes Token im Session Storage während einer GitHub-Verknüpfung.
    • +
    +

    Diese Daten können über die Browserfunktionen gelöscht werden; dadurch kann eine erneute Anmeldung erforderlich werden.

    +
    + +
    +

    5. Registrierung und Benutzerkonto

    +

    + Für Registrierung, Anmeldung und Kontoverwaltung verarbeiten wir E-Mail-Adresse, Passwort-Hash, + Verifikationsstatus, Benutzer-ID, Erstellungszeitpunkt und optional einen Benutzernamen. Zur Absicherung von + Sitzungen werden außerdem Token-Hash, Ablaufzeit, IP-Adresse, User-Agent, Gerätekennung und die Auswahl + „angemeldet bleiben“ verarbeitet. +

    +

    + Rechtsgrundlage ist Art. 6 Abs. 1 lit. b DSGVO. E-Mail-Adresse und Passwort sind für die Bereitstellung eines + Kontos erforderlich. Ohne diese Angaben kann kein Konto erstellt werden. IP-Adresse und Sitzungsmetadaten + werden zusätzlich auf Grundlage von Art. 6 Abs. 1 lit. f DSGVO zur Missbrauchs- und Angriffserkennung + verarbeitet. +

    +

    + Kontodaten werden bis zur Kontolöschung gespeichert. Abgelaufene oder widerrufene Sitzungstoken werden + regelmäßig gelöscht. Nicht dauerhaft angemeldete Sitzungen laufen nach 24 Stunden, dauerhaft angemeldete + Sitzungen nach 7 Tagen ab. +

    +
    + +
    +

    6. E-Mail-Verifikation und Passwort-Zurücksetzung

    +

    + Für Verifikations- und Passwort-Zurücksetzungsnachrichten werden E-Mail-Adresse und ein einmalig nutzbarer + Link an Resend, einen Dienst von Plus Five Five, Inc. in den USA, übermittelt. Resend wird als + Auftragsverarbeiter eingesetzt. Rechtsgrundlage ist Art. 6 Abs. 1 lit. b DSGVO. +

    +

    + Verifikationstoken sind 24 Stunden, Passwort-Reset-Token eine Stunde gültig und werden nach Nutzung oder + Ablauf gelöscht. Für Übermittlungen in die USA werden die im Auftragsverarbeitungsvertrag vorgesehenen + Garantien, insbesondere EU-Standardvertragsklauseln, verwendet, soweit kein Angemessenheitsbeschluss greift. + Weitere Informationen: Resend Privacy Policy und{' '} + Data Processing Addendum. +

    +
    + +
    +

    7. Zwei-Faktor-Authentifizierung

    +

    + Bei freiwilliger Aktivierung werden ein TOTP-Geheimnis, gehashte Einmal-Backupcodes und kurzfristige + Anmelde-Challenges verarbeitet. Ausstehende Einrichtungsdaten verfallen nach 10 Minuten, + Anmelde-Challenges nach 5 Minuten. Das TOTP-Geheimnis bleibt bis zur Deaktivierung der Zwei-Faktor- + Authentifizierung oder Kontolöschung gespeichert. Ein verwendeter Backupcode wird unmittelbar gelöscht. +

    +

    Rechtsgrundlagen sind Art. 6 Abs. 1 lit. b und lit. f DSGVO; unser berechtigtes Interesse ist die Kontosicherheit.

    +
    + +
    +

    8. Öffentliches Profil und Avatare

    +

    + Verifizierte Nutzer können durch das Setzen eines Benutzernamens ein öffentlich abrufbares Profil unter + /user/<username> bereitstellen. Öffentlich erscheinen können Benutzername, selbst + hochgeladener Avatar, Biografie, gewählter Standort, daraus abgeleitete Zeitzone und Ortszeit, GitHub- + Benutzername, Abzeichen sowie Monat und Jahr der Mitgliedschaft. +

    +

    + Rechtsgrundlage ist Art. 6 Abs. 1 lit. b DSGVO, da das Profil auf Wunsch des Nutzers bereitgestellt wird. + Freiwillige Angaben können jederzeit geändert oder entfernt werden. Durch Entfernen des Benutzernamens ist + das öffentliche Profil nicht mehr über den bisherigen Profilpfad abrufbar. +

    +

    + Hochgeladene Avatare werden lokal geprüft, quadratisch zugeschnitten, auf 400 × 400 Pixel verkleinert und als + JPEG unter einem zufälligen Dateinamen gespeichert. Originaldatei und ursprünglicher Dateiname werden nicht + dauerhaft gespeichert. Beim Austausch und bei Kontolöschung wird die bisherige Avatar-Datei gelöscht. +

    +
    + +
    +

    9. GitHub-Inhalte und GitHub OAuth

    +

    + Zur Darstellung der Open-Source-Aktivitäten ruft unser Server öffentlich verfügbare Organisations-, + Repository-, Mitglieds- und Beitragsdaten über die GitHub API ab und speichert sie kurzzeitig zwischen. + Rechtsgrundlage ist Art. 6 Abs. 1 lit. f DSGVO; das berechtigte Interesse besteht in der Darstellung unserer + öffentlich zugänglichen Projekte. Beim bloßen Seitenbesuch wird keine direkte Browseranfrage an die GitHub API ausgelöst. +

    +

    + Nutzer können ihr Konto freiwillig über GitHub OAuth verbinden. Angefordert wird der Umfang + read:user. Dauerhaft gespeichert werden GitHub-Benutzername und numerische GitHub-ID. Das + temporäre OAuth-Zugriffstoken wird nur zum Abruf des Profils verwendet und nicht dauerhaft gespeichert. + Bei der Abzeichenprüfung werden öffentlich zugängliche Beitragsdaten mit dem verknüpften GitHub-Profil + verglichen. Rechtsgrundlage ist Art. 6 Abs. 1 lit. a DSGVO. Die Einwilligung kann jederzeit durch Trennen der + Verbindung in den Kontoeinstellungen widerrufen werden; die gespeicherte GitHub-ID und der Benutzername + werden dabei gelöscht. +

    +

    + Empfänger ist GitHub, Inc., USA. Bei Nutzung von GitHub kann eine Verarbeitung in den USA stattfinden. + Informationen zur Verarbeitung und zu Übermittlungsgrundlagen enthält die{' '} + GitHub-Datenschutzerklärung. +

    +
    + +
    +

    10. Discord-Bot

    +

    + Soweit der Discord-Bot „Nexory“ unter Verantwortung des oben genannten Verantwortlichen genutzt wird, + können für die angeforderte Funktion Discord-Nutzer-ID, Server-ID, Befehlsinhalt und Konfigurationsdaten + verarbeitet werden. Rechtsgrundlagen sind Art. 6 Abs. 1 lit. b und lit. f DSGVO. Das berechtigte Interesse + besteht in Betriebssicherheit und Missbrauchsvermeidung. Funktionsdaten werden gelöscht, wenn sie für die + Funktion nicht mehr erforderlich sind oder der Bot vom Server entfernt wird; Sicherheitsprotokolle + spätestens nach 90 Tagen, sofern kein Vorfall eine längere Aufbewahrung erfordert. +

    +

    + Discord ist ein eigenständiger Anbieter und kann Daten in Drittländern verarbeiten. Es gilt ergänzend die{' '} + Datenschutzerklärung von Discord. +

    +
    + +
    +

    11. Empfänger und Auftragsverarbeitung

    +

    + Daten erhalten nur Stellen, die sie für die genannten Zwecke benötigen. Dazu gehören Hosting- und + Infrastrukturprovider, Resend für transaktionale E-Mails sowie GitHub beziehungsweise Discord bei + freiwilliger Nutzung der jeweiligen Dienste. Interne Datenbank-, Redis- und Virenscan-Komponenten werden + innerhalb der betriebenen Infrastruktur eingesetzt. Auftragsverarbeiter werden nach Art. 28 DSGVO + vertraglich verpflichtet. +

    +
    + +
    +

    12. Drittlandübermittlungen

    +

    + Bei Resend, GitHub und Discord kann eine Verarbeitung außerhalb der EU oder des EWR, insbesondere in den + USA, stattfinden. Eine Übermittlung erfolgt nur auf Grundlage eines Angemessenheitsbeschlusses nach Art. 45 + DSGVO oder geeigneter Garantien nach Art. 46 DSGVO, insbesondere EU-Standardvertragsklauseln. Trotz dieser + Garantien kann in Drittländern ein von der EU abweichendes Datenschutzniveau bestehen. +

    +
    + +
    +

    13. Löschung und Aufbewahrung

    +

    + Soweit oben keine konkrete Frist genannt ist, werden Daten gelöscht, sobald der Zweck entfällt und keine + gesetzlichen Aufbewahrungspflichten oder berechtigten Gründe für eine weitere Speicherung bestehen. Bei + Kontolöschung werden Konto-, Profil-, Sitzungs-, 2FA- und Verknüpfungsdaten sowie lokal gespeicherte Avatare + gelöscht. Gesetzlich aufzubewahrende Daten werden bis zum Ende der jeweiligen Frist gesperrt. +

    +
    + +
    +

    14. Betroffenenrechte

    +

    + Betroffene haben nach Maßgabe der gesetzlichen Voraussetzungen das Recht auf Auskunft (Art. 15 DSGVO), + Berichtigung (Art. 16), Löschung (Art. 17), Einschränkung (Art. 18), Datenübertragbarkeit (Art. 20) und + Widerspruch (Art. 21). Eine Einwilligung kann jederzeit mit Wirkung für die Zukunft widerrufen werden, ohne + dass die Rechtmäßigkeit der vorherigen Verarbeitung berührt wird. +

    +

    Anfragen können an {SUPPORT_EMAIL} gerichtet werden.

    +
    + +
    +

    15. Widerspruch gegen berechtigte Interessen

    +

    + Soweit die Verarbeitung auf Art. 6 Abs. 1 lit. f DSGVO beruht, kann aus Gründen, die sich aus der besonderen + Situation der betroffenen Person ergeben, jederzeit Widerspruch eingelegt werden. Wir verarbeiten die Daten + dann nicht weiter, sofern keine zwingenden schutzwürdigen Gründe oder Rechtsansprüche entgegenstehen. +

    +
    + +
    +

    16. Beschwerderecht

    +

    + Es besteht das Recht, sich bei einer Datenschutzaufsichtsbehörde zu beschweren. Zuständig für den + Verantwortlichen ist der Landesbeauftragte für den Datenschutz und die Informationsfreiheit Baden-Württemberg, + Lautenschlagerstraße 20, 70173 Stuttgart. Weitere Informationen:{' '} + baden-wuerttemberg.datenschutz.de. +

    +
    + +
    +

    17. Automatisierte Entscheidungen und Minderjährige

    +

    + Es findet keine ausschließlich automatisierte Entscheidungsfindung einschließlich Profiling im Sinne von + Art. 22 DSGVO statt. Das Angebot richtet sich nicht gezielt an Kinder unter 16 Jahren. Personen unter 16 + Jahren sollen kein Konto ohne Zustimmung der Erziehungsberechtigten erstellen. +

    +
    + +
    +

    18. Änderungen

    +

    Diese Erklärung wird angepasst, wenn sich Funktionen, Dienstleister oder die Rechtslage ändern.

    +
    + + ); +} + +function EnglishPrivacy() { + return ( + <> +
    +

    1. Controller

    + +

    + Phone: +49 7452 8866722
    + Email: {SUPPORT_EMAIL} +

    +
    + +
    +

    2. General information

    +

    + This policy provides the information required by Articles 12 to 14 GDPR about processing when visiting the + website, using an account, or voluntarily connecting third-party services. The GDPR applies to processing by + the controller established in Germany regardless of the user's place of residence. +

    +
    + +
    +

    3. Hosting and server logs

    +

    + The web server and hosting provider process the IP address, date and time, requested resource, HTTP status, + transferred data volume, referrer, browser, and operating-system information to deliver and secure the site. + The legal basis is Art. 6(1)(f) GDPR, based on our interest in secure and reliable operation. +

    +

    + Application security logs in production are normally deleted after no more than 90 days unless required for + investigating a specific incident. Infrastructure logs are deleted under the hosting provider's contractual + retention rules. Hosting and technical providers may act as processors under Art. 28 GDPR. The provider and + server location depend on the deployment configuration in use. +

    +
    + +
    +

    4. Cookies and browser storage

    +

    + We do not use analytics, advertising, or profiling cookies. Access to the user's device is limited to + technically necessary storage under Section 25(2)(2) TDDDG; subsequent processing is based on Art. 6(1)(b) + or (f) GDPR. +

    +
      +
    • refresh_token: HttpOnly login cookie; session duration or up to 7 days when “stay signed in” is selected.
    • +
    • access_token: authentication token in session or local storage; technically valid for 15 minutes.
    • +
    • remember_me: stores the persistent-login choice locally.
    • +
    • device_id: random local identifier used to manage login sessions.
    • +
    • language: stores the selected language until removed by the user.
    • +
    • gh_connect_token: temporary session-storage token during GitHub linking.
    • +
    +
    + +
    +

    5. Registration and accounts

    +

    + We process email address, password hash, verification status, user ID, account creation time, and an optional + username. Session security additionally uses token hash, expiry, IP address, user agent, device ID, and the + persistent-login choice. Art. 6(1)(b) GDPR is the legal basis. Email and password are required to create an + account. Security metadata is additionally processed under Art. 6(1)(f) GDPR to prevent abuse and attacks. +

    +

    + Account data is retained until account deletion. Non-persistent sessions expire after 24 hours and persistent + sessions after 7 days. Expired or revoked tokens are removed regularly. +

    +
    + +
    +

    6. Verification and password reset emails

    +

    + Email addresses and single-use links are sent to Resend, a service of Plus Five Five, Inc. in + the United States, acting as a processor. The legal basis is Art. 6(1)(b) GDPR. Verification tokens are valid + for 24 hours and password-reset tokens for one hour, and are removed after use or expiry. +

    +

    + Transfers to the United States rely on the safeguards in the processing agreement, including EU Standard + Contractual Clauses where no adequacy decision applies. See the{' '} + Resend Privacy Policy and{' '} + Data Processing Addendum. +

    +
    + +
    +

    7. Two-factor authentication

    +

    + If enabled voluntarily, we process a TOTP secret, hashed single-use backup codes, and short-lived login + challenges. Pending setup data expires after 10 minutes and login challenges after 5 minutes. The TOTP secret + remains until 2FA is disabled or the account is deleted. A used backup code is deleted immediately. The legal + bases are Art. 6(1)(b) and (f) GDPR, based on our interest in account security. +

    +
    + +
    +

    8. Public profiles and avatars

    +

    + A verified user can create a public profile by setting a username. It may display the username, uploaded + avatar, bio, selected location, derived timezone and local time, GitHub username, badges, and membership month + and year. The legal basis is Art. 6(1)(b) GDPR because the profile is provided at the user's request. Optional + fields can be changed or removed at any time; removing the username disables the existing public profile path. +

    +

    + Uploaded avatars are scanned locally, cropped, resized to 400 × 400 pixels, and stored as JPEG under a random + filename. The original file and filename are not retained. Previous avatar files are deleted when replaced or + when the account is deleted. +

    +
    + +
    +

    9. GitHub content and OAuth

    +

    + Our server retrieves publicly available organization, repository, member, and contribution data from the + GitHub API and caches it briefly to present our open-source work. The basis is Art. 6(1)(f) GDPR. A normal page + visit does not cause the browser to contact the GitHub API directly. +

    +

    + Users may voluntarily link GitHub with the read:user scope. We retain only the GitHub username + and numeric ID. The temporary OAuth token is used to retrieve the profile and is not retained. Public + contribution data is compared when checking badges. The legal basis is consent under Art. 6(1)(a) GDPR. It + can be withdrawn by disconnecting GitHub, which deletes the stored username and ID. +

    +

    + GitHub, Inc. in the United States is a recipient. See the{' '} + GitHub Privacy Statement. +

    +
    + +
    +

    10. Discord bot

    +

    + Where the “Nexory” Discord bot is operated by the controller above, Discord user ID, server ID, command input, + and configuration data may be processed for the requested function. The bases are Art. 6(1)(b) and (f) GDPR. + Function data is removed when no longer needed or when the bot is removed; security logs are retained for no + more than 90 days unless an incident requires longer retention. Discord may process data in third countries. + See Discord's Privacy Policy. +

    +
    + +
    +

    11. Recipients and international transfers

    +

    + Data is disclosed only to providers needed for the purposes above, including hosting and infrastructure + providers, Resend, GitHub, and Discord where applicable. Processors are bound under Art. 28 GDPR. Transfers + outside the EU/EEA rely on an adequacy decision under Art. 45 GDPR or appropriate safeguards under Art. 46, + particularly EU Standard Contractual Clauses. Internal database, Redis, and malware-scanning components run + within the operated infrastructure. +

    +
    + +
    +

    12. Deletion and retention

    +

    + Unless a specific period is stated above, data is deleted when its purpose ends and no legal retention duty + or overriding legitimate reason remains. Account deletion removes account, profile, session, 2FA, linked- + service data, and locally stored avatars. Data subject to statutory retention is restricted until expiry. +

    +
    + +
    +

    13. Your rights

    +

    + Subject to the legal requirements, you have rights of access (Art. 15 GDPR), rectification (Art. 16), erasure + (Art. 17), restriction (Art. 18), portability (Art. 20), and objection (Art. 21). Consent can be withdrawn at + any time for the future without affecting prior lawful processing. Contact{' '} + {SUPPORT_EMAIL} to exercise these rights. +

    +

    + Where processing relies on Art. 6(1)(f) GDPR, you may object for reasons arising from your particular + situation. Processing will stop unless compelling legitimate grounds or legal claims override the objection. +

    +
    + +
    +

    14. Complaint, automated decisions, and minors

    +

    + You may complain to a supervisory authority. The competent authority is the State Commissioner for Data + Protection and Freedom of Information Baden-Württemberg, Lautenschlagerstraße 20, 70173 Stuttgart:{' '} + baden-wuerttemberg.datenschutz.de. +

    +

    + We do not use solely automated decision-making or profiling under Art. 22 GDPR. The service is not directed + at children under 16, who should not create an account without parental consent. +

    +
    + +
    +

    15. Changes

    +

    This policy will be updated when functions, providers, or legal requirements change.

    +
    + + ); +} + export default function Privacy() { - const { t } = useLanguage(); + const { t, language } = useLanguage(); return (
    @@ -13,284 +471,10 @@ export default function Privacy() {

    {t("privacy.subtitle")}

    -
    -

    1. Data Controller

    -

    - Luca Bohnet
    - Vogelsangweg 3
    - 72202 Nagold
    - Germany -

    -

    - Email:{" "} - support@nexory-dev.de -

    -
    - -
    -

    2. General Information on Data Processing

    -

    - We process personal data only to provide and operate this website, to manage user accounts, and to ensure - security and stability. -

    -

    - The legal bases are primarily Art. 6(1)(b) GDPR (pre-contractual and contractual measures), Art. 6(1)(a) GDPR - (consent), and Art. 6(1)(f) GDPR (legitimate interests in secure and reliable operation). -

    -

    - This privacy statement is intended for visitors worldwide, including users in the EU and EEA. If you are - located in the EU/EEA, GDPR applies to you. -

    -
    - -
    -

    3. Hosting and Server Log Data

    -

    - The website is hosted on servers in Germany. When you access this website, the following technically - necessary data is processed automatically: -

    -
      -
    • IP address
    • -
    • Date and time of the request
    • -
    • Requested URL
    • -
    • HTTP status code
    • -
    • User-Agent (browser and operating system)
    • -
    • Referrer URL (if provided)
    • -
    -

    - The processing is necessary to ensure the availability, stability, and security of the website. - Legal basis: Art. 6(1)(f) GDPR. -

    -
    - -
    -

    4. Cookies and Browser Storage

    - -

    Required Cookie

    -
      -
    • - refresh_token (HttpOnly, Secure, SameSite): used for session management after login. - This cookie cannot be accessed by JavaScript. -
    • -
    - -

    Browser Storage

    -
      -
    • language: selected language (de/en)
    • -
    • access_token: short-lived JWT for API authentication
    • -
    • remember_me: remembers whether "keep me signed in" is enabled
    • -
    • device_id: technical device identifier for login sessions
    • -
    • home_github_stats: temporary cache of public GitHub statistics
    • -
    -

    No tracking, advertising, or profiling cookies are used.

    -
    - -
    -

    5. User Accounts and Authentication

    -

    - The following data is processed for account registration and authentication: -

    -
      -
    • Email address (required)
    • -
    • Password hash (stored securely, no plaintext password)
    • -
    • Username (optional, publicly visible if set)
    • -
    • Avatar (optional)
    • -
    • Verification status and account metadata
    • -
    • - Refresh token metadata (token hash, expiration, user agent, IP address, device ID) -
    • -
    • GitHub username and GitHub ID (only if you voluntarily connect your GitHub account)
    • -
    -

    Legal basis: Art. 6(1)(b) GDPR (provision of the account system).

    -
    - -
    -

    6. Two-Factor Authentication (2FA)

    -

    If you enable 2FA, the following additional data is processed:

    -
      -
    • TOTP secret for generating time-based one-time codes
    • -
    • Backup codes in hashed form (single-use)
    • -
    • Short-lived MFA challenges during login
    • -
    -

    - The code is used only for verification. Backup codes are not stored in plaintext. - Legal basis: Art. 6(1)(b) GDPR and Art. 6(1)(f) GDPR (security). -

    -
    - -
    -

    7. Public Profile Data

    -

    - If you set a username, a public profile may be available at /user/<username>. The following - data may be shown if you provided it: -

    -
      -
    • Username
    • -
    • Avatar (if set)
    • -
    • Member since (month and year)
    • -
    • Bio (optional)
    • -
    • Location (optional, from a predefined list)
    • -
    • Timezone (optional, derived from location)
    • -
    • Badges
    • -
    -

    - This information is voluntary and may be changed or removed at any time. - Legal basis: Art. 6(1)(a) GDPR (consent). -

    -

    - Uploaded avatars are processed server-side, cropped to square format (400×400 px), and stored as JPEG. - Original filenames are not saved. -

    -
    - -
    -

    8. Email Delivery

    -

    - Transactional emails such as account verification and password reset messages are sent using an external - email delivery provider. Your email address is processed only for these necessary communications. -

    -

    Legal basis: Art. 6(1)(b) GDPR.

    -
    - -
    -

    9. Public GitHub Data

    -

    - This website displays publicly available GitHub data related to the organization NexoryDev. - This includes public repository information, stars, commits, and member details. -

    -

    - Requests are made through our own API for the purpose of displaying public open source information. - Legal basis: Art. 6(1)(f) GDPR. -

    -

    - The organization is available at{' '} - - https://github.com/NexoryDev - - . For GitHub's own data processing, see{' '} - - GitHub's privacy statement - - . -

    -
    - -
    -

    10. GitHub OAuth Account Linking

    -

    - You may voluntarily connect your GitHub account to your Nexory account to access GitHub-based badges - and features. -

    -

    Only the following public GitHub data is stored permanently:

    -
      -
    • GitHub username (login name)
    • -
    • GitHub user ID (numeric identifier)
    • -
    -

    - We do not request or store GitHub email addresses, private repositories, OAuth refresh tokens, or write - access. The OAuth scope is limited to read:user. -

    -

    - The temporary GitHub OAuth token is only used to fetch the public profile and is not stored permanently. -

    -

    - You can disconnect GitHub at any time in your account settings. GitHub username and ID are deleted after - unlinking. -

    -

    Legal basis: Art. 6(1)(a) GDPR (consent).

    -
    - -
    -

    11. Discord Bot

    -

    - NexoryDev operates the Discord bot Nexory. The bot's source code is available at{' '} - - https://github.com/NexoryDev/Nexory - - . -

    -

    - When you use the bot, the following data may be processed if required for the requested bot function: - Discord user ID, Discord server ID (guild ID), bot configuration data, and command input. -

    -

    - Legal basis: Art. 6(1)(b) GDPR (execution of the requested service) and Art. 6(1)(f) GDPR (security and abuse - prevention). -

    -

    - Discord is a separate service provider. Its privacy policy also applies: - {' '} - - https://discord.com/privacy - - . -

    -
    - -
    -

    12. Data Security and Cross-Border Transfers

    -

    - We implement technical and organizational measures to protect personal data against unlawful access, - loss, and alteration. -

    -

    - The website is hosted in Germany. If personal data is shared with providers outside the EU/EEA, we use only - providers who offer appropriate safeguards such as EU standard contractual clauses or equivalent measures. -

    -
    - -
    -

    13. Retention Periods

    -
      -
    • Account and profile data (email, username, avatar): until account deletion
    • -
    • Profile information (bio, location, timezone): until update or deletion
    • -
    • Refresh tokens: until expiration or revocation
    • -
    • Email verification and password reset tokens: for a limited period only
    • -
    • 2FA backup codes: until use, regeneration, or deactivation of 2FA
    • -
    • GitHub username and GitHub ID: until unlinking or account deletion
    • -
    • Discord bot data: until deletion by the user, guild admin, or removal of the bot
    • -
    -
    - -
    -

    14. Your Rights

    -

    You have the following rights under the GDPR:

    -
      -
    • Right of access (Art. 15 GDPR)
    • -
    • Right to rectification (Art. 16 GDPR)
    • -
    • Right to erasure (Art. 17 GDPR)
    • -
    • Right to restriction of processing (Art. 18 GDPR)
    • -
    • Right to data portability (Art. 20 GDPR)
    • -
    • Right to object (Art. 21 GDPR)
    • -
    -

    - To exercise your rights, please contact us at{' '} - support@nexory-dev.de. -

    -
    - -
    -

    15. Right to Lodge a Complaint

    -

    - If you believe that the processing of your personal data is unlawful, you may lodge a complaint with a - supervisory authority in the EU member state where you live, work, or where the alleged violation occurred. -

    -
    - -
    -

    16. Changes to This Privacy Policy

    -

    - We may update this privacy policy from time to time. Significant changes will be published on this page with - a new effective date. -

    -
    + {language === "de" ? : }
    -

    Effective date: 8 May 2026 · Version 2

    +

    {language === "de" ? "Stand: 12. Juni 2026 · Version 3" : "Effective: 12 June 2026 · Version 3"}

    diff --git a/frontend/src/pages/services.jsx b/frontend/src/pages/services.jsx new file mode 100644 index 0000000..978b30d --- /dev/null +++ b/frontend/src/pages/services.jsx @@ -0,0 +1,281 @@ +import { useEffect, useState } from "react"; +import { Link } from "react-router-dom"; +import { motion } from "framer-motion"; +import { ArrowRight, Check } from "lucide-react"; +import { useLanguage } from "../context/LanguageContext"; +import { + SvgAppDevelopment, + SvgDiscordBot, + SvgIndividualSolutions, + SvgWebDevelopment, +} from "../components/icons/svgs"; +import "../styles/services.css"; + +const fadeEase = [0.16, 1, 0.3, 1]; + +const navVariants = { + hidden: {}, + visible: { + transition: { staggerChildren: 0.055, delayChildren: 0.12 }, + }, +}; + +const navItemVariants = { + hidden: { opacity: 0, y: 12 }, + visible: { + opacity: 1, + y: 0, + transition: { duration: 0.46, ease: fadeEase }, + }, +}; + +const serviceCardVariants = { + hidden: { opacity: 0, y: 42 }, + visible: (index) => ({ + opacity: 1, + y: 0, + transition: { duration: 0.72, ease: fadeEase, delay: index * 0.04 }, + }), +}; + +const serviceVisualVariants = { + hidden: { opacity: 0, y: 18 }, + visible: { + opacity: 1, + y: 0, + transition: { duration: 0.62, ease: fadeEase, delay: 0.08 }, + }, +}; + +const serviceContentVariants = { + hidden: { opacity: 0, y: 22 }, + visible: { + opacity: 1, + y: 0, + transition: { duration: 0.68, ease: fadeEase, delay: 0.16 }, + }, +}; + +const services = [ + { + id: "discord-bots", + tone: "discord", + icon: SvgDiscordBot, + titleKey: "services.discord.title", + descriptionKey: "services.discord.description", + resultKey: "services.discord.result", + featureKeys: [ + "services.discord.feature_1", + "services.discord.feature_2", + "services.discord.feature_3", + "services.discord.feature_4", + ], + tags: ["discord.py", "Slash Commands", "Webhooks", "MySQL"], + }, + { + id: "web-development", + tone: "web", + icon: SvgWebDevelopment, + titleKey: "services.web.title", + descriptionKey: "services.web.description", + resultKey: "services.web.result", + featureKeys: [ + "services.web.feature_1", + "services.web.feature_2", + "services.web.feature_3", + "services.web.feature_4", + ], + tags: ["React", "Node.js", "Python Flask", "Docker"], + }, + { + id: "app-development", + tone: "app", + icon: SvgAppDevelopment, + titleKey: "services.app.title", + descriptionKey: "services.app.description", + resultKey: "services.app.result", + featureKeys: [ + "services.app.feature_1", + "services.app.feature_2", + "services.app.feature_3", + "services.app.feature_4", + ], + tags: ["React Native", "Expo", "Push API", "App Store"], + }, + { + id: "individual-solutions", + tone: "individual", + icon: SvgIndividualSolutions, + titleKey: "services.individual.title", + descriptionKey: "services.individual.description", + resultKey: "services.individual.result", + featureKeys: [ + "services.individual.feature_1", + "services.individual.feature_2", + "services.individual.feature_3", + "services.individual.feature_4", + ], + tags: ["n8n", "Webhooks", "Admin Panels", "MySQL"], + }, +]; + +export default function Services() { + const { t } = useLanguage(); + const [activeServiceId, setActiveServiceId] = useState(services[0].id); + + useEffect(() => { + const sections = services.map(({ id }) => document.getElementById(id)).filter(Boolean); + if (!sections.length) return undefined; + + const observer = new IntersectionObserver( + (entries) => { + const visible = entries + .filter((entry) => entry.isIntersecting) + .sort((a, b) => b.intersectionRatio - a.intersectionRatio); + + if (visible[0]) setActiveServiceId(visible[0].target.id); + }, + { + rootMargin: "-34% 0px -46% 0px", + threshold: [0.18, 0.32, 0.5, 0.72], + }, + ); + + sections.forEach((section) => observer.observe(section)); + return () => observer.disconnect(); + }, []); + + const handleServiceNavClick = (event, id) => { + event.preventDefault(); + setActiveServiceId(id); + + const target = document.getElementById(id); + if (!target) return; + + const reduceMotion = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches; + target.scrollIntoView({ behavior: reduceMotion ? "auto" : "smooth", block: "start" }); + window.history.replaceState(null, "", `#${id}`); + }; + + return ( +
    +
    + +
    + {t("services.hero.eyebrow")} +

    {t("services.hero.title_1")}

    +

    {t("services.hero.description")}

    +
    + +
    + {t("services.overview.label")} + + {services.map(({ id, icon: Icon, titleKey, tone }) => ( + handleServiceNavClick(event, id)} + variants={navItemVariants} + whileHover={{ y: -2 }} + whileTap={{ scale: 0.985 }} + > + + {t(titleKey)} + + ))} + +
    +
    +
    + +
    +

    {t("services.overview.title")}

    + +
    + {services.map((service, index) => { + const Icon = service.icon; + + return ( + + + 0{index + 1} +
    +
    +
    +
    + {t("services.examples.eyebrow")} +

    {t("services.examples.title")}

    +
    +
    +
    + + {t("services.examples.status")} +
    +
    +
    + + + {t("services.detail.label")} 0{index + 1} +

    {t(service.titleKey)}

    +

    {t(service.descriptionKey)}

    +

    {t(service.resultKey)}

    + +

    {t("services.detail.scope")}

    +
      + {service.featureKeys.map((key) => ( +
    • {t(key)}
    • + ))} +
    + +

    {t("services.detail.technologies")}

    +
    + {service.tags.map((tag) => {tag})} +
    + + + {t("services.detail.cta")} + + +
    +
    + ); + })} +
    +
    + +
    +
    + {t("services.final.eyebrow")} +

    {t("services.final.title")}

    +

    {t("services.final.description")}

    + + {t("services.cta.project")} + + +
    +
    +
    + ); +} diff --git a/frontend/src/styles/Footer.css b/frontend/src/styles/Footer.css index 0f353f4..7f4c86b 100644 --- a/frontend/src/styles/Footer.css +++ b/frontend/src/styles/Footer.css @@ -12,8 +12,8 @@ color: #ffffff; margin-top: 0; border-top: var(--text-decoration) 1px solid; - background: linear-gradient(180deg, rgba(4, 13, 30, 0.45) 0%, rgba(3, 10, 24, 0.6) 100%); - box-shadow: inset 0 1px 0 var(--text-decoration-shadow), 0 0 12px var(--text-decoration-shadow); + background: linear-gradient(180deg, rgba(8, 14, 24, 0.72) 0%, rgba(3, 6, 12, 0.88) 100%); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.045); } .footer::before, @@ -31,7 +31,7 @@ } .footer::after { - background: linear-gradient(180deg, rgba(7, 12, 24, 0.404), rgba(5, 10, 20, 0.5)); + background: linear-gradient(180deg, rgba(7, 12, 24, 0.34), rgba(5, 10, 20, 0.62)); opacity: 0.35; } @@ -57,11 +57,11 @@ font-weight: 850; line-height: 1.2; letter-spacing: 0; - background: linear-gradient(135deg, #ffffff 0%, #c084fc 52%, #67e8f9 100%); + background: linear-gradient(135deg, #ffffff 0%, #9fd7ff 52%, #6ee7f2 100%); -webkit-background-clip: text; background-clip: text; color: transparent; - text-shadow: 0 0 24px var(--text-decoration-shadow); + text-shadow: none; } .footer-tagline { @@ -77,7 +77,7 @@ .footer-section-title { margin: 0 0 0.95rem; - color: rgba(192, 132, 252, 0.82); + color: rgba(110, 231, 242, 0.78); font-size: 0.72rem; font-weight: 850; letter-spacing: 0.12em; @@ -105,7 +105,9 @@ color: rgba(208, 210, 214, 0.72); font-size: 0.88rem; text-decoration: none; - transition: color var(--footer-transition), transform var(--footer-transition); + transition: + color 160ms var(--ease-out-strong), + transform 160ms var(--ease-out-strong); } .footer-nav-list a::after { @@ -115,11 +117,13 @@ bottom: -4px; width: 100%; height: 1px; - background: linear-gradient(90deg, #a855f7, #67e8f9); + background: linear-gradient(90deg, #8fb3ff, #6ee7f2); opacity: 0; transform: scaleX(0.4); transform-origin: left; - transition: opacity var(--footer-transition), transform var(--footer-transition); + transition: + opacity 160ms var(--ease-out-strong), + transform 160ms var(--ease-out-strong); } .footer-nav-list a:hover { @@ -139,7 +143,7 @@ } .footer-social-link:hover { - color: #67e8f9; + color: #6ee7f2; } .footer-bottom { diff --git a/frontend/src/styles/Github.css b/frontend/src/styles/Github.css index e98bdc8..7847ec6 100644 --- a/frontend/src/styles/Github.css +++ b/frontend/src/styles/Github.css @@ -7,7 +7,10 @@ position: relative; overflow: hidden; min-height: calc(100vh - 64px); - padding: 5rem 1.5rem 4rem; + padding: 7.5rem 1.5rem 4rem; + background: + radial-gradient(circle at 78% 10%, rgba(110, 231, 242, 0.055), transparent 32%), + var(--color-bg); } .gh-page::before { @@ -38,7 +41,7 @@ font-size: clamp(1.8rem, 3vw, 2.6rem); font-weight: 600; margin: 0 0 0.4rem; - background: linear-gradient(135deg, var(--accent), var(--secondary)); + background: linear-gradient(135deg, var(--color-accent), var(--color-accent-2)); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; @@ -65,13 +68,18 @@ color: var(--muted); font-size: 0.85rem; cursor: pointer; - transition: all 0.2s ease; + transition: + color 160ms var(--ease-out-strong), + border-color 160ms var(--ease-out-strong), + background 160ms var(--ease-out-strong), + transform 120ms var(--ease-out-strong); font-family: inherit; } .gh-filter-tab:hover { border-color: var(--color-accent-30); color: var(--text); + transform: translateY(-1px); } .gh-filter-tab.active { @@ -225,11 +233,15 @@ .gh-repo-card { background: var(--color-bg-surface-2); border: 1px solid var(--color-border); - border-radius: 14px; + border-radius: 12px; padding: 1.5rem; text-decoration: none; color: inherit; - transition: border-color 0.2s, box-shadow 0.2s, transform 0.2s; + transition: + border-color 160ms var(--ease-out-strong), + box-shadow 180ms var(--ease-out-strong), + transform 160ms var(--ease-out-strong), + background 160ms var(--ease-out-strong); display: flex; flex-direction: column; gap: 0.75rem; @@ -245,7 +257,7 @@ left: 0; right: 0; height: 2px; - background: linear-gradient(90deg, var(--accent), var(--secondary)); + background: linear-gradient(90deg, var(--color-accent), var(--color-accent-2)); opacity: 0; transition: opacity 0.25s; } @@ -258,8 +270,8 @@ backdrop-filter: blur(12px); background: var(--color-white-04); border-color: var(--color-accent-30); - box-shadow: 0 0 32px var(--color-accent-10); - transform: translateY(-4px); + box-shadow: 0 18px 42px rgba(0, 0, 0, 0.24); + transform: translateY(-2px); } .gh-repo-name { @@ -329,14 +341,18 @@ .gh-member-card { background: var(--color-bg-surface-2); border: 1px solid var(--color-border); - border-radius: 14px; + border-radius: 12px; padding: 1.25rem; text-decoration: none; color: inherit; display: flex; align-items: stretch; gap: 0.8rem; - transition: border-color 0.2s, box-shadow 0.2s, transform 0.2s; + transition: + border-color 160ms var(--ease-out-strong), + box-shadow 180ms var(--ease-out-strong), + transform 160ms var(--ease-out-strong), + background 160ms var(--ease-out-strong); min-width: 220px; position: relative; overflow: hidden; @@ -349,7 +365,7 @@ left: 0; right: 0; height: 2px; - background: linear-gradient(90deg, var(--accent), var(--secondary)); + background: linear-gradient(90deg, var(--color-accent), var(--color-accent-2)); opacity: 0; transition: opacity 0.25s; } @@ -362,8 +378,8 @@ backdrop-filter: blur(12px); background: var(--color-white-04); border-color: var(--color-accent-30); - box-shadow: 0 0 32px var(--color-accent-10); - transform: translateY(-4px); + box-shadow: 0 18px 42px rgba(0, 0, 0, 0.24); + transform: translateY(-2px); } .gh-member-avatar { diff --git a/frontend/src/styles/Home.css b/frontend/src/styles/Home.css index 154ec4b..fb0c968 100644 --- a/frontend/src/styles/Home.css +++ b/frontend/src/styles/Home.css @@ -1,16 +1,42 @@ .home-page { position: relative; - background: #030b18; + background: #03060b; color: #fff; - min-height: 100vh; + min-height: 100dvh; overflow-x: hidden; } +.home-page::before { + content: ""; + position: fixed; + inset: 0; + z-index: 0; + pointer-events: none; + background: + linear-gradient(90deg, rgba(126, 155, 190, 0.035) 1px, transparent 1px), + linear-gradient(0deg, rgba(126, 155, 190, 0.028) 1px, transparent 1px), + linear-gradient(135deg, transparent 0 49%, rgba(110, 231, 242, 0.035) 49.08% 49.18%, transparent 49.3% 100%); + background-size: 96px 96px, 96px 96px, 560px 560px; + mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.78), rgba(0, 0, 0, 0.34) 62%, transparent 100%); +} + +.home-page::after { + content: ""; + position: fixed; + inset: 0; + z-index: 0; + pointer-events: none; + background: + linear-gradient(90deg, rgba(3, 6, 11, 0.76) 0%, rgba(3, 6, 11, 0.36) 34%, transparent 58%), + linear-gradient(180deg, rgba(3, 6, 11, 0.2) 0%, transparent 35%, rgba(3, 6, 11, 0.38) 100%); +} + .home-canvas-shell { position: fixed; inset: 0; z-index: 1; pointer-events: none; + filter: saturate(1.08) contrast(1.04) brightness(1.1); } .home-canvas-shell > canvas { @@ -27,15 +53,16 @@ align-items: center; justify-content: center; background: - radial-gradient(circle at 70% 45%, rgba(112, 80, 232, 0.12) 0%, transparent 55%), - #030b18; + linear-gradient(115deg, transparent 0 42%, rgba(110, 231, 242, 0.06) 42.2% 42.42%, transparent 42.62% 100%), + linear-gradient(180deg, rgba(143, 179, 255, 0.035), transparent 58%), + #03060b; } .home-static-cube { font-size: 120px; font-weight: 900; - letter-spacing: -0.04em; - background: linear-gradient(135deg, #60c0ff 0%, #8060ff 100%); + letter-spacing: 0; + background: linear-gradient(135deg, #dff9ff 0%, #6ee7f2 100%); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; @@ -47,12 +74,18 @@ inset: 0; z-index: 0; pointer-events: none; + background: + linear-gradient(115deg, transparent 0 18%, rgba(255, 255, 255, 0.02) 18.08% 18.18%, transparent 18.32% 100%), + linear-gradient(90deg, transparent 0 65%, rgba(110, 231, 242, 0.028) 65.08% 65.18%, transparent 65.32% 100%); + opacity: 0.64; } .journey-section { position: relative; + isolation: isolate; + overflow: hidden; z-index: 2; - min-height: 100vh; + min-height: 100dvh; display: flex; align-items: center; padding: 120px 0; @@ -60,8 +93,30 @@ width: 100%; } +.journey-section::before, +.journey-section::after { + content: ""; + position: absolute; + inset: 0; + z-index: 0; + pointer-events: none; +} + +.journey-section::before { + background: + linear-gradient(90deg, rgba(126, 155, 190, 0.026) 1px, transparent 1px), + linear-gradient(0deg, rgba(126, 155, 190, 0.018) 1px, transparent 1px); + background-size: 96px 96px; + mask-image: linear-gradient(90deg, rgba(0, 0, 0, 0.42), rgba(0, 0, 0, 0.14) 42%, transparent 78%); + opacity: 0.16; +} + +.journey-section::after { + display: none; +} + .hero-journey-section { - min-height: 100vh; + min-height: 100dvh; flex-direction: column; justify-content: center; align-items: flex-start; @@ -70,6 +125,8 @@ } .journey-content { + position: relative; + z-index: 2; width: 100%; max-width: 1280px; margin: 0 auto; @@ -85,7 +142,7 @@ font-weight: 700; letter-spacing: 0.22em; text-transform: uppercase; - color: #8878ff; + color: #6ee7f2; margin-bottom: 28px; border: 0; padding: 0; @@ -96,7 +153,7 @@ content: "+"; font-size: 13px; line-height: 1; - color: #8878ff; + color: #6ee7f2; opacity: 0.8; } @@ -104,17 +161,17 @@ font-size: clamp(40px, 5.8vw, 76px); font-weight: 800; line-height: 1.06; - letter-spacing: -0.044em; + letter-spacing: 0; margin: 0 0 28px; color: #f0f4ff; max-width: 560px; } .hero-accent { - background: linear-gradient(130deg, #74a8ee 0%, #8878df 100%); + background: linear-gradient(130deg, #f7fbff 0%, #6ee7f2 46%, #8fb3ff 100%); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; - filter: drop-shadow(0 0 14px rgba(100, 130, 255, 0.14)); + filter: none; } .hero-desc { font-size: clamp(14px, 1.45vw, 16.5px); @@ -126,11 +183,13 @@ } .service-journey-section { - min-height: 100vh; + min-height: 100dvh; padding: 0; } .service-grid { + position: relative; + z-index: 2; display: grid; width: 100%; max-width: 1280px; @@ -139,7 +198,7 @@ box-sizing: border-box; grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: center; - min-height: 100vh; + min-height: 100dvh; } .service-text { @@ -148,26 +207,21 @@ align-items: flex-start; width: 100%; min-width: 0; - max-width: 520px; - padding: 48px; - border-radius: 24px; - background: rgba(4, 13, 30, 0.64); - backdrop-filter: blur(8px); - -webkit-backdrop-filter: blur(8px); - border: 1px solid rgba(255, 255, 255, 0.06); + max-width: 540px; + padding: 44px 0 44px 34px; + border-radius: 0; + background: transparent; + backdrop-filter: none; + -webkit-backdrop-filter: none; + border: 0; + border-left: 1px solid rgba(122, 151, 185, 0.18); box-shadow: - 0 16px 40px rgba(0, 0, 0, 0.24), - inset 0 1px 0 rgba(255, 255, 255, 0.03); + none; transition: border-color 0.4s cubic-bezier(0.16, 1, 0.3, 1), - box-shadow 0.4s cubic-bezier(0.16, 1, 0.3, 1), transform 0.4s cubic-bezier(0.16, 1, 0.3, 1); } -.service-text:hover { - transform: translateY(-2px); -} - .service--left .service-grid { grid-template-areas: "text cube"; } @@ -184,67 +238,89 @@ justify-self: end; align-items: flex-end; text-align: right; + padding: 44px 34px 44px 0; + border-left: 0; + border-right: 1px solid rgba(122, 151, 185, 0.18); } #hero { background: - radial-gradient(circle at 80% 25%, rgba(96, 80, 220, 0.12) 0%, transparent 55%), - radial-gradient(circle at 20% 75%, rgba(136, 120, 255, 0.05) 0%, transparent 60%); + linear-gradient(90deg, rgba(3, 6, 11, 0.72) 0%, rgba(3, 6, 11, 0.38) 34%, transparent 64%), + linear-gradient(180deg, rgba(3, 6, 11, 0.2), transparent 38%, rgba(3, 6, 11, 0.24)); } -#discord { +#hero::before { + opacity: 0.18; background: - radial-gradient(circle at 15% 30%, rgba(88, 101, 242, 0.07) 0%, transparent 50%), - radial-gradient(circle at 85% 70%, rgba(88, 101, 242, 0.02) 0%, transparent 60%); - border-top: 1px solid rgba(88, 101, 242, 0.06); - border-bottom: 1px solid rgba(88, 101, 242, 0.06); + linear-gradient(90deg, rgba(126, 155, 190, 0.036) 1px, transparent 1px), + linear-gradient(0deg, rgba(126, 155, 190, 0.024) 1px, transparent 1px); + background-size: 88px 88px; + mask-image: linear-gradient(90deg, rgba(0, 0, 0, 0.58), rgba(0, 0, 0, 0.12) 48%, transparent 74%); } -#discord .service-text:hover { - border-color: rgba(88, 101, 242, 0.2); - box-shadow: 0 18px 44px rgba(0, 0, 0, 0.3); +#discord { + --section-rgb: 88, 101, 242; + --section-color: #5865f2; + background: transparent; + border-top: 1px solid rgba(88, 101, 242, 0.06); + border-bottom: 1px solid rgba(88, 101, 242, 0.06); } #web { - background: - radial-gradient(circle at 15% 30%, rgba(14, 165, 233, 0.07) 0%, transparent 55%), - radial-gradient(circle at 85% 70%, rgba(14, 165, 233, 0.02) 0%, transparent 50%); + --section-rgb: 14, 165, 233; + --section-color: #0ea5e9; + background: transparent; border-bottom: 1px solid rgba(14, 165, 233, 0.06); } -#web .service-text:hover { - border-color: rgba(14, 165, 233, 0.2); - box-shadow: 0 18px 44px rgba(0, 0, 0, 0.3); -} - #mobile { - background: - radial-gradient(circle at 15% 30%, rgba(236, 72, 153, 0.055) 0%, transparent 50%), - radial-gradient(circle at 85% 70%, rgba(236, 72, 153, 0.018) 0%, transparent 60%); + --section-rgb: 236, 72, 153; + --section-color: #ec4899; + background: transparent; border-bottom: 1px solid rgba(236, 72, 153, 0.06); } -#mobile .service-text:hover { - border-color: rgba(236, 72, 153, 0.18); - box-shadow: 0 18px 44px rgba(0, 0, 0, 0.3); +#individual { + --section-rgb: 168, 85, 247; + --section-color: #a855f7; + background: transparent; + border-bottom: 1px solid rgba(168, 85, 247, 0.06); } -#individual { +.service-journey-section::before { + opacity: 1; background: - radial-gradient(circle at 15% 30%, rgba(168, 85, 247, 0.07) 0%, transparent 55%), - radial-gradient(circle at 85% 70%, rgba(168, 85, 247, 0.02) 0%, transparent 50%); - border-bottom: 1px solid rgba(168, 85, 247, 0.06); + linear-gradient(90deg, rgba(3, 6, 11, 0.64) 0%, rgba(3, 6, 11, 0.24) 38%, transparent 68%), + linear-gradient(90deg, rgba(var(--section-rgb), 0.045), transparent 42%), + linear-gradient(90deg, rgba(var(--section-rgb), 0.08) 1px, transparent 1px), + linear-gradient(0deg, rgba(126, 155, 190, 0.018) 1px, transparent 1px); + background-size: auto, auto, 92px 92px, 92px 92px; + mask-image: linear-gradient(90deg, rgba(0, 0, 0, 0.72), rgba(0, 0, 0, 0.16) 52%, transparent 78%); } -#individual .service-text:hover { - border-color: rgba(168, 85, 247, 0.2); - box-shadow: 0 18px 44px rgba(0, 0, 0, 0.3); +.service-journey-section::after { + display: none; +} + +.service-journey-section.service--right::before { + background: + linear-gradient(270deg, rgba(3, 6, 11, 0.64) 0%, rgba(3, 6, 11, 0.24) 38%, transparent 68%), + linear-gradient(270deg, rgba(var(--section-rgb), 0.045), transparent 42%), + linear-gradient(90deg, rgba(var(--section-rgb), 0.08) 1px, transparent 1px), + linear-gradient(0deg, rgba(126, 155, 190, 0.018) 1px, transparent 1px); + background-size: auto, auto, 92px 92px, 92px 92px; + mask-image: linear-gradient(270deg, rgba(0, 0, 0, 0.72), rgba(0, 0, 0, 0.16) 52%, transparent 78%); } #cta { background: - radial-gradient(circle at 50% 50%, rgba(96, 80, 255, 0.11) 0%, transparent 65%); - border-top: 1px solid rgba(96, 80, 255, 0.06); + linear-gradient(90deg, transparent 0%, rgba(3, 6, 11, 0.36) 50%, transparent 100%), + linear-gradient(180deg, transparent, rgba(3, 6, 11, 0.28)); + border-top: 1px solid rgba(110, 231, 242, 0.08); +} + +#cta::after { + display: none; } .svc-icon { @@ -253,16 +329,20 @@ justify-content: center; width: 52px; height: 52px; - border-radius: 14px; + border-radius: 12px; margin-bottom: 24px; - background: rgba(112, 80, 232, 0.1); - border: 1px solid rgba(112, 80, 232, 0.22); - color: #8878ff; - transition: background 0.24s, border-color 0.24s, box-shadow 0.24s; + background: rgba(110, 231, 242, 0.08); + border: 1px solid rgba(110, 231, 242, 0.2); + color: #6ee7f2; + transition: + background 180ms var(--ease-out-strong), + border-color 180ms var(--ease-out-strong), + transform 180ms var(--ease-out-strong); } .svc-icon:hover { - background: rgba(112, 80, 232, 0.13); - border-color: rgba(136, 120, 255, 0.28); + background: rgba(110, 231, 242, 0.11); + border-color: rgba(110, 231, 242, 0.3); + transform: translateY(-1px); } #discord .svc-icon { background: rgba(88,101,242,0.10); border-color: rgba(88,101,242,0.22); color: #7888f8; } #web .svc-icon { background: rgba(14,165,233,0.08); border-color: rgba(14,165,233,0.20); color: #38b8f8; } @@ -274,7 +354,7 @@ font-size: clamp(28px, 3.4vw, 48px); font-weight: 800; line-height: 1.08; - letter-spacing: -0.032em; + letter-spacing: 0; margin: 0 0 18px; color: #eef0ff; padding-bottom: 18px; @@ -287,7 +367,7 @@ width: 40px; height: 3px; border-radius: 2px; - background: #8878ff; + background: #6ee7f2; } .service--right .service-text h2::after { left: auto; @@ -319,23 +399,32 @@ align-items: center; gap: 9px; padding: 13px 28px; - border-radius: 100px; - background: linear-gradient(135deg, #5060d4 0%, #6250bc 100%); + border-radius: 999px; + background: linear-gradient(135deg, #1e5fe7 0%, #21aabd 100%); color: #fff; font-size: 14px; font-weight: 700; text-decoration: none; border: none; - box-shadow: 0 4px 16px rgba(40, 50, 130, 0.24); - transition: transform 0.28s ease, box-shadow 0.28s ease, filter 0.22s ease; + box-shadow: 0 12px 26px rgba(20, 85, 140, 0.24); + transition: + transform 160ms var(--ease-out-strong), + box-shadow 180ms var(--ease-out-strong), + filter 180ms var(--ease-out-strong); white-space: nowrap; } .home-primary-button:hover { transform: translateY(-1px); - box-shadow: 0 7px 22px rgba(40, 50, 130, 0.3); + box-shadow: 0 16px 30px rgba(20, 85, 140, 0.32); filter: brightness(1.05); } +.home-primary-button:active, +.home-secondary-button:active, +.journey-link:active { + transform: scale(0.98); +} + .home-secondary-button { display: inline-flex; align-items: center; @@ -348,7 +437,11 @@ font-weight: 600; text-decoration: none; border: 1.5px solid rgba(180, 190, 255, 0.1); - transition: transform 0.28s ease, background 0.22s ease, border-color 0.22s ease, color 0.22s ease; + transition: + transform 160ms var(--ease-out-strong), + background 180ms var(--ease-out-strong), + border-color 180ms var(--ease-out-strong), + color 180ms var(--ease-out-strong); white-space: nowrap; } .home-secondary-button:hover { @@ -376,13 +469,16 @@ font-size: 11.5px; font-weight: 600; color: #7080a8; - transition: background 0.22s, border-color 0.22s, color 0.22s, box-shadow 0.22s; + transition: + background 160ms var(--ease-out-strong), + border-color 160ms var(--ease-out-strong), + color 160ms var(--ease-out-strong); cursor: default; letter-spacing: 0.01em; } .journey-tags span:hover { background: rgba(120, 100, 255, 0.09); - border-color: rgba(136, 120, 255, 0.22); + border-color: rgba(110, 231, 242, 0.22); color: #9ca8dc; } @@ -399,7 +495,12 @@ padding: 8px 16px 8px 14px; border-radius: 100px; background: rgba(112, 128, 184, 0.06); - transition: gap 0.22s ease, color 0.22s ease, border-color 0.22s ease, background 0.22s ease; + transition: + gap 160ms var(--ease-out-strong), + color 160ms var(--ease-out-strong), + border-color 160ms var(--ease-out-strong), + background 160ms var(--ease-out-strong), + transform 120ms var(--ease-out-strong); } .journey-link:hover { gap: 12px; @@ -454,7 +555,7 @@ font-size: clamp(32px, 4.4vw, 58px); font-weight: 800; line-height: 1.08; - letter-spacing: -0.034em; + letter-spacing: 0; margin: 0 0 20px; color: #e8ecff; } @@ -467,9 +568,9 @@ } ::-webkit-scrollbar { width: 5px; } -::-webkit-scrollbar-track { background: #030b18; } -::-webkit-scrollbar-thumb { background: rgba(112, 80, 232, 0.22); border-radius: 3px; } -::-webkit-scrollbar-thumb:hover { background: rgba(136, 120, 255, 0.38); } +::-webkit-scrollbar-track { background: #05070c; } +::-webkit-scrollbar-thumb { background: rgba(110, 231, 242, 0.22); border-radius: 3px; } +::-webkit-scrollbar-thumb:hover { background: rgba(110, 231, 242, 0.36); } @media (max-width: 1100px) { .journey-content, @@ -480,7 +581,7 @@ } @media (max-width: 980px) { - .journey-section { padding: 96px 0; min-height: 90vh; } + .journey-section { padding: 96px 0; min-height: 90dvh; } .hero-journey-section { padding-top: 118px; padding-bottom: 56px; } .journey-content, @@ -490,7 +591,7 @@ grid-template-columns: 1fr; grid-template-areas: "text" !important; padding: 96px 24px; - min-height: 90vh; + min-height: 90dvh; align-items: center; justify-items: center; } @@ -502,6 +603,11 @@ max-width: 620px !important; justify-self: center !important; padding: 32px 24px !important; + border: 1px solid rgba(122, 151, 185, 0.14) !important; + border-radius: 12px !important; + background: rgba(5, 9, 17, 0.62); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); margin-left: 0 !important; margin-right: 0 !important; } @@ -525,8 +631,22 @@ .home-atmosphere { background: - radial-gradient(ellipse 90% 45% at 50% 0%, rgba(3, 11, 24, 0.45) 0%, transparent 65%), - linear-gradient(to top, rgba(3, 11, 24, 0.72) 0%, transparent 28%); + linear-gradient(90deg, rgba(126, 155, 190, 0.025) 1px, transparent 1px), + linear-gradient(0deg, rgba(126, 155, 190, 0.02) 1px, transparent 1px), + linear-gradient(to top, rgba(3, 6, 11, 0.5) 0%, transparent 28%); + background-size: 58px 58px, 58px 58px, auto; + } + + .journey-section::before { + opacity: 0.18; + mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.55), transparent 76%); + } + + .journey-section::after, + .service-journey-section::after, + #hero::after, + #cta::after { + opacity: 0.16; } } diff --git a/frontend/src/styles/Legal.css b/frontend/src/styles/Legal.css index 565401f..aab3eff 100644 --- a/frontend/src/styles/Legal.css +++ b/frontend/src/styles/Legal.css @@ -2,12 +2,20 @@ .legal-page { min-height: calc(100vh - 64px); - padding: 5rem 1.5rem 4rem; + padding: 7.5rem 1.5rem 4.5rem; + background: + radial-gradient(circle at 82% 8%, rgba(110, 231, 242, 0.06), transparent 32%), + var(--color-bg); } .legal-container { max-width: 760px; margin: 0 auto; + padding: clamp(1.5rem, 4vw, 2.5rem); + border: 1px solid var(--color-border-subtle); + border-radius: 14px; + background: rgba(8, 14, 24, 0.54); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04); } /* Header */ @@ -86,6 +94,14 @@ .legal-section a { color: var(--color-accent); + text-decoration: underline; + text-underline-offset: 3px; + text-decoration-color: rgba(110, 231, 242, 0.32); +} + +.legal-section a:hover { + color: var(--color-text-bright); + text-decoration-color: rgba(110, 231, 242, 0.64); } .legal-foot { @@ -128,7 +144,10 @@ color: var(--color-text); font-family: 'Fira Code', monospace; font-size: 0.875rem; - transition: border-color 0.2s ease; + transition: + border-color 160ms var(--ease-out-strong), + box-shadow 160ms var(--ease-out-strong), + background 160ms var(--ease-out-strong); outline: none; resize: vertical; } @@ -136,6 +155,7 @@ .form-group input:focus, .form-group textarea:focus { border-color: var(--color-accent); + box-shadow: 0 0 0 3px rgba(110, 231, 242, 0.14); } .form-submit { @@ -151,12 +171,19 @@ font-size: 0.9rem; font-weight: 600; cursor: pointer; - transition: opacity 0.2s ease; + transition: + opacity 160ms var(--ease-out-strong), + transform 120ms var(--ease-out-strong); align-self: flex-start; } .form-submit:hover { - opacity: 0.85; + opacity: 0.92; + transform: translateY(-1px); +} + +.form-submit:active { + transform: scale(0.98); } .form-submit:disabled { diff --git a/frontend/src/styles/Login.css b/frontend/src/styles/Login.css index 0dcb34d..9f34380 100644 --- a/frontend/src/styles/Login.css +++ b/frontend/src/styles/Login.css @@ -1,11 +1,11 @@ .login-hero { - height: 100vh; + min-height: 100dvh; display: flex; justify-content: center; align-items: center; background: - radial-gradient(circle at 15% 20%, rgba(96, 80, 220, 0.09) 0%, transparent 45%), - radial-gradient(circle at 85% 80%, rgba(136, 120, 255, 0.06) 0%, transparent 45%), + radial-gradient(circle at 15% 20%, rgba(110, 231, 242, 0.07) 0%, transparent 42%), + radial-gradient(circle at 85% 80%, rgba(143, 179, 255, 0.05) 0%, transparent 45%), radial-gradient(circle at 50% 50%, rgba(96, 200, 255, 0.02) 0%, transparent 60%), var(--bg); padding: 24px; @@ -19,7 +19,7 @@ position: absolute; width: 600px; height: 600px; - background: radial-gradient(circle, rgba(136, 120, 255, 0.03) 0%, transparent 70%); + background: radial-gradient(circle, rgba(110, 231, 242, 0.035) 0%, transparent 70%); top: 15%; left: 20%; pointer-events: none; @@ -35,11 +35,11 @@ width: 100%; max-width: 440px; padding: 2.5rem; - background: rgba(4, 13, 30, 0.45); + background: rgba(8, 14, 24, 0.72); backdrop-filter: blur(24px); -webkit-backdrop-filter: blur(24px); - border: 1px solid rgba(139, 92, 246, 0.16); - border-radius: 20px; + border: 1px solid rgba(122, 151, 185, 0.16); + border-radius: 14px; box-shadow: 0 24px 60px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.04); @@ -60,12 +60,12 @@ margin: 0 0 0.5rem; font-size: 2.1rem; font-weight: 800; - letter-spacing: -0.03em; - background: linear-gradient(130deg, #60aaff 0%, #9060ff 48%, #60e0ff 100%); + letter-spacing: 0; + background: linear-gradient(130deg, #f7fbff 0%, #9fd7ff 48%, #6ee7f2 100%); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; - filter: drop-shadow(0 0 12px rgba(100, 130, 255, 0.15)); + filter: none; text-align: center; } @@ -97,12 +97,15 @@ width: 100%; padding: 13px 16px; border-radius: 10px; - border: 1px solid rgba(139, 92, 246, 0.15); - background: rgba(3, 11, 24, 0.6); + border: 1px solid rgba(122, 151, 185, 0.16); + background: rgba(3, 6, 12, 0.68); color: #fff; font-family: inherit; font-size: 0.94rem; - transition: border-color 0.3s ease, box-shadow 0.3s ease, background-color 0.3s ease; + transition: + border-color 160ms var(--ease-out-strong), + box-shadow 160ms var(--ease-out-strong), + background-color 160ms var(--ease-out-strong); } .login-box input::placeholder { @@ -111,9 +114,9 @@ .login-box input:focus { outline: none; - border-color: #8878ff; + border-color: #6ee7f2; box-shadow: - 0 0 14px rgba(136, 120, 255, 0.25), + 0 0 0 3px rgba(110, 231, 242, 0.16), inset 0 1px 0 rgba(255, 255, 255, 0.02); background-color: rgba(3, 11, 24, 0.8); } @@ -152,12 +155,14 @@ display: flex; align-items: center; justify-content: center; - transition: color 0.2s ease, transform 0.2s ease; + transition: + color 140ms var(--ease-out-strong), + transform 140ms var(--ease-out-strong); padding: 4px; } .password-toggle:hover { - color: #8878ff; + color: #6ee7f2; transform: translateY(-50%) scale(1.08); } @@ -166,25 +171,28 @@ padding: 13px; border-radius: 10px; border: none; - background: linear-gradient(135deg, #4858e8 0%, #6838d8 100%); + background: linear-gradient(135deg, #1e5fe7 0%, #21aabd 100%); color: #fff; font-family: inherit; font-size: 0.94rem; font-weight: 700; cursor: pointer; - box-shadow: 0 4px 18px rgba(72, 88, 232, 0.32); - transition: transform 0.24s ease, box-shadow 0.24s ease, filter 0.2s ease; + box-shadow: 0 12px 26px rgba(20, 85, 140, 0.24); + transition: + transform 160ms var(--ease-out-strong), + box-shadow 180ms var(--ease-out-strong), + filter 180ms var(--ease-out-strong); } .login-box button:hover:not(.link-btn):not(.password-toggle) { transform: translateY(-1.5px); - box-shadow: 0 6px 24px rgba(72, 88, 232, 0.52); + box-shadow: 0 16px 32px rgba(20, 85, 140, 0.32); filter: brightness(1.08); } .login-box button:active:not(.link-btn):not(.password-toggle) { transform: translateY(0); - box-shadow: 0 3px 12px rgba(72, 88, 232, 0.4); + box-shadow: 0 8px 18px rgba(20, 85, 140, 0.24); } .login-box button:disabled { @@ -202,7 +210,7 @@ color: #6a7590; cursor: pointer; user-select: none; - transition: color 0.2s ease; + transition: color 160ms var(--ease-out-strong); } .remember:hover { @@ -213,12 +221,30 @@ width: 17px; height: 17px; border-radius: 4px; - border: 1px solid rgba(139, 92, 246, 0.3); + border: 1px solid rgba(110, 231, 242, 0.3); background: rgba(3, 11, 24, 0.6); - accent-color: #8878ff; + accent-color: #6ee7f2; cursor: pointer; } +.register-privacy-notice { + margin: 0; + color: #6a7590; + font-size: 0.8rem; + line-height: 1.5; + text-align: center; +} + +.register-privacy-notice a { + color: rgba(220, 241, 255, 0.78); + text-decoration: underline; + text-underline-offset: 2px; +} + +.register-privacy-notice a:hover { + color: #6ee7f2; +} + .switch { display: flex; justify-content: center; @@ -233,13 +259,15 @@ font-family: inherit; font-size: 0.88rem; font-weight: 500; - transition: color 0.22s ease, text-shadow 0.22s ease; + transition: + color 160ms var(--ease-out-strong), + text-shadow 160ms var(--ease-out-strong); padding: 4px 8px; } .link-btn:hover { - color: #8878ff; - text-shadow: 0 0 8px rgba(136, 120, 255, 0.3); + color: #6ee7f2; + text-shadow: none; } /* Responsive adjustments */ diff --git a/frontend/src/styles/Me.css b/frontend/src/styles/Me.css index 06ee3bb..2f018b3 100644 --- a/frontend/src/styles/Me.css +++ b/frontend/src/styles/Me.css @@ -257,7 +257,7 @@ margin: 0; font-size: 32px; line-height: 1.2; - letter-spacing: -0.2px; + letter-spacing: 0; color: var(--color-text-bright); } diff --git a/frontend/src/styles/Navbar.css b/frontend/src/styles/Navbar.css index 9887f83..5553d0c 100644 --- a/frontend/src/styles/Navbar.css +++ b/frontend/src/styles/Navbar.css @@ -10,33 +10,37 @@ display: flex; align-items: center; justify-content: space-between; - z-index: 999; - border-radius: 14px; - background: linear-gradient(180deg, rgba(4, 13, 30, 0.45) 0%, rgba(3, 10, 24, 0.6) 100%); + z-index: 80; + border-radius: 12px; + background: linear-gradient(180deg, rgba(8, 14, 24, 0.76) 0%, rgba(5, 9, 17, 0.82) 100%); backdrop-filter: blur(18px); -webkit-backdrop-filter: blur(18px); - border: 1px solid rgba(139, 92, 246, 0.12); + border: 1px solid rgba(122, 151, 185, 0.16); box-shadow: - inset 0 0 12px rgba(139, 92, 246, 0.15), - 0 12px 40px rgba(0, 0, 0, 0.5), - 0 0 30px rgba(139, 92, 246, 0.04); - transition: all 0.4s ease; + inset 0 1px 0 rgba(255, 255, 255, 0.06), + 0 18px 54px rgba(0, 0, 0, 0.38); + transition: + background 220ms var(--ease-out-strong), + border-color 220ms var(--ease-out-strong), + box-shadow 220ms var(--ease-out-strong), + height 220ms var(--ease-out-strong); } .navbar.scrolled { - background: linear-gradient(180deg, rgba(3, 8, 20, 0.7) 0%, rgba(2, 6, 16, 0.8) 100%); + height: 66px; + background: linear-gradient(180deg, rgba(6, 10, 18, 0.88) 0%, rgba(3, 6, 12, 0.92) 100%); backdrop-filter: blur(24px); -webkit-backdrop-filter: blur(24px); - border-color: rgba(139, 92, 246, 0.24); + border-color: rgba(110, 231, 242, 0.22); box-shadow: - inset 0 0 16px rgba(139, 92, 246, 0.2), - 0 16px 48px rgba(0, 0, 0, 0.6), - 0 0 40px rgba(139, 92, 246, 0.08); + inset 0 1px 0 rgba(255, 255, 255, 0.07), + 0 16px 48px rgba(0, 0, 0, 0.46); } .navbar-left { display: flex; align-items: center; + min-width: 0; } .logo-wrapper { @@ -50,29 +54,33 @@ width: 38px; height: 38px; border-radius: 999px; - border: 1.5px solid rgba(139, 92, 246, 0.3); - box-shadow: 0 0 16px rgba(139, 92, 246, 0.25); - transition: transform 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease; + border: 1.5px solid rgba(110, 231, 242, 0.24); + box-shadow: 0 10px 22px rgba(0, 0, 0, 0.26); + transition: + transform 180ms var(--ease-out-strong), + border-color 180ms var(--ease-out-strong), + box-shadow 180ms var(--ease-out-strong); } .logo-wrapper:hover .logo { - border-color: rgba(96, 112, 255, 0.6); - box-shadow: 0 0 20px rgba(96, 138, 255, 0.4); + border-color: rgba(110, 231, 242, 0.5); + box-shadow: 0 12px 24px rgba(0, 0, 0, 0.32); + transform: translateY(-1px); } .logo-text { font-size: 19px; font-weight: 800; - letter-spacing: -0.02em; - background: linear-gradient(130deg, #60aaff 0%, #9060ff 48%, #60e0ff 100%); + letter-spacing: 0; + background: linear-gradient(130deg, #f7fbff 0%, #9fd7ff 52%, #6ee7f2 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; - filter: drop-shadow(0 0 8px rgba(100, 130, 255, 0.28)); - transition: filter 0.3s ease; + filter: none; + transition: opacity 180ms var(--ease-out-strong); } .logo-wrapper:hover .logo-text { - filter: drop-shadow(0 0 12px rgba(144, 96, 255, 0.45)); + opacity: 0.86; } .navbar-menu { @@ -93,7 +101,9 @@ text-transform: none; font-weight: 500; font-family: 'Outfit', 'Inter', sans-serif; - transition: all 0.3s ease; + transition: + color 180ms var(--ease-out-strong), + transform 180ms var(--ease-out-strong); } .navbar-menu a.nav-link-featured { @@ -102,12 +112,12 @@ .navbar-menu a:hover { color: #fff; - text-shadow: 0 0 12px rgba(139, 92, 246, 0.3); + text-shadow: none; } .navbar-menu a.active { color: #ffffff; - text-shadow: 0 0 14px rgba(139, 92, 246, 0.4); + text-shadow: none; } .navbar-menu a::after { @@ -119,8 +129,8 @@ width: 0; height: 2px; border-radius: 99px; - background: linear-gradient(90deg, #60aaff, #9060ff); - transition: width 0.3s ease; + background: linear-gradient(90deg, #6ee7f2, #8fb3ff); + transition: width 180ms var(--ease-out-strong); } .navbar-menu a:hover::after, @@ -132,6 +142,7 @@ display: flex; align-items: center; gap: 12px; + min-width: 0; } .navbar-menu-controls { @@ -145,9 +156,11 @@ isolation: isolate; padding: 3px; border-radius: 999px; - background: rgba(7, 18, 40, 0.4); - border: 1px solid rgba(139, 92, 246, 0.15); - transition: border-color 0.3s ease, box-shadow 0.3s ease; + background: rgba(7, 14, 24, 0.62); + border: 1px solid rgba(122, 151, 185, 0.14); + transition: + border-color 180ms var(--ease-out-strong), + background 180ms var(--ease-out-strong); } .language-switch::before { @@ -161,8 +174,8 @@ } .language-switch:hover { - border-color: rgba(139, 92, 246, 0.3); - box-shadow: inset 0 0 8px rgba(139, 92, 246, 0.1), 0 0 16px rgba(139, 92, 246, 0.1); + border-color: rgba(110, 231, 242, 0.28); + background: rgba(10, 20, 32, 0.76); } .language-switch button { @@ -177,13 +190,16 @@ font-family: "JetBrains Mono", monospace; letter-spacing: 0.1em; font-size: 0.64rem; - transition: all 0.3s ease; + transition: + color 180ms var(--ease-out-strong), + background 180ms var(--ease-out-strong), + transform 120ms var(--ease-out-strong); } .language-switch button.active { - background: linear-gradient(135deg, #4858e8 0%, #6838d8 100%); + background: rgba(110, 231, 242, 0.16); color: #fff; - box-shadow: 0 2px 8px rgba(72, 88, 232, 0.4); + box-shadow: inset 0 0 0 1px rgba(110, 231, 242, 0.18); } .login-btn { @@ -193,16 +209,20 @@ height: 36px; padding: 0 18px; border-radius: 8px; - border: 1px solid rgba(139, 92, 246, 0.3); - background: linear-gradient(135deg, #4858e8 0%, #6838d8 100%); + border: 1px solid rgba(110, 231, 242, 0.28); + background: linear-gradient(135deg, #1e5fe7 0%, #21aabd 100%); color: #fff; font-family: "JetBrains Mono", monospace; font-weight: 800; letter-spacing: 0.1em; font-size: 0.66rem; cursor: pointer; - box-shadow: 0 4px 12px rgba(72, 88, 232, 0.25); - transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1); + box-shadow: 0 10px 22px rgba(20, 85, 140, 0.22); + transition: + transform 160ms var(--ease-out-strong), + border-color 180ms var(--ease-out-strong), + box-shadow 180ms var(--ease-out-strong), + filter 180ms var(--ease-out-strong); } .login-btn::before { @@ -221,11 +241,16 @@ } .login-btn:hover { - transform: translateY(-2px); - border-color: rgba(96, 200, 255, 0.5); + transform: translateY(-1px); + border-color: rgba(110, 231, 242, 0.5); box-shadow: - 0 6px 20px rgba(72, 88, 232, 0.4), - 0 0 12px rgba(96, 200, 255, 0.2); + 0 12px 26px rgba(20, 85, 140, 0.3); +} + +.login-btn:active, +.language-switch button:active, +.navbar-toggle:active { + transform: scale(0.97); } .navbar-toggle { @@ -243,24 +268,30 @@ top: 95px; left: 0; width: 100%; + max-height: calc(100vh - 120px); + overflow-y: auto; flex-direction: column; align-items: stretch; padding: 24px; border-radius: 14px; - background: rgba(4, 10, 24, 0.94); + background: rgba(5, 9, 17, 0.96); backdrop-filter: blur(20px); - border: 1px solid rgba(139, 92, 246, 0.2); + border: 1px solid rgba(122, 151, 185, 0.18); opacity: 0; visibility: hidden; - transform: translateY(-10px); - transition: all 0.3s ease; + transform: translateY(-10px) scale(0.98); + transform-origin: top center; + transition: + opacity 180ms var(--ease-out-strong), + visibility 180ms var(--ease-out-strong), + transform 180ms var(--ease-out-strong); gap: 18px; } .navbar-menu.active { opacity: 1; visibility: visible; - transform: translateY(0); + transform: translateY(0) scale(1); } .navbar-menu li { @@ -279,7 +310,7 @@ gap: 14px; margin-top: 6px; padding-top: 14px; - border-top: 1px solid rgba(139, 92, 246, 0.15); + border-top: 1px solid rgba(122, 151, 185, 0.14); } .navbar-menu-controls .language-switch, diff --git a/frontend/src/styles/NotFound.css b/frontend/src/styles/NotFound.css index ff945e8..faa3c76 100644 --- a/frontend/src/styles/NotFound.css +++ b/frontend/src/styles/NotFound.css @@ -1,12 +1,12 @@ .not-found-page { - min-height: 100vh; + min-height: 100dvh; background: radial-gradient(circle at top, var(--color-accent-18), transparent 35%), linear-gradient(180deg, var(--color-bg) 0%, var(--color-bg-elevated) 100%); } .not-found-hero { - min-height: 100vh; + min-height: 100dvh; display: flex; align-items: center; justify-content: center; @@ -17,7 +17,7 @@ width: min(720px, 100%); text-align: center; padding: 38px 26px; - border-radius: 22px; + border-radius: 14px; border: 1px solid var(--color-accent-25); background: rgba(22, 27, 34, 0.82); backdrop-filter: blur(12px); @@ -69,11 +69,18 @@ padding: 12px 18px; border-radius: 999px; font-weight: 600; - transition: transform 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease; + transition: + transform 140ms var(--ease-out-strong), + box-shadow 160ms var(--ease-out-strong), + background-color 160ms var(--ease-out-strong); } .not-found-btn:hover { - transform: translateY(-2px); + transform: translateY(-1px); +} + +.not-found-btn:active { + transform: scale(0.98); } .not-found-btn.primary { diff --git a/frontend/src/styles/ProfileNavbar.css b/frontend/src/styles/ProfileNavbar.css index e46c2e7..062c5b8 100644 --- a/frontend/src/styles/ProfileNavbar.css +++ b/frontend/src/styles/ProfileNavbar.css @@ -8,8 +8,8 @@ gap: 7px; position: relative; overflow: hidden; - background: rgba(7, 18, 40, 0.4); - border: 1px solid rgba(139, 92, 246, 0.2); + background: rgba(7, 14, 24, 0.62); + border: 1px solid rgba(122, 151, 185, 0.16); border-radius: 999px; font-family: 'Fira Code', monospace; font-size: 0.8rem; @@ -17,15 +17,23 @@ padding: 4px 12px 4px 6px; color: rgba(255, 255, 255, 0.8); cursor: pointer; - transition: all 0.25s ease; + transition: + color 160ms var(--ease-out-strong), + border-color 160ms var(--ease-out-strong), + background 160ms var(--ease-out-strong), + transform 120ms var(--ease-out-strong); white-space: nowrap; } .user-menu__btn:hover, .user-menu__btn.open { color: #ffffff; - border-color: rgba(139, 92, 246, 0.4); - box-shadow: 0 0 12px rgba(139, 92, 246, 0.15); + border-color: rgba(110, 231, 242, 0.3); + background: rgba(10, 20, 32, 0.78); +} + +.user-menu__btn:active { + transform: scale(0.97); } .user-menu__avatar { @@ -51,7 +59,7 @@ width: 22px; height: 22px; border-radius: 50%; - background: linear-gradient(135deg, #4858e8 0%, #6838d8 100%); + background: linear-gradient(135deg, #1e5fe7 0%, #21aabd 100%); color: #ffffff; font-size: 0.7rem; font-weight: 700; @@ -70,7 +78,7 @@ .user-menu__chevron { color: rgba(255, 255, 255, 0.6); - transition: transform 0.2s ease; + transition: transform 160ms var(--ease-out-strong); flex-shrink: 0; } @@ -83,22 +91,21 @@ top: calc(100% + 12px); right: 0; width: 220px; - background: rgba(4, 10, 24, 0.88); + background: rgba(5, 9, 17, 0.94); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); - border: 1px solid rgba(139, 92, 246, 0.18); + border: 1px solid rgba(122, 151, 185, 0.18); border-radius: 10px; box-shadow: - 0 12px 48px rgba(0, 0, 0, 0.6), - 0 0 32px rgba(139, 92, 246, 0.05); + 0 18px 48px rgba(0, 0, 0, 0.5); overflow: hidden; z-index: 2000; - animation: user-menu-in 0.14s ease; + animation: user-menu-in 160ms var(--ease-out-strong); } @keyframes user-menu-in { - from { opacity: 0; transform: translateY(-5px); } - to { opacity: 1; transform: translateY(0); } + from { opacity: 0; transform: translateY(-5px) scale(0.97); } + to { opacity: 1; transform: translateY(0) scale(1); } } .user-menu__dropdown-header { @@ -128,7 +135,7 @@ .user-menu__divider { height: 1px; - background: rgba(139, 92, 246, 0.12); + background: rgba(122, 151, 185, 0.12); } .user-menu__item { @@ -145,20 +152,27 @@ font-size: 0.8rem; font-weight: 400; color: rgba(255, 255, 255, 0.7); - transition: all 0.15s ease; + transition: + color 140ms var(--ease-out-strong), + background 140ms var(--ease-out-strong), + transform 120ms var(--ease-out-strong); } .user-menu__item svg { flex-shrink: 0; opacity: 0.6; - transition: opacity 0.15s ease; + transition: opacity 140ms var(--ease-out-strong); } .user-menu__item:hover { - background: rgba(139, 92, 246, 0.12); + background: rgba(110, 231, 242, 0.08); color: #ffffff; } +.user-menu__item:active { + transform: scale(0.98); +} + .user-menu__item:hover svg { opacity: 1; } diff --git a/frontend/src/styles/UserProfile.css b/frontend/src/styles/UserProfile.css index 8288b8e..82df2d7 100644 --- a/frontend/src/styles/UserProfile.css +++ b/frontend/src/styles/UserProfile.css @@ -71,9 +71,9 @@ padding: 0; margin-bottom: 18px; align-self: flex-start; - transition: opacity 0.15s; + transition: opacity 140ms var(--ease-out-strong); position: relative; - z-index: 9999; + z-index: 2; } .up-back-btn:hover { opacity: 0.75; } diff --git a/frontend/src/styles/global.css b/frontend/src/styles/global.css index b5f8fd4..f7e784c 100644 --- a/frontend/src/styles/global.css +++ b/frontend/src/styles/global.css @@ -1,32 +1,32 @@ :root { - --bg: #030b18; - --bg-deep: #01050f; - --surface: rgba(4, 13, 30, 0.45); + --bg: #05070c; + --bg-deep: #02040a; + --surface: rgba(8, 14, 24, 0.68); --surface-hover: rgba(255, 255, 255, 0.04); - --border: rgba(139, 92, 246, 0.12); - --border-hover: rgba(139, 92, 246, 0.3); - --border-focus: rgba(96, 200, 255, 0.4); - --accent: #8878ff; - --accent-secondary: #60aaff; + --border: rgba(122, 151, 185, 0.16); + --border-hover: rgba(110, 231, 242, 0.32); + --border-focus: rgba(110, 231, 242, 0.48); + --accent: #6ee7f2; + --accent-secondary: #8fb3ff; --text: #f0f4ff; --muted: #6a7590; --text-dim: #5a637a; - --color-bg: #030b18; - --color-bg-deep: #01050f; - --color-bg-elevated: rgba(4, 13, 30, 0.55); - --color-bg-surface: rgba(4, 13, 30, 0.45); - --color-bg-surface-2: rgba(4, 13, 30, 0.45); - --color-border: rgba(139, 92, 246, 0.12); - --color-border-subtle: rgba(139, 92, 246, 0.08); - --color-accent: #60aaff; - --color-accent-2: #8878ff; - --color-accent-soft: #a8b8ff; - --color-accent-soft-2: #b0b8f8; - --color-grad-1: #60aaff; - --color-grad-2: #8878ff; - --color-grad-3: #a855f7; - --color-grad-4: #ec4899; + --color-bg: #05070c; + --color-bg-deep: #02040a; + --color-bg-elevated: rgba(9, 16, 28, 0.78); + --color-bg-surface: rgba(8, 14, 24, 0.68); + --color-bg-surface-2: rgba(10, 18, 30, 0.72); + --color-border: rgba(122, 151, 185, 0.16); + --color-border-subtle: rgba(122, 151, 185, 0.09); + --color-accent: #6ee7f2; + --color-accent-2: #8fb3ff; + --color-accent-soft: #c7d7ff; + --color-accent-soft-2: #9adbe4; + --color-grad-1: #6ee7f2; + --color-grad-2: #8fb3ff; + --color-grad-3: #b8a4ff; + --color-grad-4: #f0a3c4; --color-text: #f0f4ff; --color-text-bright: #ffffff; --color-text-soft: #d0d8ff; @@ -36,16 +36,16 @@ --color-shadow-card: 0 24px 60px rgba(0, 0, 0, 0.35); --color-shadow-strong: rgba(0, 0, 0, 0.65); - --color-accent-06: rgba(139, 92, 246, 0.06); - --color-accent-08: rgba(139, 92, 246, 0.08); - --color-accent-10: rgba(139, 92, 246, 0.1); - --color-accent-12: rgba(139, 92, 246, 0.12); - --color-accent-18: rgba(139, 92, 246, 0.18); - --color-accent-25: rgba(139, 92, 246, 0.25); - --color-accent-30: rgba(139, 92, 246, 0.3); - --color-accent-35: rgba(139, 92, 246, 0.35); - --color-accent-40: rgba(139, 92, 246, 0.4); - --color-accent-50: rgba(139, 92, 246, 0.5); + --color-accent-06: rgba(110, 231, 242, 0.06); + --color-accent-08: rgba(110, 231, 242, 0.08); + --color-accent-10: rgba(110, 231, 242, 0.1); + --color-accent-12: rgba(110, 231, 242, 0.12); + --color-accent-18: rgba(110, 231, 242, 0.18); + --color-accent-25: rgba(110, 231, 242, 0.25); + --color-accent-30: rgba(110, 231, 242, 0.3); + --color-accent-35: rgba(110, 231, 242, 0.35); + --color-accent-40: rgba(110, 231, 242, 0.4); + --color-accent-50: rgba(110, 231, 242, 0.5); --color-success: #6ce89a; --color-success-08: rgba(108, 232, 154, 0.08); @@ -56,9 +56,15 @@ --color-danger-20: rgba(255, 138, 131, 0.2); --color-danger-25: rgba(255, 138, 131, 0.25); - --fade-purple: linear-gradient(135deg, #4858e8 0%, #6838d8 100%); - --text-decoration: rgba(139, 92, 246, 0.6); - --text-decoration-shadow: rgba(139, 92, 246, 0.4); + --fade-purple: linear-gradient(135deg, #2f6df6 0%, #34c3d6 100%); + --text-decoration: rgba(110, 231, 242, 0.48); + --text-decoration-shadow: rgba(110, 231, 242, 0.18); + --color-white-03: rgba(255, 255, 255, 0.03); + --color-white-04: rgba(255, 255, 255, 0.04); + --color-white-12: rgba(255, 255, 255, 0.12); + --color-white-78: rgba(255, 255, 255, 0.78); + --ease-out-strong: cubic-bezier(0.16, 1, 0.3, 1); + --ease-in-out-strong: cubic-bezier(0.77, 0, 0.175, 1); } *, *::before, *::after { @@ -69,18 +75,24 @@ html, body, #root { height: 100%; } +html { + scroll-behavior: smooth; +} + body { margin: 0; font-family: 'Outfit', 'Inter', system-ui, -apple-system, sans-serif; font-display: swap; background-color: var(--bg); background-image: - radial-gradient(circle at 80% 25%, rgba(96, 80, 220, 0.06) 0%, transparent 55%), - radial-gradient(circle at 20% 75%, rgba(136, 120, 255, 0.03) 0%, transparent 60%); + radial-gradient(circle at 78% 18%, rgba(110, 231, 242, 0.045) 0%, transparent 40%), + radial-gradient(circle at 16% 76%, rgba(143, 179, 255, 0.035) 0%, transparent 42%); background-attachment: fixed; color: var(--text); display: flex; flex-direction: column; + -webkit-font-smoothing: antialiased; + text-rendering: geometricPrecision; } #page { @@ -92,7 +104,7 @@ body { a { color: var(--accent-secondary); text-decoration: none; - transition: color 0.2s ease; + transition: color 180ms var(--ease-out-strong); } a:hover { @@ -103,3 +115,29 @@ main { flex: 1; width: 100%; } + +button, +a { + -webkit-tap-highlight-color: transparent; +} + +:focus-visible { + outline: 2px solid var(--border-focus); + outline-offset: 3px; +} + +::selection { + background: rgba(110, 231, 242, 0.22); + color: var(--color-text-bright); +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 1ms !important; + animation-iteration-count: 1 !important; + scroll-behavior: auto !important; + transition-duration: 1ms !important; + } +} diff --git a/frontend/src/styles/services.css b/frontend/src/styles/services.css new file mode 100644 index 0000000..98dae1c --- /dev/null +++ b/frontend/src/styles/services.css @@ -0,0 +1,410 @@ +.services-page { + --services-shell: 1180px; + position: relative; + overflow: hidden; + scroll-behavior: smooth; + background: + radial-gradient(circle at 80% 8%, rgba(110, 231, 242, 0.09), transparent 32%), + radial-gradient(circle at 8% 30%, rgba(143, 179, 255, 0.06), transparent 28%), + #05070c; + color: #f0f4ff; +} + +.services-page::before { + content: ""; + position: fixed; + inset: 0; + pointer-events: none; + opacity: 0.22; + background-image: + linear-gradient(rgba(122, 151, 185, 0.04) 1px, transparent 1px), + linear-gradient(90deg, rgba(122, 151, 185, 0.04) 1px, transparent 1px); + background-size: 72px 72px; + mask-image: linear-gradient(to bottom, black, transparent 75%); +} + +.services-visually-hidden { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +.services-shell { + position: relative; + width: min(calc(100% - 48px), var(--services-shell)); + margin-inline: auto; +} + +.services-hero { + position: relative; + z-index: 1; + padding: 132px 0 52px; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); +} + +.services-hero__content { + display: grid; + grid-template-columns: minmax(0, 0.9fr) minmax(420px, 1.1fr); + gap: clamp(48px, 8vw, 110px); + align-items: end; +} + +.services-eyebrow, +.services-section-heading > span, +.services-final-cta__card > span { + display: inline-flex; + color: #6ee7f2; + font-size: 0.72rem; + font-weight: 800; + letter-spacing: 0.18em; + text-transform: uppercase; +} + +.services-hero h1 { + max-width: 560px; + margin: 14px 0 16px; + font-size: clamp(2.25rem, 4vw, 4rem); + line-height: 1.02; + letter-spacing: 0; +} + +.services-hero__intro > p { + max-width: 620px; + margin: 0; + color: #7b86a0; + font-size: 0.98rem; + line-height: 1.7; +} + +.services-button { + display: inline-flex; + min-height: 46px; + align-items: center; + justify-content: center; + gap: 9px; + padding: 0 24px; + border: 1px solid transparent; + border-radius: 999px; + font-size: 0.88rem; + font-weight: 750; + text-decoration: none; + transition: + transform 160ms var(--ease-out-strong), + border-color 180ms var(--ease-out-strong), + background 180ms var(--ease-out-strong), + box-shadow 180ms var(--ease-out-strong); +} + +.services-button:hover { transform: translateY(-1px); } +.services-button:active { transform: scale(0.98); } + +.services-button--primary { + background: linear-gradient(135deg, #1e5fe7, #21aabd); + box-shadow: 0 12px 26px rgba(20, 85, 140, 0.24); + color: #fff; +} + +.services-button--secondary { + border-color: rgba(180, 190, 255, 0.14); + background: rgba(255, 255, 255, 0.035); + color: #aab4cf; +} + +.services-jump-nav { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 10px; + margin-top: 12px; +} + +.services-hero__navigation > span { + color: #64708a; + font-size: 0.68rem; + font-weight: 800; + letter-spacing: 0.14em; + text-transform: uppercase; +} + +.services-jump-nav__item { + position: relative; + display: flex; + min-width: 0; + align-items: center; + gap: 10px; + overflow: hidden; + padding: 14px 16px; + border: 1px solid rgba(122, 151, 185, 0.12); + border-radius: 10px; + background: rgba(8, 14, 24, 0.64); + color: #aab4cf; + font-size: 0.85rem; + font-weight: 650; + transition: + transform 160ms var(--ease-out-strong), + border-color 180ms var(--ease-out-strong), + background 180ms var(--ease-out-strong), + color 180ms var(--ease-out-strong); +} + +.services-jump-nav__item::before { + content: ""; + position: absolute; + left: 16px; + right: 16px; + bottom: 8px; + height: 2px; + border-radius: 999px; + background: linear-gradient(90deg, var(--service-color), rgba(var(--service-rgb), 0.28)); + opacity: 0; + transform: scaleX(0.24); + transform-origin: left; + transition: + opacity 180ms var(--ease-out-strong), + transform 260ms var(--ease-out-strong); +} + +.services-jump-nav__item:hover { + border-color: var(--service-color); + background: rgba(10, 18, 30, 0.82); + color: #fff; +} + +.services-jump-nav__item.is-active { + border-color: rgba(var(--service-rgb), 0.52); + background: + linear-gradient(135deg, rgba(var(--service-rgb), 0.13), rgba(10, 18, 30, 0.8) 58%), + rgba(8, 14, 24, 0.78); + color: #fff; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.055); +} + +.services-jump-nav__item.is-active::before, +.services-jump-nav__item:hover::before { + opacity: 1; + transform: scaleX(1); +} + +.services-jump-nav__item svg { + position: relative; + flex: 0 0 auto; + color: var(--service-color); +} + +.services-jump-nav__item span { + position: relative; +} + +.services-list { + position: relative; + z-index: 1; + padding: 88px 0; +} + +.services-section-heading { max-width: 700px; margin-bottom: 64px; } + +.services-section-heading h2, +.services-final-cta h2 { + margin: 14px 0 18px; + font-size: clamp(2rem, 4vw, 3.6rem); + line-height: 1.06; + letter-spacing: 0; +} + +.services-section-heading p, +.services-final-cta p { + margin: 0; + color: #6f7b95; + font-size: 1rem; + line-height: 1.75; +} + +.services-list__inner { display: grid; gap: 36px; } + +.service-detail { + --service-color: #6ee7f2; + --service-rgb: 136, 120, 255; + display: grid; + grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr); + gap: clamp(36px, 7vw, 96px); + align-items: center; + min-height: 560px; + padding: clamp(32px, 5vw, 64px); + scroll-margin-top: 120px; + border: 1px solid rgba(122, 151, 185, 0.14); + border-radius: 14px; + background: + linear-gradient(135deg, rgba(var(--service-rgb), 0.055), transparent 42%), + rgba(8, 14, 24, 0.6); + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.045), + 0 24px 64px rgba(0, 0, 0, 0.22); +} + +.service-detail:nth-child(even) .service-detail__visual { order: 2; } + +.services-tone--discord { --service-color: #7888f8; --service-rgb: 88, 101, 242; } +.services-tone--web { --service-color: #38b8f8; --service-rgb: 14, 165, 233; } +.services-tone--app { --service-color: #e878b8; --service-rgb: 236, 72, 153; } +.services-tone--individual { --service-color: #c078f8; --service-rgb: 168, 85, 247; } + +.service-detail__visual, +.service-detail__content { + min-width: 0; + will-change: transform, opacity; +} + +.service-detail__visual { position: relative; } + +.service-detail__number { + position: absolute; + top: -34px; + right: 0; + color: rgba(var(--service-rgb), 0.14); + font-size: clamp(4rem, 9vw, 7.5rem); + font-weight: 900; + line-height: 1; +} + +.service-example-placeholder { + position: relative; + display: flex; + min-height: 250px; + flex-direction: column; + justify-content: space-between; + padding: 28px; + overflow: hidden; + border: 1px solid rgba(var(--service-rgb), 0.2); + border-radius: 12px; + background: + radial-gradient(circle at 18% 18%, rgba(var(--service-rgb), 0.14), transparent 34%), + linear-gradient(145deg, rgba(var(--service-rgb), 0.07), rgba(3, 6, 12, 0.9)); + box-shadow: inset 0 1px rgba(255, 255, 255, 0.05), 0 22px 48px rgba(0, 0, 0, 0.28); +} + +.service-example-placeholder__heading { + display: flex; + align-items: center; + gap: 14px; +} + +.service-example-placeholder__heading span { + color: var(--service-color); + font-size: 0.64rem; + font-weight: 800; + letter-spacing: 0.13em; + text-transform: uppercase; +} + +.service-example-placeholder__heading h4 { + margin: 3px 0 0; + color: #e5e9f5; + font-size: 1rem; +} + +.service-detail__visual-icon { + display: grid; + width: 54px; + height: 54px; + place-items: center; + border: 1px solid rgba(var(--service-rgb), 0.3); + border-radius: 12px; + background: rgba(var(--service-rgb), 0.12); + color: var(--service-color); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05); +} + +.service-example-placeholder__status { + display: flex; + align-items: center; + gap: 8px; + margin-top: 18px; + color: #68748d; + font-size: 0.72rem; +} + +.service-example-placeholder__status i { + width: 7px; + height: 7px; + border-radius: 50%; + background: var(--service-color); + box-shadow: 0 0 12px rgba(var(--service-rgb), 0.7); +} + +.service-detail__label { + color: var(--service-color); + font-size: 0.7rem; + font-weight: 800; + letter-spacing: 0.16em; + text-transform: uppercase; +} + +.service-detail h3 { + margin: 14px 0 18px; + font-size: clamp(2rem, 4vw, 3.25rem); + line-height: 1.05; + letter-spacing: 0; +} + +.service-detail__description, +.service-detail__result { color: #75819a; line-height: 1.72; } +.service-detail__description { margin: 0 0 16px; font-size: 1rem; } +.service-detail__result { margin: 0 0 26px; padding-left: 14px; border-left: 2px solid var(--service-color); color: #aab4cf; } + +.service-detail__subheading { + margin: 0 0 12px; + color: #717d96; + font-size: 0.67rem; + font-weight: 800; + letter-spacing: 0.14em; + text-transform: uppercase; +} + +.service-detail__features { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 12px 18px; + margin: 0 0 28px; + padding: 0; + list-style: none; +} + +.service-detail__features li { display: flex; min-width: 0; align-items: flex-start; gap: 9px; color: #c5cce0; font-size: 0.88rem; line-height: 1.45; } +.service-detail__features svg { flex: 0 0 auto; margin-top: 2px; color: var(--service-color); } + +.service-detail__tags { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 28px; } +.service-detail__tags span { padding: 6px 11px; border: 1px solid rgba(var(--service-rgb), 0.17); border-radius: 999px; background: rgba(var(--service-rgb), 0.07); color: #8e99b2; font-size: 0.72rem; font-weight: 650; } + +.service-detail__link { display: inline-flex; align-items: center; gap: 8px; color: var(--service-color); font-size: 0.88rem; font-weight: 750; transition: gap 160ms var(--ease-out-strong), color 160ms var(--ease-out-strong), transform 120ms var(--ease-out-strong); } +.service-detail__link:hover { gap: 12px; color: #fff; } +.service-detail__link:active { transform: scale(0.98); } + +.services-final-cta { position: relative; z-index: 1; padding: 110px 0; } +.services-final-cta__card { padding: clamp(42px, 7vw, 84px); border: 1px solid rgba(110, 231, 242, 0.16); border-radius: 14px; background: radial-gradient(circle at 50% 0, rgba(110, 231, 242, 0.12), transparent 55%), rgba(8, 14, 24, 0.68); text-align: center; } +.services-final-cta__card p { max-width: 580px; margin: 0 auto 32px; } + +@media (max-width: 980px) { + .services-hero { padding-top: 122px; } + .services-hero__content { grid-template-columns: 1fr; gap: 34px; align-items: start; } + .service-detail { grid-template-columns: 1fr; min-height: 0; } + .service-detail:nth-child(even) .service-detail__visual { order: 0; } +} + +@media (max-width: 620px) { + .services-shell { width: min(calc(100% - 32px), var(--services-shell)); } + .services-hero { padding: 112px 0 40px; } + .services-hero h1 { font-size: clamp(2.2rem, 11vw, 3.2rem); } + .services-button { width: 100%; } + .services-jump-nav { grid-template-columns: 1fr; } + .services-list { padding: 76px 0; } + .services-section-heading { margin-bottom: 40px; } + .service-detail { padding: 22px; border-radius: 20px; } + .service-example-placeholder { min-height: 190px; padding: 20px; } + .service-detail__features { grid-template-columns: 1fr; } + .services-final-cta { padding: 76px 0; } +}