Merge branch 'frontend-migration' into 'dev'
Frontend migration See merge request rheinsw/rheinsw!1
41
frontend/.gitignore
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/versions
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
115
frontend/.gitlab-ci.yml
Normal file
@@ -0,0 +1,115 @@
|
||||
image: node:22@sha256:f6b9c31ace05502dd98ef777aaa20464362435dcc5e312b0e213121dcf7d8b95
|
||||
|
||||
workflow:
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
- if: $CI_COMMIT_BRANCH == "production"
|
||||
- if: $CI_COMMIT_BRANCH == "dev"
|
||||
- if: $CI_COMMIT_TAG =~ /^v[\d]{1,4}\.[\d]{1,2}\.[\d]{1,2}$/
|
||||
|
||||
stages:
|
||||
- build
|
||||
- docker
|
||||
- deploy
|
||||
- sync
|
||||
|
||||
cache:
|
||||
key: ${CI_COMMIT_REF_SLUG}
|
||||
paths:
|
||||
- node_modules/
|
||||
- .next/cache/
|
||||
|
||||
variables:
|
||||
NEXT_PUBLIC_ENV: "production"
|
||||
OUTPUT_DIR: ".next"
|
||||
PROJECT_NAME: $CI_PROJECT_NAME
|
||||
DOCKER_IMAGE: "registry.boomlab.party/rheinsw/$CI_PROJECT_NAME"
|
||||
|
||||
.deploy_production_rule: &deploy_production_rule
|
||||
- if: $CI_COMMIT_BRANCH == "production"
|
||||
when: manual
|
||||
allow_failure: true
|
||||
|
||||
# Reusable SSH key setup block
|
||||
.install_deploy_key: &install_deploy_key
|
||||
- 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
|
||||
- mkdir -p ~/.ssh
|
||||
- echo "$DEPLOY_KEY_BASE64" | base64 -d > ~/.ssh/deploy_key
|
||||
- eval "$(ssh-agent -s)"
|
||||
- chmod 600 ~/.ssh/deploy_key
|
||||
- ssh-add ~/.ssh/deploy_key
|
||||
- ssh-keyscan -p 22 -H '192.168.41.101' >> ~/.ssh/known_hosts || true
|
||||
|
||||
.deploy_script: &deploy_script
|
||||
- |
|
||||
echo "Deploying $DOCKER_IMAGE:$TAG to $CONTAINER_NAME on port $PORT..."
|
||||
|
||||
ssh gitlab@192.168.41.101 -p 22 "
|
||||
echo \"$CI_REGISTRY_PASSWORD\" | docker login $CI_REGISTRY -u \"$CI_REGISTRY_USER\" --password-stdin &&
|
||||
docker pull $DOCKER_IMAGE:$TAG &&
|
||||
docker stop $CONTAINER_NAME || true &&
|
||||
docker rm $CONTAINER_NAME || true &&
|
||||
docker run -d --name $CONTAINER_NAME -p $PORT:3000 $DOCKER_IMAGE:$TAG
|
||||
"
|
||||
|
||||
build:
|
||||
stage: build
|
||||
script:
|
||||
- npm install
|
||||
- npx next build
|
||||
- npm run lint
|
||||
|
||||
dockerize:
|
||||
stage: docker
|
||||
image: docker:20.10@sha256:2967f0819c84dd589ed0a023b9d25dcfe7a3c123d5bf784ffbb77edf55335f0c
|
||||
before_script:
|
||||
- echo "$CI_REGISTRY_PASSWORD" | docker login "$CI_REGISTRY" -u "$CI_REGISTRY_USER" --password-stdin
|
||||
script:
|
||||
- |
|
||||
RAW_TAG="$CI_COMMIT_REF_NAME"
|
||||
TAG="${RAW_TAG//\//_}" # replaces "/" with "_"
|
||||
echo "Sanitized tag: $TAG"
|
||||
docker build -t $DOCKER_IMAGE:$TAG -f Dockerfile .
|
||||
|
||||
if [[ "$RAW_TAG" == "dev" || "$RAW_TAG" == "production" ]]; then
|
||||
echo "Pushing Docker image $DOCKER_IMAGE:$TAG"
|
||||
docker push $DOCKER_IMAGE:$TAG
|
||||
else
|
||||
echo "Skipping Docker push for non-dev or production branch: $RAW_TAG"
|
||||
fi
|
||||
|
||||
|
||||
deploy_production:
|
||||
stage: deploy
|
||||
rules: *deploy_production_rule
|
||||
before_script: *install_deploy_key
|
||||
script:
|
||||
- TAG="production"
|
||||
- PORT="4100"
|
||||
- CONTAINER_NAME="$CI_PROJECT_NAME-production"
|
||||
- *deploy_script
|
||||
|
||||
deploy_dev:
|
||||
stage: deploy
|
||||
before_script: *install_deploy_key
|
||||
script:
|
||||
- TAG="dev"
|
||||
- PORT="5100"
|
||||
- CONTAINER_NAME="$CI_PROJECT_NAME-dev"
|
||||
- *deploy_script
|
||||
only:
|
||||
- dev
|
||||
|
||||
sync_branches:
|
||||
stage: sync
|
||||
rules:
|
||||
- if: '$CI_COMMIT_BRANCH == "production"'
|
||||
before_script:
|
||||
- git config --global user.email "gitlab-ci@rheinsw.com"
|
||||
- git config --global user.name "GitLab CI"
|
||||
script:
|
||||
- git remote set-url origin "https://oauth2:${CI_JOB_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git"
|
||||
- git checkout dev
|
||||
- git pull origin dev
|
||||
- git merge --no-ff origin/production
|
||||
- git push origin dev
|
||||
31
frontend/Dockerfile
Normal file
@@ -0,0 +1,31 @@
|
||||
# Use lightweight Node.js 20 base image
|
||||
FROM node:20-alpine@sha256:9bef0ef1e268f60627da9ba7d7605e8831d5b56ad07487d24d1aa386336d1944 as builder
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package files separately for better Docker caching
|
||||
COPY package.json package-lock.json ./
|
||||
|
||||
# Install dependencies
|
||||
RUN npm ci
|
||||
|
||||
# Copy entire project
|
||||
COPY . .
|
||||
|
||||
# Build the Next.js app
|
||||
RUN npm run build
|
||||
|
||||
# Use a minimal base image for running the app
|
||||
FROM node:20-alpine@sha256:9bef0ef1e268f60627da9ba7d7605e8831d5b56ad07487d24d1aa386336d1944
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy built files from the builder stage
|
||||
COPY --from=builder /app ./
|
||||
|
||||
# Expose port
|
||||
EXPOSE 3000
|
||||
|
||||
# Start Next.js in production mode
|
||||
CMD ["npm", "run", "start"]
|
||||
36
frontend/README.md
Normal file
@@ -0,0 +1,36 @@
|
||||
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
bun dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
||||
BIN
frontend/app/(root)/favicon.ico
Normal file
|
After Width: | Height: | Size: 25 KiB |
37
frontend/app/(root)/layout.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import type {Metadata} from "next";
|
||||
import "../globals.css";
|
||||
|
||||
import Nav from "@/components/Navbar/Nav";
|
||||
import Footer from "@/components/Footer/Footer";
|
||||
import {ThemeProvider} from "@/components/provider/ThemeProvider";
|
||||
import React from "react";
|
||||
import {cookies} from "next/headers";
|
||||
import {themeColors} from "@/components/Helper/ThemeColors";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Rhein Software",
|
||||
description: "Rhein Software Development",
|
||||
};
|
||||
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
const cookieStore = await cookies();
|
||||
const theme = cookieStore.get("theme")?.value === "dark" ? "dark" : "light";
|
||||
const bgColor = themeColors[theme].primaryBg;
|
||||
|
||||
return (
|
||||
<html lang="de" data-theme={theme}>
|
||||
<head/>
|
||||
<body className="antialiased" style={{backgroundColor: bgColor}}>
|
||||
<ThemeProvider>
|
||||
<Nav/>
|
||||
{children}
|
||||
<Footer/>
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
12
frontend/app/(root)/page.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
import Home from "@/components/Home/Home";
|
||||
|
||||
const HomePage = () => {
|
||||
return (
|
||||
<div>
|
||||
<Home />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default HomePage;
|
||||
37
frontend/app/about/layout.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import type {Metadata} from "next";
|
||||
import "../globals.css";
|
||||
|
||||
import Nav from "@/components/Navbar/Nav";
|
||||
import Footer from "@/components/Footer/Footer";
|
||||
import {ThemeProvider} from "@/components/provider/ThemeProvider";
|
||||
import React from "react";
|
||||
import {cookies} from "next/headers";
|
||||
import {themeColors} from "@/components/Helper/ThemeColors";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Über Uns | Rhein Software",
|
||||
description: "Rhein Software Development",
|
||||
};
|
||||
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
const cookieStore = await cookies();
|
||||
const theme = cookieStore.get("theme")?.value === "dark" ? "dark" : "light";
|
||||
const bgColor = themeColors[theme].primaryBg;
|
||||
|
||||
return (
|
||||
<html lang="de" data-theme={theme}>
|
||||
<head/>
|
||||
<body className="antialiased" style={{backgroundColor: bgColor}}>
|
||||
<ThemeProvider>
|
||||
<Nav/>
|
||||
{children}
|
||||
<Footer/>
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
12
frontend/app/about/page.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
import AboutContent from "@/components/About/AboutContent";
|
||||
|
||||
const AboutPage = () => {
|
||||
return (
|
||||
<div>
|
||||
<AboutContent/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AboutPage;
|
||||
54
frontend/app/api/contact/route.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import {NextRequest, NextResponse} from 'next/server';
|
||||
|
||||
const HCAPTCHA_SECRET = process.env.HCAPTCHA_SECRET ?? '';
|
||||
const SHARED_API_KEY = process.env.SHARED_API_KEY ?? '';
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const body = await req.json();
|
||||
const origin = req.headers.get("origin") || "http://localhost:3000";
|
||||
const captchaToken = body.captcha;
|
||||
|
||||
if (!captchaToken) {
|
||||
return NextResponse.json({success: false, error: 'Captcha is required'}, {status: 400});
|
||||
}
|
||||
|
||||
// Step 1: Verify hCaptcha token with their API
|
||||
const verifyResponse = await fetch('https://api.hcaptcha.com/siteverify', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: new URLSearchParams({
|
||||
secret: HCAPTCHA_SECRET,
|
||||
response: captchaToken,
|
||||
}),
|
||||
});
|
||||
|
||||
const captchaResult = await verifyResponse.json();
|
||||
|
||||
if (!captchaResult.success) {
|
||||
return NextResponse.json({success: false, error: 'Captcha verification failed'}, {status: 403});
|
||||
}
|
||||
|
||||
// Step 2: Forward valid contact request to Spring Boot backend
|
||||
const backendRes = await fetch('http://localhost:8080/api/contact', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
"Origin": origin,
|
||||
'Content-Type': 'application/json',
|
||||
'X-Frontend-Key': SHARED_API_KEY,
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
const backendText = await backendRes.text();
|
||||
|
||||
if (!backendRes.ok) {
|
||||
return NextResponse.json({success: false, error: backendText}, {status: backendRes.status});
|
||||
}
|
||||
|
||||
return NextResponse.json({success: true, message: backendText});
|
||||
} catch (err: any) {
|
||||
console.error('[ContactAPI] error:', err);
|
||||
return NextResponse.json({success: false, error: err.message}, {status: 500});
|
||||
}
|
||||
}
|
||||
37
frontend/app/contact/layout.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import type {Metadata} from "next";
|
||||
import "../globals.css";
|
||||
|
||||
import Nav from "@/components/Navbar/Nav";
|
||||
import Footer from "@/components/Footer/Footer";
|
||||
import {ThemeProvider} from "@/components/provider/ThemeProvider";
|
||||
import React from "react";
|
||||
import {cookies} from "next/headers";
|
||||
import {themeColors} from "@/components/Helper/ThemeColors";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Kontakt | Rhein Software",
|
||||
description: "Rhein Software Development",
|
||||
};
|
||||
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
const cookieStore = await cookies();
|
||||
const theme = cookieStore.get("theme")?.value === "dark" ? "dark" : "light";
|
||||
const bgColor = themeColors[theme].primaryBg;
|
||||
|
||||
return (
|
||||
<html lang="de" data-theme={theme}>
|
||||
<head/>
|
||||
<body className="antialiased" style={{backgroundColor: bgColor}}>
|
||||
<ThemeProvider>
|
||||
<Nav/>
|
||||
{children}
|
||||
<Footer/>
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
12
frontend/app/contact/page.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
import Contact from "@/components/Contact/Contact";
|
||||
|
||||
const ContactPage = () => {
|
||||
return (
|
||||
<div>
|
||||
<Contact/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ContactPage;
|
||||
27
frontend/app/globals.css
Normal file
@@ -0,0 +1,27 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer utilities {
|
||||
.nav__link {
|
||||
@apply relative text-base font-medium w-fit block after:block after:content-[''] after:absolute after:h-[3px] after:bg-pink-600 after:w-full after:scale-x-0 after:hover:scale-x-100 after:transition after:duration-300 after:origin-right;
|
||||
}
|
||||
}
|
||||
|
||||
/* Global theme transition */
|
||||
.transition-theme {
|
||||
transition: background-color 0.7s ease, color 0.7s ease;
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-6px);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-float {
|
||||
animation: float 3.5s ease-in-out infinite;
|
||||
}
|
||||
12
frontend/app/legal/imprint/page.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
import ImprintComp from "@/components/Legal/Imprint/ImprintComp";
|
||||
|
||||
const ImprintPage = () => {
|
||||
return (
|
||||
<div>
|
||||
<ImprintComp />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ImprintPage;
|
||||
38
frontend/app/legal/layout.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import type {Metadata} from "next";
|
||||
import "../globals.css";
|
||||
|
||||
import Nav from "@/components/Navbar/Nav";
|
||||
import Footer from "@/components/Footer/Footer";
|
||||
import {ThemeProvider} from "@/components/provider/ThemeProvider";
|
||||
import React from "react";
|
||||
import {cookies} from "next/headers";
|
||||
import {themeColors} from "@/components/Helper/ThemeColors";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Rechtliches | Rhein Software",
|
||||
description: "Rhein Software Development",
|
||||
};
|
||||
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
const cookieStore = await cookies();
|
||||
const theme = cookieStore.get("theme")?.value === "dark" ? "dark" : "light";
|
||||
const bgColor = themeColors[theme].primaryBg;
|
||||
|
||||
return (
|
||||
<html lang="de" data-theme={theme}>
|
||||
<head/>
|
||||
<body className="antialiased" style={{backgroundColor: bgColor}}>
|
||||
<ThemeProvider>
|
||||
<Nav/>
|
||||
{children}
|
||||
<Footer/>
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
10
frontend/app/legal/page.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
|
||||
const LegalPage = () => {
|
||||
return (
|
||||
<div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LegalPage;
|
||||
12
frontend/app/legal/privacy/page.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
import PrivacyComp from "@/components/Legal/Privacy/PrivacyComp";
|
||||
|
||||
const PrivacyPage = () => {
|
||||
return (
|
||||
<div>
|
||||
<PrivacyComp />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PrivacyPage;
|
||||
12
frontend/app/legal/revocation/page.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
import RevocationComp from "@/components/Legal/RevocationComp";
|
||||
|
||||
const RevocationPage = () => {
|
||||
return (
|
||||
<div>
|
||||
<RevocationComp />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RevocationPage;
|
||||
12
frontend/app/legal/terms-of-use/page.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
import TermsOfUseComp from "@/components/Legal/TermsOfUseComp";
|
||||
|
||||
const TermsOfUsePage = () => {
|
||||
return (
|
||||
<div>
|
||||
<TermsOfUseComp/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TermsOfUsePage;
|
||||
37
frontend/app/services/layout.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import type {Metadata} from "next";
|
||||
import "../globals.css";
|
||||
|
||||
import Nav from "@/components/Navbar/Nav";
|
||||
import Footer from "@/components/Footer/Footer";
|
||||
import {ThemeProvider} from "@/components/provider/ThemeProvider";
|
||||
import React from "react";
|
||||
import {cookies} from "next/headers";
|
||||
import {themeColors} from "@/components/Helper/ThemeColors";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Leistungen | Rhein Software",
|
||||
description: "Rhein Software Development",
|
||||
};
|
||||
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
const cookieStore = await cookies();
|
||||
const theme = cookieStore.get("theme")?.value === "dark" ? "dark" : "light";
|
||||
const bgColor = themeColors[theme].primaryBg;
|
||||
|
||||
return (
|
||||
<html lang="de" data-theme={theme}>
|
||||
<head/>
|
||||
<body className="antialiased" style={{backgroundColor: bgColor}}>
|
||||
<ThemeProvider>
|
||||
<Nav/>
|
||||
{children}
|
||||
<Footer/>
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
12
frontend/app/services/page.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
import Services from "@/components/Services/Services";
|
||||
|
||||
const ContactPage = () => {
|
||||
return (
|
||||
<div>
|
||||
<Services/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ContactPage;
|
||||
47
frontend/components/About/AboutContent.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
'use client';
|
||||
|
||||
import React from "react";
|
||||
import {motion} from "framer-motion";
|
||||
import {useThemeColors} from "@/utils/useThemeColors";
|
||||
import Section from "@/components/Section";
|
||||
import AboutHero from "@/components/About/Section/AboutHero";
|
||||
import AboutTimeline from "@/components/About/Section/AboutTimeline";
|
||||
import TeamSection from "@/components/About/Section/TeamSection";
|
||||
import AboutIntro from "@/components/About/Section/AboutIntro";
|
||||
import AboutProcess from "@/components/About/Section/AboutProcess";
|
||||
|
||||
const AboutContent = () => {
|
||||
const colors = useThemeColors();
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{opacity: 0, y: 20}}
|
||||
animate={{opacity: 1, y: 0}}
|
||||
transition={{duration: 0.7, ease: "easeOut"}}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
<Section style={{backgroundColor: colors.primaryBg}} shadow>
|
||||
<AboutHero/>
|
||||
</Section>
|
||||
|
||||
<Section style={{backgroundColor: colors.secondaryBg}} shadow>
|
||||
<AboutIntro/>
|
||||
</Section>
|
||||
|
||||
<Section style={{backgroundColor: colors.primaryBg}} shadow>
|
||||
<AboutProcess/>
|
||||
</Section>
|
||||
|
||||
<Section style={{backgroundColor: colors.secondaryBg}} shadow>
|
||||
<AboutTimeline/>
|
||||
</Section>
|
||||
|
||||
<Section style={{backgroundColor: colors.primaryBg}} shadow>
|
||||
<TeamSection/>
|
||||
</Section>
|
||||
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AboutContent;
|
||||
19
frontend/components/About/Section/AboutHero.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
'use client';
|
||||
|
||||
import React from "react";
|
||||
import SmallHero from "@/components/Helper/SmallHero";
|
||||
|
||||
const AboutHero = () => {
|
||||
return (
|
||||
<div className="relative overflow-hidden">
|
||||
<SmallHero
|
||||
title="Über uns"
|
||||
subtitle="Digitaler Partner für individuelle Softwarelösungen."
|
||||
backgroundImage="/images/contact.png"
|
||||
blurBackground
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AboutHero;
|
||||
68
frontend/components/About/Section/AboutIntro.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
'use client';
|
||||
|
||||
import {motion} from 'framer-motion';
|
||||
import {useThemeColors} from '@/utils/useThemeColors';
|
||||
|
||||
const About = () => {
|
||||
const colors = useThemeColors();
|
||||
|
||||
return (
|
||||
<section
|
||||
className="relative w-full py-24 transition-colors duration-700 ease-in-out"
|
||||
style={{backgroundColor: colors.secondaryBg, color: colors.primaryText}}
|
||||
>
|
||||
<div className="w-full max-w-6xl px-6 md:px-10 mx-auto">
|
||||
<div className="flex flex-col">
|
||||
{/* Text */}
|
||||
<div className="p-0 max-w-4xl">
|
||||
<motion.p
|
||||
className="text-base md:text-lg leading-relaxed transition-colors duration-700 ease-in-out"
|
||||
style={{color: colors.secondaryText}}
|
||||
initial={{opacity: 0, y: 20}}
|
||||
whileInView={{opacity: 1, y: 0}}
|
||||
viewport={{once: true}}
|
||||
transition={{duration: 0.5, delay: 0.2}}
|
||||
>
|
||||
Wir sind Rhein-Software – Ihr Partner für digitale Produkte und individuelle
|
||||
Softwarelösungen.
|
||||
Wir entwickeln skalierbare, wartbare Anwendungen mit klarem Fokus: Technik, die begeistert –
|
||||
von der Architektur bis zum Go-Live.
|
||||
</motion.p>
|
||||
|
||||
<motion.p
|
||||
className="mt-6 text-base md:text-lg leading-relaxed transition-colors duration-700 ease-in-out"
|
||||
style={{color: colors.secondaryText}}
|
||||
initial={{opacity: 0, y: 20}}
|
||||
whileInView={{opacity: 1, y: 0}}
|
||||
viewport={{once: true}}
|
||||
transition={{duration: 0.5, delay: 0.3}}
|
||||
>
|
||||
Ob Start-up oder etabliertes Unternehmen: Wir begleiten Sie mit einem flexiblen Netzwerk,
|
||||
klarer Kommunikation und hohem Qualitätsanspruch – agil, lösungsorientiert und nah am
|
||||
Projekt.
|
||||
</motion.p>
|
||||
</div>
|
||||
|
||||
{/* CTA Button */}
|
||||
<motion.div
|
||||
className="mt-10 flex justify-end"
|
||||
initial={{opacity: 0, y: 10}}
|
||||
whileInView={{opacity: 1, y: 0}}
|
||||
viewport={{once: true}}
|
||||
transition={{duration: 0.5, delay: 0.5}}
|
||||
>
|
||||
{/*<Link href="/about">*/}
|
||||
{/* <button*/}
|
||||
{/* className="flex items-center gap-2 bg-blue-700 hover:bg-blue-900 text-white font-semibold px-5 py-2 rounded-full shadow-lg transition-all"*/}
|
||||
{/* >*/}
|
||||
{/* Mehr über uns <FiArrowRight size={18}/>*/}
|
||||
{/* </button>*/}
|
||||
{/*</Link>*/}
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default About;
|
||||
158
frontend/components/About/Section/AboutProcess.tsx
Normal file
@@ -0,0 +1,158 @@
|
||||
'use client';
|
||||
|
||||
import React, {useState} from "react";
|
||||
import {motion} from "framer-motion";
|
||||
import {useThemeColors} from "@/utils/useThemeColors";
|
||||
|
||||
const processSteps = [
|
||||
{
|
||||
title: "Beratung",
|
||||
description: (
|
||||
<>
|
||||
In der <strong>Beratungsphase</strong> analysieren wir gemeinsam Ihre Anforderungen und
|
||||
Geschäftsziele. Dabei identifizieren wir Herausforderungen und definieren die Zielsetzung
|
||||
für Ihr Projekt.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Planung",
|
||||
description: (
|
||||
<>
|
||||
Wir erarbeiten ein <strong>technisches Konzept</strong> mit klarer Struktur, Meilensteinen und
|
||||
Ressourcenplanung. Eine solide Architektur bildet die Grundlage für ein skalierbares und wartbares
|
||||
System.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Entwicklung",
|
||||
description: (
|
||||
<>
|
||||
In iterativen Zyklen setzen wir das Projekt um. Regelmäßige <strong>Feedbackschleifen</strong>
|
||||
sorgen dafür, dass das Ergebnis Ihren Erwartungen entspricht und flexibel angepasst werden kann.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Test",
|
||||
description: (
|
||||
<>
|
||||
Durch umfangreiche <strong>Tests und Optimierungen</strong> stellen wir sicher, dass Ihre
|
||||
Anwendung robust, performant und benutzerfreundlich ist – noch vor dem Go-Live.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Go-Live",
|
||||
description: (
|
||||
<>
|
||||
Wir begleiten Sie beim <strong>produktiven Einsatz</strong> Ihrer Anwendung und unterstützen Sie
|
||||
auch nach dem Go-Live mit Support und Weiterentwicklungsmöglichkeiten.
|
||||
</>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const AboutProcess: React.FC = () => {
|
||||
const colors = useThemeColors();
|
||||
const [activeIndex, setActiveIndex] = useState<number>(0);
|
||||
|
||||
return (
|
||||
<section className="w-full px-6 sm:px-12 py-20 max-w-6xl mx-auto">
|
||||
<h2
|
||||
className="text-2xl sm:text-3xl font-bold text-left"
|
||||
style={{color: colors.primaryText}}
|
||||
>
|
||||
Unser Prozess
|
||||
</h2>
|
||||
<motion.div
|
||||
className="w-12 h-[2px] mt-2 mb-12 bg-amber-500"
|
||||
initial={{opacity: 0, x: -20}}
|
||||
whileInView={{opacity: 1, x: 0}}
|
||||
viewport={{once: true}}
|
||||
transition={{duration: 0.4, delay: 0.1}}
|
||||
/>
|
||||
|
||||
{/* Mobile View: Tab buttons */}
|
||||
<div className="block md:hidden mb-6">
|
||||
<div className="grid grid-cols-2 gap-2 sm:grid-cols-3">
|
||||
{processSteps.map((step, idx) => (
|
||||
<button
|
||||
key={idx}
|
||||
onClick={() => setActiveIndex(idx)}
|
||||
className={`w-full px-4 py-2 text-sm border rounded-full transition-colors ${
|
||||
activeIndex === idx
|
||||
? 'bg-blue-600 text-white border-blue-600'
|
||||
: 'border-gray-300 dark:border-gray-700 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800'
|
||||
}`}
|
||||
>
|
||||
{step.title}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Desktop View: 2-column layout */}
|
||||
<div className="hidden md:grid grid-cols-3 gap-8">
|
||||
{/* Left: Step List */}
|
||||
<div className="flex flex-col space-y-4">
|
||||
{processSteps.map((step, idx) => (
|
||||
<button
|
||||
key={idx}
|
||||
onClick={() => setActiveIndex(idx)}
|
||||
className={`text-left px-4 py-3 border rounded-lg transition-colors ${
|
||||
activeIndex === idx
|
||||
? 'border-blue-600 bg-blue-50 dark:bg-gray-800'
|
||||
: 'border-gray-300 dark:border-gray-700 hover:bg-gray-100 dark:hover:bg-gray-800'
|
||||
}`}
|
||||
style={{color: colors.primaryText}}
|
||||
>
|
||||
<span className="font-semibold">{idx + 1}. {step.title}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Right: Step Content */}
|
||||
<div className="md:col-span-2 p-6 border border-gray-300 dark:border-gray-700 rounded-lg"
|
||||
style={{backgroundColor: colors.primaryBg}}>
|
||||
<motion.div
|
||||
key={activeIndex}
|
||||
initial={{opacity: 0, y: 10}}
|
||||
animate={{opacity: 1, y: 0}}
|
||||
transition={{duration: 0.4}}
|
||||
>
|
||||
<h3 className="text-xl font-bold mb-4" style={{color: colors.primaryText}}>
|
||||
{processSteps[activeIndex].title}
|
||||
</h3>
|
||||
<div className="text-base space-y-1" style={{color: colors.secondaryText}}>
|
||||
{processSteps[activeIndex].description}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile View: Content Below Tabs */}
|
||||
<div className="block md:hidden">
|
||||
<div className="p-6 border border-gray-300 dark:border-gray-700 rounded-lg"
|
||||
style={{backgroundColor: colors.primaryBg}}>
|
||||
<motion.div
|
||||
key={activeIndex + '-mobile'}
|
||||
initial={{opacity: 0, y: 10}}
|
||||
animate={{opacity: 1, y: 0}}
|
||||
transition={{duration: 0.4}}
|
||||
>
|
||||
<h3 className="text-xl font-bold mb-4" style={{color: colors.primaryText}}>
|
||||
{activeIndex + 1}. {processSteps[activeIndex].title}
|
||||
</h3>
|
||||
<div className="text-base space-y-1" style={{color: colors.secondaryText}}>
|
||||
{processSteps[activeIndex].description}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default AboutProcess;
|
||||
94
frontend/components/About/Section/AboutTimeline.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
'use client';
|
||||
|
||||
import React from "react";
|
||||
import {motion} from "framer-motion";
|
||||
import {useThemeColors} from "@/utils/useThemeColors";
|
||||
|
||||
const timeline = [
|
||||
{
|
||||
date: "Oktober 2024",
|
||||
title: "Projektgründung",
|
||||
description: "Entwicklung der Idee und erste Umsetzungsschritte – inspiriert durch Technik und Nachhaltigkeit.",
|
||||
current: false,
|
||||
},
|
||||
{
|
||||
date: "Mai 2025",
|
||||
title: "Go-Live",
|
||||
description: "Offizieller Start mit Kundenprojekten und einem umfassenden Full-Service-Angebot.",
|
||||
current: true,
|
||||
},
|
||||
];
|
||||
|
||||
const AboutTimeline3 = () => {
|
||||
const colors = useThemeColors();
|
||||
|
||||
return (
|
||||
<div className="relative w-full px-6 sm:px-12 py-8 max-w-5xl mx-auto">
|
||||
<h2
|
||||
className="text-2xl sm:text-3xl font-bold mt-10"
|
||||
style={{color: colors.primaryText}}
|
||||
>
|
||||
Von der Idee bis heute
|
||||
</h2>
|
||||
<motion.div
|
||||
className="w-12 h-[2px] mt-2 mb-12 bg-amber-500"
|
||||
initial={{opacity: 0, x: -20}}
|
||||
whileInView={{opacity: 1, x: 0}}
|
||||
viewport={{once: true}}
|
||||
transition={{duration: 0.4, delay: 0.1}}
|
||||
/>
|
||||
|
||||
<div className="relative border-l-2 border-gray-300 dark:border-gray-700 ml-6">
|
||||
{timeline.map((item, idx) => (
|
||||
<motion.div
|
||||
key={idx}
|
||||
initial={{opacity: 0, y: 20}}
|
||||
whileInView={{opacity: 1, y: 0}}
|
||||
viewport={{once: true}}
|
||||
transition={{duration: 0.5, delay: idx * 0.2}}
|
||||
className="relative mb-10 pl-12"
|
||||
>
|
||||
{/* Timeline dot */}
|
||||
<div className="absolute left-[-22px] top-0 z-10">
|
||||
<div
|
||||
className={`w-10 h-10 rounded-full border-2 flex items-center justify-center bg-white dark:bg-gray-900 ${
|
||||
item.current
|
||||
? "border-blue-600"
|
||||
: "border-gray-400 dark:border-gray-600"
|
||||
}`}
|
||||
>
|
||||
{item.current && (
|
||||
<motion.span
|
||||
className="absolute w-10 h-10 rounded-full bg-blue-600 opacity-40"
|
||||
animate={{scale: [1, 1.6, 1], opacity: [0.4, 0, 0.4]}}
|
||||
transition={{repeat: Infinity, duration: 1.6}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Timeline card */}
|
||||
<motion.div
|
||||
whileHover={{scale: 1.02, translateX: 4}}
|
||||
transition={{type: "spring", stiffness: 260, damping: 20}}
|
||||
className="bg-white dark:bg-gray-900 rounded-lg shadow-md p-5 border border-gray-200 dark:border-gray-700 cursor-default"
|
||||
>
|
||||
<div className="text-sm text-blue-600 font-semibold mb-1">{item.date}</div>
|
||||
<div
|
||||
className="text-lg font-bold mb-2"
|
||||
style={{color: colors.primaryText}}
|
||||
>
|
||||
{item.title}
|
||||
</div>
|
||||
<div className="text-sm" style={{color: colors.secondaryText}}>
|
||||
{item.description}
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AboutTimeline3;
|
||||
97
frontend/components/About/Section/TeamSection.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
'use client';
|
||||
|
||||
import React from "react";
|
||||
import Image from "next/image";
|
||||
import {motion} from "framer-motion";
|
||||
import {useThemeColors} from "@/utils/useThemeColors";
|
||||
|
||||
const team = [
|
||||
{
|
||||
name: "Thatsaphorn",
|
||||
role: "Gründer & Entwickler",
|
||||
picture: "",
|
||||
},
|
||||
{
|
||||
name: "Anonym",
|
||||
role: "Vertrieb",
|
||||
picture: "",
|
||||
},
|
||||
];
|
||||
|
||||
const fallbackImage = "/images/team/default-avatar.jpg";
|
||||
|
||||
const TeamSection = () => {
|
||||
const colors = useThemeColors();
|
||||
|
||||
return (
|
||||
<section className="w-full px-6 sm:px-12 py-16 max-w-6xl mx-auto">
|
||||
<motion.h2
|
||||
className="text-2xl sm:text-3xl font-bold text-left"
|
||||
style={{color: colors.primaryText}}
|
||||
initial={{opacity: 0, y: 20}}
|
||||
whileInView={{opacity: 1, y: 0}}
|
||||
viewport={{once: true}}
|
||||
transition={{duration: 0.5}}
|
||||
>
|
||||
Das Team
|
||||
</motion.h2>
|
||||
<motion.div
|
||||
className="w-12 h-[2px] mt-2 mb-12 bg-amber-500"
|
||||
initial={{opacity: 0, x: -20}}
|
||||
whileInView={{opacity: 1, x: 0}}
|
||||
viewport={{once: true}}
|
||||
transition={{duration: 0.4, delay: 0.1}}
|
||||
/>
|
||||
|
||||
<div className="flex justify-center">
|
||||
<div
|
||||
className={`grid gap-8
|
||||
grid-cols-1
|
||||
sm:grid-cols-${Math.min(team.length, 2)}
|
||||
md:grid-cols-${Math.min(team.length, 3)}
|
||||
lg:grid-cols-${Math.min(team.length, 4)}`}
|
||||
>
|
||||
{team.map((member, idx) => (
|
||||
<motion.div
|
||||
key={member.name}
|
||||
initial={{opacity: 0, y: 20}}
|
||||
whileInView={{opacity: 1, y: 0}}
|
||||
viewport={{once: true}}
|
||||
transition={{duration: 0.4, delay: idx * 0.1}}
|
||||
whileHover={{scale: 1.015}}
|
||||
className="flex flex-col items-center text-center
|
||||
rounded-xl border border-gray-200 dark:border-gray-700
|
||||
shadow-md hover:shadow-lg transition-all p-6"
|
||||
style={{backgroundColor: colors.secondaryBg}}
|
||||
>
|
||||
<motion.div
|
||||
whileHover={{scale: 1.05}}
|
||||
transition={{type: "spring", stiffness: 300, damping: 20}}
|
||||
className="w-28 h-28 relative mb-4"
|
||||
>
|
||||
<Image
|
||||
src={member.picture || fallbackImage}
|
||||
alt={member.name}
|
||||
fill
|
||||
sizes="112px"
|
||||
className="rounded-full object-cover shadow"
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
<div className="h-px w-8 bg-gray-300 dark:bg-gray-600 my-4"/>
|
||||
|
||||
<div className="text-lg font-semibold" style={{color: colors.primaryText}}>
|
||||
{member.name}
|
||||
</div>
|
||||
<div className="text-sm mt-1" style={{color: colors.secondaryText}}>
|
||||
{member.role}
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default TeamSection;
|
||||
31
frontend/components/Contact/Contact.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
'use client';
|
||||
|
||||
import React from "react";
|
||||
import {motion} from "framer-motion";
|
||||
import {useThemeColors} from "@/utils/useThemeColors";
|
||||
import ContactHero from "@/components/Contact/Section/ContactHero";
|
||||
import ContactFormSection from "@/components/Contact/Section/ContactFormSection";
|
||||
import Section from "@/components/Section";
|
||||
|
||||
const Contact = () => {
|
||||
const colors = useThemeColors();
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{opacity: 0, y: 20}}
|
||||
animate={{opacity: 1, y: 0}}
|
||||
transition={{duration: 0.7, ease: "easeOut"}}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
<Section style={{backgroundColor: colors.primaryBg}} shadow>
|
||||
<ContactHero/>
|
||||
</Section>
|
||||
|
||||
<Section style={{backgroundColor: colors.secondaryBg}} shadow>
|
||||
<ContactFormSection/>
|
||||
</Section>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Contact;
|
||||
192
frontend/components/Contact/Section/ContactFormSection.tsx
Normal file
@@ -0,0 +1,192 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { useThemeColors } from "@/utils/useThemeColors";
|
||||
import HCaptcha from "@hcaptcha/react-hcaptcha";
|
||||
|
||||
const ContactFormSection = () => {
|
||||
const colors = useThemeColors();
|
||||
|
||||
const [form, setForm] = useState({
|
||||
name: "",
|
||||
email: "",
|
||||
company: "",
|
||||
phone: "",
|
||||
website: "",
|
||||
message: "",
|
||||
});
|
||||
|
||||
const [captchaToken, setCaptchaToken] = useState("");
|
||||
const [submitted, setSubmitted] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
|
||||
const isDev = process.env.NODE_ENV === "development";
|
||||
const hCaptchaSiteKey = isDev
|
||||
? "10000000-ffff-ffff-ffff-000000000001" // hCaptcha test sitekey
|
||||
: "ES_ff59a664dc764f92870bf2c7b4eab7c5";
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
setForm({ ...form, [e.target.name]: e.target.value });
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
setError("");
|
||||
|
||||
if (!captchaToken) {
|
||||
setError("Bitte löse das CAPTCHA, um fortzufahren.");
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch("/api/contact", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ ...form, captcha: captchaToken }),
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
setSubmitted(true);
|
||||
setForm({ name: "", email: "", company: "", phone: "", website: "", message: "" });
|
||||
} else {
|
||||
const resJson = await res.json();
|
||||
setError(resJson?.error || "Ein Fehler ist aufgetreten. Bitte versuche es später erneut.");
|
||||
}
|
||||
} catch (_err) {
|
||||
setError("Serverfehler. Bitte versuche es später erneut.");
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full px-6 sm:px-12 py-20 text-left transition-theme">
|
||||
<motion.h2
|
||||
className="text-2xl sm:text-3xl font-bold mb-2"
|
||||
style={{ color: colors.primaryText }}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5 }}
|
||||
>
|
||||
Schreib uns eine Nachricht
|
||||
</motion.h2>
|
||||
|
||||
<motion.p
|
||||
className="text-sm mb-8 max-w-xl"
|
||||
style={{ color: colors.secondaryText }}
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: 0.2 }}
|
||||
>
|
||||
Wir freuen uns über dein Interesse und melden uns schnellstmöglich bei dir zurück.
|
||||
</motion.p>
|
||||
|
||||
{submitted ? (
|
||||
<div className="text-green-600 font-semibold text-lg">✅ Deine Nachricht wurde erfolgreich gesendet!</div>
|
||||
) : (
|
||||
<form className="space-y-6" onSubmit={handleSubmit}>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{[
|
||||
{ label: "Dein Name *", name: "name", type: "text", required: true, placeholder: "Max Mustermann" },
|
||||
{ label: "Deine E-Mail *", name: "email", type: "email", required: true, placeholder: "max@example.com" },
|
||||
{ label: "Firmenname (optional)", name: "company", type: "text", required: false, placeholder: "Mustermann GmbH" },
|
||||
{ label: "Telefonnummer (optional)", name: "phone", type: "tel", required: false, placeholder: "+49 123 456789" },
|
||||
{ label: "Webseite (optional)", name: "website", type: "url", required: false, placeholder: "https://..." },
|
||||
].map((field, index) => (
|
||||
<motion.div
|
||||
key={field.name}
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}
|
||||
>
|
||||
<label className="block font-semibold mb-1" style={{ color: colors.primaryText }}>
|
||||
{field.label}
|
||||
</label>
|
||||
<input
|
||||
type={field.type}
|
||||
name={field.name}
|
||||
value={form[field.name as keyof typeof form]}
|
||||
onChange={handleChange}
|
||||
required={field.required}
|
||||
placeholder={field.placeholder}
|
||||
className="w-full p-3 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 transition"
|
||||
style={{
|
||||
backgroundColor: colors.inputFieldBg,
|
||||
border: `1px solid ${colors.inputBorder}`,
|
||||
color: colors.primaryText,
|
||||
}}
|
||||
/>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: 0.6 }}
|
||||
>
|
||||
<label className="block font-semibold mb-1" style={{ color: colors.primaryText }}>
|
||||
Deine Nachricht *
|
||||
</label>
|
||||
<textarea
|
||||
name="message"
|
||||
rows={4}
|
||||
required
|
||||
value={form.message}
|
||||
onChange={handleChange}
|
||||
placeholder="Worum geht es?"
|
||||
className="w-full p-3 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 transition"
|
||||
style={{
|
||||
backgroundColor: colors.inputFieldBg,
|
||||
border: `1px solid ${colors.inputBorder}`,
|
||||
color: colors.primaryText,
|
||||
}}
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
className="pt-2"
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: 0.7 }}
|
||||
>
|
||||
<HCaptcha sitekey={hCaptchaSiteKey} onVerify={setCaptchaToken} />
|
||||
</motion.div>
|
||||
|
||||
{error && (
|
||||
<div className="text-red-600 font-medium pt-2">
|
||||
❌ {error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<motion.div
|
||||
className="pt-4 flex justify-end"
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: 0.8 }}
|
||||
>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="px-6 py-3 bg-blue-600 text-white text-sm sm:text-base font-semibold rounded-lg shadow-md hover:bg-blue-700 transition-all disabled:opacity-50"
|
||||
>
|
||||
{loading ? "Sende..." : "📩 Nachricht senden"}
|
||||
</button>
|
||||
</motion.div>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ContactFormSection;
|
||||
19
frontend/components/Contact/Section/ContactHero.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
'use client';
|
||||
|
||||
import React from "react";
|
||||
import SmallHero from "@/components/Helper/SmallHero";
|
||||
|
||||
const ContactHero = () => {
|
||||
return (
|
||||
<div className="relative overflow-hidden">
|
||||
<SmallHero
|
||||
title="Kontakt"
|
||||
subtitle="Du hast Fragen oder möchtest ein Projekt besprechen? Schreib uns!"
|
||||
backgroundImage="/images/contact.png"
|
||||
blurBackground
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ContactHero;
|
||||
97
frontend/components/Footer/Footer.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import Link from 'next/link';
|
||||
import {motion} from 'framer-motion';
|
||||
|
||||
const Footer = () => {
|
||||
return (
|
||||
<motion.footer
|
||||
initial={{opacity: 0, y: 20}}
|
||||
whileInView={{opacity: 1, y: 0}}
|
||||
viewport={{once: true}}
|
||||
transition={{duration: 0.6, ease: 'easeOut'}}
|
||||
className="py-10 transition-theme text-white"
|
||||
style={{
|
||||
backgroundColor: '#16171f', // modern dark blue-purple tone
|
||||
}}
|
||||
>
|
||||
<div className="w-[90%] mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-8">
|
||||
{/* Logo and description */}
|
||||
<div>
|
||||
<h1 className="text-xl md:text-2xl font-bold text-white">
|
||||
<span className="text-3xl md:text-4xl text-pink-700">R</span>hein Software
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{/* Informationen */}
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-white">Informationen</h3>
|
||||
<ul className="mt-4 space-y-4 text-sm font-semibold text-gray-400">
|
||||
<li>
|
||||
<Link href="/contact">
|
||||
<p className="nav_link transition-all duration-300 ease-in-out hover:text-white">
|
||||
Kontakt
|
||||
</p>
|
||||
</Link>
|
||||
</li>
|
||||
{/*<li>*/}
|
||||
{/* <Link href="/contact">*/}
|
||||
{/* <p className="nav_link transition-all duration-300 ease-in-out hover:text-white">*/}
|
||||
{/* Zahlung und Versand*/}
|
||||
{/* </p>*/}
|
||||
{/* </Link>*/}
|
||||
{/*</li>*/}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Rechtliches */}
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-white">Rechtliches</h3>
|
||||
<ul className="mt-4 space-y-4 text-sm font-semibold text-gray-400">
|
||||
{/*<li>*/}
|
||||
{/* <Link href="/legal/terms-of-use">*/}
|
||||
{/* <p className="nav_link transition-all duration-300 ease-in-out hover:text-white">*/}
|
||||
{/* AGB*/}
|
||||
{/* </p>*/}
|
||||
{/* </Link>*/}
|
||||
{/*</li>*/}
|
||||
{/*<li>*/}
|
||||
{/* <Link href="/legal/revocation">*/}
|
||||
{/* <p className="nav_link transition-all duration-300 ease-in-out hover:text-white">*/}
|
||||
{/* Widerruf*/}
|
||||
{/* </p>*/}
|
||||
{/* </Link>*/}
|
||||
{/*</li>*/}
|
||||
<li>
|
||||
<Link href="/legal/privacy">
|
||||
<p className="nav_link transition-all duration-300 ease-in-out hover:text-white">
|
||||
Datenschutz
|
||||
</p>
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/legal/imprint">
|
||||
<p className="nav_link transition-all duration-300 ease-in-out hover:text-white">
|
||||
Impressum
|
||||
</p>
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom Section */}
|
||||
<div
|
||||
className="mt-8 border-t border-gray-600 pt-8 flex flex-col md:flex-row justify-between items-center text-sm text-gray-400">
|
||||
<p className="text-center md:text-left">
|
||||
© 2025 Rhein Software Development. All rights reserved.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</motion.footer>
|
||||
);
|
||||
};
|
||||
|
||||
export default Footer;
|
||||
34
frontend/components/Helper/SectionDivider.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
export const SectionDivider1 = () => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="w-full h-20 transition-all duration-500 ease-in-out"
|
||||
style={{
|
||||
background: `linear-gradient(to bottom, var(--primary-bg), var(--secondary-bg))`
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const SectionDivider2 = () => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="w-full h-20 transition-all duration-500 ease-in-out"
|
||||
style={{
|
||||
background: `linear-gradient(to bottom, var(--secondary-bg), var(--primary-bg))`
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const SectionDivider3 = () => {
|
||||
return (
|
||||
<div
|
||||
className="w-full h-20 transition-all duration-500 ease-in-out"
|
||||
style={{
|
||||
background: `linear-gradient(to bottom, var(--primary-bg), var(--footer-bg))`
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
58
frontend/components/Helper/SmallHero.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
'use client';
|
||||
|
||||
import React, {useContext} from "react";
|
||||
import {ThemeContext} from "@/components/provider/ThemeProvider";
|
||||
import {themeColors} from "@/components/Helper/ThemeColors";
|
||||
import {motion} from "framer-motion";
|
||||
|
||||
type SmallHeroProps = {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
backgroundImage?: string;
|
||||
blurBackground?: boolean;
|
||||
};
|
||||
|
||||
const SmallHero = ({title, subtitle, backgroundImage, blurBackground}: SmallHeroProps) => {
|
||||
const {theme} = useContext(ThemeContext);
|
||||
const colors = themeColors[theme];
|
||||
|
||||
const primaryTextColor = backgroundImage ? "#ffffff" : colors.primaryText;
|
||||
const secondaryTextColor = backgroundImage ? "rgba(255, 255, 255, 0.8)" : "#6B7280"; // Tailwind gray-500
|
||||
|
||||
return (
|
||||
<div className="relative w-full py-36 overflow-hidden">
|
||||
{backgroundImage && blurBackground && (
|
||||
<div
|
||||
className="absolute inset-0 bg-cover bg-center blur-sm scale-[1.05] z-0 will-change-transform"
|
||||
style={{backgroundImage: `url(${backgroundImage})`}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Text content */}
|
||||
<div className="relative z-10 px-6 sm:px-12 max-w-5xl mx-auto">
|
||||
<motion.h1
|
||||
className="text-3xl sm:text-4xl font-bold text-left"
|
||||
initial={{opacity: 0, y: 20}}
|
||||
animate={{opacity: 1, y: 0}}
|
||||
transition={{duration: 0.6}}
|
||||
style={{color: primaryTextColor}}
|
||||
>
|
||||
{title}
|
||||
</motion.h1>
|
||||
{subtitle && (
|
||||
<motion.p
|
||||
className="mt-3 text-lg text-left"
|
||||
initial={{opacity: 0, y: 10}}
|
||||
animate={{opacity: 1, y: 0}}
|
||||
transition={{duration: 0.6, delay: 0.2}}
|
||||
style={{color: secondaryTextColor}}
|
||||
>
|
||||
{subtitle}
|
||||
</motion.p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SmallHero;
|
||||
34
frontend/components/Helper/ThemeColors.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
export const themeColors: Record<
|
||||
"light" | "dark",
|
||||
{
|
||||
primaryBg: string;
|
||||
secondaryBg: string;
|
||||
navBg: string;
|
||||
footerBg: string;
|
||||
primaryText: string;
|
||||
secondaryText: string;
|
||||
inputFieldBg: string;
|
||||
inputBorder: string;
|
||||
}
|
||||
> = {
|
||||
light: {
|
||||
primaryBg: "#F3F4F6",
|
||||
secondaryBg: "#eff1f3",
|
||||
navBg: "#F9FAFB",
|
||||
footerBg: "#E5E7EB",
|
||||
primaryText: "#1E293B",
|
||||
secondaryText: "#475569",
|
||||
inputFieldBg: "#ffffff",
|
||||
inputBorder: "#cbd5e1",
|
||||
},
|
||||
dark: {
|
||||
primaryBg: "#1A1A23",
|
||||
secondaryBg: "#22222C",
|
||||
navBg: "#2A2A35",
|
||||
footerBg: "#1F1F29",
|
||||
primaryText: "#F0F0F3",
|
||||
secondaryText: "#C0C2CC",
|
||||
inputFieldBg: "#2D2D38",
|
||||
inputBorder: "#4B4B5A",
|
||||
},
|
||||
};
|
||||
45
frontend/components/Home/Home.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
'use client';
|
||||
|
||||
import React from "react";
|
||||
import ContactCTA from "@/components/Home/Sections/ContactCTA";
|
||||
import HomeServices from "@/components/Home/Sections/HomeServices";
|
||||
import TechStack from "@/components/Home/Sections/TechStack";
|
||||
import Section from "@/components/Section";
|
||||
import {motion} from "framer-motion";
|
||||
import {useThemeColors} from "@/utils/useThemeColors";
|
||||
import Hero from "@/components/Home/Sections/Hero";
|
||||
|
||||
const Home = () => {
|
||||
const colors = useThemeColors();
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{opacity: 0, y: 20}}
|
||||
animate={{opacity: 1, y: 0}}
|
||||
transition={{duration: 0.7, ease: "easeOut"}}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
<Section style={{backgroundColor: colors.primaryBg}}>
|
||||
<Hero/>
|
||||
</Section>
|
||||
|
||||
{/*<Section style={{backgroundColor: colors.secondaryBg}} shadow>*/}
|
||||
{/* <About/>*/}
|
||||
{/*</Section>*/}
|
||||
|
||||
<Section style={{backgroundColor: colors.primaryBg}} shadow>
|
||||
<HomeServices/>
|
||||
</Section>
|
||||
|
||||
<Section style={{backgroundColor: colors.secondaryBg}} shadow>
|
||||
<TechStack/>
|
||||
</Section>
|
||||
|
||||
<Section style={{backgroundColor: colors.primaryBg}} shadow>
|
||||
<ContactCTA/>
|
||||
</Section>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
||||
84
frontend/components/Home/Sections/About.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
'use client';
|
||||
|
||||
// import Link from 'next/link';
|
||||
// import {FiArrowRight} from 'react-icons/fi';
|
||||
import {motion} from 'framer-motion';
|
||||
import {useThemeColors} from '@/utils/useThemeColors';
|
||||
|
||||
const About = () => {
|
||||
const colors = useThemeColors();
|
||||
|
||||
return (
|
||||
<section
|
||||
className="relative w-full py-24 transition-colors duration-700 ease-in-out"
|
||||
style={{backgroundColor: colors.secondaryBg, color: colors.primaryText}}
|
||||
>
|
||||
<div className="w-full max-w-6xl px-6 md:px-10 mx-auto">
|
||||
<div className="flex flex-col">
|
||||
{/* Title */}
|
||||
<motion.h2
|
||||
className="text-3xl md:text-4xl font-bold mb-1 text-left transition-colors duration-700 ease-in-out"
|
||||
>
|
||||
Über uns
|
||||
</motion.h2>
|
||||
|
||||
<motion.div
|
||||
className="w-12 h-[2px] mt-2 mb-6 bg-amber-500"
|
||||
initial={{opacity: 0, x: -20}}
|
||||
whileInView={{opacity: 1, x: 0}}
|
||||
viewport={{once: true}}
|
||||
transition={{duration: 0.4, delay: 0.1}}
|
||||
/>
|
||||
|
||||
{/* Text */}
|
||||
<div className="p-0 max-w-4xl">
|
||||
<motion.p
|
||||
className="text-base md:text-lg leading-relaxed transition-colors duration-700 ease-in-out"
|
||||
style={{color: colors.secondaryText}}
|
||||
initial={{opacity: 0, y: 20}}
|
||||
whileInView={{opacity: 1, y: 0}}
|
||||
viewport={{once: true}}
|
||||
transition={{duration: 0.5, delay: 0.2}}
|
||||
>
|
||||
Wir sind Rhein-Software – ein Team, das sich auf individuelle Softwarelösungen und digitale
|
||||
Services spezialisiert hat. Unsere Anwendungen sind technisch solide, skalierbar und
|
||||
durchdacht – gebaut für langfristigen Erfolg.
|
||||
</motion.p>
|
||||
|
||||
<motion.p
|
||||
className="mt-6 text-base md:text-lg leading-relaxed transition-colors duration-700 ease-in-out"
|
||||
style={{color: colors.secondaryText}}
|
||||
initial={{opacity: 0, y: 20}}
|
||||
whileInView={{opacity: 1, y: 0}}
|
||||
viewport={{once: true}}
|
||||
transition={{duration: 0.5, delay: 0.3}}
|
||||
>
|
||||
Von der ersten Idee bis zum Go-Live begleiten wir Unternehmen und Startups mit einem
|
||||
flexiblen Netzwerk, klarer Kommunikation und einem hohen Anspruch an Qualität.
|
||||
Unsere Lösungen sind intuitiv, effizient – und genau auf deine Anforderungen zugeschnitten.
|
||||
</motion.p>
|
||||
</div>
|
||||
|
||||
{/* CTA Button */}
|
||||
<motion.div
|
||||
className="mt-10 flex justify-end"
|
||||
initial={{opacity: 0, y: 10}}
|
||||
whileInView={{opacity: 1, y: 0}}
|
||||
viewport={{once: true}}
|
||||
transition={{duration: 0.5, delay: 0.5}}
|
||||
>
|
||||
{/*<Link href="/about">*/}
|
||||
{/* <button*/}
|
||||
{/* className="flex items-center gap-2 bg-blue-700 hover:bg-blue-900 text-white font-semibold px-5 py-2 rounded-full shadow-lg transition-all"*/}
|
||||
{/* >*/}
|
||||
{/* Mehr über uns <FiArrowRight size={18}/>*/}
|
||||
{/* </button>*/}
|
||||
{/*</Link>*/}
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default About;
|
||||
62
frontend/components/Home/Sections/ContactCTA.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import {motion} from 'framer-motion';
|
||||
import {FiArrowRight} from 'react-icons/fi';
|
||||
import {useThemeColors} from '@/utils/useThemeColors';
|
||||
|
||||
type ContactCTAProps = {
|
||||
title?: string;
|
||||
description?: string;
|
||||
buttonLabel?: string;
|
||||
};
|
||||
|
||||
const ContactCTA = ({
|
||||
title = "Interesse geweckt?",
|
||||
description = "Lass uns über dein Projekt sprechen. Wir freuen uns darauf, deine Ideen in die Realität umzusetzen.",
|
||||
buttonLabel = "Jetzt Kontakt aufnehmen",
|
||||
}: ContactCTAProps) => {
|
||||
const colors = useThemeColors();
|
||||
|
||||
return (
|
||||
<section
|
||||
className="relative w-full py-24 overflow-hidden transition-colors duration-700 ease-in-out"
|
||||
style={{backgroundColor: colors.primaryBg, color: colors.primaryText}}
|
||||
>
|
||||
<div className="w-full max-w-4xl px-6 md:px-10 mx-auto text-center">
|
||||
<motion.h2 className="text-3xl md:text-4xl font-bold">
|
||||
{title}
|
||||
</motion.h2>
|
||||
|
||||
<motion.p
|
||||
className="mt-4 text-sm md:text-base max-w-xl mx-auto"
|
||||
style={{color: colors.secondaryText}}
|
||||
initial={{opacity: 0, y: 20}}
|
||||
whileInView={{opacity: 1, y: 0}}
|
||||
viewport={{once: true}}
|
||||
transition={{duration: 0.5, delay: 0.2}}
|
||||
>
|
||||
{description}
|
||||
</motion.p>
|
||||
|
||||
<motion.div
|
||||
className="mt-8 flex justify-center"
|
||||
initial={{opacity: 0, y: 20}}
|
||||
whileInView={{opacity: 1, y: 0}}
|
||||
viewport={{once: true}}
|
||||
transition={{duration: 0.5, delay: 0.3}}
|
||||
>
|
||||
<Link href="/contact">
|
||||
<button
|
||||
className="inline-flex items-center gap-2 px-6 py-3 text-sm md:text-base font-semibold rounded-full bg-blue-700 hover:bg-blue-900 text-white shadow-md transition-all duration-300"
|
||||
>
|
||||
{buttonLabel} <FiArrowRight size={18}/>
|
||||
</button>
|
||||
</Link>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default ContactCTA;
|
||||
100
frontend/components/Home/Sections/Hero.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
'use client';
|
||||
|
||||
import Image from 'next/image';
|
||||
import {Typewriter} from 'react-simple-typewriter';
|
||||
import {motion} from 'framer-motion';
|
||||
|
||||
const Hero = () => {
|
||||
return (
|
||||
<div
|
||||
className="relative w-full pt-[4vh] md:pt-[12vh] h-screen flex flex-col overflow-hidden"
|
||||
style={{
|
||||
backgroundColor: 'var(--primary-bg)',
|
||||
color: 'white',
|
||||
}}
|
||||
>
|
||||
{/* Background Image */}
|
||||
<div className="absolute inset-0 z-0">
|
||||
<Image
|
||||
src="/images/home_hero.jpg"
|
||||
alt="Rhein river aerial view"
|
||||
layout="fill"
|
||||
objectFit="cover"
|
||||
className="blur-md scale-105"
|
||||
priority
|
||||
/>
|
||||
<div className="absolute inset-0 bg-black/40"/>
|
||||
</div>
|
||||
|
||||
{/* Main content */}
|
||||
<div className="relative z-10 flex justify-center flex-col w-[90%] sm:w-[80%] h-full mx-auto">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 items-center gap-12">
|
||||
{/* Text Content */}
|
||||
<div>
|
||||
<motion.h1
|
||||
className="text-3xl sm:text-4xl md:text-5xl mt-6 mb-6 font-bold text-white"
|
||||
initial={{opacity: 0, y: 30}}
|
||||
animate={{opacity: 1, y: 0}}
|
||||
transition={{duration: 0.6, ease: 'easeOut'}}
|
||||
>
|
||||
Rhein-Software Development
|
||||
</motion.h1>
|
||||
|
||||
<motion.p
|
||||
className="text-lg md:text-xl text-gray-400"
|
||||
initial={{opacity: 0, y: 30}}
|
||||
animate={{opacity: 1, y: 0}}
|
||||
transition={{duration: 0.6, delay: 0.2, ease: 'easeOut'}}
|
||||
>
|
||||
Digitale Lösungen für dein Unternehmen.
|
||||
</motion.p>
|
||||
|
||||
<motion.p
|
||||
className="mt-4 text-lg md:text-xl font-semibold text-white"
|
||||
initial={{opacity: 0}}
|
||||
animate={{opacity: 1}}
|
||||
transition={{delay: 0.9, duration: 0.6}}
|
||||
>
|
||||
<Typewriter
|
||||
words={['Beratung', 'Entwicklung', 'Wartung', 'Fehlerbehebung']}
|
||||
loop={true}
|
||||
cursor
|
||||
cursorStyle="_"
|
||||
typeSpeed={60}
|
||||
deleteSpeed={40}
|
||||
delaySpeed={1500}
|
||||
/>
|
||||
</motion.p>
|
||||
</div>
|
||||
|
||||
{/* Floating Image */}
|
||||
<motion.div
|
||||
className="hidden lg:block"
|
||||
initial={{opacity: 0, y: 30}}
|
||||
animate={{opacity: 1, y: 0}}
|
||||
transition={{duration: 0.8, ease: 'easeOut', delay: 0.3}}
|
||||
>
|
||||
<motion.div
|
||||
animate={{y: [0, -10, 0]}}
|
||||
transition={{
|
||||
duration: 4,
|
||||
repeat: Infinity,
|
||||
ease: 'easeInOut',
|
||||
}}
|
||||
className="animate-float"
|
||||
>
|
||||
<Image
|
||||
src="/images/hero.png"
|
||||
alt="hero graphic"
|
||||
width={700}
|
||||
height={700}
|
||||
/>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Hero;
|
||||
105
frontend/components/Home/Sections/HomeServices.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
'use client';
|
||||
|
||||
import {FiServer, FiTool, FiMonitor, FiZap, FiArrowRight} from 'react-icons/fi';
|
||||
import {motion} from 'framer-motion';
|
||||
import {useThemeColors} from '@/utils/useThemeColors';
|
||||
|
||||
const services = [
|
||||
{
|
||||
title: 'Beratung',
|
||||
icon: <FiMonitor size={24}/>,
|
||||
description:
|
||||
'Strategische und technische Beratung rund um digitale Produkte und Prozesse. Wir analysieren bestehende Systeme, identifizieren Potenziale und helfen dir, die passende Architektur für dein Projekt zu finden.',
|
||||
},
|
||||
{
|
||||
title: 'Entwicklung',
|
||||
icon: <FiZap size={24}/>,
|
||||
description:
|
||||
'Individuelle Softwareentwicklung – skalierbar, wartbar, zukunftssicher. Ob Web-App, API oder internes Tool: Wir setzen moderne Technologien ein, um genau die Lösung zu bauen, die du brauchst.',
|
||||
},
|
||||
{
|
||||
title: 'Managed Services',
|
||||
icon: <FiServer size={24}/>,
|
||||
description:
|
||||
'Wir betreuen Infrastruktur, Server und Systeme – verlässlich und performant. Unser Team kümmert sich um Hosting, Monitoring, Backups und sorgt für einen reibungslosen Betrieb deiner Plattform.',
|
||||
},
|
||||
{
|
||||
title: 'Fehlerbehebung',
|
||||
icon: <FiTool size={24}/>,
|
||||
description:
|
||||
'Schnelle Hilfe bei Bugs, Performance-Problemen oder Sicherheitslücken. Wir analysieren Probleme, beheben sie gezielt und sorgen dafür, dass dein System wieder stabil läuft – langfristig und zuverlässig.',
|
||||
},
|
||||
];
|
||||
|
||||
const HomeServices = () => {
|
||||
const colors = useThemeColors();
|
||||
|
||||
return (
|
||||
<section
|
||||
className="w-full py-24 transition-colors duration-700 ease-in-out"
|
||||
style={{backgroundColor: colors.primaryBg}}
|
||||
>
|
||||
<div className="w-full max-w-6xl px-6 md:px-10 mx-auto" style={{color: colors.primaryText}}>
|
||||
<motion.h2
|
||||
className="text-3xl md:text-4xl font-bold mb-1 text-left transition-colors duration-700 ease-in-out"
|
||||
>
|
||||
Leistungen
|
||||
</motion.h2>
|
||||
|
||||
<motion.div
|
||||
className="w-12 h-[2px] mt-2 mb-10 bg-amber-500"
|
||||
initial={{opacity: 0, x: -20}}
|
||||
whileInView={{opacity: 1, x: 0}}
|
||||
viewport={{once: true}}
|
||||
transition={{duration: 0.4, delay: 0.1}}
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{services.map((service, index) => (
|
||||
<motion.div
|
||||
key={service.title}
|
||||
className="p-6 rounded-xl border shadow-md transition-colors duration-700 ease-in-out"
|
||||
style={{
|
||||
backgroundColor: colors.secondaryBg,
|
||||
borderColor: colors.secondaryBg,
|
||||
color: colors.primaryText,
|
||||
}}
|
||||
whileHover={{
|
||||
scale: 1.03,
|
||||
boxShadow: '0px 10px 20px rgba(0,0,0,0.1)',
|
||||
}}
|
||||
initial={{opacity: 0, y: 30}}
|
||||
whileInView={{opacity: 1, y: 0}}
|
||||
viewport={{once: true}}
|
||||
transition={{duration: 0.4, delay: index * 0.1}}
|
||||
>
|
||||
<div className="mb-3 text-blue-600">{service.icon}</div>
|
||||
<h3 className="text-xl font-semibold mb-2">{service.title}</h3>
|
||||
<p className="text-sm leading-relaxed transition-colors duration-700 ease-in-out"
|
||||
style={{color: colors.secondaryText}}>
|
||||
{service.description}
|
||||
</p>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
className="mt-10 flex justify-end"
|
||||
initial={{opacity: 0}}
|
||||
whileInView={{opacity: 1}}
|
||||
viewport={{once: true}}
|
||||
transition={{duration: 0.4, delay: 0.3}}
|
||||
>
|
||||
<a
|
||||
href="/services"
|
||||
className="text-sm font-semibold text-blue-600 hover:underline flex items-center gap-1"
|
||||
>
|
||||
Weitere Leistungen <FiArrowRight size={16}/>
|
||||
</a>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default HomeServices;
|
||||
139
frontend/components/Home/Sections/TechStack.tsx
Normal file
@@ -0,0 +1,139 @@
|
||||
'use client';
|
||||
|
||||
import Image from 'next/image';
|
||||
import {motion} from 'framer-motion';
|
||||
import {useThemeColors} from "@/utils/useThemeColors";
|
||||
|
||||
const techStack = {
|
||||
row1: [
|
||||
{
|
||||
category: 'Programmiersprachen & Frameworks – Backend',
|
||||
items: [
|
||||
{id: 'java', label: 'Java'},
|
||||
{id: 'dart', label: 'Dart'},
|
||||
{id: 'kotlin', label: 'Kotlin'},
|
||||
{id: 'spring', label: 'Spring'},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: 'Programmiersprachen & Frameworks – Frontend',
|
||||
items: [
|
||||
{id: 'html', label: 'HTML'},
|
||||
{id: 'css', label: 'CSS'},
|
||||
{id: 'bootstrap', label: 'Bootstrap'},
|
||||
{id: 'nextjs', label: 'Next.js'},
|
||||
{id: 'typescript', label: 'TypeScript'},
|
||||
{id: 'flutter', label: 'Flutter'},
|
||||
],
|
||||
},
|
||||
],
|
||||
row2: [
|
||||
{
|
||||
category: 'Betriebssysteme',
|
||||
items: [
|
||||
{id: 'macos', label: 'macOS'},
|
||||
{id: 'debian', label: 'Debian'},
|
||||
{id: 'ubuntu', label: 'Ubuntu'},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: 'Version Control & Collaboration',
|
||||
items: [
|
||||
{id: 'gitlab', label: 'GitLab'},
|
||||
{id: 'outline', label: 'Outline'},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: 'DevOps & Infrastruktur',
|
||||
items: [
|
||||
{id: 'gitlab-ci', label: 'GitLab CI'},
|
||||
{id: 'docker', label: 'Docker'},
|
||||
{id: 'proxmox', label: 'Proxmox'},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const TechStack = () => {
|
||||
const colors = useThemeColors();
|
||||
|
||||
return (
|
||||
<section
|
||||
className="w-full py-20 transition-colors duration-700 ease-in-out"
|
||||
style={{backgroundColor: colors.secondaryBg}}
|
||||
>
|
||||
<div className="w-full max-w-6xl px-6 md:px-10 mx-auto" style={{color: colors.primaryText}}>
|
||||
<motion.h2
|
||||
className="text-3xl md:text-4xl font-bold mb-1 text-left transition-colors duration-700 ease-in-out"
|
||||
>
|
||||
Technologien
|
||||
</motion.h2>
|
||||
|
||||
<motion.div
|
||||
className="w-12 h-[2px] mt-2 mb-6 bg-amber-500"
|
||||
initial={{opacity: 0, x: -20}}
|
||||
whileInView={{opacity: 1, x: 0}}
|
||||
viewport={{once: true}}
|
||||
transition={{duration: 0.4, delay: 0.1}}
|
||||
/>
|
||||
|
||||
<motion.p
|
||||
className="text-sm md:text-base mb-10 transition-colors duration-700 ease-in-out"
|
||||
style={{color: colors.secondaryText}}
|
||||
>
|
||||
Mit diesen Technologien realisieren wir moderne, leistungsstarke Softwarelösungen.
|
||||
</motion.p>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
|
||||
{techStack.row1.map((group, index) => (
|
||||
<TechCard key={group.category} group={group} delay={index * 0.2} colors={colors}/>
|
||||
))}
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
|
||||
{techStack.row2.map((group, index) => (
|
||||
<TechCard key={group.category} group={group} delay={index * 0.2 + 0.4} colors={colors}/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
const TechCard = ({
|
||||
group,
|
||||
delay,
|
||||
colors,
|
||||
}: {
|
||||
group: { category: string; items: { id: string; label: string }[] };
|
||||
delay: number;
|
||||
colors: ReturnType<typeof useThemeColors>;
|
||||
}) => (
|
||||
<motion.div
|
||||
className="p-4 rounded-lg border shadow-md transition-colors duration-700 ease-in-out"
|
||||
style={{
|
||||
backgroundColor: colors.primaryBg,
|
||||
borderColor: colors.primaryBg,
|
||||
color: colors.primaryText,
|
||||
}}
|
||||
initial={{opacity: 0, y: 20}}
|
||||
whileInView={{opacity: 1, y: 0}}
|
||||
whileHover={{scale: 1.03, boxShadow: '0 10px 20px rgba(0,0,0,0.1)'}}
|
||||
viewport={{once: true}}
|
||||
transition={{duration: 0.4, delay}}
|
||||
>
|
||||
<h3 className="text-base font-semibold mb-4">{group.category}</h3>
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
{group.items.map(({id, label}) => (
|
||||
<div key={id} className="flex flex-col items-center text-center">
|
||||
<Image src={`/images/svg/${id}.svg`} alt={label} width={32} height={32} className="object-contain"/>
|
||||
<span className="text-[10px] mt-1 transition-colors duration-700 ease-in-out"
|
||||
style={{color: colors.secondaryText}}>
|
||||
{label}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
|
||||
export default TechStack;
|
||||
131
frontend/components/Legal/Imprint/ImprintComp.tsx
Normal file
@@ -0,0 +1,131 @@
|
||||
"use client";
|
||||
|
||||
import React, {useContext} from "react";
|
||||
import {ThemeContext} from "@/components/provider/ThemeProvider";
|
||||
import {themeColors} from "@/components/Helper/ThemeColors";
|
||||
import {motion} from "framer-motion";
|
||||
|
||||
const fadeInUp = {
|
||||
hidden: {opacity: 0, y: 30},
|
||||
visible: (i: number) => ({
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: {
|
||||
duration: 0.6,
|
||||
delay: i * 0.2,
|
||||
ease: "easeOut",
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
const ImprintComp = () => {
|
||||
const {theme} = useContext(ThemeContext);
|
||||
const colors = themeColors[theme];
|
||||
|
||||
return (
|
||||
<div
|
||||
className="overflow-hidden transition-colors duration-500"
|
||||
style={{backgroundColor: colors.secondaryBg, color: colors.primaryText}}
|
||||
>
|
||||
{/* Imprint Content */}
|
||||
<div className="mt-16 w-[90%] sm:w-[80%] mx-auto py-12 space-y-10 text-base leading-relaxed">
|
||||
{[
|
||||
{
|
||||
title: "Impressum",
|
||||
content: (
|
||||
<>
|
||||
Thatsaphorn Atchariyaphap<br/>
|
||||
Rhein-Software (Einzelunternehmer)<br/>
|
||||
Mühlenstrasse 13<br/>
|
||||
79664 Wehr
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Kontakt",
|
||||
content: (
|
||||
<>
|
||||
Telefon: +49 (0) 151 24003632<br/>
|
||||
E-Mail:{" "}
|
||||
<a
|
||||
href="mailto:contact@rhein-software.dev"
|
||||
className="underline text-blue-500"
|
||||
>
|
||||
contact@rhein-software.dev
|
||||
</a>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "EU-Streitschlichtung",
|
||||
content: (
|
||||
<>
|
||||
Die Europäische Kommission stellt eine Plattform zur
|
||||
Online-Streitbeilegung (OS) bereit:{" "}
|
||||
<a
|
||||
href="https://ec.europa.eu/consumers/odr/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="underline text-blue-500"
|
||||
>
|
||||
https://ec.europa.eu/consumers/odr/
|
||||
</a>
|
||||
.<br/>
|
||||
Unsere E-Mail-Adresse finden Sie oben im Impressum.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title:
|
||||
"Verbraucherstreitbeilegung / Universalschlichtungsstelle",
|
||||
content: (
|
||||
<>
|
||||
Wir sind nicht bereit oder verpflichtet, an
|
||||
Streitbeilegungsverfahren vor einer
|
||||
Verbraucherschlichtungsstelle teilzunehmen.
|
||||
</>
|
||||
),
|
||||
},
|
||||
].map((section, i) => (
|
||||
<motion.div
|
||||
key={section.title}
|
||||
custom={i}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
variants={fadeInUp}
|
||||
>
|
||||
<h2 className="text-2xl font-bold">{section.title}</h2>
|
||||
<motion.div
|
||||
className="w-6 h-[2px] mt-2 mb-6 bg-amber-500"
|
||||
initial={{opacity: 0, x: -20}}
|
||||
whileInView={{opacity: 1, x: 0}}
|
||||
viewport={{once: true}}
|
||||
transition={{duration: 0.4, delay: 0.1}}
|
||||
/>
|
||||
<p>{section.content}</p>
|
||||
</motion.div>
|
||||
))}
|
||||
|
||||
<motion.p
|
||||
className="text-sm text-gray-500"
|
||||
custom={4}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
variants={fadeInUp}
|
||||
>
|
||||
Quelle:{" "}
|
||||
<a
|
||||
href="https://www.e-recht24.de"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="underline"
|
||||
>
|
||||
www.e-recht24.de
|
||||
</a>
|
||||
</motion.p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ImprintComp;
|
||||
145
frontend/components/Legal/Privacy/PrivacyComp.tsx
Normal file
@@ -0,0 +1,145 @@
|
||||
"use client";
|
||||
|
||||
import React, {useContext} from "react";
|
||||
import {ThemeContext} from "@/components/provider/ThemeProvider";
|
||||
import {themeColors} from "@/components/Helper/ThemeColors";
|
||||
import {motion} from "framer-motion";
|
||||
|
||||
const fadeInUp = {
|
||||
hidden: {opacity: 0, y: 30},
|
||||
visible: (i: number) => ({
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: {
|
||||
duration: 0.6,
|
||||
delay: i * 0.2,
|
||||
ease: "easeOut",
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
const PrivacyComp = () => {
|
||||
const {theme} = useContext(ThemeContext);
|
||||
const colors = themeColors[theme];
|
||||
|
||||
const sections = [
|
||||
{
|
||||
title: "1. Datenschutz auf einen Blick",
|
||||
content: (
|
||||
<>
|
||||
<p>
|
||||
Die folgenden Hinweise geben einen einfachen Überblick darüber, was mit Ihren personenbezogenen
|
||||
Daten passiert, wenn Sie diese Website besuchen. Personenbezogene Daten sind alle Daten, mit
|
||||
denen Sie persönlich identifiziert werden können.
|
||||
</p>
|
||||
<p>
|
||||
Die Datenverarbeitung auf dieser Website erfolgt durch den Websitebetreiber. Dessen Kontaktdaten
|
||||
finden Sie im Abschnitt „Hinweis zur Verantwortlichen Stelle“.
|
||||
</p>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "2. Allgemeine Hinweise und Pflichtinformationen",
|
||||
content: (
|
||||
<>
|
||||
<p>
|
||||
Wir behandeln Ihre personenbezogenen Daten vertraulich und entsprechend den gesetzlichen
|
||||
Datenschutzvorschriften sowie dieser Datenschutzerklärung. Wir weisen darauf hin, dass die
|
||||
Datenübertragung im Internet Sicherheitslücken aufweisen kann.
|
||||
</p>
|
||||
<p>
|
||||
Verantwortlich für die Datenverarbeitung:
|
||||
<br/>
|
||||
Rhein-Software Development
|
||||
<br/>
|
||||
Mühlenstrasse 13
|
||||
<br/>
|
||||
79664 Wehr
|
||||
<br/><br/>
|
||||
Telefon: +49 (0) 151 24003632
|
||||
<br/>
|
||||
E-Mail:{" "}
|
||||
<a
|
||||
href="mailto:contact@rhein-software.dev"
|
||||
className="underline text-blue-500"
|
||||
>
|
||||
contact@rhein-software.dev
|
||||
</a>
|
||||
</p>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "3. Ihre Rechte",
|
||||
content: (
|
||||
<p>
|
||||
Sie haben das Recht auf Auskunft, Berichtigung, Löschung, Einschränkung der Verarbeitung,
|
||||
Datenübertragbarkeit sowie auf Widerspruch gegen die Verarbeitung Ihrer Daten. Zudem haben Sie
|
||||
ein Beschwerderecht bei der zuständigen Aufsichtsbehörde.
|
||||
</p>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "4. Analyse-Tools und Cookies",
|
||||
content: (
|
||||
<p>
|
||||
Beim Besuch dieser Website kann Ihr Surfverhalten statistisch ausgewertet werden. Das geschieht
|
||||
vor allem mit sogenannten Analyseprogrammen und Cookies. Detaillierte Informationen hierzu
|
||||
entnehmen Sie bitte unserer vollständigen Datenschutzerklärung.
|
||||
</p>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "5. Sicherheit",
|
||||
content: (
|
||||
<p>
|
||||
Diese Seite nutzt eine SSL- bzw. TLS-Verschlüsselung. Eine verschlüsselte Verbindung erkennen
|
||||
Sie an der Adresszeile des Browsers („https://“) und dem Schloss-Symbol.
|
||||
</p>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Quelle",
|
||||
content: (
|
||||
<p className="text-sm text-gray-500">
|
||||
Quelle:{" "}
|
||||
<a
|
||||
href="https://www.e-recht24.de"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="underline"
|
||||
>
|
||||
www.e-recht24.de
|
||||
</a>
|
||||
</p>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div
|
||||
className="overflow-hidden transition-colors duration-500"
|
||||
style={{backgroundColor: colors.secondaryBg, color: colors.primaryText}}
|
||||
>
|
||||
{/* Privacy Content */}
|
||||
<div className="mt-16 w-[90%] sm:w-[80%] mx-auto py-12 space-y-10 text-base leading-relaxed">
|
||||
{sections.map((section, i) => (
|
||||
<motion.div
|
||||
key={section.title}
|
||||
custom={i}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{once: true, amount: 0.2}}
|
||||
variants={fadeInUp}
|
||||
>
|
||||
<h2 className="text-2xl font-bold mb-4">{section.title}</h2>
|
||||
<div className="space-y-4">{section.content}</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PrivacyComp;
|
||||
98
frontend/components/Legal/RevocationComp.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
"use client";
|
||||
|
||||
import SmallHero from "@/components/Helper/SmallHero";
|
||||
import React, {useContext, useEffect} from "react";
|
||||
import {ThemeContext} from "@/components/provider/ThemeProvider";
|
||||
import {themeColors} from "@/components/Helper/ThemeColors";
|
||||
import AOS from "aos";
|
||||
|
||||
const RevocationComp = () => {
|
||||
const {theme} = useContext(ThemeContext);
|
||||
const colors = themeColors[theme];
|
||||
|
||||
useEffect(() => {
|
||||
AOS.init({
|
||||
duration: 1000,
|
||||
easing: "ease",
|
||||
once: true,
|
||||
anchorPlacement: "top-bottom",
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="overflow-hidden transition-colors duration-500"
|
||||
style={{backgroundColor: colors.secondaryBg, color: colors.primaryText}}>
|
||||
|
||||
{/* Hero Section */}
|
||||
<div className="mt-[10vh]">
|
||||
<SmallHero
|
||||
title="Widerruf"
|
||||
subtitle=""
|
||||
backgroundImage="/images/contact.png"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Contact Form */}
|
||||
<div className="mt-16 w-[90%] sm:w-[80%] mx-auto py-12">
|
||||
<h2 className="text-2xl md:text-3xl font-bold text-center"
|
||||
data-aos="fade-up"
|
||||
data-aos-delay="400"
|
||||
>
|
||||
Schreib uns eine Nachricht
|
||||
</h2>
|
||||
<p data-aos="fade-up" data-aos-delay="600"
|
||||
className="text-center mt-3 text-[var(--secondary-text)]">
|
||||
Wir melden uns schnellstmöglich bei dir!
|
||||
</p>
|
||||
|
||||
<form className="mt-8 max-w-2xl mx-auto space-y-6">
|
||||
{/* Name & Email */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{["Dein Name", "Deine E-Mail"].map((label, index) => (
|
||||
<div key={index} data-aos="fade-up" data-aos-delay={index * 100}>
|
||||
<label className="block font-semibold">{label}</label>
|
||||
<input
|
||||
type={index === 0 ? "text" : "email"}
|
||||
placeholder={index === 0 ? "Max Mustermann" : "max@example.com"}
|
||||
className="w-full p-3 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 transition"
|
||||
style={{
|
||||
backgroundColor: colors.inputFieldBg,
|
||||
border: `1px solid ${colors.inputBorder}`,
|
||||
color: colors.primaryText
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Message */}
|
||||
<div data-aos="fade-up" data-aos-delay="300">
|
||||
<label className="block font-semibold">Deine Nachricht</label>
|
||||
<textarea
|
||||
rows={4}
|
||||
placeholder="Schreibe deine Nachricht..."
|
||||
className="w-full p-3 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 transition"
|
||||
style={{
|
||||
backgroundColor: colors.inputFieldBg,
|
||||
border: `1px solid ${colors.inputBorder}`,
|
||||
color: colors.primaryText
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Submit Button */}
|
||||
<div className="text-center" data-aos="fade-up" data-aos-delay="400">
|
||||
<button
|
||||
type="submit"
|
||||
className="px-6 py-3 bg-blue-600 text-white text-lg font-semibold rounded-lg shadow-md hover:bg-blue-700 transition-all"
|
||||
>
|
||||
📩 Nachricht senden
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);};
|
||||
|
||||
export default RevocationComp;
|
||||
99
frontend/components/Legal/TermsOfUseComp.tsx
Normal file
@@ -0,0 +1,99 @@
|
||||
"use client";
|
||||
|
||||
import SmallHero from "@/components/Helper/SmallHero";
|
||||
import React, {useContext, useEffect} from "react";
|
||||
import {ThemeContext} from "@/components/provider/ThemeProvider";
|
||||
import {themeColors} from "@/components/Helper/ThemeColors";
|
||||
import AOS from "aos";
|
||||
|
||||
const TermsOfUseComp = () => {
|
||||
const {theme} = useContext(ThemeContext);
|
||||
const colors = themeColors[theme];
|
||||
|
||||
useEffect(() => {
|
||||
AOS.init({
|
||||
duration: 1000,
|
||||
easing: "ease",
|
||||
once: true,
|
||||
anchorPlacement: "top-bottom",
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="overflow-hidden transition-colors duration-500"
|
||||
style={{backgroundColor: colors.secondaryBg, color: colors.primaryText}}>
|
||||
|
||||
{/* Hero Section */}
|
||||
<div className="mt-[10vh]">
|
||||
<SmallHero
|
||||
title="AGB"
|
||||
subtitle=""
|
||||
backgroundImage="/images/contact.png"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Contact Form */}
|
||||
<div className="mt-16 w-[90%] sm:w-[80%] mx-auto py-12">
|
||||
<h2 className="text-2xl md:text-3xl font-bold text-center"
|
||||
data-aos="fade-up"
|
||||
data-aos-delay="400"
|
||||
>
|
||||
Schreib uns eine Nachricht
|
||||
</h2>
|
||||
<p data-aos="fade-up" data-aos-delay="600"
|
||||
className="text-center mt-3 text-[var(--secondary-text)]">
|
||||
Wir melden uns schnellstmöglich bei dir!
|
||||
</p>
|
||||
|
||||
<form className="mt-8 max-w-2xl mx-auto space-y-6">
|
||||
{/* Name & Email */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{["Dein Name", "Deine E-Mail"].map((label, index) => (
|
||||
<div key={index} data-aos="fade-up" data-aos-delay={index * 100}>
|
||||
<label className="block font-semibold">{label}</label>
|
||||
<input
|
||||
type={index === 0 ? "text" : "email"}
|
||||
placeholder={index === 0 ? "Max Mustermann" : "max@example.com"}
|
||||
className="w-full p-3 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 transition"
|
||||
style={{
|
||||
backgroundColor: colors.inputFieldBg,
|
||||
border: `1px solid ${colors.inputBorder}`,
|
||||
color: colors.primaryText
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Message */}
|
||||
<div data-aos="fade-up" data-aos-delay="300">
|
||||
<label className="block font-semibold">Deine Nachricht</label>
|
||||
<textarea
|
||||
rows={4}
|
||||
placeholder="Schreibe deine Nachricht..."
|
||||
className="w-full p-3 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 transition"
|
||||
style={{
|
||||
backgroundColor: colors.inputFieldBg,
|
||||
border: `1px solid ${colors.inputBorder}`,
|
||||
color: colors.primaryText
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Submit Button */}
|
||||
<div className="text-center" data-aos="fade-up" data-aos-delay="400">
|
||||
<button
|
||||
type="submit"
|
||||
className="px-6 py-3 bg-blue-600 text-white text-lg font-semibold rounded-lg shadow-md hover:bg-blue-700 transition-all"
|
||||
>
|
||||
📩 Nachricht senden
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TermsOfUseComp;
|
||||
110
frontend/components/Navbar/DesktopNav.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
"use client";
|
||||
|
||||
import {usePathname} from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import React, {useContext, useEffect, useState} from "react";
|
||||
import {HiBars3BottomRight} from "react-icons/hi2";
|
||||
import {ThemeContext} from "@/components/provider/ThemeProvider";
|
||||
import {useThemeColors} from "@/utils/useThemeColors";
|
||||
import {navLinks} from "@/constant/Constant";
|
||||
|
||||
type Props = {
|
||||
openNav: () => void;
|
||||
};
|
||||
|
||||
const Nav = ({openNav}: Props) => {
|
||||
const [navBg, setNavBg] = useState(false);
|
||||
const [navHeight, setNavHeight] = useState("h-[10vh]");
|
||||
const [contentSize, setContentSize] = useState("text-base md:text-lg");
|
||||
const [buttonSize, setButtonSize] = useState("md:px-6 md:py-2 px-4 py-1 text-sm");
|
||||
|
||||
const {theme, toggleTheme} = useContext(ThemeContext);
|
||||
const colors = useThemeColors();
|
||||
const pathname = usePathname();
|
||||
|
||||
const navColorClass = theme === "dark" || !navBg ? "text-white" : "text-black";
|
||||
|
||||
useEffect(() => {
|
||||
const handler = () => {
|
||||
if (window.scrollY >= 90) {
|
||||
setNavBg(true);
|
||||
setNavHeight("h-[8vh]");
|
||||
setContentSize("text-sm md:text-base");
|
||||
setButtonSize("md:px-5 md:py-1.5 px-3 py-1 text-xs");
|
||||
} else {
|
||||
setNavBg(false);
|
||||
setNavHeight("h-[10vh]");
|
||||
setContentSize("text-base md:text-lg");
|
||||
setButtonSize("md:px-6 md:py-2 px-4 py-1 text-sm");
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("scroll", handler);
|
||||
return () => window.removeEventListener("scroll", handler);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`fixed w-full transition-all duration-300 ease-in-out ${navHeight} z-[1000] ${
|
||||
navBg ? "shadow-md" : ""
|
||||
}`}
|
||||
style={{backgroundColor: navBg ? colors.navBg : "transparent"}}
|
||||
>
|
||||
<div className="flex items-center h-full justify-between w-[90%] xl:w-[80%] mx-auto">
|
||||
<Link href="/">
|
||||
<h1 className={`${contentSize} font-bold cursor-pointer ${navColorClass}`}>
|
||||
<span className="text-lg md:text-xl text-pink-700">R</span>hein Software
|
||||
</h1>
|
||||
</Link>
|
||||
|
||||
<div className="hidden lg:flex items-center space-x-6">
|
||||
{navLinks.map((link) => (
|
||||
<Link href={link.url} key={link.id}>
|
||||
<p className={`relative group ${contentSize} uppercase ${getNavLinkClasses(pathname === link.url, navBg, theme, navColorClass)}`}>
|
||||
{link.label}
|
||||
{pathname !== link.url && (
|
||||
<span
|
||||
className="absolute bottom-0 left-0 w-full h-[2px] bg-current transform transition-transform duration-300 origin-right scale-x-0 group-hover:scale-x-100"/>
|
||||
)}
|
||||
</p>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-3">
|
||||
<Link href="/contact">
|
||||
<button
|
||||
className={`${buttonSize} text-white font-semibold bg-blue-700 hover:bg-blue-900 rounded-full`}>
|
||||
Kontakt
|
||||
</button>
|
||||
</Link>
|
||||
|
||||
<button
|
||||
onClick={toggleTheme}
|
||||
className={`w-7 h-7 flex items-center justify-center rounded-full ${navColorClass}`}
|
||||
style={{backgroundColor: colors.secondaryBg}}
|
||||
>
|
||||
{theme === "dark" ? "🌙" : "☀️"}
|
||||
</button>
|
||||
<HiBars3BottomRight
|
||||
onClick={openNav}
|
||||
className={`w-6 h-6 cursor-pointer lg:hidden ${navColorClass}`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getNavLinkClasses = (
|
||||
isActive: boolean,
|
||||
navBg: boolean,
|
||||
theme: string,
|
||||
navColorClass: string
|
||||
): string => {
|
||||
if (isActive) return !navBg ? "text-white font-bold" : `${navColorClass} font-bold`;
|
||||
if (!navBg) return "text-white font-medium";
|
||||
return theme === "dark" ? "text-gray-300 font-medium" : "text-gray-700 font-medium";
|
||||
};
|
||||
|
||||
export default Nav;
|
||||
62
frontend/components/Navbar/MobileNav.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
'use client';
|
||||
|
||||
import {navLinks} from "@/constant/Constant";
|
||||
import Link from "next/link";
|
||||
import React, {useContext} from "react";
|
||||
import {CgClose} from "react-icons/cg";
|
||||
import {ThemeContext} from "@/components/provider/ThemeProvider";
|
||||
import {useThemeColors} from "@/utils/useThemeColors";
|
||||
|
||||
type Props = {
|
||||
showNav: boolean;
|
||||
closeNav: () => void;
|
||||
};
|
||||
|
||||
const MobileNav = ({closeNav, showNav}: Props) => {
|
||||
const navOpen = showNav ? "translate-y-0 opacity-100" : "-translate-y-20 opacity-0 pointer-events-none";
|
||||
const {theme, toggleTheme} = useContext(ThemeContext);
|
||||
const colors = useThemeColors();
|
||||
|
||||
const textClass = theme === "dark" ? "text-white" : "text-black";
|
||||
|
||||
return (
|
||||
<div className="lg:hidden">
|
||||
<div
|
||||
className={`fixed inset-0 z-[10000] transition-opacity duration-500 ${
|
||||
showNav ? "opacity-60 bg-black" : "opacity-0 pointer-events-none"
|
||||
}`}
|
||||
onClick={closeNav}
|
||||
/>
|
||||
|
||||
<div
|
||||
className={`fixed top-0 left-0 w-full z-[10006] transform ${navOpen} transition-all duration-500 ease-in-out shadow-md rounded-b-2xl`}
|
||||
style={{backgroundColor: colors.navBg}}
|
||||
>
|
||||
<div className={`flex flex-col items-center justify-center py-8 space-y-4 px-4 relative ${textClass}`}>
|
||||
<CgClose
|
||||
onClick={closeNav}
|
||||
className={`absolute top-4 right-6 sm:right-8 sm:w-7 sm:h-7 w-6 h-6 cursor-pointer p-1 ${textClass}`}
|
||||
/>
|
||||
|
||||
{navLinks.map((link) => (
|
||||
<Link href={link.url} key={link.id}>
|
||||
<p className="nav__link uppercase text-[14px] sm:text-[16px] border-b pb-1 border-gray-400 transition-all duration-300 ease-in-out hover:scale-105">
|
||||
{link.label}
|
||||
</p>
|
||||
</Link>
|
||||
))}
|
||||
|
||||
<button
|
||||
onClick={toggleTheme}
|
||||
className={`mt-4 w-8 h-8 flex items-center justify-center rounded-full border border-gray-400 transition-all duration-300 ${textClass}`}
|
||||
style={{backgroundColor: colors.secondaryBg}}
|
||||
>
|
||||
{theme === "dark" ? "🌙" : "☀️"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MobileNav;
|
||||
24
frontend/components/Navbar/Nav.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
"use client";
|
||||
|
||||
import React, {useState} from "react";
|
||||
import DesktopNav from "./DesktopNav";
|
||||
import MobileNav from "./MobileNav";
|
||||
|
||||
const Nav = () => {
|
||||
const [showNav, setShowNav] = useState(false);
|
||||
const handleNavShow = () => {
|
||||
setShowNav(true);
|
||||
};
|
||||
const handleNavHide = () => {
|
||||
setShowNav(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<DesktopNav openNav={handleNavShow}/>
|
||||
<MobileNav showNav={showNav} closeNav={handleNavHide}/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Nav;
|
||||
34
frontend/components/Section.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
'use client';
|
||||
|
||||
import React, {CSSProperties} from "react";
|
||||
import clsx from "clsx";
|
||||
|
||||
interface SectionProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
shadow?: boolean;
|
||||
}
|
||||
|
||||
const Section: React.FC<SectionProps> = ({
|
||||
children,
|
||||
className = "",
|
||||
style,
|
||||
shadow = false,
|
||||
}) => {
|
||||
return (
|
||||
<section
|
||||
className={clsx("relative transition-colors duration-500", className)}
|
||||
style={style}
|
||||
>
|
||||
<div className="relative z-10">{children}</div>
|
||||
{shadow && (
|
||||
<div
|
||||
className="absolute bottom-0 left-0 w-full h-16 pointer-events-none"
|
||||
/>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default Section;
|
||||
81
frontend/components/Services/Section/OverviewTabs.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
'use client';
|
||||
|
||||
import React, {useState} from 'react';
|
||||
import {motion, AnimatePresence} from 'framer-motion';
|
||||
import {FiZap, FiMonitor, FiServer, FiTool} from 'react-icons/fi';
|
||||
import Development from "@/components/Services/Section/overview/Development";
|
||||
import Consulting from "@/components/Services/Section/overview/Consulting";
|
||||
import ManagedServices from "@/components/Services/Section/overview/ManagedServices";
|
||||
import BugFixing from "@/components/Services/Section/overview/BugFixing";
|
||||
import {useThemeColors} from "@/utils/useThemeColors";
|
||||
|
||||
const tabs = [
|
||||
{key: 'entwicklung', label: 'Entwicklung', icon: <FiZap size={20}/>},
|
||||
{key: 'beratung', label: 'Beratung', icon: <FiMonitor size={20}/>},
|
||||
{key: 'services', label: 'Managed Services', icon: <FiServer size={20}/>},
|
||||
{key: 'support', label: 'Fehlerbehebung', icon: <FiTool size={20}/>},
|
||||
];
|
||||
|
||||
const tabContent: Record<string, React.ReactNode> = {
|
||||
entwicklung: <Development/>,
|
||||
beratung: <Consulting/>,
|
||||
services: <ManagedServices/>,
|
||||
support: <BugFixing/>,
|
||||
};
|
||||
|
||||
const OverviewTabs = () => {
|
||||
const [activeTab, setActiveTab] = useState("entwicklung");
|
||||
const colors = useThemeColors();
|
||||
|
||||
return (
|
||||
<div className="max-w-5xl mx-auto px-6 py-16 transition-theme text-left">
|
||||
<h2 className="text-3xl font-bold mb-2" style={{color: colors.primaryText}}>
|
||||
Was wir tun
|
||||
</h2>
|
||||
|
||||
<motion.div
|
||||
className="w-12 h-[2px] mb-6 bg-amber-500"
|
||||
initial={{opacity: 0, x: -20}}
|
||||
whileInView={{opacity: 1, x: 0}}
|
||||
viewport={{once: true}}
|
||||
transition={{duration: 0.4, delay: 0.1}}
|
||||
/>
|
||||
<p className="text-sm mb-10" style={{color: colors.secondaryText}}>
|
||||
In diesem Abschnitt geben wir dir einen Überblick über unsere zentralen Leistungen – von der technischen
|
||||
Entwicklung über Beratung bis hin zu Betrieb und Support.
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap gap-4 mb-10">
|
||||
{tabs.map((tab) => (
|
||||
<button
|
||||
key={tab.key}
|
||||
onClick={() => setActiveTab(tab.key)}
|
||||
className="flex-1 min-w-[150px] flex items-center justify-start gap-2 px-4 py-2 rounded-full border text-sm font-medium transition-all"
|
||||
style={{
|
||||
color: activeTab === tab.key ? '#ffffff' : colors.primaryText,
|
||||
backgroundColor: activeTab === tab.key ? '#1D4ED8' : 'transparent',
|
||||
borderColor: activeTab === tab.key ? '#1D4ED8' : colors.inputBorder,
|
||||
}}
|
||||
>
|
||||
{tab.icon}
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.div
|
||||
key={activeTab}
|
||||
initial={{opacity: 0, y: 10}}
|
||||
animate={{opacity: 1, y: 0}}
|
||||
exit={{opacity: 0, y: -10}}
|
||||
transition={{duration: 0.4}}
|
||||
>
|
||||
{tabContent[activeTab]}
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default OverviewTabs;
|
||||
19
frontend/components/Services/Section/ServiceHero.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
'use client';
|
||||
|
||||
import React from "react";
|
||||
import SmallHero from "@/components/Helper/SmallHero";
|
||||
|
||||
const ServiceHero = () => {
|
||||
return (
|
||||
<div className="relative overflow-hidden">
|
||||
<SmallHero
|
||||
title="Unsere Leistungen"
|
||||
subtitle="Wir bieten maßgeschneiderte Lösungen – von der Beratung bis zum Betrieb."
|
||||
backgroundImage="/images/contact.png"
|
||||
blurBackground
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServiceHero;
|
||||
59
frontend/components/Services/Section/overview/BugFixing.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
'use client';
|
||||
|
||||
import React from "react";
|
||||
import {motion} from "framer-motion";
|
||||
import {useThemeColors} from "@/utils/useThemeColors";
|
||||
|
||||
const BugFixing = () => {
|
||||
const colors = useThemeColors();
|
||||
|
||||
return (
|
||||
<div
|
||||
className="w-[95%] mx-auto grid grid-cols-1 gap-6 mt-8 mb-16 text-left"
|
||||
style={{color: colors.primaryText}}
|
||||
>
|
||||
<div>
|
||||
<motion.h2
|
||||
className="text-xl sm:text-2xl md:text-3xl font-bold mb-4"
|
||||
initial={{opacity: 0, y: 20}}
|
||||
whileInView={{opacity: 1, y: 0}}
|
||||
transition={{duration: 0.5}}
|
||||
>
|
||||
🐞 Fehlerbehebung & Optimierung
|
||||
</motion.h2>
|
||||
|
||||
<motion.p
|
||||
className="text-sm font-medium leading-7"
|
||||
style={{color: colors.secondaryText}}
|
||||
initial={{opacity: 0, y: 10}}
|
||||
whileInView={{opacity: 1, y: 0}}
|
||||
transition={{duration: 0.5, delay: 0.2}}
|
||||
>
|
||||
Wir analysieren und beheben Fehler in bestehenden Systemen, optimieren die Performance und sorgen
|
||||
dafür, dass deine Software stabil und zuverlässig läuft.
|
||||
</motion.p>
|
||||
|
||||
<motion.h3
|
||||
className="mt-8 text-lg font-semibold"
|
||||
initial={{opacity: 0}}
|
||||
whileInView={{opacity: 1}}
|
||||
transition={{duration: 0.4, delay: 0.2}}
|
||||
>
|
||||
🔎 Fokusbereiche
|
||||
</motion.h3>
|
||||
|
||||
<ul
|
||||
className="list-disc list-inside text-sm mt-4 space-y-1"
|
||||
style={{color: colors.secondaryText}}
|
||||
>
|
||||
<li>Debugging & Troubleshooting</li>
|
||||
<li>Performance-Analyse</li>
|
||||
<li>Refactoring von Legacy-Code</li>
|
||||
<li>Stabilitäts- und Sicherheitsupdates</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BugFixing;
|
||||
60
frontend/components/Services/Section/overview/Consulting.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
'use client';
|
||||
|
||||
import React from "react";
|
||||
import {motion} from "framer-motion";
|
||||
import {useThemeColors} from "@/utils/useThemeColors";
|
||||
|
||||
const Consulting = () => {
|
||||
const colors = useThemeColors();
|
||||
|
||||
return (
|
||||
<div
|
||||
className="w-[95%] mx-auto grid grid-cols-1 lg:grid-cols-2 gap-6 mt-8 mb-16 text-left"
|
||||
style={{color: colors.primaryText}}
|
||||
>
|
||||
<div>
|
||||
<motion.h2
|
||||
className="text-xl sm:text-2xl md:text-3xl font-bold mb-4"
|
||||
initial={{opacity: 0, y: 20}}
|
||||
whileInView={{opacity: 1, y: 0}}
|
||||
transition={{duration: 0.5}}
|
||||
>
|
||||
🧠 Technische Beratung
|
||||
</motion.h2>
|
||||
|
||||
<motion.p
|
||||
className="text-sm font-medium leading-7"
|
||||
style={{color: colors.secondaryText}}
|
||||
initial={{opacity: 0, y: 10}}
|
||||
whileInView={{opacity: 1, y: 0}}
|
||||
transition={{duration: 0.5, delay: 0.2}}
|
||||
>
|
||||
Wir unterstützen dich dabei, technische Entscheidungen fundiert zu treffen – von der Auswahl
|
||||
geeigneter Technologien bis hin zur Planung skalierbarer Architekturen. Gemeinsam finden wir den
|
||||
effizientesten Weg von der Idee bis zur Umsetzung – praxisnah, zielgerichtet und verständlich.
|
||||
</motion.p>
|
||||
|
||||
<motion.h3
|
||||
className="mt-8 text-lg font-semibold"
|
||||
initial={{opacity: 0}}
|
||||
whileInView={{opacity: 1}}
|
||||
transition={{duration: 0.4, delay: 0.2}}
|
||||
>
|
||||
🔍 Themenbereiche
|
||||
</motion.h3>
|
||||
|
||||
<ul
|
||||
className="list-disc list-inside text-sm mt-4 space-y-1"
|
||||
style={{color: colors.secondaryText}}
|
||||
>
|
||||
<li>Software-Architektur & Microservices</li>
|
||||
<li>Technologie- und Framework-Auswahl</li>
|
||||
<li>Prototyping & Machbarkeitsanalysen</li>
|
||||
<li>Projektplanung und agile Methodik</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Consulting;
|
||||
210
frontend/components/Services/Section/overview/Development.tsx
Normal file
@@ -0,0 +1,210 @@
|
||||
'use client';
|
||||
|
||||
import React from "react";
|
||||
import {motion} from "framer-motion";
|
||||
import {useThemeColors} from "@/utils/useThemeColors";
|
||||
import Image from "next/image";
|
||||
|
||||
const techStack = {
|
||||
row1: [
|
||||
{
|
||||
category: 'Programmiersprachen & Frameworks – Backend',
|
||||
items: [
|
||||
{id: 'java', label: 'Java'},
|
||||
{id: 'dart', label: 'Dart'},
|
||||
{id: 'kotlin', label: 'Kotlin'},
|
||||
{id: 'spring', label: 'Spring'},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: 'Programmiersprachen & Frameworks – Frontend',
|
||||
items: [
|
||||
{id: 'html', label: 'HTML'},
|
||||
{id: 'css', label: 'CSS'},
|
||||
{id: 'bootstrap', label: 'Bootstrap'},
|
||||
{id: 'nextjs', label: 'Next.js'},
|
||||
{id: 'typescript', label: 'TypeScript'},
|
||||
{id: 'flutter', label: 'Flutter'},
|
||||
],
|
||||
},
|
||||
],
|
||||
row2: [
|
||||
{
|
||||
category: 'Betriebssysteme',
|
||||
items: [
|
||||
{id: 'macos', label: 'macOS'},
|
||||
{id: 'debian', label: 'Debian'},
|
||||
{id: 'ubuntu', label: 'Ubuntu'},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: 'Version Control & Collaboration',
|
||||
items: [
|
||||
{id: 'gitlab', label: 'GitLab'},
|
||||
{id: 'outline', label: 'Outline'},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: 'DevOps & Infrastruktur',
|
||||
items: [
|
||||
{id: 'gitlab-ci', label: 'GitLab CI'},
|
||||
{id: 'docker', label: 'Docker'},
|
||||
{id: 'proxmox', label: 'Proxmox'},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const TechCard = ({group}: {
|
||||
group: { category: string; items: { id: string; label: string }[] };
|
||||
}) => {
|
||||
const colors = useThemeColors();
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{opacity: 0, y: 20}}
|
||||
animate={{opacity: 1, y: 0}}
|
||||
transition={{duration: 0.4}}
|
||||
className="p-4 rounded-lg border shadow-md transition-colors duration-700 ease-in-out"
|
||||
style={{
|
||||
backgroundColor: colors.primaryBg,
|
||||
borderColor: colors.primaryBg,
|
||||
color: colors.primaryText,
|
||||
}}
|
||||
>
|
||||
<h3 className="text-base font-semibold mb-4">{group.category}</h3>
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
{group.items.map(({id, label}) => (
|
||||
<div key={id} className="flex flex-col items-center text-center">
|
||||
<Image
|
||||
src={`/images/svg/${id}.svg`}
|
||||
alt={label}
|
||||
width={32}
|
||||
height={32}
|
||||
className="object-contain"
|
||||
/>
|
||||
<span
|
||||
className="text-[10px] mt-1 transition-colors duration-700 ease-in-out"
|
||||
style={{color: colors.secondaryText}}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
const Development = () => {
|
||||
const colors = useThemeColors();
|
||||
|
||||
return (
|
||||
<div
|
||||
className="w-[95%] mx-auto grid grid-cols-1 gap-6 mt-8 mb-16 text-left"
|
||||
style={{color: colors.primaryText}}
|
||||
>
|
||||
<div>
|
||||
<motion.h2
|
||||
className="text-xl sm:text-2xl md:text-3xl font-bold mb-4"
|
||||
initial={{opacity: 0, y: 20}}
|
||||
animate={{opacity: 1, y: 0}}
|
||||
transition={{duration: 0.5}}
|
||||
>
|
||||
💻 Full-Stack Entwicklung
|
||||
</motion.h2>
|
||||
|
||||
<motion.p
|
||||
className="text-sm font-medium leading-7"
|
||||
style={{color: colors.secondaryText}}
|
||||
initial={{opacity: 0, y: 10}}
|
||||
animate={{opacity: 1, y: 0}}
|
||||
transition={{duration: 0.5, delay: 0.2}}
|
||||
>
|
||||
Wir entwickeln individuelle Softwarelösungen – von der nativen Mobile-App über moderne Webseiten bis
|
||||
hin zu internen Tools.
|
||||
Unser Fokus liegt auf skalierbaren Architekturen, performanten Frontends und wartbaren Backends.
|
||||
<br/><br/>
|
||||
Egal ob API-Entwicklung, Admin-Dashboard oder komplexe Plattform – wir setzen moderne Technologien
|
||||
gezielt ein, um robuste, zukunftssichere Anwendungen zu realisieren.
|
||||
</motion.p>
|
||||
|
||||
<motion.div
|
||||
className="mt-6 text-sm font-medium space-y-3 pl-2"
|
||||
style={{color: colors.secondaryText}}
|
||||
initial={{opacity: 0, y: 10}}
|
||||
animate={{opacity: 1, y: 0}}
|
||||
transition={{duration: 0.5, delay: 0.3}}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs">🚀</span>
|
||||
<p>Native Mobile-Apps mit Flutter</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs">🌐</span>
|
||||
<p>Webseiten & Web-Portale mit Next.js</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs">🧩</span>
|
||||
<p>Skalierbare Backends mit Spring Boot</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs">📊</span>
|
||||
<p>Individuelle Dashboards & Admin-Panels</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs">🔌</span>
|
||||
<p>API-Entwicklung (REST & GraphQL)</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs">⚙️</span>
|
||||
<p>Automatisierte interne Tools</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs">📦</span>
|
||||
<p>CI/CD & Container mit GitLab CI & Docker</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<motion.h3
|
||||
className="mt-10 text-lg font-semibold"
|
||||
initial={{opacity: 0}}
|
||||
animate={{opacity: 1}}
|
||||
transition={{duration: 0.4, delay: 0.2}}
|
||||
>
|
||||
🔧 Unser Tech Stack im Überblick
|
||||
</motion.h3>
|
||||
|
||||
<motion.p
|
||||
className="text-sm font-medium mb-4"
|
||||
style={{color: colors.secondaryText}}
|
||||
initial={{opacity: 0, y: 10}}
|
||||
animate={{opacity: 1, y: 0}}
|
||||
transition={{duration: 0.5, delay: 0.2}}
|
||||
>
|
||||
Mit diesem Stack entwickeln wir robuste, moderne Softwarelösungen – abgestimmt auf deine
|
||||
Anforderungen.
|
||||
</motion.p>
|
||||
|
||||
<motion.div
|
||||
initial={{opacity: 0}}
|
||||
animate={{opacity: 1}}
|
||||
transition={{duration: 0.5, delay: 0.1}}
|
||||
>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
|
||||
{techStack.row1.map((group) => (
|
||||
<TechCard key={group.category} group={group}/>
|
||||
))}
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
|
||||
{techStack.row2.map((group) => (
|
||||
<TechCard key={group.category} group={group}/>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Development;
|
||||
@@ -0,0 +1,62 @@
|
||||
'use client';
|
||||
|
||||
import React from "react";
|
||||
import {motion} from "framer-motion";
|
||||
import {useThemeColors} from "@/utils/useThemeColors";
|
||||
|
||||
const ManagedServices = () => {
|
||||
const colors = useThemeColors();
|
||||
|
||||
return (
|
||||
<div
|
||||
className="w-[95%] mx-auto grid grid-cols-1 lg:grid-cols-2 gap-6 mt-8 mb-16 text-left"
|
||||
style={{color: colors.primaryText}}
|
||||
>
|
||||
<div>
|
||||
<motion.h2
|
||||
className="text-xl sm:text-2xl md:text-3xl font-bold mb-4"
|
||||
initial={{opacity: 0, y: 20}}
|
||||
animate={{opacity: 1, y: 0}}
|
||||
transition={{duration: 0.5}}
|
||||
>
|
||||
🛠️ Managed Services
|
||||
</motion.h2>
|
||||
|
||||
<motion.p
|
||||
className="text-sm font-medium leading-7"
|
||||
style={{color: colors.secondaryText}}
|
||||
initial={{opacity: 0, y: 10}}
|
||||
animate={{opacity: 1, y: 0}}
|
||||
transition={{duration: 0.5, delay: 0.2}}
|
||||
>
|
||||
Wir übernehmen den Betrieb und die Wartung deiner Anwendungen – zuverlässig, sicher und skalierbar.
|
||||
So kannst du dich voll auf dein Geschäft konzentrieren.
|
||||
</motion.p>
|
||||
|
||||
<motion.h3
|
||||
className="mt-8 text-lg font-semibold"
|
||||
initial={{opacity: 0}}
|
||||
animate={{opacity: 1}}
|
||||
transition={{duration: 0.4, delay: 0.2}}
|
||||
>
|
||||
🧰 Leistungen
|
||||
</motion.h3>
|
||||
|
||||
<motion.ul
|
||||
className="list-disc list-inside text-sm mt-4 space-y-1"
|
||||
style={{color: colors.secondaryText}}
|
||||
initial={{opacity: 0, y: 10}}
|
||||
animate={{opacity: 1, y: 0}}
|
||||
transition={{duration: 0.5, delay: 0.3}}
|
||||
>
|
||||
<li>Monitoring & Logging</li>
|
||||
<li>Security Updates & Wartung</li>
|
||||
<li>Cloud Deployment & Hosting</li>
|
||||
<li>24/7 Systemüberwachung</li>
|
||||
</motion.ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ManagedServices;
|
||||
38
frontend/components/Services/Services.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
'use client';
|
||||
|
||||
import React from "react";
|
||||
import {motion} from "framer-motion";
|
||||
import {useThemeColors} from "@/utils/useThemeColors";
|
||||
import ServiceHero from "@/components/Services/Section/ServiceHero";
|
||||
import OverviewTabs from "@/components/Services/Section/OverviewTabs";
|
||||
import ContactCTA from "@/components/Home/Sections/ContactCTA";
|
||||
import Section from "@/components/Section";
|
||||
|
||||
const Home = () => {
|
||||
const colors = useThemeColors();
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{opacity: 0, y: 20}}
|
||||
animate={{opacity: 1, y: 0}}
|
||||
transition={{duration: 0.7, ease: "easeOut"}}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
<Section style={{backgroundColor: colors.primaryBg}} shadow>
|
||||
<ServiceHero/>
|
||||
</Section>
|
||||
<Section style={{backgroundColor: colors.secondaryBg}} shadow>
|
||||
<OverviewTabs/>
|
||||
</Section>
|
||||
<Section style={{backgroundColor: colors.primaryBg}} shadow>
|
||||
<ContactCTA
|
||||
title="Nichts Passendes gefunden?"
|
||||
description="Nimm einfach Kontakt mit uns auf – gemeinsam finden wir die passende Lösung für dein Vorhaben."
|
||||
buttonLabel="Kontakt aufnehmen"
|
||||
/>
|
||||
</Section>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
||||
50
frontend/components/provider/ThemeProvider.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
'use client';
|
||||
|
||||
import {createContext, useEffect, useState} from "react";
|
||||
import Cookies from "js-cookie";
|
||||
|
||||
type ThemeType = "light" | "dark";
|
||||
|
||||
export const ThemeContext = createContext<{
|
||||
theme: ThemeType;
|
||||
toggleTheme: () => void;
|
||||
}>({
|
||||
theme: "light",
|
||||
toggleTheme: () => {
|
||||
},
|
||||
});
|
||||
|
||||
export const ThemeProvider = ({children}: { children: React.ReactNode }) => {
|
||||
const [theme, setTheme] = useState<ThemeType | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const saved = Cookies.get("theme") as ThemeType | undefined;
|
||||
if (saved === "dark" || saved === "light") {
|
||||
setTheme(saved);
|
||||
} else {
|
||||
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
const defaultTheme: ThemeType = prefersDark ? "dark" : "light";
|
||||
setTheme(defaultTheme);
|
||||
Cookies.set("theme", defaultTheme, {expires: 365});
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!theme) return;
|
||||
document.documentElement.setAttribute("data-theme", theme);
|
||||
}, [theme]);
|
||||
|
||||
const toggleTheme = () => {
|
||||
const next = theme === "dark" ? "light" : "dark";
|
||||
setTheme(next);
|
||||
Cookies.set("theme", next, {expires: 365});
|
||||
};
|
||||
|
||||
if (!theme) return null;
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={{theme, toggleTheme}}>
|
||||
{children}
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
};
|
||||
17
frontend/constant/Constant.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
export const navLinks = [
|
||||
{
|
||||
id: 1,
|
||||
url: '/',
|
||||
label: 'Start',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
url: '/about',
|
||||
label: 'Über Uns',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
url: '/services',
|
||||
label: 'Leistungen',
|
||||
}
|
||||
];
|
||||
27
frontend/eslint.config.mjs
Normal file
@@ -0,0 +1,27 @@
|
||||
import {dirname} from "path";
|
||||
import {fileURLToPath} from "url";
|
||||
import {FlatCompat} from "@eslint/eslintrc";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
});
|
||||
|
||||
const eslintConfig = [
|
||||
...compat.extends("next/core-web-vitals", "next/typescript"),
|
||||
...compat.config({
|
||||
rules: {
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'warn',
|
||||
{
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_'
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
];
|
||||
|
||||
export default eslintConfig;
|
||||
7
frontend/next.config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
7079
frontend/package-lock.json
generated
Normal file
40
frontend/package.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "web",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --turbopack",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"aos": "^2.3.4",
|
||||
"clsx": "^2.1.1",
|
||||
"framer-motion": "^12.6.5",
|
||||
"js-cookie": "^3.0.5",
|
||||
"next": "15.1.7",
|
||||
"nodemailer": "^6.10.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-icons": "^5.4.0",
|
||||
"react-simple-typewriter": "^5.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3",
|
||||
"@hcaptcha/react-hcaptcha": "^1.12.0",
|
||||
"@tailwindcss/postcss": "^4.0.17",
|
||||
"@types/aos": "^3.0.7",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/node": "^20",
|
||||
"@types/nodemailer": "^6.4.17",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.1.7",
|
||||
"postcss": "^8.5.3",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
8
frontend/postcss.config.mjs
Normal file
@@ -0,0 +1,8 @@
|
||||
/** @type {import('postcss-load-config').Config} */
|
||||
const config = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
BIN
frontend/public/images/About_Picture.png
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
BIN
frontend/public/images/contact.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
frontend/public/images/dart_logo.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
frontend/public/images/flutter_logo.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
frontend/public/images/hero.png
Normal file
|
After Width: | Height: | Size: 1003 KiB |
BIN
frontend/public/images/home_hero.jpg
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
frontend/public/images/java_logo.png
Normal file
|
After Width: | Height: | Size: 141 KiB |
BIN
frontend/public/images/nextjs_logo.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
frontend/public/images/software_dev.jpg
Normal file
|
After Width: | Height: | Size: 221 KiB |
10
frontend/public/images/svg/bootstrap.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
|
||||
<g>
|
||||
<path d="M0,222.991225 C0,241.223474 14.7785318,256 33.0087747,256 L222.991225,256 C241.223474,256 256,241.221468 256,222.991225 L256,33.0087747 C256,14.7765263 241.221468,0 222.991225,0 L33.0087747,0 C14.7765263,0 0,14.7785318 0,33.0087747 L0,222.991225 Z" fill="#563D7C">
|
||||
|
||||
</path>
|
||||
<path d="M106.157563,113.238095 L106.157563,76.9845938 L138.069328,76.9845938 C141.108559,76.9845938 144.039202,77.2378593 146.861345,77.7443978 C149.683488,78.2509362 152.179961,79.1554557 154.35084,80.4579832 C156.52172,81.7605107 158.258397,83.5695496 159.560924,85.8851541 C160.863452,88.2007585 161.514706,91.1675823 161.514706,94.7857143 C161.514706,101.298352 159.560944,106.001853 155.653361,108.896359 C151.745779,111.790864 146.752832,113.238095 140.67437,113.238095 L106.157563,113.238095 L106.157563,113.238095 Z M72.07493,50.5 L72.07493,205.5 L147.186975,205.5 C154.133788,205.5 160.899594,204.631661 167.484594,202.894958 C174.069594,201.158255 179.93088,198.480877 185.068627,194.862745 C190.206375,191.244613 194.294803,186.577293 197.334034,180.860644 C200.373264,175.143996 201.892857,168.37819 201.892857,160.563025 C201.892857,150.866431 199.541107,142.581033 194.837535,135.706583 C190.133963,128.832132 183.00635,124.020088 173.454482,121.270308 C180.401295,117.941627 185.647508,113.672295 189.193277,108.462185 C192.739047,103.252075 194.511905,96.7395349 194.511905,88.9243697 C194.511905,81.6881057 193.317939,75.6097352 190.929972,70.6890756 C188.542005,65.7684161 185.177193,61.8247114 180.835434,58.8578431 C176.493676,55.8909749 171.283644,53.756309 165.205182,52.4537815 C159.12672,51.151254 152.397096,50.5 145.016106,50.5 L72.07493,50.5 L72.07493,50.5 Z M106.157563,179.015406 L106.157563,136.466387 L143.279412,136.466387 C150.660401,136.466387 156.594049,138.166883 161.080532,141.567927 C165.567016,144.968971 167.810224,150.649353 167.810224,158.609244 C167.810224,162.661552 167.122789,165.990183 165.747899,168.595238 C164.373009,171.200293 162.527789,173.262597 160.212185,174.782213 C157.89658,176.301828 155.219203,177.387252 152.179972,178.038515 C149.140741,178.689779 145.956833,179.015406 142.628151,179.015406 L106.157563,179.015406 L106.157563,179.015406 Z" fill="#FFFFFF">
|
||||
|
||||
</path>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
6
frontend/public/images/svg/css.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 28L4 3H28L26 28L16 31L6 28Z" fill="#1172B8"/>
|
||||
<path d="M26 5H16V29.5L24 27L26 5Z" fill="#33AADD"/>
|
||||
<path d="M19.5 17.5H9.5L9 14L17 11.5H9L8.5 8.5H24L23.5 12L17 14.5H23L22 24L16 26L10 24L9.5 19H12.5L13 21.5L16 22.5L19 21.5L19.5 17.5Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 488 B |
18
frontend/public/images/svg/dart.svg
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
|
||||
<g>
|
||||
<path d="M70.534,69.696 L53.988,53.15 L54.058,172.75 L54.256,178.34 C54.338,180.97 54.826,183.938 55.64,187.014 L186.744,233.244 L219.516,218.724 L219.528,218.684 L70.534,69.696" fill="#00D2B8">
|
||||
|
||||
</path>
|
||||
<path d="M55.64,187.014 L55.648,187.022 C55.64,186.968 55.612,186.908 55.612,186.852 C55.612,186.908 55.62,186.96 55.64,187.014 L55.64,187.014 Z M219.516,218.724 L186.744,233.244 L55.648,187.022 C58.152,196.63 63.696,207.43 69.662,213.336 L112.446,255.876 L207.576,256 L219.528,218.684 L219.516,218.724 L219.516,218.724 Z" fill="#55DDCA">
|
||||
|
||||
</path>
|
||||
<path d="M3.034,130.116 C-1.202,134.638 0.902,143.966 7.722,150.838 L37.14,180.5 L55.64,187.014 C54.826,183.938 54.338,180.97 54.256,178.34 L54.058,172.75 L53.988,53.15 L3.034,130.116 Z" fill="#0081C6">
|
||||
|
||||
</path>
|
||||
<path d="M187.82,54.686 C184.744,53.9 181.794,53.414 179.12,53.33 L173.212,53.126 L53.988,53.142 L219.544,218.684 L219.558,218.684 L234.098,185.88 L187.82,54.686" fill="#0079B3">
|
||||
|
||||
</path>
|
||||
<path d="M187.67,54.654 C187.734,54.668 187.784,54.686 187.826,54.692 L187.82,54.686 C187.784,54.668 187.734,54.668 187.67,54.654 L187.67,54.654 Z M214.118,68.732 C208.11,62.674 197.452,57.168 187.826,54.692 L234.098,185.88 L219.558,218.684 L219.544,218.684 L255.076,207.336 L255.152,109.92 L214.118,68.732 L214.118,68.732 Z" fill="#00A4E4">
|
||||
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
4
frontend/public/images/svg/debian.svg
Normal file
|
After Width: | Height: | Size: 8.3 KiB |
2
frontend/public/images/svg/docker.svg
Normal file
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="none"><path fill="#2396ED" d="M12.342 4.536l.15-.227.262.159.116.083c.28.216.869.768.996 1.684.223-.04.448-.06.673-.06.534 0 .893.124 1.097.227l.105.057.068.045.191.156-.066.2a2.044 2.044 0 01-.47.73c-.29.299-.8.652-1.609.698l-.178.005h-.148c-.37.977-.867 2.078-1.702 3.066a7.081 7.081 0 01-1.74 1.488 7.941 7.941 0 01-2.549.968c-.644.125-1.298.187-1.953.185-1.45 0-2.73-.288-3.517-.792-.703-.449-1.243-1.182-1.606-2.177a8.25 8.25 0 01-.461-2.83.516.516 0 01.432-.516l.068-.005h10.54l.092-.007.149-.016c.256-.034.646-.11.92-.27-.328-.543-.421-1.178-.268-1.854a3.3 3.3 0 01.3-.81l.108-.187zM2.89 5.784l.04.007a.127.127 0 01.077.082l.006.04v1.315l-.006.041a.127.127 0 01-.078.082l-.039.006H1.478a.124.124 0 01-.117-.088l-.007-.04V5.912l.007-.04a.127.127 0 01.078-.083l.039-.006H2.89zm1.947 0l.039.007a.127.127 0 01.078.082l.006.04v1.315l-.007.041a.127.127 0 01-.078.082l-.039.006H3.424a.125.125 0 01-.117-.088L3.3 7.23V5.913a.13.13 0 01.085-.123l.039-.007h1.413zm1.976 0l.039.007a.127.127 0 01.077.082l.007.04v1.315l-.007.041a.127.127 0 01-.078.082l-.039.006H5.4a.124.124 0 01-.117-.088l-.006-.04V5.912l.006-.04a.127.127 0 01.078-.083l.039-.006h1.413zm1.952 0l.039.007a.127.127 0 01.078.082l.007.04v1.315a.13.13 0 01-.085.123l-.04.006H7.353a.124.124 0 01-.117-.088l-.006-.04V5.912l.006-.04a.127.127 0 01.078-.083l.04-.006h1.412zm1.97 0l.039.007a.127.127 0 01.078.082l.006.04v1.315a.13.13 0 01-.085.123l-.039.006H9.322a.124.124 0 01-.117-.088l-.006-.04V5.912l.006-.04a.127.127 0 01.078-.083l.04-.006h1.411zM4.835 3.892l.04.007a.127.127 0 01.077.081l.007.041v1.315a.13.13 0 01-.085.123l-.039.007H3.424a.125.125 0 01-.117-.09l-.007-.04V4.021a.13.13 0 01.085-.122l.039-.007h1.412zm1.976 0l.04.007a.127.127 0 01.077.081l.007.041v1.315a.13.13 0 01-.085.123l-.039.007H5.4a.125.125 0 01-.117-.09l-.006-.04V4.021l.006-.04a.127.127 0 01.078-.082l.039-.007h1.412zm1.953 0c.054 0 .1.037.117.088l.007.041v1.315a.13.13 0 01-.085.123l-.04.007H7.353a.125.125 0 01-.117-.09l-.006-.04V4.021l.006-.04a.127.127 0 01.078-.082l.04-.007h1.412zm0-1.892c.054 0 .1.037.117.088l.007.04v1.316a.13.13 0 01-.085.123l-.04.006H7.353a.124.124 0 01-.117-.088l-.006-.04V2.128l.006-.04a.127.127 0 01.078-.082L7.353 2h1.412z"/></svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
24
frontend/public/images/svg/flutter.svg
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="-30.5 0 317 317" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
|
||||
<defs>
|
||||
<linearGradient x1="3.9517088%" y1="26.9930287%" x2="75.8970734%" y2="52.9192657%" id="linearGradient-1">
|
||||
<stop stop-color="#000000" offset="0%">
|
||||
|
||||
</stop>
|
||||
<stop stop-color="#000000" stop-opacity="0" offset="100%">
|
||||
|
||||
</stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g>
|
||||
<polygon fill="#47C5FB" points="157.665785 0.000549356223 0.000549356223 157.665785 48.8009614 206.466197 255.267708 0.000549356223">
|
||||
|
||||
</polygon>
|
||||
<polygon fill="#47C5FB" points="156.567183 145.396793 72.1487107 229.815265 121.132608 279.530905 169.842925 230.820587 255.267818 145.396793">
|
||||
|
||||
</polygon>
|
||||
<polygon fill="#00569E" points="121.133047 279.531124 158.214592 316.61267 255.267159 316.61267 169.842266 230.820807">
|
||||
|
||||
</polygon>
|
||||
<polygon fill="#00B5F8" points="71.5995742 230.364072 120.401085 181.562561 169.842046 230.821136 121.132827 279.531454">
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
20
frontend/public/images/svg/gitlab-ci.svg
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 -10 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
|
||||
<g>
|
||||
<path d="M128.07485,236.074667 L128.07485,236.074667 L175.17885,91.1043048 L80.9708495,91.1043048 L128.07485,236.074667 L128.07485,236.074667 Z" fill="#E24329">
|
||||
|
||||
</path>
|
||||
<path d="M128.07485,236.074423 L80.9708495,91.104061 L14.9557638,91.104061 L128.07485,236.074423 L128.07485,236.074423 Z" fill="#FC6D26">
|
||||
|
||||
</path>
|
||||
<path d="M14.9558857,91.1044267 L14.9558857,91.1044267 L0.641828571,135.159589 C-0.663771429,139.17757 0.766171429,143.57955 4.18438095,146.06275 L128.074971,236.074789 L14.9558857,91.1044267 L14.9558857,91.1044267 Z" fill="#FCA326">
|
||||
|
||||
</path>
|
||||
<path d="M14.9558857,91.1045486 L80.9709714,91.1045486 L52.6000762,3.79026286 C51.1408762,-0.703146667 44.7847619,-0.701927619 43.3255619,3.79026286 L14.9558857,91.1045486 L14.9558857,91.1045486 Z" fill="#E24329">
|
||||
|
||||
</path>
|
||||
<path d="M128.07485,236.074423 L175.17885,91.104061 L241.193935,91.104061 L128.07485,236.074423 L128.07485,236.074423 Z" fill="#FC6D26">
|
||||
|
||||
</path>
|
||||
<path d="M241.193935,91.1044267 L241.193935,91.1044267 L255.507992,135.159589 C256.813592,139.17757 255.38365,143.57955 251.96544,146.06275 L128.07485,236.074789 L241.193935,91.1044267 L241.193935,91.1044267 Z" fill="#FCA326">
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
2
frontend/public/images/svg/gitlab.svg
Normal file
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="none"><path fill="#FC6D26" d="M14.975 8.904L14.19 6.55l-1.552-4.67a.268.268 0 00-.255-.18.268.268 0 00-.254.18l-1.552 4.667H5.422L3.87 1.879a.267.267 0 00-.254-.179.267.267 0 00-.254.18l-1.55 4.667-.784 2.357a.515.515 0 00.193.583l6.78 4.812 6.778-4.812a.516.516 0 00.196-.583z"/><path fill="#E24329" d="M8 14.296l2.578-7.75H5.423L8 14.296z"/><path fill="#FC6D26" d="M8 14.296l-2.579-7.75H1.813L8 14.296z"/><path fill="#FCA326" d="M1.81 6.549l-.784 2.354a.515.515 0 00.193.583L8 14.3 1.81 6.55z"/><path fill="#E24329" d="M1.812 6.549h3.612L3.87 1.882a.268.268 0 00-.254-.18.268.268 0 00-.255.18L1.812 6.549z"/><path fill="#FC6D26" d="M8 14.296l2.578-7.75h3.614L8 14.296z"/><path fill="#FCA326" d="M14.19 6.549l.783 2.354a.514.514 0 01-.193.583L8 14.296l6.188-7.747h.001z"/><path fill="#E24329" d="M14.19 6.549H10.58l1.551-4.667a.267.267 0 01.255-.18c.115 0 .217.073.254.18l1.552 4.667z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
6
frontend/public/images/svg/html.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 28L4 3H28L26 28L16 31L6 28Z" fill="#E44D26"/>
|
||||
<path d="M26 5H16V29.5L24 27L26 5Z" fill="#F16529"/>
|
||||
<path d="M9.5 17.5L8.5 8H24L23.5 11H11.5L12 14.5H23L22 24L16 26L10 24L9.5 19H12.5L13 21.5L16 22.5L19 21.5L19.5 17.5H9.5Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 475 B |
11
frontend/public/images/svg/java.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16.0497 8.44062C22.6378 3.32607 19.2566 0 19.2566 0C19.7598 5.28738 13.813 6.53583 12.2189 10.1692C11.1312 12.6485 12.9638 14.8193 16.0475 17.5554C15.7749 16.9494 15.3544 16.3606 14.9288 15.7645C13.4769 13.7313 11.9645 11.6132 16.0497 8.44062Z" fill="#E76F00"/>
|
||||
<path d="M17.1015 18.677C17.1015 18.677 19.0835 17.0779 17.5139 15.3008C12.1931 9.27186 23.3333 6.53583 23.3333 6.53583C16.5317 9.8125 17.5471 11.7574 19.2567 14.1202C21.0871 16.6538 17.1015 18.677 17.1015 18.677Z" fill="#E76F00"/>
|
||||
<path d="M22.937 23.4456C29.0423 20.3258 26.2195 17.3278 24.2492 17.7317C23.7662 17.8305 23.5509 17.9162 23.5509 17.9162C23.5509 17.9162 23.7302 17.64 24.0726 17.5204C27.9705 16.1729 30.9682 21.4949 22.8143 23.6028C22.8143 23.6029 22.9088 23.5198 22.937 23.4456Z" fill="#5382A1"/>
|
||||
<path d="M10.233 19.4969C6.41312 18.9953 12.3275 17.6139 12.3275 17.6139C12.3275 17.6139 10.0307 17.4616 7.20592 18.8043C3.86577 20.3932 15.4681 21.1158 21.474 19.5625C22.0984 19.1432 22.9614 18.7798 22.9614 18.7798C22.9614 18.7798 20.5037 19.2114 18.0561 19.4145C15.0612 19.6612 11.8459 19.7093 10.233 19.4969Z" fill="#5382A1"/>
|
||||
<path d="M11.6864 22.4758C9.55624 22.2592 10.951 21.2439 10.951 21.2439C5.43898 23.0429 14.0178 25.083 21.7199 22.8682C20.9012 22.5844 20.3806 22.0653 20.3806 22.0653C16.6163 22.7781 14.441 22.7553 11.6864 22.4758Z" fill="#5382A1"/>
|
||||
<path d="M12.6145 25.6991C10.486 25.4585 11.7295 24.7474 11.7295 24.7474C6.72594 26.1222 14.7729 28.9625 21.1433 26.2777C20.0999 25.8787 19.3528 25.4181 19.3528 25.4181C16.5111 25.9469 15.1931 25.9884 12.6145 25.6991Z" fill="#5382A1"/>
|
||||
<path d="M25.9387 27.3388C25.9387 27.3388 26.8589 28.0844 24.9252 28.6612C21.2481 29.7566 9.62093 30.0874 6.39094 28.7049C5.22984 28.2082 7.40723 27.5189 8.09215 27.3742C8.80646 27.2219 9.21466 27.2503 9.21466 27.2503C7.9234 26.3558 0.868489 29.0067 5.63111 29.7659C18.6195 31.8372 29.3077 28.8331 25.9387 27.3388Z" fill="#5382A1"/>
|
||||
<path d="M28 28.9679C27.7869 31.6947 18.7877 32.2683 12.9274 31.8994C9.10432 31.6583 8.33812 31.0558 8.32691 31.047C11.9859 31.6402 18.1549 31.7482 23.1568 30.8225C27.5903 30.0016 28 28.9679 28 28.9679Z" fill="#5382A1"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
2
frontend/public/images/svg/kotlin.svg
Normal file
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><linearGradient id="a" x1="73.714" y1="910.226" x2="105.452" y2="878.134" gradientTransform="translate(-64.139 -782.556) scale(0.893)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#0296d8"/><stop offset="1" stop-color="#8371d9"/></linearGradient><linearGradient id="b" x1="69.813" y1="905.226" x2="102.279" y2="875.745" gradientTransform="translate(-64.139 -782.556) scale(0.893)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#cb55c0"/><stop offset="1" stop-color="#f28e0e"/></linearGradient></defs><title>file_type_kotlin</title><path d="M2,2V30H30v-.047l-6.95-7L16.1,15.946l6.95-7.012L29.938,2Z" style="fill:url(#a)"/><path d="M16.318,2,2,16.318V30h.124L16.132,15.992l-.031-.031L23.05,8.95,29.938,2Z" style="fill:url(#b)"/></svg>
|
||||
|
After Width: | Height: | Size: 1018 B |
8
frontend/public/images/svg/macos.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 -196 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
|
||||
<g>
|
||||
<path d="M0,118.067201 L20.0767036,118.067201 L20.0767036,65.8336281 C20.0767036,55.5319609 26.8493059,48.0471816 36.5248058,48.0471816 C45.8774864,48.0471816 51.6828689,53.9224284 51.6828689,63.4191233 L51.6828689,118.065347 L71.1946592,118.065347 L71.1946592,65.3496558 C71.1946592,55.1285476 77.8060582,48.0461515 87.4813517,48.0461515 C97.3178485,48.0461515 102.800618,54.0017513 102.800618,64.4645365 L102.800618,118.066171 L122.87693,118.066171 L122.87693,59.5568223 C122.87693,42.4947891 111.830892,31.1468846 94.9790592,31.1468846 C82.6429825,31.1468846 72.4849047,37.7463386 68.371228,48.2894768 L67.887453,48.2894768 C64.74306,37.3439555 56.1158974,31.1468846 44.022761,31.1468846 C32.4124087,31.1468846 23.3821348,37.6659856 19.6730143,47.5652696 L19.2698822,47.5652696 L19.2698822,32.7566231 L0,32.7566231 L0,118.067201 L0,118.067201 Z M166.822061,104.304916 C158.114194,104.304916 152.309224,99.8783716 152.309224,92.8763285 C152.309224,86.1159624 157.872492,81.7698951 167.547785,81.1260409 L187.301484,79.9188091 L187.301484,86.4379101 C187.301484,96.7395773 178.190505,104.304916 166.822061,104.304916 L166.822061,104.304916 Z M160.774977,119.435469 C171.740309,119.435469 182.464147,113.72134 187.382808,104.46591 L187.78594,104.46591 L187.78594,118.067407 L207.136527,118.067407 L207.136527,59.3149391 C207.136527,42.1723469 193.349208,30.9853545 172.144474,30.9853545 C150.374805,30.9853545 136.749928,42.413818 135.862381,58.3486428 L154.487244,58.3486428 C155.777283,51.2662466 161.824367,46.6787082 171.338457,46.6787082 C181.255659,46.6787082 187.302743,51.8295417 187.302743,60.7629415 L187.302743,66.8796594 L164.726026,68.1673678 C143.924403,69.4550761 132.233552,78.5495939 132.233552,93.6804766 C132.233552,109.052418 144.247016,119.435469 160.77541,119.435469 L160.774977,119.435469 Z M295.265169,63.1780643 C293.894507,45.2307058 279.945903,30.9853545 257.371043,30.9853545 C232.538306,30.9853545 216.411785,48.1279467 216.411785,75.4102639 C216.411785,103.097025 232.537274,119.75482 257.53204,119.75482 C278.979715,119.75482 293.733634,107.19956 295.345667,88.1245815 L276.398192,88.1245815 C274.543755,97.7823945 267.932232,103.577082 257.773329,103.577082 C245.034141,103.577082 236.810709,93.2754151 236.810709,75.4082036 C236.810709,57.8632282 244.954055,47.2393249 257.612332,47.2393249 C268.335757,47.2393249 274.705248,54.08025 276.3179,63.1747678 L295.265375,63.1747678 L295.265169,63.1780643 Z M357.271674,0 C323.328164,0 302.041486,23.0983981 302.041486,60.0401765 C302.041486,96.9819549 323.3261,120 357.271674,120 C391.134685,120 412.421363,96.9819549 412.421363,60.0401765 C412.421363,23.0983981 391.136749,0 357.271674,0 Z M357.271674,17.7060934 C377.992799,17.7060934 391.215184,34.1244785 391.215184,60.0397644 C391.215184,85.8742853 377.992386,102.293082 357.271674,102.293082 C336.470051,102.293082 323.328164,85.8746974 323.328164,60.0397644 C323.328164,34.1248905 336.470464,17.7060934 357.271674,17.7060934 Z M420.890212,85.3921673 C421.777119,106.801092 439.353871,119.999588 466.12208,119.999588 C494.261446,119.999588 512,106.15662 512,84.1044589 C512,66.8009546 502.002093,57.0625826 478.378484,51.67069 L464.994276,48.6123311 C450.723141,45.2321481 444.837466,40.7251687 444.837466,32.9989183 C444.837466,23.3411054 453.706537,16.9025634 466.84863,16.9025634 C480.152133,16.9025634 489.262286,23.4216644 490.230332,34.2866267 L510.064735,34.2866267 C509.58096,13.8442045 492.649207,0.000618100029 467.010453,0.000618100029 C441.692661,0.000618100029 423.712611,13.9239394 423.712611,34.5276857 C423.712611,51.1069828 433.871721,61.408856 455.317538,66.3186306 L470.394896,69.8599317 C485.069143,73.3206737 491.035522,78.1494772 491.035522,86.5195818 C491.035522,96.1773947 481.279524,103.098879 467.251329,103.098879 C453.060898,103.098879 442.338093,96.0970417 441.048054,85.3927854 L420.891244,85.3927854 L420.890212,85.3921673 Z" fill="#000000" fill-rule="nonzero">
|
||||
|
||||
</path>
|
||||
</g>
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
4
frontend/public/images/svg/nextjs.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 7.5C0 3.35786 3.35786 0 7.5 0C11.6421 0 15 3.35786 15 7.5C15 10.087 13.6902 12.3681 11.6975 13.7163L4.90687 4.20942C4.78053 4.03255 4.5544 3.95756 4.34741 4.02389C4.14042 4.09022 4 4.28268 4 4.50004V12H5V6.06027L10.8299 14.2221C9.82661 14.7201 8.696 15 7.5 15C3.35786 15 0 11.6421 0 7.5ZM10 10V4H11V10H10Z" fill="#000000"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 603 B |
1
frontend/public/images/svg/outline.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 512 512"><path d="m354.5 441.9-32.8-9.9V80l32.8-9.9zM92 143.7 288.9 32.8V479L92 368.1zm32.8 202.6 32.8 16.5V149.2l-32.8 16.5zm295.3-233.8-32.8 4.1v278.8l32.8 4.1z" style="fill:#fff;stroke:#000;stroke-miterlimit:10"/><path d="M321.7 466.3v12.8c0 18.2-14.7 32.8-32.8 32.8-5.7 0-11.1-1.5-16.1-4.2l-197-110.8c-10.3-5.8-16.7-16.8-16.7-28.6V143.7c0-11.8 6.4-22.8 16.7-28.6l197-110.9c15.8-8.9 35.8-3.3 44.7 12.5 2.7 4.9 4.2 10.5 4.2 16.1v12.8l23.4-7c17.4-5.2 35.7 4.7 40.9 22 .9 3.1 1.4 6.2 1.4 9.4v13.3l28.7-3.6c18-2.3 34.4 10.6 36.6 28.5.1 1.4.2 2.7.2 4.1v287c0 18.2-14.7 32.8-32.8 32.8-1.4 0-2.7-.1-4.1-.2l-28.7-3.6v13.3c0 18.2-14.7 32.8-32.8 32.8-3.2 0-6.4-.5-9.4-1.4zm0-34.3 32.8 9.9V70.1L321.7 80zm65.6-315.4v278.8l32.8 4.1v-287zM92 143.7v224.4L288.9 479V32.8zm32.8 22 32.8-16.5v213.6l-32.8-16.5z" style="fill:#333"/></svg>
|
||||
|
After Width: | Height: | Size: 896 B |
5
frontend/public/images/svg/proxmox.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="512" cy="512" r="512" style="fill:#e57000"/>
|
||||
<path d="M512 497.8 342.7 311.6c6.6-6.6 14.2-11.7 22.9-15.5 8.7-3.8 18.1-5.7 28.1-5.7 10.7.1 20.4 2.2 29.3 6.3 8.9 4.1 16.6 9.8 23.1 17l65.8 71.9 65.4-71.9c6.8-7.2 14.7-12.9 23.6-17 9-4.1 18.7-6.2 29.2-6.3 10 .1 19.4 2 28.1 5.7 8.7 3.8 16.4 8.9 22.9 15.5L512 497.8m0 28.4L342.7 712.4c6.6 6.6 14.2 11.7 22.9 15.5 8.7 3.8 18.1 5.7 28.1 5.7 10.5-.1 20.2-2.2 29.2-6.3s16.9-9.8 23.6-17l65.4-71.9 65.8 71.9c6.5 7.2 14.2 12.9 23.1 17 8.9 4.1 18.6 6.2 29.3 6.3 10-.1 19.4-2 28.1-5.7 8.7-3.8 16.4-8.9 22.9-15.5L512 526.2M497.8 512 370.3 372.2c-7.4-7.9-16-14.1-25.9-18.7-9.8-4.5-20.5-6.8-31.9-6.9-11 .1-21.3 2.2-30.8 6.3-9.6 4.1-17.9 9.8-25.1 16.9L385.9 512 256.5 654.2c7.2 7.4 15.6 13.2 25.1 17.4 9.6 4.2 19.8 6.3 30.8 6.3 11.5-.1 22.2-2.4 32.1-6.9 9.9-4.5 18.5-10.8 25.7-18.7L497.8 512m28.4 0 127.5 140.3c7.2 7.9 15.8 14.1 25.7 18.7 9.9 4.5 20.6 6.8 32.1 6.9 11-.1 21.3-2.2 30.8-6.3 9.6-4.2 17.9-9.9 25.1-17.4L638.1 512l129.4-142.2c-7.2-7.2-15.6-12.8-25.1-16.9-9.6-4.1-19.8-6.2-30.8-6.3-11.4.1-22.1 2.4-31.9 6.9-9.8 4.5-18.5 10.8-25.9 18.7L526.2 512" style="fill:#fff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
8
frontend/public/images/svg/spring.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
|
||||
<path d="M0 0h32v32H0z"/>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
2
frontend/public/images/svg/typescript.svg
Normal file
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><title>file_type_typescript_official</title><rect x="2" y="2" width="28" height="28" rx="1.312" style="fill:#3178c6"/><path d="M18.245,23.759v3.068a6.492,6.492,0,0,0,1.764.575,11.56,11.56,0,0,0,2.146.192,9.968,9.968,0,0,0,2.088-.211,5.11,5.11,0,0,0,1.735-.7,3.542,3.542,0,0,0,1.181-1.266,4.469,4.469,0,0,0,.186-3.394,3.409,3.409,0,0,0-.717-1.117,5.236,5.236,0,0,0-1.123-.877,12.027,12.027,0,0,0-1.477-.734q-.6-.249-1.08-.484a5.5,5.5,0,0,1-.813-.479,2.089,2.089,0,0,1-.516-.518,1.091,1.091,0,0,1-.181-.618,1.039,1.039,0,0,1,.162-.571,1.4,1.4,0,0,1,.459-.436,2.439,2.439,0,0,1,.726-.283,4.211,4.211,0,0,1,.956-.1,5.942,5.942,0,0,1,.808.058,6.292,6.292,0,0,1,.856.177,5.994,5.994,0,0,1,.836.3,4.657,4.657,0,0,1,.751.422V13.9a7.509,7.509,0,0,0-1.525-.4,12.426,12.426,0,0,0-1.9-.129,8.767,8.767,0,0,0-2.064.235,5.239,5.239,0,0,0-1.716.733,3.655,3.655,0,0,0-1.171,1.271,3.731,3.731,0,0,0-.431,1.845,3.588,3.588,0,0,0,.789,2.34,6,6,0,0,0,2.395,1.639q.63.26,1.175.509a6.458,6.458,0,0,1,.942.517,2.463,2.463,0,0,1,.626.585,1.2,1.2,0,0,1,.23.719,1.1,1.1,0,0,1-.144.552,1.269,1.269,0,0,1-.435.441,2.381,2.381,0,0,1-.726.292,4.377,4.377,0,0,1-1.018.105,5.773,5.773,0,0,1-1.969-.35A5.874,5.874,0,0,1,18.245,23.759Zm-5.154-7.638h4V13.594H5.938v2.527H9.92V27.375h3.171Z" style="fill:#ffffff;fill-rule:evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
5
frontend/public/images/svg/ubuntu.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M30 16C30 23.728 23.735 30 16 30C8.265 30 2 23.728 2 16C2 8.265 8.265 2 16 2C23.735 2 30 8.265 30 16Z" fill="#E95420"/>
|
||||
<path d="M6.82154 14.1563C5.81185 14.1563 5 14.9865 5 16.0035C5 17.0205 5.81867 17.8507 6.82154 17.8507C7.82442 17.8507 8.64309 17.0205 8.64309 16.0035C8.64309 14.9795 7.82442 14.1563 6.82154 14.1563ZM19.8316 22.5482C18.9583 23.0602 18.665 24.1879 19.163 25.0734C19.6679 25.959 20.7799 26.2634 21.6531 25.7514C22.5264 25.2395 22.8197 24.1118 22.3217 23.2262C21.8101 22.3476 20.698 22.0432 19.8316 22.5482ZM10.7785 16.0035C10.7785 14.177 11.6722 12.565 13.0434 11.5896L11.7131 9.32725C10.1167 10.4065 8.93645 12.06 8.43842 13.9902C9.01149 14.4676 9.37989 15.194 9.37989 16.0035C9.37989 16.8129 9.01149 17.5393 8.43842 18.0167C8.92962 19.9469 10.1167 21.6004 11.7131 22.6797L13.0434 20.4174C11.6722 19.4419 10.7785 17.8299 10.7785 16.0035ZM16.0998 10.6071C18.8833 10.6071 21.1619 12.7657 21.4007 15.5261L24 15.4846C23.8704 13.4506 22.9971 11.6241 21.6463 10.282C20.9504 10.5449 20.1522 10.5034 19.4632 10.1021C18.7673 9.69393 18.3375 9.01593 18.2147 8.26875C17.5393 8.08195 16.8298 7.97817 16.0998 7.97817C14.8377 7.97817 13.6506 8.27566 12.5932 8.80838L13.8621 11.1053C14.5375 10.787 15.3016 10.6071 16.0998 10.6071ZM16.0998 21.3998C15.3016 21.3998 14.5375 21.2199 13.8553 20.9017L12.5864 23.1985C13.6438 23.7313 14.8377 24.0287 16.093 24.0287C16.823 24.0287 17.5325 23.925 18.2079 23.7382C18.3239 22.9979 18.7605 22.313 19.4564 21.9048C20.1454 21.4966 20.9504 21.462 21.6395 21.7249C22.9835 20.3828 23.8636 18.5563 23.9932 16.5223L21.3939 16.4808C21.1619 19.2343 18.8833 21.3998 16.0998 21.3998ZM19.8248 9.45178C20.698 9.96374 21.8101 9.65933 22.3149 8.77378C22.8197 7.88824 22.5196 6.76054 21.6463 6.24859C20.7731 5.73663 19.661 6.04104 19.1562 6.92659C18.6582 7.81213 18.9583 8.93983 19.8248 9.45178Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
BIN
frontend/public/images/team/default-avatar.jpg
Normal file
|
After Width: | Height: | Size: 14 KiB |
12
frontend/tailwind.config.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import type { Config } from "tailwindcss";
|
||||
|
||||
export default {
|
||||
content: [
|
||||
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./utils/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
],
|
||||
theme: {},
|
||||
plugins: [],
|
||||
} satisfies Config;
|
||||
30
frontend/tsconfig.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/components/*": ["./components/*"],
|
||||
"@/utils/*": ["./utils/*"],
|
||||
"@/*": ["./*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
10
frontend/utils/useThemeColors.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
'use client';
|
||||
|
||||
import {useContext} from "react";
|
||||
import {ThemeContext} from "@/components/provider/ThemeProvider";
|
||||
import {themeColors} from "@/components/Helper/ThemeColors";
|
||||
|
||||
export const useThemeColors = () => {
|
||||
const {theme} = useContext(ThemeContext);
|
||||
return themeColors[theme];
|
||||
};
|
||||