This commit is contained in:
Linrador 2025-09-09 13:12:57 +02:00
parent 5531a68da0
commit 144335f503
10 changed files with 668 additions and 521 deletions

View File

@ -1,12 +1,13 @@
'use client' 'use client'
import React from 'react'
type CardWidth = type CardWidth =
| 'sm' // 24rem (maxwsm) | 'sm' // 24rem
| 'md' // 28rem (maxwmd) | 'md' // 28rem
| 'lg' // 32rem (maxwlg) | 'lg' // 32rem
| 'xl' // 36rem (maxwxl) | 'xl' // 36rem
| '2xl' // 42rem (maxw2xl) | '2xl' // 42rem
| 'full' // 100% (wfull) | 'full' // 100%
| 'auto' // keine Begrenzung | 'auto' // keine Begrenzung
type CardProps = { type CardProps = {
@ -15,24 +16,31 @@ type CardProps = {
children?: React.ReactNode children?: React.ReactNode
/** links, rechts oder (Default) zentriert */ /** links, rechts oder (Default) zentriert */
align?: 'left' | 'right' | 'center' align?: 'left' | 'right' | 'center'
/** gewünschte MaxBreite (Default: lg) */ /** gewünschte Max-Breite (Default: lg) */
maxWidth?: CardWidth maxWidth?: CardWidth
/** zusätzliche Klassen für den Body-Bereich (Padding etc.) */
className?: string
/** bei Überlauf innerhalb der Card scrollen (Default: false) */
bodyScrollable?: boolean
/** Höhe der Card (z.B. 'inherit', '100%', '80vh', 600 ...) */
height?: 'inherit' | string | number
} }
export default function Card({ export default function Card({
children, children,
align = 'center', align = 'center',
maxWidth = 'lg' maxWidth = 'lg',
className,
bodyScrollable = false,
height,
}: CardProps) { }: CardProps) {
/* Ausrichtung bestimmen */ // Ausrichtung
const alignClasses = const alignClasses =
align === 'left' align === 'left' ? 'mr-auto'
? 'mr-auto' : align === 'right' ? 'ml-auto'
: align === 'right' : 'mx-auto'
? 'ml-auto'
: 'mx-auto' // center
/* Breite in TailwindKlasse übersetzen */ // Breite
const widthClasses: Record<CardWidth, string> = { const widthClasses: Record<CardWidth, string> = {
sm: 'max-w-sm', sm: 'max-w-sm',
md: 'max-w-md', md: 'max-w-md',
@ -40,19 +48,32 @@ export default function Card({
xl: 'max-w-xl', xl: 'max-w-xl',
'2xl': 'max-w-2xl', '2xl': 'max-w-2xl',
full: 'w-full', full: 'w-full',
auto: '' // keine Begrenzung auto: '',
} }
// style.height ableiten (Zahl => px)
const style: React.CSSProperties | undefined = height != null
? { height: typeof height === 'number' ? `${height}px` : height }
: undefined
return ( return (
<div <div
className={` style={style}
flex flex-col bg-white border border-gray-200 shadow-2xs rounded-xl className={[
dark:bg-neutral-800 dark:border-neutral-700 dark:shadow-neutral-700/70 'flex flex-col rounded-xl border border-gray-200 bg-white shadow-2xs',
${alignClasses} ${widthClasses[maxWidth]} 'dark:bg-neutral-800 dark:border-neutral-700 dark:shadow-neutral-700/70',
`} alignClasses,
widthClasses[maxWidth],
].join(' ')}
> >
<div className="p-3"> <div
{children && <div>{children}</div>} className={[
'flex-1 min-h-0 p-3',
bodyScrollable ? 'overflow-auto' : 'overflow-hidden',
className ?? '',
].join(' ')}
>
{children}
</div> </div>
</div> </div>
) )

View File

@ -106,6 +106,31 @@ export default function MatchReadyOverlay({
try { new Audio('/assets/sounds/menu_accept.wav').play() } catch {} try { new Audio('/assets/sounds/menu_accept.wav').play() } catch {}
} }
// --- sofort verbinden helper ---
const startConnectingNow = useCallback(() => {
if (finished) return
stopBeeps()
setFinished(true)
setConnecting(true)
try { sound.play('loading') } catch {}
const doConnect = () => {
try { window.location.href = effectiveConnectHref }
catch {
try {
const a = document.createElement('a')
a.href = effectiveConnectHref
document.body.appendChild(a)
a.click()
a.remove()
} catch {}
}
try { onTimeout?.() } catch {}
}
// mini delay für UI Feedback
setTimeout(doConnect, 200)
}, [finished, effectiveConnectHref, onTimeout])
// NUR nach Acceptance laden/aktualisieren // NUR nach Acceptance laden/aktualisieren
const loadReady = useCallback(async () => { const loadReady = useCallback(async () => {
try { try {
@ -266,6 +291,14 @@ export default function MatchReadyOverlay({
return () => { cleanup(); stopBeeps() } return () => { cleanup(); stopBeeps() }
}, [showContent]) // vorher: [isVisible] }, [showContent]) // vorher: [isVisible]
// ⏩ Sofort verbinden, wenn alle bereit sind
useEffect(() => {
if (!isVisible) return
if (total > 0 && countReady >= total && !finished) {
startConnectingNow()
}
}, [isVisible, total, countReady, finished, startConnectingNow])
// ----- countdown / timeout ----- // ----- countdown / timeout -----
const rafRef = useRef<number | null>(null) const rafRef = useRef<number | null>(null)
useEffect(() => { useEffect(() => {
@ -274,29 +307,12 @@ export default function MatchReadyOverlay({
const t = Date.now() const t = Date.now()
setNow(t) setNow(t)
if (effectiveDeadline - t <= 0 && !finished) { if (effectiveDeadline - t <= 0 && !finished) {
if (accepted) {
startConnectingNow()
} else {
stopBeeps() stopBeeps()
setFinished(true) setFinished(true)
setShowWaitHint(true)
if (accepted) {
setConnecting(true)
try { sound.play('loading') } catch {}
const doConnect = () => {
try { window.location.href = effectiveConnectHref }
catch {
try {
const a = document.createElement('a')
a.href = effectiveConnectHref
document.body.appendChild(a)
a.click()
a.remove()
} catch {}
}
try { onTimeout?.() } catch {}
}
setTimeout(doConnect, 2000)
} else {
setShowWaitHint(true) // ⬅️ triggert Hinweis „Dein Team wartet auf dich!“
} }
return return
} }
@ -378,7 +394,7 @@ export default function MatchReadyOverlay({
{/* Backdrop: 2s-Fade */} {/* Backdrop: 2s-Fade */}
<div <div
className={[ className={[
'absolute inset-0 bg-black/30 transition-opacity duration-[2000ms] ease-out', 'absolute inset-0 bg-black/60 transition-opacity duration-[300ms] ease-out',
showBackdrop ? 'opacity-100' : 'opacity-0' showBackdrop ? 'opacity-100' : 'opacity-0'
].join(' ')} ].join(' ')}
/> />

View File

@ -1,91 +0,0 @@
'use client'
import Link from "next/link"
import { usePathname } from 'next/navigation'
export default function Navbar({ children }: { children?: React.ReactNode }) {
const pathname = usePathname()
return (
<>
<header className="flex flex-wrap sm:justify-start sm:flex-nowrap z-50 w-full bg-white text-sm py-3 sm:py-0 dark:bg-neutral-900">
<nav className="max-w-[85rem] w-full mx-auto px-4 md:px-6 lg:px-8">
<div className="relative sm:flex sm:items-center">
<div className="flex items-center justify-between">
<a className="flex-none font-semibold text-xl text-black focus:outline-hidden focus:opacity-80 dark:text-white" href="#" aria-label="Brand">Brand</a>
<div className="sm:hidden">
<button type="button" className="hs-collapse-toggle p-2 inline-flex justify-center items-center gap-x-2 rounded-lg border border-gray-200 bg-white text-gray-800 shadow-2xs hover:bg-gray-50 focus:outline-hidden focus:bg-gray-50 disabled:opacity-50 disabled:pointer-events-none dark:bg-transparent dark:border-neutral-700 dark:text-white dark:hover:bg-white/10 dark:focus:bg-white/10" id="hs-navbar-to-overlay-collapse" aria-haspopup="dialog" aria-expanded="false" aria-controls="hs-navbar-to-overlay" aria-label="Toggle navigation" data-hs-overlay="#hs-navbar-to-overlay" data-hs-overlay-options='{"moveOverlayToBody": 640}'>
<svg className="shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="3" x2="21" y1="6" y2="6"/><line x1="3" x2="21" y1="12" y2="12"/><line x1="3" x2="21" y1="18" y2="18"/></svg>
</button>
</div>
</div>
<div id="hs-navbar-to-overlay" className="hs-overlay hs-overlay-open:translate-x-0 [--auto-close:sm] -translate-x-full fixed top-0 start-0 transition-all duration-300 transform h-full w-full sm:w-96 z-60 bg-white border-e sm:static sm:block sm:h-auto sm:w-full sm:border-e-transparent sm:transition-none sm:transform-none sm:translate-x-0 sm:z-40 dark:bg-neutral-800 sm:dark:bg-neutral-900 dark:border-e-neutral-700 sm:dark:border-e-transparent hidden" role="dialog" tabIndex={-1} aria-label="Sidebar" data-hs-overlay-close-on-resize>
<div className="overflow-hidden overflow-y-auto h-full [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-track]:bg-gray-100 [&::-webkit-scrollbar-thumb]:bg-gray-300 dark:[&::-webkit-scrollbar-track]:bg-neutral-700 dark:[&::-webkit-scrollbar-thumb]:bg-neutral-500">
<div className="flex flex-col gap-y-3 sm:gap-y-0 sm:flex-row sm:items-center sm:justify-end p-2 sm:p-0">
<div className="py-3 sm:hidden flex justify-between items-center border-b border-gray-200 dark:border-neutral-700">
<h3 className="font-medium text-gray-800 dark:text-neutral-200">
Menu
</h3>
<button type="button" className="py-1.5 px-2 inline-flex justify-center items-center gap-x-1 rounded-full border border-gray-200 text-xs text-gray-800 hover:bg-gray-100 focus:outline-hidden focus:bg-gray-100 disabled:opacity-50 disabled:pointer-events-none dark:bg-neutral-800 dark:border-neutral-700 dark:hover:bg-neutral-700 dark:text-neutral-200 dark:focus:bg-neutral-700" aria-label="Close" data-hs-overlay="#hs-navbar-to-overlay" aria-expanded="true">
Close
<svg className="shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"></path><path d="m6 6 12 12"></path></svg>
</button>
</div>
<a className="sm:p-2 font-medium text-sm text-blue-500 focus:outline-hidden" href="#" aria-current="page">Active</a>
<div className="hs-dropdown [--strategy:static] sm:[--strategy:absolute] [--adaptive:none] sm:[--trigger:hover] [--is-collapse:true] sm:[--is-collapse:false] ">
<button id="hs-mega-menu" type="button" className="hs-dropdown-toggle sm:p-2 flex items-center w-full text-gray-600 font-medium text-sm hover:text-gray-400 focus:outline-hidden focus:text-gray-400 dark:text-neutral-400 dark:hover:text-neutral-500 dark:focus:text-neutral-500" aria-haspopup="menu" aria-expanded="false" aria-label="Mega Menu">
Mega Menu
<svg className="hs-dropdown-open:-rotate-180 sm:hs-dropdown-open:rotate-0 duration-300 ms-2 shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>
</button>
<div className="hs-dropdown-menu sm:transition-[opacity,margin] sm:ease-in-out sm:duration-[150ms] hs-dropdown-open:opacity-100 opacity-0 w-full hidden z-10 sm:mt-3 top-full start-0 min-w-60 bg-white sm:shadow-md rounded-lg py-2 sm:px-2 dark:bg-neutral-800 sm:dark:border dark:border-neutral-700 dark:divide-neutral-700 before:absolute" role="menu" aria-orientation="vertical" aria-labelledby="hs-mega-menu">
<div className="sm:grid sm:grid-cols-3">
<div className="flex flex-col">
<a className="flex items-center gap-x-3.5 py-2 px-3 rounded-lg text-sm text-gray-800 hover:bg-gray-100 focus:outline-hidden focus:bg-gray-100 dark:text-neutral-400 dark:hover:bg-neutral-700 dark:hover:text-neutral-300 dark:focus:bg-neutral-700 dark:focus:text-neutral-300" href="#">
About
</a>
<a className="flex items-center gap-x-3.5 py-2 px-3 rounded-lg text-sm text-gray-800 hover:bg-gray-100 focus:outline-hidden focus:bg-gray-100 dark:text-neutral-400 dark:hover:bg-neutral-700 dark:hover:text-neutral-300 dark:focus:bg-neutral-700 dark:focus:text-neutral-300" href="#">
Services
</a>
<a className="flex items-center gap-x-3.5 py-2 px-3 rounded-lg text-sm text-gray-800 hover:bg-gray-100 focus:outline-hidden focus:bg-gray-100 dark:text-neutral-400 dark:hover:bg-neutral-700 dark:hover:text-neutral-300 dark:focus:bg-neutral-700 dark:focus:text-neutral-300" href="#">
Customer Story
</a>
</div>
<div className="flex flex-col">
<a className="flex items-center gap-x-3.5 py-2 px-3 rounded-lg text-sm text-gray-800 hover:bg-gray-100 focus:outline-hidden focus:bg-gray-100 dark:text-neutral-400 dark:hover:bg-neutral-700 dark:hover:text-neutral-300 dark:focus:bg-neutral-700 dark:focus:text-neutral-300" href="#">
Careers
</a>
<a className="flex items-center gap-x-3.5 py-2 px-3 rounded-lg text-sm text-gray-800 hover:bg-gray-100 focus:outline-hidden focus:bg-gray-100 dark:text-neutral-400 dark:hover:bg-neutral-700 dark:hover:text-neutral-300 dark:focus:bg-neutral-700 dark:focus:text-neutral-300" href="#">
Careers: Overview
</a>
<a className="flex items-center gap-x-3.5 py-2 px-3 rounded-lg text-sm text-gray-800 hover:bg-gray-100 focus:outline-hidden focus:bg-gray-100 dark:text-neutral-400 dark:hover:bg-neutral-700 dark:hover:text-neutral-300 dark:focus:bg-neutral-700 dark:focus:text-neutral-300" href="#">
Careers: Apply
</a>
</div>
<div className="flex flex-col">
<a className="flex items-center gap-x-3.5 py-2 px-3 rounded-lg text-sm text-gray-800 hover:bg-gray-100 focus:outline-hidden focus:bg-gray-100 dark:text-neutral-400 dark:hover:bg-neutral-700 dark:hover:text-neutral-300 dark:focus:bg-neutral-700 dark:focus:text-neutral-300" href="#">
Log In
</a>
<a className="flex items-center gap-x-3.5 py-2 px-3 rounded-lg text-sm text-gray-800 hover:bg-gray-100 focus:outline-hidden focus:bg-gray-100 dark:text-neutral-400 dark:hover:bg-neutral-700 dark:hover:text-neutral-300 dark:focus:bg-neutral-700 dark:focus:text-neutral-300" href="#">
Sign Up
</a>
</div>
</div>
</div>
</div>
<a className="sm:p-2 font-medium text-sm text-gray-600 hover:text-gray-400 focus:outline-hidden focus:text-gray-400 dark:text-neutral-400 dark:hover:text-neutral-500 dark:focus:text-neutral-500" href="#">Project</a>
</div>
</div>
</div>
</div>
</nav>
</header>
</>
)
}

View File

@ -34,7 +34,7 @@ export default function PremierRankBadge({ rank }: Props) {
<rect x="5" y="0" width="3" height="28" fill="#A7B8CC" /> <rect x="5" y="0" width="3" height="28" fill="#A7B8CC" />
</svg> </svg>
<div className="rank-box" style={{ backgroundColor: '#464952', color: '#A7B8CC' }}> <div className="rank-box" style={{ backgroundColor: '#464952', color: '#A7B8CC' }}>
<span></span> <span></span>
</div> </div>
</div> </div>
) )

View File

@ -14,7 +14,7 @@ export default function Profile() {
<div className="absolute bottom-0 -end-2"> <div className="absolute bottom-0 -end-2">
<button type="button" className="group p-2 max-w-[125px] inline-flex justify-center items-center gap-x-2 text-start bg-red-600 border border-red-600 text-white text-xs font-medium rounded-full shadow-2xs align-middle focus:outline-hidden focus:bg-red-500" data-hs-overlay="#hs-pro-dsm"> <button type="button" className="group p-2 max-w-[125px] inline-flex justify-center items-center gap-x-2 text-start bg-red-600 border border-red-600 text-white text-xs font-medium rounded-full shadow-2xs align-middle focus:outline-hidden focus:bg-red-500" data-hs-overlay="#hs-pro-dsm">
<svg className="shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M8 14s1.5 2 4 2 4-2 4-2"/><line x1="9" x2="9.01" y1="9" y2="9"/><line x1="15" x2="15.01" y1="9" y2="9"/></svg> <svg className="shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"/><path d="M8 14s1.5 2 4 2 4-2 4-2"/><line x1="9" x2="9.01" y1="9" y2="9"/><line x1="15" x2="15.01" y1="9" y2="9"/></svg>
<span className="group-hover:block hidden">Offline</span> <span className="group-hover:block hidden">Offline</span>
</button> </button>
</div> </div>
@ -48,19 +48,19 @@ export default function Profile() {
}'> }'>
<nav className="hs-scroll-nav-body flex flex-nowrap gap-x-1 overflow-x-auto [&::-webkit-scrollbar]:h-0 snap-x snap-mandatory pb-1.5"> <nav className="hs-scroll-nav-body flex flex-nowrap gap-x-1 overflow-x-auto [&::-webkit-scrollbar]:h-0 snap-x snap-mandatory pb-1.5">
<a className="snap-start relative inline-flex flex-nowrap items-center gap-x-2 px-2.5 py-1.5 hover:bg-gray-100 text-gray-500 hover:text-gray-800 text-sm whitespace-nowrap rounded-lg disabled:opacity-50 disabled:pointer-events-none focus:outline-hidden focus:bg-gray-100 after:absolute after:-bottom-0.5 after:inset-x-0 after:z-10 after:w-1/4 after:h-0.5 after:rounded-full after:mx-auto after:pointer-events-none dark:text-neutral-500 dark:hover:text-neutral-300 dark:hover:bg-neutral-700 dark:focus:bg-neutral-700 after:bg-gray-600 text-gray-800 font-medium dark:bg-neutral-800 dark:text-white dark:after:bg-neutral-200 active" href="../../pro/dashboard/user-profile-my-profile.html"> <a className="snap-start relative inline-flex flex-nowrap items-center gap-x-2 px-2.5 py-1.5 hover:bg-gray-100 text-gray-500 hover:text-gray-800 text-sm whitespace-nowrap rounded-lg disabled:opacity-50 disabled:pointer-events-none focus:outline-hidden focus:bg-gray-100 after:absolute after:-bottom-0.5 after:inset-x-0 after:z-10 after:w-1/4 after:h-0.5 after:rounded-full after:mx-auto after:pointer-events-none dark:text-neutral-500 dark:hover:text-neutral-300 dark:hover:bg-neutral-700 dark:focus:bg-neutral-700 after:bg-gray-600 text-gray-800 font-medium dark:bg-neutral-800 dark:text-white dark:after:bg-neutral-200 active" href="../../pro/dashboard/user-profile-my-profile.html">
<svg className="shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="15" r="3"/><circle cx="9" cy="7" r="4"/><path d="M10 15H6a4 4 0 0 0-4 4v2"/><path d="m21.7 16.4-.9-.3"/><path d="m15.2 13.9-.9-.3"/><path d="m16.6 18.7.3-.9"/><path d="m19.1 12.2.3-.9"/><path d="m19.6 18.7-.4-1"/><path d="m16.8 12.3-.4-1"/><path d="m14.3 16.6 1-.4"/><path d="m20.7 13.8 1-.4"/></svg> <svg className="shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="18" cy="15" r="3"/><circle cx="9" cy="7" r="4"/><path d="M10 15H6a4 4 0 0 0-4 4v2"/><path d="m21.7 16.4-.9-.3"/><path d="m15.2 13.9-.9-.3"/><path d="m16.6 18.7.3-.9"/><path d="m19.1 12.2.3-.9"/><path d="m19.6 18.7-.4-1"/><path d="m16.8 12.3-.4-1"/><path d="m14.3 16.6 1-.4"/><path d="m20.7 13.8 1-.4"/></svg>
My Profile My Profile
</a> </a>
<a className="snap-start relative inline-flex flex-nowrap items-center gap-x-2 px-2.5 py-1.5 hover:bg-gray-100 text-gray-500 hover:text-gray-800 text-sm whitespace-nowrap rounded-lg disabled:opacity-50 disabled:pointer-events-none focus:outline-hidden focus:bg-gray-100 after:absolute after:-bottom-0.5 after:inset-x-0 after:z-10 after:w-1/4 after:h-0.5 after:rounded-full after:mx-auto after:pointer-events-none dark:text-neutral-500 dark:hover:text-neutral-300 dark:hover:bg-neutral-700 dark:focus:bg-neutral-700 " href="../../pro/dashboard/user-profile-teams.html"> <a className="snap-start relative inline-flex flex-nowrap items-center gap-x-2 px-2.5 py-1.5 hover:bg-gray-100 text-gray-500 hover:text-gray-800 text-sm whitespace-nowrap rounded-lg disabled:opacity-50 disabled:pointer-events-none focus:outline-hidden focus:bg-gray-100 after:absolute after:-bottom-0.5 after:inset-x-0 after:z-10 after:w-1/4 after:h-0.5 after:rounded-full after:mx-auto after:pointer-events-none dark:text-neutral-500 dark:hover:text-neutral-300 dark:hover:bg-neutral-700 dark:focus:bg-neutral-700 " href="../../pro/dashboard/user-profile-teams.html">
<svg className="shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg> <svg className="shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
Teams Teams
</a> </a>
<a className="snap-start relative inline-flex flex-nowrap items-center gap-x-2 px-2.5 py-1.5 hover:bg-gray-100 text-gray-500 hover:text-gray-800 text-sm whitespace-nowrap rounded-lg disabled:opacity-50 disabled:pointer-events-none focus:outline-hidden focus:bg-gray-100 after:absolute after:-bottom-0.5 after:inset-x-0 after:z-10 after:w-1/4 after:h-0.5 after:rounded-full after:mx-auto after:pointer-events-none dark:text-neutral-500 dark:hover:text-neutral-300 dark:hover:bg-neutral-700 dark:focus:bg-neutral-700 " href="../../pro/dashboard/user-profile-files.html"> <a className="snap-start relative inline-flex flex-nowrap items-center gap-x-2 px-2.5 py-1.5 hover:bg-gray-100 text-gray-500 hover:text-gray-800 text-sm whitespace-nowrap rounded-lg disabled:opacity-50 disabled:pointer-events-none focus:outline-hidden focus:bg-gray-100 after:absolute after:-bottom-0.5 after:inset-x-0 after:z-10 after:w-1/4 after:h-0.5 after:rounded-full after:mx-auto after:pointer-events-none dark:text-neutral-500 dark:hover:text-neutral-300 dark:hover:bg-neutral-700 dark:focus:bg-neutral-700 " href="../../pro/dashboard/user-profile-files.html">
<svg className="shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15.5 2H8.6c-.4 0-.8.2-1.1.5-.3.3-.5.7-.5 1.1v12.8c0 .4.2.8.5 1.1.3.3.7.5 1.1.5h9.8c.4 0 .8-.2 1.1-.5.3-.3.5-.7.5-1.1V6.5L15.5 2z"/><path d="M3 7.6v12.8c0 .4.2.8.5 1.1.3.3.7.5 1.1.5h9.8"/><path d="M15 2v5h5"/></svg> <svg className="shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M15.5 2H8.6c-.4 0-.8.2-1.1.5-.3.3-.5.7-.5 1.1v12.8c0 .4.2.8.5 1.1.3.3.7.5 1.1.5h9.8c.4 0 .8-.2 1.1-.5.3-.3.5-.7.5-1.1V6.5L15.5 2z"/><path d="M3 7.6v12.8c0 .4.2.8.5 1.1.3.3.7.5 1.1.5h9.8"/><path d="M15 2v5h5"/></svg>
Files Files
</a> </a>
<a className="snap-start relative inline-flex flex-nowrap items-center gap-x-2 px-2.5 py-1.5 hover:bg-gray-100 text-gray-500 hover:text-gray-800 text-sm whitespace-nowrap rounded-lg disabled:opacity-50 disabled:pointer-events-none focus:outline-hidden focus:bg-gray-100 after:absolute after:-bottom-0.5 after:inset-x-0 after:z-10 after:w-1/4 after:h-0.5 after:rounded-full after:mx-auto after:pointer-events-none dark:text-neutral-500 dark:hover:text-neutral-300 dark:hover:bg-neutral-700 dark:focus:bg-neutral-700 " href="../../pro/dashboard/user-profile-connections.html"> <a className="snap-start relative inline-flex flex-nowrap items-center gap-x-2 px-2.5 py-1.5 hover:bg-gray-100 text-gray-500 hover:text-gray-800 text-sm whitespace-nowrap rounded-lg disabled:opacity-50 disabled:pointer-events-none focus:outline-hidden focus:bg-gray-100 after:absolute after:-bottom-0.5 after:inset-x-0 after:z-10 after:w-1/4 after:h-0.5 after:rounded-full after:mx-auto after:pointer-events-none dark:text-neutral-500 dark:hover:text-neutral-300 dark:hover:bg-neutral-700 dark:focus:bg-neutral-700 " href="../../pro/dashboard/user-profile-connections.html">
<svg className="shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3 4 7l4 4"/><path d="M4 7h16"/><path d="m16 21 4-4-4-4"/><path d="M20 17H4"/></svg> <svg className="shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M8 3 4 7l4 4"/><path d="M4 7h16"/><path d="m16 21 4-4-4-4"/><path d="M20 17H4"/></svg>
Connections Connections
</a> </a>
</nav> </nav>
@ -77,13 +77,13 @@ export default function Profile() {
</h3> </h3>
<button type="button" className="size-8 shrink-0 flex justify-center items-center gap-x-2 rounded-full border border-transparent bg-gray-100 text-gray-800 hover:bg-gray-200 disabled:opacity-50 disabled:pointer-events-none focus:outline-hidden focus:bg-gray-200 dark:bg-neutral-700 dark:hover:bg-neutral-600 dark:text-neutral-400 dark:focus:bg-neutral-600" aria-label="Close" data-hs-overlay="#hs-pro-dsm"> <button type="button" className="size-8 shrink-0 flex justify-center items-center gap-x-2 rounded-full border border-transparent bg-gray-100 text-gray-800 hover:bg-gray-200 disabled:opacity-50 disabled:pointer-events-none focus:outline-hidden focus:bg-gray-200 dark:bg-neutral-700 dark:hover:bg-neutral-600 dark:text-neutral-400 dark:focus:bg-neutral-600" aria-label="Close" data-hs-overlay="#hs-pro-dsm">
<span className="sr-only">Close</span> <span className="sr-only">Close</span>
<svg className="shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg> <svg className="shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
</button> </button>
</div> </div>
<div id="hs-modal-status-body" className="p-4 space-y-6 max-h-[75dvh] overflow-y-auto [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-track]:bg-gray-100 [&::-webkit-scrollbar-thumb]:bg-gray-300 dark:[&::-webkit-scrollbar-track]:bg-neutral-700 dark:[&::-webkit-scrollbar-thumb]:bg-neutral-500"> <div id="hs-modal-status-body" className="p-4 space-y-6 max-h-[75dvh] overflow-y-auto [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-track]:bg-gray-100 [&::-webkit-scrollbar-thumb]:bg-gray-300 dark:[&::-webkit-scrollbar-track]:bg-neutral-700 dark:[&::-webkit-scrollbar-thumb]:bg-neutral-500">
<div className="flex items-center border border-gray-200 rounded-lg dark:border-neutral-700"> <div className="flex items-center border border-gray-200 rounded-lg dark:border-neutral-700">
<div className="p-3 border-e border-gray-200 dark:border-neutral-700"> <div className="p-3 border-e border-gray-200 dark:border-neutral-700">
<svg className="shrink-0 size-4 text-gray-500 dark:text-neutral-400" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M8 14s1.5 2 4 2 4-2 4-2"/><line x1="9" x2="9.01" y1="9" y2="9"/><line x1="15" x2="15.01" y1="9" y2="9"/></svg> <svg className="shrink-0 size-4 text-gray-500 dark:text-neutral-400" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"/><path d="M8 14s1.5 2 4 2 4-2 4-2"/><line x1="9" x2="9.01" y1="9" y2="9"/><line x1="15" x2="15.01" y1="9" y2="9"/></svg>
</div> </div>
<input type="text" className="py-1.5 sm:py-2 px-3 block w-full border-transparent rounded-e-md sm:text-sm placeholder:text-gray-400 focus:border-blue-500 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none dark:bg-transparent dark:border-transparent dark:text-neutral-300 dark:placeholder:text-white/60 dark:focus:ring-neutral-600" placeholder="Whats your status?" /> <input type="text" className="py-1.5 sm:py-2 px-3 block w-full border-transparent rounded-e-md sm:text-sm placeholder:text-gray-400 focus:border-blue-500 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none dark:bg-transparent dark:border-transparent dark:text-neutral-300 dark:placeholder:text-white/60 dark:focus:ring-neutral-600" placeholder="Whats your status?" />
</div> </div>
@ -217,7 +217,7 @@ export default function Profile() {
"toggleClasses": "hs-select-disabled:pointer-events-none hs-select-disabled:opacity-50 relative py-2 px-4 pe-7 flex text-nowrap w-full cursor-pointer bg-gray-100 rounded-lg text-start text-sm text-gray-800 focus:outline-hidden focus:bg-gray-200 before:absolute before:inset-0 before:z-1 dark:bg-neutral-700 dark:text-neutral-200 dark:focus:bg-neutral-700", "toggleClasses": "hs-select-disabled:pointer-events-none hs-select-disabled:opacity-50 relative py-2 px-4 pe-7 flex text-nowrap w-full cursor-pointer bg-gray-100 rounded-lg text-start text-sm text-gray-800 focus:outline-hidden focus:bg-gray-200 before:absolute before:inset-0 before:z-1 dark:bg-neutral-700 dark:text-neutral-200 dark:focus:bg-neutral-700",
"dropdownClasses": "mt-2 z-50 w-full min-w-36 max-h-72 p-1 space-y-0.5 overflow-hidden overflow-y-auto bg-white rounded-xl shadow-xl [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-track]:bg-gray-100 [&::-webkit-scrollbar-thumb]:bg-gray-300 dark:[&::-webkit-scrollbar-track]:bg-neutral-700 dark:[&::-webkit-scrollbar-thumb]:bg-neutral-500 dark:bg-neutral-900 dark:bg-neutral-900", "dropdownClasses": "mt-2 z-50 w-full min-w-36 max-h-72 p-1 space-y-0.5 overflow-hidden overflow-y-auto bg-white rounded-xl shadow-xl [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-track]:bg-gray-100 [&::-webkit-scrollbar-thumb]:bg-gray-300 dark:[&::-webkit-scrollbar-track]:bg-neutral-700 dark:[&::-webkit-scrollbar-thumb]:bg-neutral-500 dark:bg-neutral-900 dark:bg-neutral-900",
"optionClasses": "hs-selected:bg-gray-100 dark:hs-selected:bg-neutral-800 py-2 px-4 w-full text-sm text-gray-800 cursor-pointer hover:bg-gray-100 rounded-lg focus:outline-hidden focus:bg-gray-100 dark:text-neutral-300 dark:hover:bg-neutral-800 dark:focus:bg-neutral-800", "optionClasses": "hs-selected:bg-gray-100 dark:hs-selected:bg-neutral-800 py-2 px-4 w-full text-sm text-gray-800 cursor-pointer hover:bg-gray-100 rounded-lg focus:outline-hidden focus:bg-gray-100 dark:text-neutral-300 dark:hover:bg-neutral-800 dark:focus:bg-neutral-800",
"optionTemplate": "<div className=\"flex justify-between items-center w-full\"><span data-title></span><span className=\"hidden hs-selected:block\"><svg className=\"shrink-0 size-3.5 text-gray-800 dark:text-neutral-200\" xmlns=\"http:.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"20 6 9 17 4 12\"/></svg></span></div>", "optionTemplate": "<div className=\"flex justify-between items-center w-full\"><span data-title></span><span className=\"hidden hs-selected:block\"><svg className=\"shrink-0 size-3.5 text-gray-800 dark:text-neutral-200\" xmlns=\"http:.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"><polyline points=\"20 6 9 17 4 12\"/></svg></span></div>",
"viewport": "#hs-modal-status-body" "viewport": "#hs-modal-status-body"
}' className="hidden"> }' className="hidden">
<option value="">Choose</option> <option value="">Choose</option>
@ -248,7 +248,7 @@ export default function Profile() {
</select> </select>
<div className="absolute top-1/2 end-2.5 -translate-y-1/2"> <div className="absolute top-1/2 end-2.5 -translate-y-1/2">
<svg className="shrink-0 size-3.5 text-gray-500 dark:text-neutral-500" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m7 15 5 5 5-5"/><path d="m7 9 5-5 5 5"/></svg> <svg className="shrink-0 size-3.5 text-gray-500 dark:text-neutral-500" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m7 15 5 5 5-5"/><path d="m7 9 5-5 5 5"/></svg>
</div> </div>
</div> </div>
@ -261,7 +261,7 @@ export default function Profile() {
"toggleClasses": "hs-select-disabled:pointer-events-none hs-select-disabled:opacity-50 relative py-2 px-4 pe-7 flex text-nowrap w-full cursor-pointer bg-gray-100 rounded-lg text-start text-sm text-gray-800 focus:outline-hidden focus:bg-gray-200 before:absolute before:inset-0 before:z-1 dark:bg-neutral-700 dark:text-neutral-200 dark:focus:bg-neutral-700", "toggleClasses": "hs-select-disabled:pointer-events-none hs-select-disabled:opacity-50 relative py-2 px-4 pe-7 flex text-nowrap w-full cursor-pointer bg-gray-100 rounded-lg text-start text-sm text-gray-800 focus:outline-hidden focus:bg-gray-200 before:absolute before:inset-0 before:z-1 dark:bg-neutral-700 dark:text-neutral-200 dark:focus:bg-neutral-700",
"dropdownClasses": "mt-2 z-50 w-full min-w-36 max-h-72 p-1 space-y-0.5 overflow-hidden overflow-y-auto bg-white rounded-xl shadow-xl [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-track]:bg-gray-100 [&::-webkit-scrollbar-thumb]:bg-gray-300 dark:[&::-webkit-scrollbar-track]:bg-neutral-700 dark:[&::-webkit-scrollbar-thumb]:bg-neutral-500 dark:bg-neutral-900 dark:bg-neutral-900", "dropdownClasses": "mt-2 z-50 w-full min-w-36 max-h-72 p-1 space-y-0.5 overflow-hidden overflow-y-auto bg-white rounded-xl shadow-xl [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-track]:bg-gray-100 [&::-webkit-scrollbar-thumb]:bg-gray-300 dark:[&::-webkit-scrollbar-track]:bg-neutral-700 dark:[&::-webkit-scrollbar-thumb]:bg-neutral-500 dark:bg-neutral-900 dark:bg-neutral-900",
"optionClasses": "hs-selected:bg-gray-100 dark:hs-selected:bg-neutral-800 py-2 px-4 w-full text-sm text-gray-800 cursor-pointer hover:bg-gray-100 rounded-lg focus:outline-hidden focus:bg-gray-100 dark:text-neutral-300 dark:hover:bg-neutral-800 dark:focus:bg-neutral-800", "optionClasses": "hs-selected:bg-gray-100 dark:hs-selected:bg-neutral-800 py-2 px-4 w-full text-sm text-gray-800 cursor-pointer hover:bg-gray-100 rounded-lg focus:outline-hidden focus:bg-gray-100 dark:text-neutral-300 dark:hover:bg-neutral-800 dark:focus:bg-neutral-800",
"optionTemplate": "<div className=\"flex justify-between items-center w-full\"><span data-title></span><span className=\"hidden hs-selected:block\"><svg className=\"shrink-0 size-3.5 text-gray-800 dark:text-neutral-200\" xmlns=\"http:.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"20 6 9 17 4 12\"/></svg></span></div>", "optionTemplate": "<div className=\"flex justify-between items-center w-full\"><span data-title></span><span className=\"hidden hs-selected:block\"><svg className=\"shrink-0 size-3.5 text-gray-800 dark:text-neutral-200\" xmlns=\"http:.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"><polyline points=\"20 6 9 17 4 12\"/></svg></span></div>",
"viewport": "#hs-modal-status-body" "viewport": "#hs-modal-status-body"
}' className="hidden"> }' className="hidden">
<option value="">Choose</option> <option value="">Choose</option>
@ -292,7 +292,7 @@ export default function Profile() {
</select> </select>
<div className="absolute top-1/2 end-2.5 -translate-y-1/2"> <div className="absolute top-1/2 end-2.5 -translate-y-1/2">
<svg className="shrink-0 size-3.5 text-gray-500 dark:text-neutral-500" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m7 15 5 5 5-5"/><path d="m7 9 5-5 5 5"/></svg> <svg className="shrink-0 size-3.5 text-gray-500 dark:text-neutral-500" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m7 15 5 5 5-5"/><path d="m7 9 5-5 5 5"/></svg>
</div> </div>
</div> </div>
</div> </div>
@ -314,7 +314,7 @@ export default function Profile() {
"toggleClasses": "hs-select-disabled:pointer-events-none hs-select-disabled:opacity-50 relative py-2 ps-3 pe-7 inline-flex justify-center items-center text-start bg-white border border-gray-200 text-gray-800 text-sm rounded-lg shadow-2xs align-middle focus:outline-hidden focus:ring-2 focus:ring-blue-500 before:absolute before:inset-0 before:z-1 hover:bg-gray-50 dark:bg-neutral-800 dark:border-neutral-600 dark:text-neutral-200 dark:hover:bg-neutral-700 dark:focus:bg-neutral-700", "toggleClasses": "hs-select-disabled:pointer-events-none hs-select-disabled:opacity-50 relative py-2 ps-3 pe-7 inline-flex justify-center items-center text-start bg-white border border-gray-200 text-gray-800 text-sm rounded-lg shadow-2xs align-middle focus:outline-hidden focus:ring-2 focus:ring-blue-500 before:absolute before:inset-0 before:z-1 hover:bg-gray-50 dark:bg-neutral-800 dark:border-neutral-600 dark:text-neutral-200 dark:hover:bg-neutral-700 dark:focus:bg-neutral-700",
"dropdownClasses": "mt-2 z-50 w-48 p-1 space-y-0.5 bg-white rounded-xl shadow-xl dark:bg-neutral-900", "dropdownClasses": "mt-2 z-50 w-48 p-1 space-y-0.5 bg-white rounded-xl shadow-xl dark:bg-neutral-900",
"optionClasses": "hs-selected:bg-gray-100 dark:hs-selected:bg-neutral-800 py-2 px-4 w-full text-sm text-gray-800 cursor-pointer hover:bg-gray-100 rounded-lg focus:outline-hidden focus:bg-gray-100 dark:bg-neutral-900 dark:hover:bg-neutral-800 dark:text-neutral-200 dark:focus:bg-neutral-800", "optionClasses": "hs-selected:bg-gray-100 dark:hs-selected:bg-neutral-800 py-2 px-4 w-full text-sm text-gray-800 cursor-pointer hover:bg-gray-100 rounded-lg focus:outline-hidden focus:bg-gray-100 dark:bg-neutral-900 dark:hover:bg-neutral-800 dark:text-neutral-200 dark:focus:bg-neutral-800",
"optionTemplate": "<div className=\"flex justify-between items-center w-full\"><span data-title></span><span className=\"hidden hs-selected:block\"><svg className=\"shrink-0 size-3.5 text-gray-800 dark:text-neutral-200\" xmlns=\"http:.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"20 6 9 17 4 12\"/></svg></span></div>", "optionTemplate": "<div className=\"flex justify-between items-center w-full\"><span data-title></span><span className=\"hidden hs-selected:block\"><svg className=\"shrink-0 size-3.5 text-gray-800 dark:text-neutral-200\" xmlns=\"http:.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"><polyline points=\"20 6 9 17 4 12\"/></svg></span></div>",
"viewport": "#hs-modal-status-body" "viewport": "#hs-modal-status-body"
}' className="hidden"> }' className="hidden">
<option value="">Choose</option> <option value="">Choose</option>
@ -326,7 +326,7 @@ export default function Profile() {
</select> </select>
<div className="absolute top-1/2 end-2.5 -translate-y-1/2"> <div className="absolute top-1/2 end-2.5 -translate-y-1/2">
<svg className="shrink-0 size-3.5 text-gray-400 dark:text-neutral-500" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m7 15 5 5 5-5"/><path d="m7 9 5-5 5 5"/></svg> <svg className="shrink-0 size-3.5 text-gray-400 dark:text-neutral-500" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m7 15 5 5 5-5"/><path d="m7 9 5-5 5 5"/></svg>
</div> </div>
</div> </div>
</div> </div>
@ -349,7 +349,7 @@ export default function Profile() {
<option value="">Choose</option> <option value="">Choose</option>
<option value="1" selected data-hs-select-option='{ <option value="1" selected data-hs-select-option='{
"description": "Your status will be visible to everyone", "description": "Your status will be visible to everyone",
"icon": "<svg className=\"shrink-0 size-4\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" className=\"lucide lucide-globe-2\"><path d=\"M21.54 15H17a2 2 0 0 0-2 2v4.54\"/><path d=\"M7 3.34V5a3 3 0 0 0 3 3v0a2 2 0 0 1 2 2v0c0 1.1.9 2 2 2v0a2 2 0 0 0 2-2v0c0-1.1.9-2 2-2h3.17\"/><path d=\"M11 21.95V18a2 2 0 0 0-2-2v0a2 2 0 0 1-2-2v-1a2 2 0 0 0-2-2H2.05\"/><circle cx=\"12\" cy=\"12\" r=\"10\"/></svg>" "icon": "<svg className=\"shrink-0 size-4\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" className=\"lucide lucide-globe-2\"><path d=\"M21.54 15H17a2 2 0 0 0-2 2v4.54\"/><path d=\"M7 3.34V5a3 3 0 0 0 3 3v0a2 2 0 0 1 2 2v0c0 1.1.9 2 2 2v0a2 2 0 0 0 2-2v0c0-1.1.9-2 2-2h3.17\"/><path d=\"M11 21.95V18a2 2 0 0 0-2-2v0a2 2 0 0 1-2-2v-1a2 2 0 0 0-2-2H2.05\"/><circle cx=\"12\" cy=\"12\" r=\"10\"/></svg>"
}'>Anyone</option> }'>Anyone</option>
<option value="2" data-hs-select-option='{ <option value="2" data-hs-select-option='{
"icon": "<svg className=\"inline-block size-4\" width=\"32\" height=\"32\" viewBox=\"0 0 32 32\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M16.0355 1.75926C10.6408 1.75926 5.30597 1.49951 0.0111241 1C-0.288584 7.23393 5.50578 13.1282 12.7987 14.5668L13.9975 14.7266C14.3372 12.4289 15.9956 3.7773 16.595 1.73928C16.4152 1.75926 16.2353 1.75926 16.0355 1.75926Z\" fill=\"#A49DFF\"/><path d=\"M16.615 1.75926C16.615 1.75926 25.2266 7.9932 28.5234 16.3451C30.0419 11.3499 31.1608 6.15498 32 1C26.8051 1.49951 21.71 1.75926 16.615 1.75926Z\" fill=\"#28289A\"/><path d=\"M13.9975 14.7466L13.8177 15.9455C13.8177 15.9455 12.2592 28.4133 23.1886 31.9699C25.2266 26.8748 27.0049 21.6599 28.5234 16.3251C21.9698 15.8456 13.9975 14.7466 13.9975 14.7466Z\" fill=\"#5ADCEE\"/><path d=\"M16.6149 1.75927C16.0155 3.79729 14.3571 12.4089 14.0175 14.7466C14.0175 14.7466 21.9897 15.8456 28.5233 16.3251C25.1866 7.9932 16.6149 1.75927 16.6149 1.75927Z\" fill=\"#7878FF\"/></svg>" "icon": "<svg className=\"inline-block size-4\" width=\"32\" height=\"32\" viewBox=\"0 0 32 32\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M16.0355 1.75926C10.6408 1.75926 5.30597 1.49951 0.0111241 1C-0.288584 7.23393 5.50578 13.1282 12.7987 14.5668L13.9975 14.7266C14.3372 12.4289 15.9956 3.7773 16.595 1.73928C16.4152 1.75926 16.2353 1.75926 16.0355 1.75926Z\" fill=\"#A49DFF\"/><path d=\"M16.615 1.75926C16.615 1.75926 25.2266 7.9932 28.5234 16.3451C30.0419 11.3499 31.1608 6.15498 32 1C26.8051 1.49951 21.71 1.75926 16.615 1.75926Z\" fill=\"#28289A\"/><path d=\"M13.9975 14.7466L13.8177 15.9455C13.8177 15.9455 12.2592 28.4133 23.1886 31.9699C25.2266 26.8748 27.0049 21.6599 28.5234 16.3251C21.9698 15.8456 13.9975 14.7466 13.9975 14.7466Z\" fill=\"#5ADCEE\"/><path d=\"M16.6149 1.75927C16.0155 3.79729 14.3571 12.4089 14.0175 14.7466C14.0175 14.7466 21.9897 15.8456 28.5233 16.3251C25.1866 7.9932 16.6149 1.75927 16.6149 1.75927Z\" fill=\"#7878FF\"/></svg>"
@ -357,7 +357,7 @@ export default function Profile() {
</select> </select>
<div className="absolute top-1/2 end-2.5 -translate-y-1/2"> <div className="absolute top-1/2 end-2.5 -translate-y-1/2">
<svg className="shrink-0 size-3.5 text-gray-400 dark:text-neutral-500" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m7 15 5 5 5-5"/><path d="m7 9 5-5 5 5"/></svg> <svg className="shrink-0 size-3.5 text-gray-400 dark:text-neutral-500" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m7 15 5 5 5-5"/><path d="m7 9 5-5 5 5"/></svg>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,88 +1,86 @@
'use client' 'use client'
import { useState } from "react" import { useState, useMemo } from 'react'
import { usePathname, useRouter } from 'next/navigation' import { useRouter, usePathname } from 'next/navigation'
import SidebarFooter from "./SidebarFooter" import Button from './Button'
import Button from "./Button" import SidebarFooter from './SidebarFooter'
export default function Sidebar({ children }: { children?: React.ReactNode }) { type Submenu = 'teams' | 'players' | null
export default function Sidebar() {
const router = useRouter() const router = useRouter()
const pathname = usePathname() const pathname = usePathname()
const [isOpen, setIsOpen] = useState(false) const [isOpen, setIsOpen] = useState(false) // mobile drawer
const [openSubmenu, setOpenSubmenu] = useState<'teams' | 'players' | null>(null) const [openSubmenu, setOpenSubmenu] = useState<Submenu>(null)
const toggleSidebar = () => setIsOpen(prev => !prev)
const handleSubmenuToggle = (menu: 'teams' | 'players') => {
setOpenSubmenu(prev => (prev === menu ? null : menu))
}
const isActive = (path: string) => pathname === path const isActive = (path: string) => pathname === path
return ( const navBtnBase =
<> 'w-full flex items-center gap-x-3.5 py-2 px-2.5 text-sm rounded-lg transition-colors'
<Button
onClick={toggleSidebar}
className="absolute items-center p-2 mt-2 ms-3 text-sm text-gray-500 rounded-lg sm:hidden focus:bg-gray-100 dark:text-neutral-400 dark:hover:bg-neutral-700 dark:focus:bg-neutral-700 dark:hover:text-neutral-200 dark:focus:text-neutral-200"
>
<span className="sr-only">Open sidebar</span>
<svg className="w-6 h-6" fill="currentColor" viewBox="0 0 20 20">
<path clipRule="evenodd" fillRule="evenodd" d="M2 4.75A.75.75 0 012.75 4h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 4.75zm0 10.5a.75.75 0 01.75-.75h7.5a.75.75 0 010 1.5h-7.5a.75.75 0 01-.75-.75zM2 10a.75.75 0 01.75-.75h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 10z" />
</svg>
</Button>
<div className="flex"> const activeClasses =
<aside className={`fixed top-0 left-0 z-40 h-screen w-64 bg-white dark:bg-neutral-800 border-e border-gray-200 dark:border-neutral-700 transition-transform sm:translate-x-0 ${isOpen ? 'translate-x-0' : '-translate-x-full'}`} aria-label="Sidebar"> 'bg-gray-100 dark:bg-neutral-700 text-gray-900 dark:text-white'
<div className="flex flex-col h-full">
<header className="p-4 flex justify-between items-center"> const idleClasses =
<a href="#" className="font-semibold text-xl text-black dark:text-white">Iron:e</a> 'text-gray-800 hover:bg-gray-100 dark:text-neutral-300 dark:hover:bg-neutral-700'
<button onClick={toggleSidebar} className="lg:hidden">
<svg className="size-4" viewBox="0 0 24 24" stroke="currentColor" fill="none"> const toggleSubmenu = (key: Exclude<Submenu, null>) =>
setOpenSubmenu(prev => (prev === key ? null : key))
// Gemeinsamer Inhalt (wird in Desktop-Aside und im Mobile-Drawer benutzt)
const SidebarInner = useMemo(
() => (
<div className="flex flex-col h-full min-h-0">
<header className="p-4 flex items-center justify-between">
<span className="font-semibold text-xl text-black dark:text-white">Iron:e</span>
{/* Close-Button nur im mobilen Drawer sichtbar */}
<button
onClick={() => setIsOpen(false)}
className="sm:hidden inline-flex items-center justify-center rounded-md p-2 text-gray-500 hover:bg-gray-100 dark:text-neutral-400 dark:hover:bg-neutral-700"
aria-label="Close sidebar"
>
<svg className="size-5" viewBox="0 0 24 24" stroke="currentColor" fill="none">
<path d="M18 6L6 18M6 6l12 12" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /> <path d="M18 6L6 18M6 6l12 12" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg> </svg>
</button> </button>
</header> </header>
<nav className="flex-1 overflow-y-auto px-2"> <nav className="flex-1 min-h-0 overflow-y-auto px-2">
<ul className="space-y-1"> <ul className="space-y-1">
{/* Dashboard */} {/* Dashboard */}
<li> <li>
<Button <Button
onClick={() => router.push('/dashboard')} onClick={() => { router.push('/dashboard'); setIsOpen(false) }}
size="sm" size="sm"
variant="link" variant="link"
className={`w-full flex items-center gap-x-3.5 py-2 px-2.5 text-sm rounded-lg transition-colors ${ className={`${navBtnBase} ${isActive('/dashboard') ? activeClasses : idleClasses}`}
isActive('/dashboard')
? 'bg-gray-100 dark:bg-neutral-700 text-gray-900 dark:text-white'
: 'text-gray-800 hover:bg-gray-100 dark:text-neutral-300 dark:hover:bg-neutral-700'
}`}
> >
<svg className="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z"/><path d="M9 22V12h6v10"/></svg> <svg className="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z" />
<path d="M9 22V12h6v10" />
</svg>
Dashboard Dashboard
</Button> </Button>
</li> </li>
{/* Teams */} {/* Teams (mit Submenu) */}
<li> <li>
<Button <Button
onClick={() => handleSubmenuToggle('teams')} onClick={() => toggleSubmenu('teams')}
size="sm" size="sm"
variant="link" variant="link"
className="w-full flex items-center justify-between gap-x-3.5 py-2 px-2.5 text-sm text-gray-800 rounded-lg hover:bg-gray-100 dark:text-neutral-200 dark:hover:bg-neutral-700" className={`${navBtnBase} ${idleClasses} justify-between`}
> >
<span className="flex items-center gap-x-3.5"> <span className="flex items-center gap-x-3.5">
<svg className="size-5" viewBox="0 0 640 640" fill="currentColor"> <svg className="size-5" viewBox="0 0 640 640" fill="currentColor">
<path d="M320 64C355.3 64 384 92.7 384 128C384 163.3 355.3 192 320 192C284.7 192 256 163.3 256 128C256 92.7 284.7 64 320 64zM416 376C416 401 403.3 423 384 435.9V528C384 554.5 362.5 576 336 576H304C277.5 576 256 554.5 256 528V435.9C236.7 423 224 401 224 376V336C224 283 267 240 320 240C373 240 416 283 416 336V376zM160 96C190.9 96 216 121.1 216 152C216 182.9 190.9 208 160 208C129.1 208 104 182.9 104 152C104 121.1 129.1 96 160 96zM176 336V368C176 400.5 188.1 430.1 208 452.7V528C208 529.2 208 530.5 208.1 531.7C199.6 539.3 188.4 544 176 544H144C117.5 544 96 522.5 96 496V439.4C76.9 428.4 64 407.7 64 384V352C64 299 107 256 160 256C172.7 256 184.8 258.5 195.9 262.9C183.3 284.3 176 309.3 176 336zM432 528V452.7C451.9 430.2 464 400.5 464 368V336C464 309.3 456.7 284.4 444.1 262.9C455.2 258.4 467.3 256 480 256C533 256 576 299 576 352V384C576 407.7 563.1 428.4 544 439.4V496C544 522.5 522.5 544 496 544H464C451.7 544 440.4 539.4 431.9 531.7C431.9 530.5 432 529.2 432 528zM480 96C510.9 96 536 121.1 536 152C536 182.9 510.9 208 480 208C449.1 208 424 182.9 424 152C424 121.1 449.1 96 480 96z" /> <path d="M320 64c35.3 0 64 28.7 64 64s-28.7 64-64 64-64-28.7-64-64 28.7-64 64-64zm96 312c0 25-12.7 47-32 59.9V528c0 26.5-21.5 48-48 48h-32c-26.5 0-48-21.5-48-48v-92.1c-19.3-12.9-32-34.9-32-59.9v-40c0-53 43-96 96-96s96 43 96 96v40zM160 96c30.9 0 56 25.1 56 56s-25.1 56-56 56-56-25.1-56-56 25.1-56 56-56zm16 240v32c0 32.5 12.1 62.1 32 84.7V528c0 1.2 0 2.5.1 3.7-8.6 7.6-19.8 12.3-31.1 12.3H144c-26.5 0-48-21.5-48-48v-56.6C76.9 428.4 64 407.7 64 384v-32c0-53 43-96 96-96 12.7 0 24.8 2.5 35.9 6.9-12.6 21.4-19.9 46.8-19.9 73.1zM480 96c30.9 0 56 25.1 56 56s-25.1 56-56 56-56-25.1-56-56 25.1-56 56-56zm-48 432v-75.3c19.9-22.5 32-52.2 32-84.7v-32c0-26.7-7.3-52.1-19.9-73.1 11.1-4.4 23.2-6.9 35.9-6.9 53 0 96 43 96 96v32c0 23.7-12.9 44.4-32 55.4V496c0 26.5-21.5 48-48 48h-32c-10.8 0-21-3.6-29.1-9.7.1-1.2.1-2.5.1-3.7z" />
</svg> </svg>
Teams Teams
</span> </span>
<svg <svg
className={`size-4 transition-transform ${openSubmenu === 'teams' ? 'rotate-180' : ''}`} className={`size-4 transition-transform ${openSubmenu === 'teams' ? 'rotate-180' : ''}`}
viewBox="0 0 24 24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"
fill="none"
stroke="currentColor"
strokeWidth="2"
> >
<path d="M6 9l6 6 6-6" /> <path d="M6 9l6 6 6-6" />
</svg> </svg>
@ -91,12 +89,22 @@ export default function Sidebar({ children }: { children?: React.ReactNode }) {
{openSubmenu === 'teams' && ( {openSubmenu === 'teams' && (
<ul className="pl-6 space-y-1 mt-1"> <ul className="pl-6 space-y-1 mt-1">
<li> <li>
<Button onClick={() => router.push('/teams')} size="sm" variant="link" className="w-full text-start"> <Button
onClick={() => { router.push('/teams'); setIsOpen(false) }}
size="sm"
variant="link"
className="w-full text-start text-sm px-2 py-1.5 rounded hover:bg-gray-100 dark:hover:bg-neutral-700"
>
Übersicht Übersicht
</Button> </Button>
</li> </li>
<li> <li>
<Button onClick={() => router.push('/teams/manage')} size="sm" variant="link" className="w-full text-start"> <Button
onClick={() => { router.push('/teams/manage'); setIsOpen(false) }}
size="sm"
variant="link"
className="w-full text-start text-sm px-2 py-1.5 rounded hover:bg-gray-100 dark:hover:bg-neutral-700"
>
Teamverwaltung Teamverwaltung
</Button> </Button>
</li> </li>
@ -104,32 +112,51 @@ export default function Sidebar({ children }: { children?: React.ReactNode }) {
)} )}
</li> </li>
{/* Spieler */} {/* Spieler (mit Submenu) */}
<li> <li>
<Button <Button
onClick={() => handleSubmenuToggle('players')} onClick={() => toggleSubmenu('players')}
size="sm" size="sm"
variant="link" variant="link"
className="w-full flex items-center justify-between gap-x-3.5 py-2 px-2.5 text-sm text-gray-800 rounded-lg hover:bg-gray-100 dark:text-neutral-200 dark:hover:bg-neutral-700" className={`${navBtnBase} ${idleClasses} justify-between`}
> >
<span className="flex items-center gap-x-3.5"> <span className="flex items-center gap-x-3.5">
<svg className="size-4" fill="currentColor" viewBox="0 0 24 24"> <svg className="size-4" viewBox="0 0 24 24" fill="currentColor">
<path fillRule="evenodd" d="M12 4a4 4 0 1 0 0 8 4 4 0 0 0 0-8Zm-2 9a4 4 0 0 0-4 4v1a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2v-1a4 4 0 0 0-4-4h-4Z" clipRule="evenodd"/> <path
fillRule="evenodd"
d="M12 4a4 4 0 1 0 0 8 4 4 0 0 0 0-8Zm-2 9a4 4 0 0 0-4 4v1a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2v-1a4 4 0 0 0-4-4h-4Z"
clipRule="evenodd"
/>
</svg> </svg>
Spieler Spieler
</span> </span>
<svg className={`size-4 transition-transform ${openSubmenu === 'players' ? 'rotate-180' : ''}`} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M6 9l6 6 6-6" /></svg> <svg
className={`size-4 transition-transform ${openSubmenu === 'players' ? 'rotate-180' : ''}`}
viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"
>
<path d="M6 9l6 6 6-6" />
</svg>
</Button> </Button>
{openSubmenu === 'players' && ( {openSubmenu === 'players' && (
<ul className="pl-6 space-y-1 mt-1"> <ul className="pl-6 space-y-1 mt-1">
<li> <li>
<Button onClick={() => router.push('/players')} size="sm" variant="link" className="w-full text-start"> <Button
onClick={() => { router.push('/players'); setIsOpen(false) }}
size="sm"
variant="link"
className="w-full text-start text-sm px-2 py-1.5 rounded hover:bg-gray-100 dark:hover:bg-neutral-700"
>
Übersicht Übersicht
</Button> </Button>
</li> </li>
<li> <li>
<Button onClick={() => router.push('/players/stats')} size="sm" variant="link" className="w-full text-start"> <Button
onClick={() => { router.push('/players/stats'); setIsOpen(false) }}
size="sm"
variant="link"
className="w-full text-start text-sm px-2 py-1.5 rounded hover:bg-gray-100 dark:hover:bg-neutral-700"
>
Statistiken Statistiken
</Button> </Button>
</li> </li>
@ -140,21 +167,17 @@ export default function Sidebar({ children }: { children?: React.ReactNode }) {
{/* Spielplan */} {/* Spielplan */}
<li> <li>
<Button <Button
onClick={() => router.push('/schedule')} onClick={() => { router.push('/schedule'); setIsOpen(false) }}
size="sm" size="sm"
variant="link" variant="link"
className={`w-full flex items-center gap-x-3.5 py-2 px-2.5 text-sm rounded-lg transition-colors ${ className={`${navBtnBase} ${isActive('/schedule') ? activeClasses : idleClasses}`}
isActive('/schedule')
? 'bg-gray-100 dark:bg-neutral-700 text-gray-900 dark:text-white'
: 'text-gray-800 hover:bg-gray-100 dark:text-neutral-300 dark:hover:bg-neutral-700'
}`}
> >
<svg className="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"> <svg className="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<rect width="18" height="18" x="3" y="4" rx="2" ry="2"/> <rect width="18" height="18" x="3" y="4" rx="2" ry="2" />
<line x1="16" x2="16" y1="2" y2="6"/> <line x1="16" x2="16" y1="2" y2="6" />
<line x1="8" x2="8" y1="2" y2="6"/> <line x1="8" x2="8" y1="2" y2="6" />
<line x1="3" x2="21" y1="10" y2="10"/> <line x1="3" x2="21" y1="10" y2="10" />
<path d="M8 14h.01M12 14h.01M16 14h.01M8 18h.01M12 18h.01M16 18h.01"/> <path d="M8 14h.01M12 14h.01M16 14h.01M8 18h.01M12 18h.01M16 18h.01" />
</svg> </svg>
Spielplan Spielplan
</Button> </Button>
@ -166,12 +189,64 @@ export default function Sidebar({ children }: { children?: React.ReactNode }) {
<SidebarFooter /> <SidebarFooter />
</footer> </footer>
</div> </div>
),
// eslint-disable-next-line react-hooks/exhaustive-deps
[pathname, openSubmenu]
)
return (
<>
{/* Mobile Toggle Button (nur < sm) */}
<button
onClick={() => setIsOpen(true)}
className="sm:hidden fixed z-50 top-3 left-3 inline-flex items-center justify-center p-2 rounded-md text-gray-500 hover:bg-gray-100 dark:text-neutral-400 dark:hover:bg-neutral-700"
aria-label="Open sidebar"
>
<svg className="w-6 h-6" fill="currentColor" viewBox="0 0 20 20">
<path
clipRule="evenodd" fillRule="evenodd"
d="M2 4.75A.75.75 0 012.75 4h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 4.75zm0 10.5a.75.75 0 01.75-.75h7.5a.75.75 0 010 1.5h-7.5a.75.75 0 01-.75-.75zM2 10a.75.75 0 01.75-.75h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 10z"
/>
</svg>
</button>
{/* Desktop Sidebar (sticky) */}
<aside
className="
hidden sm:flex sm:flex-col
w-64 shrink-0
border-e border-gray-200 dark:border-neutral-700
bg-white dark:bg-neutral-800
sticky top-0 h-dvh
"
aria-label="Sidebar"
>
{SidebarInner}
</aside> </aside>
<main className="sm:ml-64 flex-1 h-screen p-6 bg-white dark:bg-black"> {/* Mobile Drawer */}
{children} {isOpen && (
</main> <div className="sm:hidden fixed inset-0 z-50" role="dialog" aria-modal="true">
{/* Backdrop */}
<div
className="absolute inset-0 bg-black/40"
onClick={() => setIsOpen(false)}
/>
{/* Panel */}
<div
className="
absolute inset-y-0 left-0 w-64
bg-white dark:bg-neutral-800
border-e border-gray-200 dark:border-neutral-700
shadow-xl
transform transition-transform duration-200 ease-out
"
>
{SidebarInner}
</div> </div>
</div>
)}
</> </>
) )
} }

View File

@ -3,6 +3,7 @@
import { useEffect, useMemo, useRef, useState } from 'react' import { useEffect, useMemo, useRef, useState } from 'react'
import MetaSocket from './MetaSocket' import MetaSocket from './MetaSocket'
import PositionsSocket from './PositionsSocket' import PositionsSocket from './PositionsSocket'
import TeamSidebar from './TeamSidebar'
/* ───────── UI config ───────── */ /* ───────── UI config ───────── */
const UI = { const UI = {
@ -12,8 +13,8 @@ const UI = {
dirLenRel: 0.70, dirLenRel: 0.70,
dirMinLenPx: 6, dirMinLenPx: 6,
lineWidthRel: 0.25, lineWidthRel: 0.25,
stroke: '#ffffff', // normaler Outline (weiß) stroke: '#ffffff',
bombStroke: '#ef4444', // Outline wenn Bombe (rot) bombStroke: '#ef4444',
fillCT: '#3b82f6', fillCT: '#3b82f6',
fillT: '#f59e0b', fillT: '#f59e0b',
dirColor: 'auto' as 'auto' | string, dirColor: 'auto' as 'auto' | string,
@ -30,7 +31,7 @@ const UI = {
minRadiusPx: 6, minRadiusPx: 6,
}, },
death: { death: {
stroke: '#9ca3af', // graues X stroke: '#9ca3af',
lineWidthPx: 2, lineWidthPx: 2,
sizePx: 10, sizePx: 10,
}, },
@ -59,7 +60,6 @@ function mapTeam(t: any): 'T' | 'CT' | string {
return String(t ?? '') return String(t ?? '')
} }
// Versuche robust zu erkennen, ob ein Spieler die Bombe hat
function detectHasBomb(src: any): boolean { function detectHasBomb(src: any): boolean {
const flags = [ const flags = [
'hasBomb','has_bomb','bomb','c4','hasC4','carryingBomb','bombCarrier','isBombCarrier' 'hasBomb','has_bomb','bomb','c4','hasC4','carryingBomb','bombCarrier','isBombCarrier'
@ -94,13 +94,7 @@ function detectHasBomb(src: any): boolean {
return false return false
} }
// URL-Builder function makeWsUrl(host?: string, port?: string, path?: string, scheme?: string) {
function makeWsUrl(
host?: string,
port?: string,
path?: string,
scheme?: string
) {
const h = (host ?? '').trim() || '127.0.0.1' const h = (host ?? '').trim() || '127.0.0.1'
const p = (port ?? '').trim() || '8081' const p = (port ?? '').trim() || '8081'
const pa = (path ?? '').trim() || '/telemetry' const pa = (path ?? '').trim() || '/telemetry'
@ -146,7 +140,12 @@ type PlayerState = {
yaw?: number | null yaw?: number | null
alive?: boolean alive?: boolean
hasBomb?: boolean hasBomb?: boolean
hp?: number | null
armor?: number | null
helmet?: boolean | null
defuse?: boolean | null
} }
type Grenade = { type Grenade = {
id: string id: string
kind: 'smoke' | 'molotov' | 'he' | 'flash' | 'decoy' | 'unknown' kind: 'smoke' | 'molotov' | 'he' | 'flash' | 'decoy' | 'unknown'
@ -157,17 +156,18 @@ type Grenade = {
expiresAt?: number | null expiresAt?: number | null
team?: 'T' | 'CT' | string | null team?: 'T' | 'CT' | string | null
} }
type DeathMarker = { id: string; x: number; y: number; t: number } type DeathMarker = { id: string; x: number; y: number; t: number }
type Trail = { id: string; kind: Grenade['kind']; pts: {x:number,y:number}[]; lastSeen: number } type Trail = { id: string; kind: Grenade['kind']; pts: {x:number,y:number}[]; lastSeen: number }
type Overview = { posX: number; posY: number; scale: number; rotate?: number } type Overview = { posX: number; posY: number; scale: number; rotate?: number }
type Mapper = (xw: number, yw: number) => { x: number; y: number } type Mapper = (xw: number, yw: number) => { x: number; y: number }
type WsStatus = 'idle'|'connecting'|'open'|'closed'|'error'
/* ───────── Komponente ───────── */ /* ───────── Komponente ───────── */
export default function LiveRadar() { export default function LiveRadar() {
// WS-Status // WS-Status
const [metaWsStatus, setMetaWsStatus] = useState<'idle'|'connecting'|'open'|'closed'|'error'>('idle') const [metaWsStatus, setMetaWsStatus] = useState<WsStatus>('idle')
const [posWsStatus, setPosWsStatus] = useState<'idle'|'connecting'|'open'|'closed'|'error'>('idle') const [posWsStatus, setPosWsStatus] = useState<WsStatus>('idle')
// Map // Map
const [activeMapKey, setActiveMapKey] = useState<string | null>(null) const [activeMapKey, setActiveMapKey] = useState<string | null>(null)
@ -291,9 +291,20 @@ export default function LiveRadar() {
const old = playersRef.current.get(id) const old = playersRef.current.get(id)
const nextAlive = (e.alive !== undefined) ? !!e.alive : old?.alive const nextAlive = (e.alive !== undefined) ? !!e.alive : old?.alive
const hp = Number(
e.hp ?? e.health ?? e.state?.health
)
const armor = Number(
e.armor ?? e.state?.armor
)
const helmet = Boolean(
e.helmet ?? e.hasHelmet ?? e.state?.helmet
)
const defuse = Boolean(
e.defuse ?? e.hasDefuse ?? e.hasDefuser ?? e.state?.defusekit
)
const hasBomb = detectHasBomb(e) || old?.hasBomb const hasBomb = detectHasBomb(e) || old?.hasBomb
// Alive→Dead → Death-X an aktueller Position speichern
if (old?.alive !== false && nextAlive === false) addDeathMarker(x, y, id) if (old?.alive !== false && nextAlive === false) addDeathMarker(x, y, id)
playersRef.current.set(id, { playersRef.current.set(id, {
@ -304,6 +315,10 @@ export default function LiveRadar() {
yaw: Number.isFinite(yaw) ? yaw : old?.yaw ?? null, yaw: Number.isFinite(yaw) ? yaw : old?.yaw ?? null,
alive: nextAlive, alive: nextAlive,
hasBomb: !!hasBomb, hasBomb: !!hasBomb,
hp: Number.isFinite(hp) ? hp : old?.hp ?? null,
armor: Number.isFinite(armor) ? armor : old?.armor ?? null,
helmet: helmet ?? old?.helmet ?? null,
defuse: defuse ?? old?.defuse ?? null,
}) })
} }
@ -320,6 +335,10 @@ export default function LiveRadar() {
const id = String(key) const id = String(key)
const old = playersRef.current.get(id) const old = playersRef.current.get(id)
const isAlive = p.state?.health > 0 || p.state?.health == null const isAlive = p.state?.health > 0 || p.state?.health == null
const hp = Number(p.state?.health)
const armor = Number(p.state?.armor)
const helmet = !!p.state?.helmet
const defuse = !!p.state?.defusekit
const hasBomb = detectHasBomb(p) || old?.hasBomb const hasBomb = detectHasBomb(p) || old?.hasBomb
if ((old?.alive ?? true) && !isAlive) addDeathMarker(pos.x, pos.y, id) if ((old?.alive ?? true) && !isAlive) addDeathMarker(pos.x, pos.y, id)
@ -332,13 +351,16 @@ export default function LiveRadar() {
yaw, yaw,
alive: isAlive, alive: isAlive,
hasBomb: !!hasBomb, hasBomb: !!hasBomb,
hp: Number.isFinite(hp) ? hp : old?.hp ?? null,
armor: Number.isFinite(armor) ? armor : old?.armor ?? null,
helmet: helmet ?? old?.helmet ?? null,
defuse: defuse ?? old?.defuse ?? null,
}) })
total++ total++
if (isAlive) aliveCount++ if (isAlive) aliveCount++
} }
// Heuristik: Neue Runde → alles leeren
if (total > 0 && aliveCount === total && (deathMarkersRef.current.length > 0 || trailsRef.current.size > 0 || grenadesRef.current.size > 0)) { if (total > 0 && aliveCount === total && (deathMarkersRef.current.length > 0 || trailsRef.current.size > 0 || grenadesRef.current.size > 0)) {
clearRoundArtifacts() clearRoundArtifacts()
} }
@ -433,7 +455,7 @@ export default function LiveRadar() {
prev.lastSeen = now prev.lastSeen = now
trailsRef.current.set(it.id, prev) trailsRef.current.set(it.id, prev)
} }
// Nicht mehr gesehene Trails ausdünnen // Trails ausdünnen
for (const [id, tr] of trailsRef.current) { for (const [id, tr] of trailsRef.current) {
if (!seen.has(id) && now - tr.lastSeen > UI.trail.fadeMs) { if (!seen.has(id) && now - tr.lastSeen > UI.trail.fadeMs) {
trailsRef.current.delete(id) trailsRef.current.delete(id)
@ -467,6 +489,7 @@ export default function LiveRadar() {
`/assets/resource/overviews/${base}_s2.json`, `/assets/resource/overviews/${base}_s2.json`,
] ]
} }
const parseOverviewJson = (j: any): Overview | null => { const parseOverviewJson = (j: any): Overview | null => {
const posX = Number(j?.posX ?? j?.pos_x) const posX = Number(j?.posX ?? j?.pos_x)
const posY = Number(j?.posY ?? j?.pos_y) const posY = Number(j?.posY ?? j?.pos_y)
@ -475,6 +498,7 @@ export default function LiveRadar() {
if (![posX, posY, scale].every(Number.isFinite)) return null if (![posX, posY, scale].every(Number.isFinite)) return null
return { posX, posY, scale, rotate } return { posX, posY, scale, rotate }
} }
const parseValveKvOverview = (txt: string): Overview | null => { const parseValveKvOverview = (txt: string): Overview | null => {
const clean = txt.replace(/\/\/.*$/gm, '') const clean = txt.replace(/\/\/.*$/gm, '')
const pick = (k: string) => { const m = clean.match(new RegExp(`"\\s*${k}\\s*"\\s*"([^"]+)"`)); return m ? Number(m[1]) : NaN } const pick = (k: string) => { const m = clean.match(new RegExp(`"\\s*${k}\\s*"\\s*"([^"]+)"`)); return m ? Number(m[1]) : NaN }
@ -483,6 +507,7 @@ export default function LiveRadar() {
if (![posX, posY, scale].every(Number.isFinite)) return null if (![posX, posY, scale].every(Number.isFinite)) return null
return { posX, posY, scale, rotate } return { posX, posY, scale, rotate }
} }
useEffect(() => { useEffect(() => {
let cancel = false let cancel = false
;(async () => { ;(async () => {
@ -521,22 +546,6 @@ export default function LiveRadar() {
useEffect(() => { setSrcIdx(0) }, [folderKey]) useEffect(() => { setSrcIdx(0) }, [folderKey])
const currentSrc = imageCandidates[srcIdx] const currentSrc = imageCandidates[srcIdx]
const headerRef = useRef<HTMLDivElement | null>(null)
const [maxImgHeight, setMaxImgHeight] = useState<number | null>(null)
useEffect(() => {
const update = () => {
const bottom = headerRef.current?.getBoundingClientRect().bottom ?? 0
setMaxImgHeight(Math.max(120, Math.floor(window.innerHeight - bottom - 16)))
}
update()
window.addEventListener('resize', update)
window.addEventListener('scroll', update, { passive: true })
return () => {
window.removeEventListener('resize', update)
window.removeEventListener('scroll', update)
}
}, [])
const [imgSize, setImgSize] = useState<{ w: number; h: number } | null>(null) const [imgSize, setImgSize] = useState<{ w: number; h: number } | null>(null)
/* ───────── Welt → Pixel ───────── */ /* ───────── Welt → Pixel ───────── */
@ -604,7 +613,7 @@ export default function LiveRadar() {
}, [imgSize, overview]) }, [imgSize, overview])
/* ───────── Status-Badge ───────── */ /* ───────── Status-Badge ───────── */
const WsDot = ({ status, label }: { status: typeof metaWsStatus, label: string }) => { const WsDot = ({ status, label }: { status: WsStatus, label: string }) => {
const color = const color =
status === 'open' ? 'bg-green-500' : status === 'open' ? 'bg-green-500' :
status === 'connecting' ? 'bg-amber-500' : status === 'connecting' ? 'bg-amber-500' :
@ -624,11 +633,11 @@ export default function LiveRadar() {
) )
} }
/* ───────── Render ───────── */ /* ───────── Render (Fix A: reines Flex-Layout) ───────── */
return ( return (
<div className="p-4"> <div className="h-full min-h-0 w-full flex flex-col overflow-hidden">
{/* Head + WS-Badges */} {/* Header */}
<div ref={headerRef} className="mb-4 flex items-center justify-between"> <div className="mb-4 shrink-0 flex items-center justify-between">
<h2 className="text-xl font-semibold">Live Radar</h2> <h2 className="text-xl font-semibold">Live Radar</h2>
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div className="text-sm opacity-80"> <div className="text-sm opacity-80">
@ -657,23 +666,38 @@ export default function LiveRadar() {
onGrenades={(g)=> { handleGrenades(g); scheduleFlush() }} onGrenades={(g)=> { handleGrenades(g); scheduleFlush() }}
/> />
{/* Inhalt: 3-Spalten-Layout (T | Radar | CT) */}
<div className="flex-1 min-h-0">
{!activeMapKey ? ( {!activeMapKey ? (
<div className="p-4 rounded bg-amber-100 text-amber-900 dark:bg-amber-900/30 dark:text-amber-100"> <div className="h-full grid place-items-center">
<div className="px-4 py-3 rounded bg-amber-100 text-amber-900 dark:bg-amber-900/30 dark:text-amber-100">
Keine Map erkannt. Keine Map erkannt.
</div> </div>
</div>
) : ( ) : (
<div className="w-full"> <div className="h-full min-h-0 grid grid-cols-[minmax(180px,240px)_1fr_minmax(180px,240px)] gap-4">
<div {/* Left: T */}
className="relative mx-auto rounded-lg overflow-hidden border border-neutral-300 dark:border-neutral-700 bg-neutral-100 dark:bg-neutral-800 inline-block" <TeamSidebar
style={{ maxHeight: (typeof window !== 'undefined' ? (window.innerHeight - (headerRef.current?.getBoundingClientRect().bottom ?? 0) - 16) : undefined) ?? undefined }} team="T"
> players={players
.filter(p => p.team === 'T')
.map(p => ({
id: p.id, name: p.name, hp: p.hp, armor: p.armor, helmet: p.helmet,
defuse: p.defuse, hasBomb: p.hasBomb, alive: p.alive
}))
}
/>
{/* Center: Radar */}
<div className="relative min-h-0 rounded-lg overflow-hidden border border-neutral-700 bg-neutral-800">
{currentSrc ? ( {currentSrc ? (
<> <div className="absolute inset-0">
{/* Bild füllt Container (Letterboxing via object-contain) */}
<img <img
key={currentSrc} key={currentSrc}
src={currentSrc} src={currentSrc}
alt={activeMapKey} alt={activeMapKey ?? 'map'}
className="block h-auto max-w-full" className="absolute inset-0 h-full w-full object-contain object-center"
onLoad={(e) => { onLoad={(e) => {
const img = e.currentTarget const img = e.currentTarget
setImgSize({ w: img.naturalWidth, h: img.naturalHeight }) setImgSize({ w: img.naturalWidth, h: img.naturalHeight })
@ -683,9 +707,10 @@ export default function LiveRadar() {
}} }}
/> />
{/* Overlay skaliert deckungsgleich zum Bild */}
{imgSize && ( {imgSize && (
<svg <svg
className="absolute inset-0 w-full h-full pointer-events-none" className="absolute inset-0 h-full w-full object-contain pointer-events-none"
viewBox={`0 0 ${imgSize.w} ${imgSize.h}`} viewBox={`0 0 ${imgSize.w} ${imgSize.h}`}
preserveAspectRatio="xMidYMid meet" preserveAspectRatio="xMidYMid meet"
> >
@ -712,13 +737,13 @@ export default function LiveRadar() {
{/* Grenades */} {/* Grenades */}
{grenades.map((g) => { {grenades.map((g) => {
const P = worldToPx(g.x, g.y) const P = worldToPx(g.x, g.y)
const defaultRadius = const defR =
g.kind === 'smoke' ? 150 : g.kind === 'smoke' ? 150 :
g.kind === 'molotov'? 120 : g.kind === 'molotov'? 120 :
g.kind === 'he' ? 40 : g.kind === 'he' ? 40 :
g.kind === 'flash' ? 36 : g.kind === 'flash' ? 36 :
g.kind === 'decoy' ? 80 : 60 g.kind === 'decoy' ? 80 : 60
const rPx = Math.max(UI.nade.minRadiusPx, unitsToPx(g.radius ?? defaultRadius)) const rPx = Math.max(UI.nade.minRadiusPx, unitsToPx(g.radius ?? defR))
const stroke = g.team === 'CT' ? UI.nade.teamStrokeCT const stroke = g.team === 'CT' ? UI.nade.teamStrokeCT
: g.team === 'T' ? UI.nade.teamStrokeT : g.team === 'T' ? UI.nade.teamStrokeT
: UI.nade.stroke : UI.nade.stroke
@ -747,7 +772,7 @@ export default function LiveRadar() {
return <circle key={g.id} cx={P.x} cy={P.y} r={Math.max(4, rPx*0.4)} fill={g.kind === 'he' ? UI.nade.heFill : '#999'} stroke={stroke} strokeWidth={Math.max(1, sw*0.8)} /> return <circle key={g.id} cx={P.x} cy={P.y} r={Math.max(4, rPx*0.4)} fill={g.kind === 'he' ? UI.nade.heFill : '#999'} stroke={stroke} strokeWidth={Math.max(1, sw*0.8)} />
})} })}
{/* Spieler (nur lebende anzeigen; Tote werden als X separat gezeichnet) */} {/* Spieler */}
{players {players
.filter(p => (p.team === 'CT' || p.team === 'T') && p.alive !== false) .filter(p => (p.team === 'CT' || p.team === 'T') && p.alive !== false)
.map((p) => { .map((p) => {
@ -792,7 +817,7 @@ export default function LiveRadar() {
) )
})} })}
{/* Death-Marker (graues X an Todesposition) */} {/* Death-Marker */}
{deathMarkers.map(dm => { {deathMarkers.map(dm => {
const P = worldToPx(dm.x, dm.y) const P = worldToPx(dm.x, dm.y)
const s = UI.death.sizePx const s = UI.death.sizePx
@ -807,17 +832,29 @@ export default function LiveRadar() {
})} })}
</svg> </svg>
)} )}
</> </div>
) : ( ) : (
<div className="p-6 text-center">Keine Radar-Grafik gefunden.</div> <div className="absolute inset-0 grid place-items-center p-6 text-center">
Keine Radar-Grafik gefunden.
</div>
)} )}
</div> </div>
<div className="mt-2 text-xs opacity-70 text-center"> {/* Right: CT */}
Quelle: <code className="px-1">{currentSrc ?? '—'}</code> <TeamSidebar
</div> team="CT"
align="right"
players={players
.filter(p => p.team === 'CT')
.map(p => ({
id: p.id, name: p.name, hp: p.hp, armor: p.armor, helmet: p.helmet,
defuse: p.defuse, hasBomb: p.hasBomb, alive: p.alive
}))
}
/>
</div> </div>
)} )}
</div> </div>
</div>
) )
} }

View File

@ -0,0 +1,82 @@
'use client'
import React from 'react'
export type Team = 'T' | 'CT'
export type SidebarPlayer = {
id: string
name?: string | null
hp?: number | null
armor?: number | null
helmet?: boolean | null
defuse?: boolean | null
hasBomb?: boolean | null
alive?: boolean | null
}
export default function TeamSidebar({
team,
players,
align = 'left',
}: {
team: Team
players: SidebarPlayer[]
align?: 'left' | 'right'
}) {
const teamName = team === 'CT' ? 'Counter-Terrorists' : 'Terrorists'
const teamColor = team === 'CT' ? 'text-blue-400' : 'text-amber-400'
const barArmor = team === 'CT' ? 'bg-blue-500' : 'bg-amber-500'
const aliveCount = players.filter(p => p.alive !== false && (p.hp ?? 1) > 0).length
const sorted = [...players].sort((a, b) => {
// alive first, then hp desc, then name
const al = (b.alive ? 1 : 0) - (a.alive ? 1 : 0)
if (al !== 0) return al
const hp = (b.hp ?? -1) - (a.hp ?? -1)
if (hp !== 0) return hp
return (a.name ?? '').localeCompare(b.name ?? '')
})
return (
<aside className="h-full min-h-0 flex flex-col rounded-md border border-neutral-700/60 bg-neutral-900/30 p-2 overflow-hidden">
<div className="flex items-center justify-between text-xs uppercase tracking-wide opacity-80">
<span className={`font-semibold ${teamColor}`}>{teamName}</span>
<span className="tabular-nums">{aliveCount}/{players.length}</span>
</div>
<div className="mt-2 flex-1 overflow-auto space-y-2 pr-1">
{sorted.map(p => {
const hp = clamp(p.alive === false ? 0 : p.hp ?? 100, 0, 100)
const armor = clamp(p.armor ?? 0, 0, 100)
const dead = p.alive === false || hp <= 0
return (
<div key={p.id} className={`rounded-md px-2 py-1.5 bg-neutral-800/40 ${dead ? 'opacity-50' : ''}`}>
<div className="flex items-center gap-2">
<span className={`inline-block w-1.5 h-1.5 rounded-full ${team === 'CT' ? 'bg-blue-400' : 'bg-amber-400'}`} />
<div className="truncate flex-1">{p.name || p.id}</div>
{/* kleine Status-Icons */}
{p.hasBomb && team === 'T' && <span title="Bomb" className="text-red-400">💣</span>}
{p.helmet && <span title="Helmet" className="opacity-80">🪖</span>}
{p.defuse && team === 'CT' && <span title="Defuse Kit" className="opacity-80">🗝</span>}
<div className={`text-xs tabular-nums ${align === 'right' ? 'text-right' : ''}`}>{hp}</div>
</div>
{/* HP-Bar */}
<div className="mt-1 h-2 rounded bg-neutral-700/60 overflow-hidden">
<div className="h-full bg-green-500" style={{ width: `${hp}%` }} />
</div>
{/* Armor-Bar */}
<div className="mt-1 h-1 rounded bg-neutral-700/60 overflow-hidden">
<div className={`h-full ${barArmor}`} style={{ width: `${armor}%` }} />
</div>
</div>
)
})}
</div>
</aside>
)
}
function clamp(n: number, a: number, b: number) {
return Math.max(a, Math.min(b, n))
}

View File

@ -8,8 +8,7 @@ import { Providers } from './components/Providers';
import Sidebar from './components/Sidebar'; import Sidebar from './components/Sidebar';
import ThemeProvider from "@/theme/theme-provider"; import ThemeProvider from "@/theme/theme-provider";
import Script from "next/script"; import Script from "next/script";
import NotificationBell from './components/NotificationBell' import NotificationBell from './components/NotificationBell';
import Navbar from "./components/Navbar";
import SSEHandler from "./lib/SSEHandler"; import SSEHandler from "./lib/SSEHandler";
import UserActivityTracker from "./components/UserActivityTracker"; import UserActivityTracker from "./components/UserActivityTracker";
import AudioPrimer from "./components/AudioPrimer"; import AudioPrimer from "./components/AudioPrimer";
@ -30,35 +29,38 @@ export const metadata = {
description: 'Steam Auth Dashboard', description: 'Steam Auth Dashboard',
} }
export default function RootLayout({ export default function RootLayout({ children }: { children: React.ReactNode }) {
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return ( return (
<html lang="en" suppressHydrationWarning> <html lang="en" suppressHydrationWarning>
<body className={`${geistSans.variable} ${geistMono.variable} antialiased bg-white dark:bg-black`}> <body className="antialiased bg-white dark:bg-black min-h-dvh">
<ThemeProvider <ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
<Providers> <Providers>
<SSEHandler /> <SSEHandler />
<UserActivityTracker /> <UserActivityTracker />
<AudioPrimer /> <AudioPrimer />
<ReadyOverlayHost /> <ReadyOverlayHost />
{/* Sidebar und Content direkt nebeneinander */}
<Sidebar> {/* App-Shell: Sidebar | Main */}
<div className="min-h-dvh grid grid-cols-1 sm:grid-cols-[16rem_1fr]">
<Sidebar />
{/* rechte Spalte */}
<div className="min-w-0 flex flex-col">
<main className="flex-1 in-w-0 overflow-hidden">
<div className="h-full box-border p-4 sm:p-6">
{children} {children}
</Sidebar> </div>
</main>
</div>
</div>
<NotificationBell /> <NotificationBell />
</Providers> </Providers>
</ThemeProvider> </ThemeProvider>
<PrelineScriptWrapper /> <PrelineScriptWrapper />
</body> </body>
</html> </html>
); )
} }

View File

@ -1,7 +1,12 @@
// /app/match-details/[matchId]/radar/page.tsx // /app/match-details/[matchId]/radar/page.tsx
import Card from '@/app/components/Card'
import LiveRadar from '@/app/components/radar/LiveRadar' import LiveRadar from '@/app/components/radar/LiveRadar'
export default function RadarPage({ params }: { params: { matchId: string } }) { export default function RadarPage({ params }: { params: { matchId: string } }) {
return <LiveRadar /> return (
<Card maxWidth="full" height="inherit">
<LiveRadar />
</Card>
);
} }