@@ -13,25 +13,44 @@ async function api(path, opts = {}) {
return res . json ( ) ;
}
function parseTag ( tag ) {
// erlaubt: null | "i" | "m" | "s" | "s.AL"
if ( ! tag ) return { base : null , chip : null } ;
if ( tag === "i" ) return { base : "i" , chip : null } ;
if ( tag === "m" ) return { base : "m" , chip : null } ;
if ( tag === "s" ) return { base : "s" , chip : null } ;
if ( tag . s tartsWith ( "s." ) ) return { base : "s" , chip : tag . slice ( 2 ) || null } ;
return { base : tag , chip : null } ;
/**
* Backend erlaubt: null | "i" | "m" | "s"
* Rotation:
* null -> i -> m -> s (Popup) -> null
*/
function cycleTag ( tag ) {
if ( ! tag ) return "i" ;
if ( tag === "i" ) return "m" ;
if ( tag === "m" ) return "s" ;
return null ; // "s" -> null
}
function nextBaseTag ( tag ) {
const { base } = parseTag ( tag ) ;
if ( ! base ) return "i" ;
if ( base === "i" ) return "m" ;
if ( base === "m" ) return "s" ; // hier öffnen wir dann das Popup
// wenn s (egal ob s oder s.XY), dann zurück auf leer
/* ========= Chip localStorage (Frontend-only) ========= */
function chipStorageKey ( gameId , entryId ) {
return ` chip: ${ gameId } : ${ entryId } ` ;
}
function getChipLS ( gameId , entryId ) {
try {
return localStorage . getItem ( chipStorageKey ( gameId , entryId ) ) ;
} catch {
return null ;
}
}
function setChipLS ( gameId , entryId , chip ) {
try {
localStorage . setItem ( chipStorageKey ( gameId , entryId ) , chip ) ;
} catch { }
}
function clearChipLS ( gameId , entryId ) {
try {
localStorage . removeItem ( chipStorageKey ( gameId , entryId ) ) ;
} catch { }
}
/* ========= Admin Panel ========= */
function AdminPanel ( ) {
const [ users , setUsers ] = useState ( [ ] ) ;
@@ -164,6 +183,7 @@ function AdminPanel() {
) ;
}
/* ========= App ========= */
export default function App ( ) {
const [ me , setMe ] = useState ( null ) ;
const [ loginEmail , setLoginEmail ] = useState ( "" ) ;
@@ -175,11 +195,21 @@ export default function App() {
const [ sheet , setSheet ] = useState ( null ) ;
const [ pulseId , setPulseId ] = useState ( null ) ;
const [ chipPickOpen , setChipPickOpen ] = useState ( false ) ;
const [ chipPickEntry , setChipPickEntry ] = useState ( null ) ;
// Chip popup
const [ chipOpen , setChipOpen ] = useState ( false ) ;
const [ chipEntry , setChipEntry ] = useState ( null ) ;
const [ helpOpen , setHelpOpen ] = useState ( false ) ;
// User dropdown + Passwort Modal
const [ userMenuOpen , setUserMenuOpen ] = useState ( false ) ;
const [ pwOpen , setPwOpen ] = useState ( false ) ;
const [ pw1 , setPw1 ] = useState ( "" ) ;
const [ pw2 , setPw2 ] = useState ( "" ) ;
const [ pwMsg , setPwMsg ] = useState ( "" ) ;
const [ pwSaving , setPwSaving ] = useState ( false ) ;
const load = async ( ) => {
const m = await api ( "/auth/me" ) ;
setMe ( m ) ;
@@ -190,6 +220,18 @@ export default function App() {
if ( gs [ 0 ] && ! gameId ) setGameId ( gs [ 0 ] . id ) ;
} ;
// Usermanager
useEffect ( ( ) => {
const onDown = ( e ) => {
// wenn Dropdown offen & Klick ist NICHT in einem Element mit data-user-menu
const root = e . target ? . closest ? . ( "[data-user-menu]" ) ;
if ( ! root ) setUserMenuOpen ( false ) ;
} ;
if ( userMenuOpen ) document . addEventListener ( "mousedown" , onDown ) ;
return ( ) => document . removeEventListener ( "mousedown" , onDown ) ;
} , [ userMenuOpen ] ) ;
// Google Fonts
useEffect ( ( ) => {
if ( document . getElementById ( "hp-fonts" ) ) return ;
@@ -225,7 +267,6 @@ export default function App() {
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes popIn { from { opacity: 0; transform: translateY(8px) scale(0.985); } to { opacity: 1; transform: translateY(0) scale(1); } }
@keyframes rowPulse { 0%{ transform: scale(1); } 50%{ transform: scale(1.01); } 100%{ transform: scale(1); } }
@keyframes candleGlow {
0% { opacity: .55; transform: translateY(0px) scale(1); filter: blur(16px); }
35% { opacity: .85; transform: translateY(-2px) scale(1.02); filter: blur(18px); }
@@ -246,7 +287,7 @@ export default function App() {
document . body . style . padding = "0" ;
} , [ ] ) ;
// Global CSS: Dark + Gold base
// Global CSS
useEffect ( ( ) => {
if ( document . getElementById ( "hp-global-style" ) ) return ;
const style = document . createElement ( "style" ) ;
@@ -261,15 +302,11 @@ export default function App() {
background: ${ stylesTokens . pageBg } ;
color: ${ stylesTokens . textMain } ;
}
body {
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
}
#root { background: transparent; }
/* Safari/Chrome tap highlight reduzieren (kein hover/flash) */
* { -webkit-tap-highlight-color: transparent; }
` ;
document . head . appendChild ( style ) ;
@@ -314,6 +351,52 @@ export default function App() {
setSheet ( null ) ;
} ;
// ===== Password change =====
const openPwModal = ( ) => {
setPwMsg ( "" ) ;
setPw1 ( "" ) ;
setPw2 ( "" ) ;
setPwOpen ( true ) ;
setUserMenuOpen ( false ) ;
} ;
const closePwModal = ( ) => {
setPwOpen ( false ) ;
setPwMsg ( "" ) ;
setPw1 ( "" ) ;
setPw2 ( "" ) ;
} ;
const savePassword = async ( ) => {
setPwMsg ( "" ) ;
if ( ! pw1 || pw1 . length < 8 ) {
setPwMsg ( "❌ Passwort muss mindestens 8 Zeichen haben." ) ;
return ;
}
if ( pw1 !== pw2 ) {
setPwMsg ( "❌ Passwörter stimmen nicht überein." ) ;
return ;
}
setPwSaving ( true ) ;
try {
await api ( "/auth/password" , {
method : "PATCH" ,
body : JSON . stringify ( { password : pw1 } ) ,
} ) ;
setPwMsg ( "✅ Passwort gespeichert." ) ;
// Optional: nach kurzer Zeit automatisch schließen
setTimeout ( ( ) => closePwModal ( ) , 650 ) ;
} catch ( e ) {
// dein api() wirft res.text() -> oft JSON Detail. Wir zeigen es einfach roh.
setPwMsg ( "❌ Fehler: " + ( e ? . message || "unknown" ) ) ;
} finally {
setPwSaving ( false ) ;
}
} ;
const newGame = async ( ) => {
const g = await api ( "/games" , {
method : "POST" ,
@@ -347,40 +430,93 @@ export default function App() {
setTimeout ( ( ) => setPulseId ( null ) , 220 ) ;
} ;
// Notiz-Button: i -> m -> (Popup) s -> null
const toggleTag = async ( entry ) => {
const next = nextBas eTag( entry . note _tag ) ;
const next = cycl eTag( entry . note _tag ) ;
// Wenn wir bei "s" angekommen sind -> Popup öffnen statt sofort setz en
// Wenn wir zu "s" gehen würden -> Chip Popup öffnen, aber NICHT ins Backend schreib en
if ( next === "s" ) {
setChipPick Entry ( entry ) ;
setChipPick Open ( true ) ;
setChipEntry ( entry ) ;
setChipOpen ( true ) ;
return ;
}
// normal setzen (— / i / m )
// Wenn wir auf null gehen, Chip lokal löschen (weil s -> — )
if ( next === null ) {
clearChipLS ( gameId , entry . entry _id ) ;
}
await api ( ` /games/ ${ gameId } /sheet/ ${ entry . entry _id } ` , {
method : "PATCH" ,
body : JSON . stringify ( { note _tag : next } ) ,
} ) ;
await reloadSheet ( ) ;
} ;
const closeChipPick = ( ) => {
setChipPickOpen ( false ) ;
setChipPickEntry ( null ) ;
} ;
// Chip wählen:
// Backend: note_tag = "s"
// Frontend: Chip in localStorage
const chooseChip = async ( chip ) => {
if ( ! chipPick Entry ) return ;
if ( ! chipEntry ) return ;
await api ( ` /games/ ${ gameId } /sheet/ ${ chipPickEntry . entry _id } ` , {
// UI sofort schließen -> fühlt sich besser an
const entry = chipEntry ;
setChipOpen ( false ) ;
setChipEntry ( null ) ;
// local speichern
setChipLS ( gameId , entry . entry _id , chip ) ;
try {
// Backend bekommt nur "s"
await api ( ` /games/ ${ gameId } /sheet/ ${ entry . entry _id } ` , {
method : "PATCH" ,
body : JSON . stringify ( { note _tag : ` s. ${ chip } ` } ) ,
body : JSON . stringify ( { note _tag : "s" } ) ,
} ) ;
closeChipPick ( ) ;
} finally {
await reloadSheet ( ) ;
}
} ;
// X im Modal:
// Backend zurück auf null und lokalen Chip löschen
const closeChipModalToDash = async ( ) => {
if ( ! chipEntry ) {
setChipOpen ( false ) ;
return ;
}
// UI sofort schließen
const entry = chipEntry ;
setChipOpen ( false ) ;
setChipEntry ( null ) ;
// Frontend-only Chip entfernen
clearChipLS ( gameId , entry . entry _id ) ;
try {
// Backend zurück auf —
await api ( ` /games/ ${ gameId } /sheet/ ${ entry . entry _id } ` , {
method : "PATCH" ,
body : JSON . stringify ( { note _tag : null } ) ,
} ) ;
} finally {
await reloadSheet ( ) ;
}
} ;
// Anzeige im Tag-Button:
// - "s" wird zu "s.AL" (aus localStorage), sonst "s"
const displayTag = ( entry ) => {
const t = entry . note _tag ;
if ( ! t ) return "—" ;
if ( t === "s" ) {
const chip = getChipLS ( gameId , entry . entry _id ) ;
return chip ? ` s. ${ chip } ` : "s" ; // <-- genau wie gewünscht
}
return t ; // i oder m
} ;
// --- helpers ---
@@ -408,7 +544,8 @@ export default function App() {
const getStatusBadge = ( status ) => {
if ( status === 2 ) return { color : "#baf3c9" , background : "rgba(0,190,80,0.18)" } ;
if ( status === 1 ) return { color : "#ffb3b3" , background : "rgba(255,35,35,0.18)" } ;
if ( status === 3 ) return { color : "rgba(233,216,166,0.85)" , background : "rgba(140,140,140,0.14)" } ;
if ( status === 3 )
return { color : "rgba(233,216,166,0.85)" , background : "rgba(140,140,140,0.14)" } ;
return { color : "rgba(233,216,166,0.75)" , background : "rgba(255,255,255,0.08)" } ;
} ;
@@ -427,9 +564,7 @@ export default function App() {
< div style = { styles . loginCard } >
< div style = { styles . loginTitle } > Zauber - Detektiv Notizbogen < / div >
< div style = { styles . loginSubtitle } >
Melde dich an , um dein Cluedo - Magie - Sheet zu öffnen
< / div >
< div style = { styles . loginSubtitle } > Melde dich an , um dein Cluedo - Magie - Sheet zu öffnen < / div >
< div style = { { marginTop : 18 , display : "grid" , gap : 12 } } >
< div style = { styles . loginFieldWrap } >
@@ -497,15 +632,42 @@ export default function App() {
< div style = { styles . topBar } >
< div >
< div style = { { fontWeight : 900 , color : stylesTokens . textGold } } > { me . email } < / div >
< div style = { { fontSize : 12 , opacity : 0.8 , color : stylesTokens . textDim } } >
{ me . role }
< / div >
< div style = { { fontSize : 12 , opacity : 0.8 , color : stylesTokens . textDim } } > { me . role } < / div >
< / div >
< div style = { { display : "flex" , gap : 8 } } >
< button onClick = { doLogout } style = { styles . secondaryBtn } >
< div style = { { display : "flex" , gap : 8 , alignItems : "center" } } data - user - menu >
< div style = { { position : "relative" } } >
< button
onClick = { ( ) => setUserMenuOpen ( ( v ) => ! v ) }
style = { styles . userBtn }
title = "User Menü"
>
< span style = { { fontSize : 16 , lineHeight : 1 } } > 👤 < / span >
< span > Account < / span >
< span style = { { opacity : 0.8 } } > ▾ < / span >
< / button >
{ userMenuOpen && (
< div style = { styles . userDropdown } >
< button onClick = { openPwModal } style = { styles . userDropdownItem } >
Passwort setzen
< / button >
< div style = { styles . userDropdownDivider } / >
< button
onClick = { ( ) => {
setUserMenuOpen ( false ) ;
doLogout ( ) ;
} }
style = { { ... styles . userDropdownItem , color : "#ffb3b3" } }
>
Logout
< / button >
< / div >
) }
< / div >
< button onClick = { newGame } style = { styles . primaryBtn } >
+ Neues Spiel
< / button >
@@ -558,25 +720,45 @@ export default function App() {
< span style = { { ... styles . helpBadge , background : "rgba(0,190,80,0.18)" , color : "#baf3c9" } } >
✓
< / span >
< div > < b > Grün < / b > = bestätigt / fix richtig < / div >
< div >
< b > Grün < / b > = bestätigt / fix richtig
< / div >
< / div >
< div style = { styles . helpListRow } >
< span style = { { ... styles . helpBadge , background : "rgba(255,35,35,0.18)" , color : "#ffb3b3" } } >
✕
< / span >
< div > < b > Rot < / b > = ausgeschlossen / fix falsch < / div >
< div >
< b > Rot < / b > = ausgeschlossen / fix falsch
< / div >
< / div >
< div style = { styles . helpListRow } >
< span style = { { ... styles . helpBadge , background : "rgba(140,140,140,0.14)" , color : "rgba(233,216,166,0.85)" } } >
< span
style = { {
... styles . helpBadge ,
background : "rgba(140,140,140,0.14)" ,
color : "rgba(233,216,166,0.85)" ,
} }
>
?
< / span >
< div > < b > Grau < / b > = unsicher / „ vielleicht “ < / div >
< div >
< b > Grau < / b > = unsicher / „ vielleicht “
< / div >
< / div >
< div style = { styles . helpListRow } >
< span style = { { ... styles . helpBadge , background : "rgba(255,255,255,0.08)" , color : "rgba(233,216,166,0.75)" } } >
< span
style = { {
... styles . helpBadge ,
background : "rgba(255,255,255,0.08)" ,
color : "rgba(233,216,166,0.75)" ,
} }
>
–
< / span >
< div > < b > Leer < / b > = unknown / noch nicht bewertet < / div >
< div >
< b > Leer < / b > = unknown / noch nicht bewertet
< / div >
< / div >
< / div >
@@ -590,73 +772,115 @@ export default function App() {
< div style = { styles . helpList } >
< div style = { styles . helpListRow } >
< span style = { styles . helpMiniTag } > i < / span >
< div > < b > i < / b > = „ Ich habe diese Geheimkarte “ < / div >
< div >
< b > i < / b > = „ Ich habe diese Geheimkarte “
< / div >
< / div >
< div style = { styles . helpListRow } >
< span style = { styles . helpMiniTag } > m < / span >
< div > < b > m < / b > = „ Geheimkarte aus dem mittleren Deck “ < / div >
< div >
< b > m < / b > = „ Geheimkarte aus dem mittleren Deck “
< / div >
< / div >
< div style = { styles . helpListRow } >
< span style = { styles . helpMiniTag } > s < / span >
< div > < b > s < / b > = „ Ein anderer Spieler hat diese Karte “ < / div >
< div >
< b > s < / b > = „ Ein anderer Spieler hat diese Karte “ ( Chip Auswahl )
< / div >
< / div >
< div style = { styles . helpListRow } >
< span style = { styles . helpMiniTag } > — < / span >
< div > < b > — < / b > = keine Notiz < / div >
< div >
< b > — < / b > = keine Notiz
< / div >
< / div >
< / div >
< div style = { styles . helpDivider } / >
< div style = { styles . helpText } >
Tipp : Jeder Spieler sieht nur seine eigenen Notizen – andere Spieler können nicht in deinen Zettel schauen .
Tipp : Jeder Spieler sieht nur seine eigenen Notizen – andere Spieler können nicht in deinen
Zettel schauen .
< / div >
< / div >
< / div >
< / div >
) }
{ chipPick Open && (
< div style = { styles . modalOverlay } onMouseDown = { closeChipPick } >
{ pw Open && (
< div style = { styles . modalOverlay } onMouseDown = { closePwModal } >
< div style = { styles . modalCard } onMouseDown = { ( e ) => e . stopPropagation ( ) } >
< div style = { styles . modalHeader } >
< div style = { { fontWeight : 1000 , color : stylesTokens . textGold } } >
Wer hat die Karte ?
< / div >
< button
onClick = { closeChipPick }
style = { styles . modalCloseBtn }
aria - label = "Schließen"
>
< div style = { { fontWeight : 1000 , color : stylesTokens . textGold } } > Passwort setzen < / div >
< button onClick = { closePwModal } style = { styles . modalCloseBtn } aria - label = "Schließen" >
✕
< / button >
< / div >
< div style = { { marginTop : 12 , color : stylesTokens . textMain , opacity : 0.9 } } >
Chip auswählen :
< div style = { { marginTop : 12 , display : "grid" , gap : 10 } } >
< input
value = { pw1 }
onChange = { ( e ) => setPw1 ( e . target . value ) }
placeholder = "Neues Passwort"
type = "password"
style = { styles . input }
autoFocus
/ >
< input
value = { pw2 }
onChange = { ( e ) => setPw2 ( e . target . value ) }
placeholder = "Neues Passwort wiederholen"
type = "password"
style = { styles . input }
/ >
{ pwMsg && < div style = { { opacity : 0.92 , color : stylesTokens . textMain } } > { pwMsg } < / div > }
< div style = { { display : "flex" , gap : 8 , justifyContent : "flex-end" , marginTop : 4 } } >
< button onClick = { closePwModal } style = { styles . secondaryBtn } disabled = { pwSaving } >
Abbrechen
< / button >
< button onClick = { savePassword } style = { styles . primaryBtn } disabled = { pwSaving } >
{ pwSaving ? "Speichern..." : "Speichern" }
< / button >
< / div >
< div style = { { fontSize : 12 , opacity : 0.75 , color : stylesTokens . textDim } } >
Hinweis : Mindestens 8 Zeichen empfohlen .
< / div >
< / div >
< / div >
< / div >
) }
{ /* Chip Popup */ }
{ chipOpen && (
< div style = { styles . modalOverlay } onMouseDown = { closeChipModalToDash } >
< div style = { styles . modalCard } onMouseDown = { ( e ) => e . stopPropagation ( ) } >
< div style = { styles . modalHeader } >
< div style = { { fontWeight : 1000 , color : stylesTokens . textGold } } > Wer hat die Karte ? < / div >
< button onClick = { closeChipModalToDash } style = { styles . modalCloseBtn } aria - label = "Schließen" >
✕
< / button >
< / div >
< div style = { { marginTop : 12 , color : stylesTokens . textMain } } > Chip auswählen : < / div >
< div style = { styles . chipGrid } >
{ CHIP _LIST . map ( ( c ) => (
< button
key = { c }
onClick = { ( ) => chooseChip ( c ) }
style = { styles . chipBtn }
title = { ` Setze s. ${ c } ` }
>
< button key = { c } onClick = { ( ) => chooseChip ( c ) } style = { styles . chipBtn } >
{ c }
< / button >
) ) }
< / div >
< div style = { { marginTop : 10 , fontSize : 12 , color : stylesTokens . textDim } } >
Tipp : Wenn du wieder auf den Notiz - Button klickst , geht ’ s von < b > s . XX < / b > zurück auf < b > — < / b > .
< div style = { { marginTop : 12 , fontSize : 12 , color : stylesTokens . textDim } } >
Tipp : Wenn du wieder auf den Notiz - Button klickst , geht ’ s von < b > s . XX < / b > zurück auf — .
< / div >
< / div >
< / div >
) }
< div style = { { marginTop : 14 , display : "grid" , gap : 14 } } >
{ sections . map ( ( sec ) => (
< div key = { sec . key } style = { styles . card } >
@@ -664,29 +888,37 @@ export default function App() {
< div style = { { display : "grid" } } >
{ sec . entries . map ( ( e ) => {
const badge = getStatusBadge ( e . status ) ;
// UI "rot" wenn note_tag i oder s (Backend s wird als s.XX angezeigt)
const isIorMorS = e . note _tag === "i" || e . note _tag === "m" || e . note _tag === "s" ;
const effectiveStatus = e . status === 0 && isIorMorS ? 1 : e . status ;
const badge = getStatusBadge ( effectiveStatus ) ;
return (
< div
key = { e . entry _id }
className = "hp-row"
style = { {
... styles . row ,
background : getRowBg ( e. s tatus ) ,
background : getRowBg ( effectiveS tatus ) ,
animation : pulseId === e . entry _id ? "rowPulse 220ms ease-out" : "none" ,
borderLeft :
e. s tatus === 2 ? "4px solid rgba(0,190,80,0.55)" :
e . status === 1 ? "4px solid rgba(255,35,35 ,0.55)" :
e . status === 3 ? "4px solid rgba(233,216,166,0.22)" :
"4px solid rgba(0,0,0,0)" ,
effectiveS tatus === 2
? "4px solid rgba(0,190,80 ,0.55)"
: effectiveStatus === 1
? "4px solid rgba(255,35,35,0.55)"
: effectiveStatus === 3
? "4px solid rgba(233,216,166,0.22)"
: "4px solid rgba(0,0,0,0)" ,
} }
>
< div
onClick = { ( ) => cycleStatus ( e ) }
style = { {
... styles . name ,
textDecoration : e. s tatus === 1 ? "line-through" : "none" ,
color : getNameColor ( e. s tatus ) ,
opacity : e. s tatus === 1 ? 0.8 : 1 ,
textDecoration : effectiveS tatus === 1 ? "line-through" : "none" ,
color : getNameColor ( effectiveS tatus ) ,
opacity : effectiveS tatus === 1 ? 0.8 : 1 ,
} }
title = "Klick: Grün → Rot → Grau → Leer"
>
@@ -695,12 +927,12 @@ export default function App() {
< div style = { styles . statusCell } >
< span style = { { ... styles . statusBadge , color : badge . color , background : badge . background } } >
{ getStatusSymbol ( e. s tatus ) }
{ getStatusSymbol ( effectiveS tatus ) }
< / span >
< / div >
< button onClick = { ( ) => toggleTag ( e ) } style = { styles . tagBtn } title = "i → m → s → leer " >
{ e . note _tag || "—" }
< button onClick = { ( ) => toggleTag ( e ) } style = { styles . tagBtn } title = "— → i → m → s.(Chip) → — " >
{ displayTag ( e ) }
< / button >
< / div >
) ;
@@ -1152,7 +1384,6 @@ const styles = {
backgroundSize : "cover" ,
backgroundPosition : "center" ,
backgroundRepeat : "no-repeat" ,
/* kleines Dark-Wash, damit Schwarz/Gold lesbar ist */
filter : "saturate(0.9) contrast(1.05) brightness(0.55)" ,
} ,
@@ -1164,14 +1395,59 @@ const styles = {
} ,
chipBtn : {
padding : "10px 0 " ,
padding : "10px 14px " ,
borderRadius : 12 ,
border : ` 1px solid rgba(233,216,166,0.22) ` ,
border : " 1px solid rgba(233,216,166,0.18)" ,
background : "rgba(255,255,255,0.06)" ,
color : stylesTokens . textGold ,
fontWeight : 11 00 ,
fontWeight : 10 00 ,
cursor : "pointer" ,
minWidth : 64 ,
} ,
userBtn : {
display : "inline-flex" ,
alignItems : "center" ,
gap : 8 ,
padding : "10px 12px" ,
borderRadius : 12 ,
border : ` 1px solid rgba(233,216,166,0.18) ` ,
background : "rgba(255,255,255,0.05)" ,
color : stylesTokens . textMain ,
fontWeight : 900 ,
cursor : "pointer" ,
boxShadow : "inset 0 1px 0 rgba(255,255,255,0.06)" ,
maxWidth : 180 ,
} ,
userDropdown : {
position : "absolute" ,
right : 0 ,
top : "calc(100% + 8px)" ,
minWidth : 220 ,
borderRadius : 14 ,
border : ` 1px solid rgba(233,216,166,0.18) ` ,
background : "linear-gradient(180deg, rgba(20,20,24,0.96), rgba(12,12,14,0.92))" ,
boxShadow : "0 18px 55px rgba(0,0,0,0.70)" ,
overflow : "hidden" ,
zIndex : 10000 ,
backdropFilter : "blur(8px)" ,
} ,
userDropdownItem : {
width : "100%" ,
textAlign : "left" ,
padding : "10px 12px" ,
border : "none" ,
background : "transparent" ,
color : stylesTokens . textMain ,
fontWeight : 900 ,
cursor : "pointer" ,
} ,
userDropdownDivider : {
height : 1 ,
background : "rgba(233,216,166,0.12)" ,
} ,
} ;