Compare commits

...

16 Commits

Author SHA1 Message Date
5368f1a64d feat(ui): smooth radial fade on EU badge and setup custom image path
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 26s
Build & Deploy / 🧪 QA (push) Successful in 1m25s
Build & Deploy / 🏗️ Build (push) Has been cancelled
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
2026-06-15 16:45:40 +02:00
241c97e346 feat(ui): refine EU badge into subtle triangular corner fade with text
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 27s
Build & Deploy / 🧪 QA (push) Successful in 1m25s
Build & Deploy / 🏗️ Build (push) Successful in 2m56s
Build & Deploy / 🚀 Deploy (push) Successful in 30s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 57s
Build & Deploy / 🔔 Notify (push) Successful in 3s
2026-06-15 16:34:50 +02:00
3334e327bf fix(ui): correct z-index and remove mix-blend-screen on EU badge
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 27s
Build & Deploy / 🧪 QA (push) Successful in 1m31s
Build & Deploy / 🏗️ Build (push) Successful in 2m56s
Build & Deploy / 🚀 Deploy (push) Successful in 31s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 58s
Build & Deploy / 🔔 Notify (push) Successful in 3s
2026-06-15 16:21:51 +02:00
145bff90b6 ci: implement dynamic environment priority gate (prod > rc > testing > branch)
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 34s
Build & Deploy / 🧪 QA (push) Successful in 1m32s
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
Build & Deploy / 🏗️ Build (push) Has been cancelled
2026-06-15 16:14:16 +02:00
d2bf48fc60 chore: add project instructions and workflow rules to INSTRUCTIONS.md 2026-06-15 16:08:23 +02:00
5e7ee84dc9 ci: fix deployment concurrency groups by target environment
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 26s
Build & Deploy / 🧪 QA (push) Successful in 1m26s
Build & Deploy / 🏗️ Build (push) Successful in 2m56s
Build & Deploy / 🚀 Deploy (push) Successful in 30s
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s
2026-06-15 16:05:42 +02:00
b42e53495d 2.2.13-rc.0
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 25s
Build & Deploy / 🧪 QA (push) Successful in 1m24s
Build & Deploy / 🏗️ Build (push) Successful in 3m2s
Build & Deploy / 🚀 Deploy (push) Successful in 29s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 55s
Build & Deploy / 🔔 Notify (push) Successful in 3s
2026-06-15 15:54:45 +02:00
8813ed3910 fix: enforce hero remounts on route change 2026-06-15 15:54:38 +02:00
1d3c6a0f70 fix(ui): remove dummy certificates from english page
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 35s
Build & Deploy / 🧪 QA (push) Successful in 1m33s
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
Build & Deploy / 🏗️ Build (push) Has been cancelled
2026-06-15 15:52:36 +02:00
8fb3b96bd3 feat(ui): redesign EU flag with perspective image and gradient fade mask
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 27s
Build & Deploy / 🧪 QA (push) Successful in 1m40s
Build & Deploy / 🏗️ Build (push) Successful in 2m54s
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
Build & Deploy / 🚀 Deploy (push) Has been cancelled
2026-06-15 15:46:41 +02:00
67dc56d3ee fix(ui): remove hardcoded dummy certificates without downloads
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 42s
Build & Deploy / 🧪 QA (push) Successful in 1m31s
Build & Deploy / 🏗️ Build (push) Successful in 3m2s
Build & Deploy / 🚀 Deploy (push) Successful in 31s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m3s
Build & Deploy / 🔔 Notify (push) Successful in 3s
2026-06-15 15:40:19 +02:00
e5933bad69 feat(ui): optimize career page header layout 2026-06-15 15:40:14 +02:00
11b9e4630b feat(ui): add EU Funding Badge to global layout
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 26s
Build & Deploy / 🧪 QA (push) Successful in 1m24s
Build & Deploy / 🏗️ Build (push) Successful in 2m32s
Build & Deploy / 🔔 Notify (push) Has been cancelled
Build & Deploy / 🚀 Deploy (push) Successful in 30s
Build & Deploy / 🧪 Post-Deploy Verification (push) Has started running
2026-06-15 15:29:23 +02:00
a47d378521 fix(ui): prevent text selection and ghost images during references slider drag
Some checks failed
Build & Deploy / 🔍 Prepare (push) Has been cancelled
Build & Deploy / 🧪 QA (push) Has been cancelled
Build & Deploy / 🏗️ Build (push) Has been cancelled
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
2026-06-15 15:28:21 +02:00
30047cb1c4 fix(nav): optimize dropdown behavior on navigation and scroll
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 44s
Build & Deploy / 🧪 QA (push) Has started running
Build & Deploy / 🏗️ Build (push) Has been cancelled
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
2026-06-15 15:24:46 +02:00
394ac5a19c style(mobile): optimize mobile layout and component spacing
Some checks failed
Build & Deploy / 🔍 Prepare (push) Has been cancelled
Build & Deploy / 🧪 QA (push) Has been cancelled
Build & Deploy / 🏗️ Build (push) Has been cancelled
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
- Hide CorporateBackground on mobile to prevent content overlap
- Increase Header padding on mobile for better logo spacing
- Reduce top spacing in HeroSection on mobile
- Change SubCompanyTiles to horizontally scrollable row on mobile
- Add bottom padding to Footer to clear MobileBottomNav
- Reduce Footer Bento card padding for compact mobile view
2026-06-15 15:23:23 +02:00
22 changed files with 219 additions and 81 deletions

View File

@@ -17,9 +17,6 @@ env:
PUPPETEER_SKIP_DOWNLOAD: "true"
COREPACK_NPM_REGISTRY: "https://registry.npmmirror.com"
concurrency:
group: deploy-pipeline
cancel-in-progress: true
jobs:
# ──────────────────────────────────────────────────────────────────────────────
@@ -146,6 +143,13 @@ jobs:
echo "short_sha=$SHORT_SHA"
} >> "$GITHUB_OUTPUT"
- name: 🚦 Priority Gate
env:
GITEA_PAT: ${{ secrets.GITEA_PAT }}
CURRENT_RUN_ID: ${{ github.run_id }}
CURRENT_TARGET: ${{ steps.determine.outputs.target }}
run: bash scripts/priority-gate.sh
# ──────────────────────────────────────────────────────────────────────────────
# JOB 2: QA (Lint, Typecheck, Test)
# ──────────────────────────────────────────────────────────────────────────────

25
INSTRUCTIONS.md Normal file
View File

@@ -0,0 +1,25 @@
# Antigravity Instructions & Project Rules
This document contains critical project-specific rules for the e-tib.com codebase. Antigravity must read and follow these rules at all times.
## 1. Bilingual Site (German & English) - STRICT
- **Always update BOTH languages!** The website is bilingual.
- When changing content, adding pages, or updating text, you MUST always check and update both `content/de/` and `content/en/` simultaneously.
- When adding new components with text, ensure they are built to support both languages or update the respective translation files/mechanisms. Do NOT just hardcode German text and forget the English version.
## 2. No Dummy Data / No Hallucinations
- Do not invent or hallucinate placeholder text (e.g., "Nachweis liegt vor" for certificates that don't exist).
- Only implement what is explicitly requested or what matches provided assets/data.
- If data is missing, ask the user instead of inventing placeholders.
## 3. Git Workflow & RC Tagging
- When instructed to "commit and tag" (or similar), always follow this process:
1. Add and commit the changes with conventional commits (`feat:`, `fix:`, `chore:`, etc.).
2. Check the latest tag using `git describe --tags --abbrev=0`.
3. Increment the RC (Release Candidate) number of the current version (e.g., if latest is `v2.2.13-rc19`, the new tag is `v2.2.13-rc20`).
4. Create the tag: `git tag vX.Y.Z-rcN`.
5. Push the commit and the tag: `git push origin main --tags`.
## 4. Work Exclusively on the Correct Branch
- Follow the `USER_GLOBAL` directive: If on `main`, create a branch unless explicitly told to work on `main`.
- For e-tib.com, often small fixes are done on `main` if the user commands it ("auf main arbeiten"), but always verify context.

View File

@@ -186,7 +186,7 @@ export default async function Page(props: { params: Promise<{ locale: string; sl
<div className={`flex flex-col min-h-screen ${isFullBleed ? '' : 'bg-white'}`}>
{/* Generic Hero Section (only for standard pages) */}
{!isFullBleed && (
<section className="bg-primary-dark text-white pt-40 pb-12 md:pt-56 md:pb-16 min-h-[40vh] md:min-h-[45vh] flex flex-col justify-end relative overflow-hidden">
<section key={`hero-section-${slug}`} className="bg-primary-dark text-white pt-40 pb-12 md:pt-56 md:pb-16 min-h-[40vh] md:min-h-[45vh] flex flex-col justify-end relative overflow-hidden">
<div className="absolute inset-0 opacity-20 pointer-events-none">
<div className="absolute top-0 left-0 w-full h-full bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-accent/50 via-transparent to-transparent" />
</div>

View File

@@ -19,6 +19,7 @@ import { TransitionProvider } from '@/components/providers/TransitionProvider';
import { PageTransitionShutter } from '@/components/providers/PageTransitionShutter';
import { InitialLoader } from '@/components/providers/InitialLoader';
import { CorporateBackground } from '@/components/decorations/CorporateBackground';
import { EUFundingBadge } from '@/components/decorations/EUFundingBadge';
const inter = Inter({
subsets: ['latin'],
@@ -202,6 +203,7 @@ export default async function Layout(props: {
<PageTransitionShutter />
<SkipLink />
<Header navLinks={navLinks} />
<EUFundingBadge />
<MobileBottomNav navLinks={navLinks} currentLocale={safeLocale} />
<main

View File

@@ -65,7 +65,7 @@ export default async function StandortDetail(props: { params: Promise<{ locale:
return (
<div className="flex flex-col min-h-screen bg-white">
{/* Generic Hero Section to match standard pages */}
<section className="bg-primary-dark text-white pt-40 pb-12 md:pt-56 md:pb-16 min-h-[40vh] md:min-h-[45vh] flex flex-col justify-end relative overflow-hidden">
<section key={`standort-hero-${slug}`} className="bg-primary-dark text-white pt-40 pb-12 md:pt-56 md:pb-16 min-h-[40vh] md:min-h-[45vh] flex flex-col justify-end relative overflow-hidden">
<div className="absolute inset-0 opacity-20 pointer-events-none">
<div className="absolute top-0 left-0 w-full h-full bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-accent/50 via-transparent to-transparent" />
</div>

View File

@@ -68,31 +68,7 @@ const defaultCertificates: Certificate[] = [
type: 'tax',
date: '13.02.2025',
},
{
title: 'Unbedenklichkeit IHK',
description: 'Industrie- und Handelskammer',
type: 'general',
},
{
title: 'Unbedenklichkeit HWK',
description: 'Handwerkskammer',
type: 'general',
},
{
title: 'Handelsregisterauszug',
description: 'Amtsgericht Cottbus',
type: 'general',
},
{
title: 'Gewerbe-Anmeldung',
description: 'Stadt Guben',
type: 'general',
},
{
title: 'Gewerbezentralregister',
description: 'Auskunft aus dem Register',
type: 'general',
},
];
export function CertificatesBlock({ badge, title, description, certificates = defaultCertificates, hideHeader = false }: CertificatesBlockProps) {

View File

@@ -2,6 +2,7 @@
import React from 'react';
import Image from 'next/image';
import { usePathname } from 'next/navigation';
import { motion } from 'framer-motion';
import { Badge, Container, Heading } from '@/components/ui';
import { Button } from '@/components/ui/Button';
@@ -39,13 +40,15 @@ const itemVariants = {
export const HeroSection: React.FC<HeroSectionProps> = (props) => {
const { title, badge, subtitle, backgroundImage, alignment, ctaLabel, ctaHref } = props;
const bgSrc = backgroundImage?.sizes?.card?.url || backgroundImage?.url;
const pathname = usePathname();
return (
<section
className={`relative min-h-[45vh] md:min-h-[55vh] flex items-center pt-32 pb-20 md:pt-36 md:pb-24 overflow-hidden bg-neutral-dark ${alignment === 'center' ? 'justify-center text-center' : ''}`}
className={`relative min-h-[45vh] md:min-h-[55vh] flex items-center pt-24 pb-16 md:pt-36 md:pb-24 overflow-hidden bg-neutral-dark ${alignment === 'center' ? 'justify-center text-center' : ''}`}
>
{bgSrc && (
<motion.div
key={`hero-bg-${pathname}`}
className="absolute inset-0 z-0"
initial={{ scale: 1.05, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
@@ -75,6 +78,7 @@ export const HeroSection: React.FC<HeroSectionProps> = (props) => {
)}
<Container className={`relative z-10 ${alignment === 'center' ? 'max-w-5xl' : ''}`}>
<motion.div
key={`hero-content-${pathname}`}
className={`max-w-4xl ${alignment === 'center' ? 'mx-auto' : ''}`}
variants={containerVariants}
initial="hidden"

View File

@@ -4,6 +4,7 @@ import { Button } from '@/components/ui/Button';
import Image from 'next/image';
import { motion, AnimatePresence } from 'framer-motion';
import { useState, useEffect } from 'react';
import { usePathname } from 'next/navigation';
interface HeroVideoProps {
data?: any;
@@ -28,6 +29,7 @@ export function HeroVideo(props: HeroVideoProps) {
const subtitle = props.subtitle || props.description || data?.subtitle || 'Wir verbinden Infrastruktur mit Präzision. Von Horizontalbohrungen bis zu komplexen Leitungsnetzen.';
const explicitNoVideo = props.videoUrl === 'none' || props.videoUrl === '';
const videoUrl = explicitNoVideo ? null : (props.videoUrl || data?.videoUrl || '/assets/dummy-hero.mp4');
const pathname = usePathname();
const posterSrc = props.backgroundImage?.url || props.posterImage?.url || data?.posterImage?.url || "/assets/photos/DJI_0048.JPG";
const posterAlt = props.backgroundImage?.alt || data?.posterImage?.alt || "E-TIB Gruppe Baustelle";
@@ -56,7 +58,7 @@ export function HeroVideo(props: HeroVideoProps) {
{videoUrl ? (
<video
key={videoUrl}
key={`${pathname}-${videoUrl}`}
className="absolute inset-0 w-full h-full object-cover z-1 pointer-events-none filter contrast-125 saturate-110 brightness-90"
src={videoUrl}
autoPlay
@@ -67,6 +69,7 @@ export function HeroVideo(props: HeroVideoProps) {
/>
) : (
<Image
key={`img-${pathname}-${posterSrc}`}
src={posterSrc}
alt={posterAlt}
fill
@@ -88,6 +91,7 @@ export function HeroVideo(props: HeroVideoProps) {
<div className="container relative z-[4] text-center px-4">
<motion.div
key={`hero-content-${pathname}`}
initial={{ opacity: 0, y: 40 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 1.2, ease: [0.16, 1, 0.3, 1] }}

View File

@@ -4,7 +4,7 @@ import React, { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { MapPin, Factory, Zap, CheckCircle2, ArrowUpRight } from 'lucide-react';
import Image from 'next/image';
import { useRouter } from 'next/navigation';
import { useRouter, usePathname } from 'next/navigation';
import Link from 'next/link';
import { AnimatedGlossyBorder } from '@/components/ui/AnimatedGlossyBorder';
import { Location, defaultLocations, minorLocations, projectLocations } from '@/lib/map-data';
@@ -37,6 +37,7 @@ export function InteractiveGermanyMap({
}: InteractiveGermanyMapProps) {
const [activeLocation, setActiveLocation] = useState<Location | null>(null);
const router = useRouter();
const pathname = usePathname();
const locale = useLocale();
const t = useTranslations('InteractiveGermanyMap');
const tStandard = useTranslations('StandardPage');
@@ -57,7 +58,7 @@ export function InteractiveGermanyMap({
const minorNodes = locations.filter((l) => l.type === 'minor_node');
return (
<div className={isHero ? "relative w-full" : "relative w-full max-w-7xl mx-auto py-16 md:py-24 px-4 sm:px-6 mt-16 md:mt-20"}>
<div key={isHero ? `hero-map-${pathname}` : undefined} className={isHero ? "relative w-full" : "relative w-full max-w-7xl mx-auto py-16 md:py-24 px-4 sm:px-6 mt-16 md:mt-20"}>
<div className={`bg-[#050B14] ${isHero ? 'pt-32 pb-16 md:pt-40 md:pb-24' : 'rounded-[2.5rem] md:rounded-[3.5rem] shadow-2xl border border-white/5'} overflow-visible relative group/map`}>
{/* Animated Border Glow */}
{!isHero && <AnimatedGlossyBorder opacity={0.7} className="z-30" />}

View File

@@ -133,14 +133,16 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
<Link
href={`/${locale}/referenzen/${ref.slug}`}
data-testid="reference-tile"
draggable={false}
onClick={(e) => {
if (dragDistanceRef.current > 15) {
e.preventDefault();
}
}}
className="block relative aspect-[16/10] bg-neutral-800 rounded-2xl overflow-hidden mb-5 border border-white/5 shadow-2xl"
className="block relative select-none aspect-[16/10] bg-neutral-800 rounded-2xl overflow-hidden mb-5 border border-white/5 shadow-2xl"
>
<Image
draggable={false}
src={imgSrc}
alt={ref.title}
fill

View File

@@ -79,7 +79,7 @@ export function SubCompanyTiles(props: SubCompanyTilesProps) {
)}
<motion.div
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6"
className="flex overflow-x-auto snap-x snap-mandatory pb-6 md:pb-0 md:grid md:grid-cols-2 lg:grid-cols-4 gap-4 md:gap-6 hide-scrollbar -mx-4 px-4 md:mx-0 md:px-0"
variants={containerVariants}
initial="hidden"
whileInView="visible"
@@ -236,7 +236,7 @@ export function SubCompanyTiles(props: SubCompanyTilesProps) {
<motion.div
key={index}
variants={itemVariants}
className="h-full"
className="h-full flex-shrink-0 snap-center w-[85%] sm:w-[70%] md:w-full"
>
{hasUrl ? (
isExternal ? (

View File

@@ -78,7 +78,7 @@ export function CorporateBackground() {
const y5 = useTransform(scrollYProgress, [0, 1], [0, -500]);
return (
<div className="absolute inset-0 z-[50] overflow-hidden pointer-events-none" aria-hidden="true">
<div className="absolute inset-0 z-[50] overflow-hidden pointer-events-none hidden md:block" aria-hidden="true">
{/* 1. Top Right - Medium, rotating moderately */}
<motion.div
style={{

View File

@@ -0,0 +1,41 @@
'use client';
import { motion } from 'framer-motion';
import Image from 'next/image';
export function EUFundingBadge() {
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 1.5, ease: [0.16, 1, 0.3, 1] }}
className="absolute top-0 right-0 w-[260px] md:w-[380px] h-[260px] md:h-[380px] z-[40] pointer-events-none select-none overflow-hidden"
style={{
// Perfekter weicher Fade, der sicherstellt, dass harte Kanten komplett unsichtbar werden
maskImage: 'radial-gradient(100% 100% at top right, black 10%, rgba(0,0,0,0.8) 30%, transparent 70%)',
WebkitMaskImage: 'radial-gradient(100% 100% at top right, black 10%, rgba(0,0,0,0.8) 30%, transparent 70%)',
}}
>
{/* Background Flag */}
<div className="absolute inset-0 w-full h-full opacity-95">
<Image
src="/assets/eu-flag.jpg"
alt="European Union Background"
fill
className="object-cover object-top"
priority
/>
</div>
{/* Text overlaid on the flag */}
<div className="absolute top-0 right-0 p-5 md:p-8 flex flex-col items-end text-right z-10 drop-shadow-[0_2px_8px_rgba(0,0,0,0.9)]">
<span className="block text-[9px] md:text-[11px] font-bold text-white/90 tracking-wide leading-snug drop-shadow-md">
Kofinanziert von der
</span>
<span className="block text-[11px] md:text-[13px] font-extrabold text-white tracking-wider leading-snug uppercase drop-shadow-md">
Europäischen Union
</span>
</div>
</motion.div>
);
}

View File

@@ -8,6 +8,7 @@ import { useAnalytics } from '../analytics/useAnalytics';
import { AnalyticsEvents } from '../analytics/analytics-events';
import { useState, useEffect, useRef, useCallback } from 'react';
import { ChevronRight } from 'lucide-react';
import { usePathname } from 'next/navigation';
import { AISearchResults } from '../search/AISearchResults';
const HeroIllustration = dynamic(() => import('./HeroIllustration'), { ssr: false });
const AIOrb = dynamic(() => import('../search/AIOrb'), { ssr: false });
@@ -15,6 +16,7 @@ const AIOrb = dynamic(() => import('../search/AIOrb'), { ssr: false });
export default function Hero({ data }: { data?: any }) {
const t = useTranslations('Home.hero');
const locale = useLocale();
const pathname = usePathname();
const { trackEvent } = useAnalytics();
const [searchQuery, setSearchQuery] = useState('');
@@ -85,7 +87,7 @@ export default function Hero({ data }: { data?: any }) {
return (
<>
<Section className="relative min-h-[85vh] md:h-[90vh] flex flex-col items-center justify-center overflow-hidden bg-primary py-12 md:py-0 lg:py-0">
<Section key={`home-hero-${pathname}`} className="relative min-h-[85vh] md:h-[90vh] flex flex-col items-center justify-center overflow-hidden bg-primary py-12 md:py-0 lg:py-0">
<Container className="relative z-10 text-center md:text-left text-white w-full order-2 md:order-none">
<div className="max-w-5xl mx-auto md:mx-0">
<div>

View File

@@ -18,7 +18,7 @@ export function Footer({ companyInfo }: FooterProps) {
const locale = useLocale();
return (
<footer className="bg-[#050B14] text-neutral-light py-10 md:py-16 relative overflow-hidden border-t border-white/5">
<footer className="bg-[#050B14] text-neutral-light pt-10 pb-28 md:py-16 relative overflow-hidden border-t border-white/5">
{/* Subtle background tech grid */}
<div className="absolute inset-0 bg-[linear-gradient(to_right,#8080800a_1px,transparent_1px),linear-gradient(to_bottom,#8080800a_1px,transparent_1px)] bg-[size:32px_32px] [mask-image:radial-gradient(ellipse_80%_50%_at_50%_0%,#000_70%,transparent_100%)] pointer-events-none" />
@@ -28,7 +28,7 @@ export function Footer({ companyInfo }: FooterProps) {
<div className="container relative z-10 grid grid-cols-1 md:grid-cols-4 lg:grid-cols-12 gap-4 md:gap-6 lg:gap-8 mb-8 md:mb-12">
{/* Brand Column Bento */}
<div className="col-span-1 md:col-span-4 lg:col-span-5 flex flex-col justify-between bg-white/[0.02] border border-white/5 rounded-3xl p-6 md:p-8 lg:p-10 backdrop-blur-md relative group overflow-hidden">
<div className="col-span-1 md:col-span-4 lg:col-span-5 flex flex-col justify-between bg-white/[0.02] border border-white/5 rounded-3xl p-5 md:p-8 lg:p-10 backdrop-blur-md relative group overflow-hidden">
<div className="absolute inset-0 bg-gradient-to-br from-white/[0.04] to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-700" />
<div className="space-y-4 md:space-y-6 relative z-10">
@@ -96,7 +96,7 @@ export function Footer({ companyInfo }: FooterProps) {
<div className="col-span-1 md:col-span-4 lg:col-span-7 grid grid-cols-1 sm:grid-cols-2 gap-4 md:gap-6 lg:gap-8">
{/* Company Column */}
<div className="bg-white/[0.02] border border-white/5 rounded-3xl p-6 md:p-8 lg:p-10 backdrop-blur-md relative group overflow-hidden flex flex-col">
<div className="bg-white/[0.02] border border-white/5 rounded-3xl p-5 md:p-8 lg:p-10 backdrop-blur-md relative group overflow-hidden flex flex-col">
<div className="absolute inset-0 bg-gradient-to-bl from-white/[0.04] to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-700 pointer-events-none" />
<h4 className="font-heading font-bold uppercase tracking-widest text-sm mb-6 md:mb-8 text-white">
{locale === 'de' ? 'Unternehmen' : 'Company'}
@@ -124,7 +124,7 @@ export function Footer({ companyInfo }: FooterProps) {
</div>
{/* Legal Column */}
<div className="bg-white/[0.02] border border-white/5 rounded-3xl p-6 md:p-8 lg:p-10 backdrop-blur-md relative group overflow-hidden flex flex-col">
<div className="bg-white/[0.02] border border-white/5 rounded-3xl p-5 md:p-8 lg:p-10 backdrop-blur-md relative group overflow-hidden flex flex-col">
<div className="absolute inset-0 bg-gradient-to-tr from-white/[0.04] to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-700 pointer-events-none" />
<h4 className="font-heading font-bold uppercase tracking-widest text-sm mb-6 md:mb-8 text-white">
{locale === 'de' ? 'Rechtliches' : 'Legal'}

View File

@@ -72,7 +72,7 @@ export function Header({ navLinks }: HeaderProps) {
className={`fixed top-0 left-0 right-0 z-50 flex justify-center transition-all duration-700 ease-[cubic-bezier(0.16,1,0.3,1)] ${
isStandard
? 'bg-[#050B14]/95 backdrop-blur-xl border-b border-white/10'
: (isSolidMode ? 'pt-4 px-4' : 'pt-0 px-0')
: (isSolidMode ? 'pt-4 px-4 sm:px-6' : 'pt-0 px-0')
}`}
>
<div
@@ -80,7 +80,7 @@ export function Header({ navLinks }: HeaderProps) {
isStandard
? 'py-4 px-6 md:px-8'
: isSolidMode
? 'rounded-full bg-white/50 backdrop-blur-xl shadow-[0_8px_32px_rgba(0,0,0,0.08)] border border-white/40 py-2.5 px-6 md:px-8'
? 'rounded-full bg-white/50 backdrop-blur-xl shadow-[0_8px_32px_rgba(0,0,0,0.08)] border border-white/40 py-2.5 px-8 md:px-10'
: 'rounded-none bg-transparent py-6 px-4 md:px-8 border border-transparent'
}`}
>
@@ -165,6 +165,20 @@ export function Header({ navLinks }: HeaderProps) {
function NavItem({ link, currentLocale, pathname, isSolidMode }: { link: NavLink, currentLocale: string, pathname: string, isSolidMode: boolean }) {
const [isHovered, setIsHovered] = React.useState(false);
// Close dropdown on navigation
React.useEffect(() => {
setIsHovered(false);
}, [pathname]);
// Close dropdown on scroll
React.useEffect(() => {
const handleScroll = () => {
if (isHovered) setIsHovered(false);
};
window.addEventListener('scroll', handleScroll, { passive: true });
return () => window.removeEventListener('scroll', handleScroll);
}, [isHovered]);
const mappedUrl = link.url.startsWith('/') && !link.url.match(/^\/(en|de)/)
? `/${currentLocale}${link.url}`
: link.url;
@@ -237,6 +251,7 @@ function NavItem({ link, currentLocale, pathname, isSolidMode }: { link: NavLink
<TransitionLink
key={child.url}
href={childUrl}
onClick={() => setIsHovered(false)}
className={`group/dropitem relative block px-4 py-3.5 rounded-2xl text-sm font-medium transition-all duration-300 overflow-hidden ${
isSolidMode
? isChildActive ? 'bg-primary/10 text-primary' : 'text-neutral-600 hover:text-primary hover:bg-neutral-50/80'

View File

@@ -5,13 +5,14 @@ excerpt: "Werden Sie Teil eines dynamischen Teams und bauen Sie mit uns die Infr
layout: "fullBleed"
---
<HomeHero
<HeroSection
badge="Karriere"
title="Zukunft bauen"
description="Suchen Sie eine neue Herausforderung in einem zukunftssicheren Markt? Bei E-TIB arbeiten wir täglich daran, Deutschland zu vernetzen und die Energiewende voranzutreiben."
videoUrl="/assets/videos/web/hero-kabelpflug.mp4"
linkText="Offene Stellen"
linkHref="#stellen"
subtitle="Suchen Sie eine neue Herausforderung in einem zukunftssicheren Markt? Bei E-TIB arbeiten wir täglich daran, Deutschland zu vernetzen und die Energiewende voranzutreiben."
backgroundImage={{ url: '/assets/photos/Etib_E-tib_Tiefbau_Guben_Netzanbindung_Energie-28.jpg' }}
alignment="center"
ctaLabel="Offene Stellen"
ctaHref="#stellen"
/>
<JobListingBlock showFairs={false} showJobs={true} />

View File

@@ -5,13 +5,14 @@ excerpt: "Become part of a dynamic team and help us build the infrastructure of
layout: "fullBleed"
---
<HomeHero
<HeroSection
badge="Careers"
title="Build the Future"
description="Are you looking for a new challenge in a future-proof market? At E-TIB, we work daily to network Germany and drive the energy transition forward."
videoUrl="/assets/videos/web/hero-kabelpflug.mp4"
linkText="Open Positions"
linkHref="#stellen"
subtitle="Are you looking for a new challenge in a future-proof market? At E-TIB, we work daily to network Germany and drive the energy transition forward."
backgroundImage={{ url: '/assets/photos/Etib_E-tib_Tiefbau_Guben_Netzanbindung_Energie-28.jpg' }}
alignment="center"
ctaLabel="Open Positions"
ctaHref="#stellen"
/>
<JobListingBlock showFairs={false} showJobs={true} />

View File

@@ -60,30 +60,6 @@ layout: "fullBleed"
type: 'tax',
date: '13.02.2025',
},
{
title: 'IHK Clearance Certificate',
description: 'Chamber of Industry and Commerce',
type: 'general',
},
{
title: 'HWK Clearance Certificate',
description: 'Chamber of Crafts',
type: 'general',
},
{
title: 'Commercial Register Extract',
description: 'District Court Cottbus',
type: 'general',
},
{
title: 'Trade Registration',
description: 'City of Guben',
type: 'general',
},
{
title: 'Central Trade Register',
description: 'Information from the Register',
type: 'general',
}
]}
/>

View File

@@ -143,7 +143,7 @@
"prepare": "husky",
"preinstall": "npx only-allow pnpm"
},
"version": "2.2.12",
"version": "2.2.13-rc.0",
"pnpm": {
"onlyBuiltDependencies": [
"@parcel/watcher",

Binary file not shown.

After

Width:  |  Height:  |  Size: 572 KiB

84
scripts/priority-gate.sh Executable file
View File

@@ -0,0 +1,84 @@
#!/bin/bash
set -e
if [ -z "$GITEA_PAT" ]; then
echo "No GITEA_PAT provided, skipping priority gate."
exit 0
fi
if [ -z "$CURRENT_RUN_ID" ] || [ -z "$CURRENT_TARGET" ] || [ -z "$GITHUB_REPOSITORY" ]; then
echo "Missing required environment variables."
exit 1
fi
declare -A PRIO=( ["production"]=40 ["staging"]=30 ["testing"]=20 ["branch"]=10 )
CURRENT_PRIO=${PRIO[$CURRENT_TARGET]:-0}
echo "Current Run: $CURRENT_RUN_ID, Target: $CURRENT_TARGET, Priority: $CURRENT_PRIO"
API_BASE="https://git.infra.mintel.me/api/v1/repos/$GITHUB_REPOSITORY/actions/runs"
# Fetch running and waiting workflows
LIMIT=50
RESPONSE=$(curl -s -H "Authorization: token $GITEA_PAT" "$API_BASE?limit=$LIMIT")
# Verify response is valid JSON
if ! echo "$RESPONSE" | jq empty 2>/dev/null; then
echo "Failed to parse API response from Gitea."
echo "$RESPONSE"
exit 1
fi
# Use jq to extract running or waiting jobs that are NOT the current one
RUNS=$(echo "$RESPONSE" | jq -c '.[] | select(.id != '"$CURRENT_RUN_ID"') | select(.status == "running" or .status == "waiting")')
if [ -z "$RUNS" ]; then
echo "No other running/waiting workflows found."
exit 0
fi
while IFS= read -r run; do
if [ -z "$run" ]; then continue; fi
RUN_ID=$(echo "$run" | jq -r '.id')
REF=$(echo "$run" | jq -r '.ref')
REF_NAME=${REF#refs/tags/}
REF_NAME=${REF_NAME#refs/heads/}
# Determine target of the running workflow
TARGET="branch"
if [ "$REF" == "refs/heads/main" ] || [ "$REF_NAME" == "main" ]; then
TARGET="testing"
elif [[ "$REF" == refs/tags/* ]] || [[ "$REF" == refs/heads/v* ]]; then
if [[ "$REF_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
TARGET="production"
else
TARGET="staging"
fi
fi
RUN_PRIO=${PRIO[$TARGET]:-0}
echo "Found running workflow ID: $RUN_ID (Target: $TARGET, Priority: $RUN_PRIO)"
if [ "$CURRENT_PRIO" -gt "$RUN_PRIO" ]; then
echo "Current workflow has HIGHER priority ($CURRENT_PRIO > $RUN_PRIO). Canceling run $RUN_ID..."
curl -s -X POST -H "Authorization: token $GITEA_PAT" "$API_BASE/$RUN_ID/cancel"
elif [ "$CURRENT_PRIO" -lt "$RUN_PRIO" ]; then
echo "Current workflow has LOWER priority ($CURRENT_PRIO < $RUN_PRIO). Aborting self."
curl -s -X POST -H "Authorization: token $GITEA_PAT" "$API_BASE/$CURRENT_RUN_ID/cancel"
exit 1
else
# Equal priority: Newer wins
if [ "$CURRENT_RUN_ID" -gt "$RUN_ID" ]; then
echo "Equal priority, but current is newer. Canceling run $RUN_ID..."
curl -s -X POST -H "Authorization: token $GITEA_PAT" "$API_BASE/$RUN_ID/cancel"
else
echo "Equal priority, but current is older. Aborting self."
curl -s -X POST -H "Authorization: token $GITEA_PAT" "$API_BASE/$CURRENT_RUN_ID/cancel"
exit 1
fi
fi
done <<< "$RUNS"
echo "Priority gate cleared. Continuing deployment."