2026-03-29 21:41:17 +03:00
<!DOCTYPE html>
< html lang = "ro" >
< head >
< meta charset = "UTF-8" >
< title > Red Valley — Resource Synapse Map< / title >
< style >
* { margin : 0 ; padding : 0 ; box-sizing : border-box }
body { background : #0a0a1a ; color : #e0e0e0 ; font-family : 'Segoe UI' , system-ui , sans-serif ; overflow : hidden }
# app { display : flex ; height : 100 vh }
# sidebar { width : 360 px ; background : #12122a ; border-right : 1 px solid #2a2a5a ; overflow-y : auto ; padding : 16 px ; flex-shrink : 0 }
# sidebar h1 { font-size : 18 px ; background : linear-gradient ( 135 deg , #8b5cf6 , #ec4899 ) ; -webkit- background-clip : text ; background-clip : text ; -webkit- text-fill-color : transparent ; margin-bottom : 2 px }
# sidebar . sub { font-size : 11 px ; color : #666 ; margin-bottom : 12 px }
. stats { display : flex ; gap : 8 px ; margin-bottom : 12 px }
. stat { flex : 1 ; background : #1a1a3a ; border-radius : 8 px ; padding : 8 px ; text-align : center ; font-size : 11 px }
. stat b { display : block ; font-size : 18 px ; color : #8b5cf6 }
# search { width : 100 % ; padding : 10 px 14 px ; background : #1a1a3a ; border : 1 px solid #333 ; border-radius : 10 px ; color : #fff ; font-size : 13 px ; margin-bottom : 12 px ; transition : border .2 s }
# search : focus { outline : none ; border-color : #8b5cf6 ; box-shadow : 0 0 12 px rgba ( 139 , 92 , 246 , .2 ) }
# teamFilter { display : flex ; gap : 4 px ; margin-bottom : 12 px ; flex-wrap : wrap }
# teamFilter button { padding : 4 px 10 px ; border-radius : 12 px ; border : 1 px solid #333 ; background : #1a1a3a ; color : #aaa ; font-size : 10 px ; cursor : pointer ; transition : all .2 s }
# teamFilter button : hover , # teamFilter button . active { border-color : #8b5cf6 ; color : #fff ; background : #2a2a5a }
# info { font-size : 12 px ; line-height : 1.7 }
# info h2 { font-size : 14 px ; margin : 10 px 0 4 px ; display : flex ; align-items : center ; gap : 6 px }
# info . tag { display : inline-block ; padding : 2 px 8 px ; border-radius : 10 px ; font-size : 9 px ; margin : 1 px 2 px ; background : #1e1e3f ; border : 1 px solid #333 }
# info . dep-item { padding : 4 px 0 ; border-bottom : 1 px solid #1a1a3a ; cursor : pointer ; transition : all .15 s ; padding-left : 4 px ; border-radius : 4 px }
# info . dep-item : hover { color : #fff ; background : #1e1e3f ; padding-left : 8 px }
. legend { margin-top : 14 px ; font-size : 10 px ; border-top : 1 px solid #1e1e3f ; padding-top : 10 px }
. legend-title { font-size : 11 px ; color : #666 ; margin-bottom : 6 px }
. legend div { display : flex ; align-items : center ; gap : 6 px ; margin : 3 px 0 }
. legend span { width : 10 px ; height : 10 px ; border-radius : 50 % ; display : inline-block ; flex-shrink : 0 }
. link-legend { margin-top : 8 px }
. link-legend div span { width : 16 px ; height : 3 px ; border-radius : 2 px }
svg { flex : 1 }
. link { stroke-opacity : .25 ; fill : none ; cursor : pointer }
. link : hover { stroke-opacity : .8 ; stroke-width : 3 !important }
. link . highlighted { stroke-opacity : 1 ; stroke-width : 3 !important }
. link . dimmed { stroke-opacity : .03 }
. node-circle { stroke-width : 1.5 ; cursor : pointer ; filter : drop-shadow ( 0 0 4 px rgba ( 0 , 0 , 0 , .5 ) ) }
. node-circle . dimmed { opacity : .07 }
. node-circle . highlighted { stroke : #fff ; stroke-width : 3 ; filter : drop-shadow ( 0 0 8 px rgba ( 139 , 92 , 246 , .6 ) ) }
. node-label { fill : #bbb ; pointer-events : none ; text-anchor : middle ; font-weight : 500 }
. node-label . dimmed { opacity : .05 }
. tooltip { position : fixed ; background : #1e1e3f ee ; border : 1 px solid #8b5cf6 ; border-radius : 10 px ; padding : 10 px 14 px ; font-size : 12 px ; pointer-events : none ; display : none ; z-index : 100 ; max-width : 420 px ; backdrop-filter : blur ( 8 px ) ; box-shadow : 0 8 px 32 px rgba ( 0 , 0 , 0 , .4 ) }
. tooltip . pinned { pointer-events : auto ; max-height : 80 vh ; overflow-y : auto }
. tooltip b { color : #c084fc }
. tooltip . tt-row { padding : 3 px 0 ; border-bottom : 1 px solid #1e1e3f ; font-size : 11 px }
. tooltip . tt-type { display : inline-block ; padding : 1 px 6 px ; border-radius : 8 px ; font-size : 9 px ; margin-left : 4 px }
. tooltip . tt-close { position : absolute ; top : 6 px ; right : 10 px ; cursor : pointer ; color : #666 ; font-size : 14 px ; line-height : 1 }
. tooltip . tt-close : hover { color : #fff }
. tooltip . tt-code { background : #0d0d1a ; border : 1 px solid #2a2a4a ; border-radius : 6 px ; padding : 8 px 10 px ; margin : 6 px 0 ; font-family : 'Cascadia Code' , 'Fira Code' , monospace ; font-size : 10 px ; line-height : 1.6 ; color : #a5b4fc ; white-space : pre-wrap ; word-break : break-all }
. tooltip . tt-code . cm { color : #666 ; font-style : italic }
. tooltip . tt-code . fn { color : #c084fc }
. tooltip . tt-code . str { color : #86efac }
. tooltip . tt-code . kw { color : #f59e0b }
< / style >
< / head >
< body >
< div id = "app" >
< div id = "sidebar" >
< h1 > 🧠 Resource Synapse Map< / h1 >
< div class = "sub" > Red Valley Roleplay — Click nod/linie | Scroll = zoom< / div >
< div class = "stats" >
< div class = "stat" > < b id = "nodeCount" > 0< / b > Resurse< / div >
< div class = "stat" > < b id = "linkCount" > 0< / b > Conexiuni< / div >
< div class = "stat" > < b id = "teamCount" > 6< / b > Echipe Dev< / div >
< / div >
< input id = "search" placeholder = "🔍 Caută resursă sau keyword..." autocomplete = "off" >
< div id = "teamFilter" >
< button class = "active" data-team = "all" > Toate< / button >
< button data-team = "17mov" > 17 Movement< / button >
< button data-team = "wasabi" > Wasabi< / button >
< button data-team = "quasar" > Quasar< / button >
< button data-team = "kq" > KuzQuality< / button >
< button data-team = "rcore" > rCore< / button >
< button data-team = "t1ger" > T1GER< / button >
< button data-team = "qb" > QBCore< / button >
< button data-team = "other" > Altele< / button >
< / div >
< div id = "info" > < p style = "color:#555" > Click pe un nod sau pe o linie pentru detalii.< / p > < / div >
< div class = "legend" >
< div class = "legend-title" > CULORI PER ECHIPĂ< / div >
< div > < span style = "background:#ef4444" > < / span > QBCore< / div >
< div > < span style = "background:#f97316" > < / span > 17 Movement< / div >
< div > < span style = "background:#3b82f6" > < / span > Wasabi< / div >
< div > < span style = "background:#eab308" > < / span > Quasar< / div >
< div > < span style = "background:#a3e635" > < / span > KuzQuality< / div >
< div > < span style = "background:#f43f5e" > < / span > rCore< / div >
< div > < span style = "background:#14b8a6" > < / span > T1GER< / div >
< div > < span style = "background:#8b5cf6" > < / span > Admin/UI< / div >
< div > < span style = "background:#64748b" > < / span > Dependencies/Other< / div >
< div class = "link-legend" >
< div class = "legend-title" style = "margin-top:8px" > TIPURI CONEXIUNI (săgeți)< / div >
< div > < span style = "background:#818cf8" > < / span > Export call< / div >
< div > < span style = "background:#f59e0b" > < / span > Event trigger< / div >
< div > < span style = "background:#10b981" > < / span > Config read< / div >
< div > < span style = "background:#475569" > < / span > Hard dependency< / div >
< / div >
< / div >
< / div >
< svg id = "graph" > < / svg >
< / div >
< div class = "tooltip" id = "tooltip" > < / div >
< script src = "https://d3js.org/d3.v7.min.js" > < / script >
< script >
// === TEAM COLORS (culori diferite per echipă) ===
const teamColors = {
qb : "#ef4444" , "17mov" : "#f97316" , wasabi : "#3b82f6" , quasar : "#eab308" ,
kq : "#a3e635" , rcore : "#f43f5e" , t1ger : "#14b8a6" , ui : "#8b5cf6" , other : "#64748b" ,
cfx : "#94a3b8" , stream : "#475569"
} ;
const linkColors = { export : "#818cf8" , event : "#f59e0b" , config : "#10b981" , dep : "#475569" } ;
const teamMap = { "17mov" : "17 Movement" , wasabi : "Wasabi" , quasar : "Quasar" , kq : "KuzQuality" , rcore : "rCore" , t1ger : "T1GER" , qb : "QBCore" , other : "Altele" , cfx : "CFX" , stream : "Stream" , ui : "Admin/UI" } ;
const nodes = [
// === QBCORE ===
{ id : "qb-core" , team : "qb" , r : 32 , desc : "Nucleul QBCore — jobs, items, player, events" , keywords : "framework core player job item shared" } ,
2026-06-17 16:53:15 +03:00
{ id : "qb-target" , team : "qb" , r : 18 , desc : "Target system (ld-target engine) — provides qb-target + qtarget" , keywords : "target interact eye ld-target qtarget provide" } ,
2026-03-29 21:41:17 +03:00
{ id : "qb-menu" , team : "qb" , r : 10 , desc : "Menu system QBCore" , keywords : "menu context" } ,
{ id : "qb-smallresources" , team : "qb" , r : 14 , desc : "Consumables, AFK, seatbelt etc" , keywords : "consumable food drink stress seatbelt" } ,
{ id : "qb-input" , team : "qb" , r : 8 , desc : "Input dialog QBCore" , keywords : "input dialog form" } ,
{ id : "qb-management" , team : "qb" , r : 14 , desc : "Boss menu, society, gang management" , keywords : "management boss society money hire fire" } ,
{ id : "qb-weathersync" , team : "qb" , r : 10 , desc : "Weather & time sync" , keywords : "weather time sync rain" } ,
{ id : "qb-interior" , team : "qb" , r : 8 , desc : "Interior IPL loader" , keywords : "interior ipl room" } ,
// === 17 MOVEMENT ===
{ id : "17mov_CharacterSystem" , team : "17mov" , r : 24 , desc : "Character select/create, clothing, outfits, skin" , keywords : "character clothing outfit skin wardrobe dress clothes save ped" } ,
{ id : "17mov-plugin-char-creator" , team : "17mov" , r : 10 , desc : "Plugin outfit în character creator" , keywords : "creator spawn plugin char" } ,
{ id : "17mov_Hud" , team : "17mov" , r : 22 , desc : "HUD — notify, stress, hunger/thirst, progress, radar" , keywords : "hud notify notification stress hunger thirst progress bar radar" } ,
{ id : "17mov_JobCenter" , team : "17mov" , r : 12 , desc : "Job center multiplayer" , keywords : "jobcenter employment hire" } ,
{ id : "17mov_Electrician" , team : "17mov" , r : 10 , desc : "Job electrician" , keywords : "electrician wire job citizen" } ,
{ id : "17mov_BuilderJob" , team : "17mov" , r : 9 , desc : "Job constructor" , keywords : "builder construction citizen job" } ,
{ id : "17mov_Deliverer" , team : "17mov" , r : 9 , desc : "Job livrări" , keywords : "delivery courier citizen job" } ,
{ id : "17mov_GarbageCollector" , team : "17mov" , r : 9 , desc : "Job gunoi" , keywords : "garbage trash collector citizen job" } ,
{ id : "17mov_Lumberjack" , team : "17mov" , r : 9 , desc : "Job lemne" , keywords : "lumberjack wood tree citizen job" } ,
{ id : "17mov_Miner" , team : "17mov" , r : 9 , desc : "Job miner" , keywords : "miner mining ore rock citizen job" } ,
{ id : "17mov_OilRig" , team : "17mov" , r : 9 , desc : "Job platformă petrol" , keywords : "oil rig petrol citizen job" } ,
{ id : "17mov_Postman" , team : "17mov" , r : 9 , desc : "Job poștas" , keywords : "postman mail letter citizen job" } ,
{ id : "17mov_TreasureHunter" , team : "17mov" , r : 9 , desc : "Job vânător de comori" , keywords : "treasure hunter dig citizen job" } ,
{ id : "17mov_WindowCleaning" , team : "17mov" , r : 9 , desc : "Job spălat geamuri" , keywords : "window cleaning building citizen job" } ,
// === QUASAR ===
{ id : "qs-inventory" , team : "quasar" , r : 24 , desc : "Inventar — items, stash, crafting, trunk" , keywords : "inventory item stash trunk glovebox slot weight craft" } ,
{ id : "qs-vehiclekeys" , team : "quasar" , r : 20 , desc : "Chei vehicule — lock/unlock, hotwire" , keywords : "key keys lock unlock hotwire vehicle car plate givekey remote" } ,
{ id : "qs-advancedgarages" , team : "quasar" , r : 16 , desc : "Garaje avansate — stash, wardrobe" , keywords : "garage park vehicle store wardrobe plate impound" } ,
{ id : "qs-shops" , team : "quasar" , r : 14 , desc : "Magazine configurabile" , keywords : "shop store buy sell market" } ,
{ id : "qs-smartphone-pro" , team : "quasar" , r : 18 , desc : "Telefon PRO — apps, calls, camera" , keywords : "phone smartphone call sms camera gallery radio remote lock" } ,
{ id : "qs-housing" , team : "quasar" , r : 14 , desc : "Sistem case — stash, wardrobe, furniture" , keywords : "house housing property home furniture stash wardrobe" } ,
{ id : "qs-weed" , team : "quasar" , r : 10 , desc : "Sistem weed growing" , keywords : "weed drugs grow plant" } ,
{ id : "qs-notify" , team : "quasar" , r : 8 , desc : "Notificări Quasar" , keywords : "notify notification alert" } ,
{ id : "qs-weapondraw" , team : "quasar" , r : 8 , desc : "Animații scoatere armă" , keywords : "weapon draw holster animation" } ,
// === WASABI ===
{ id : "wasabi_bridge" , team : "wasabi" , r : 16 , desc : "Bridge framework Wasabi" , keywords : "bridge framework wasabi dependency" } ,
{ id : "wasabi_police" , team : "wasabi" , r : 18 , desc : "Job poliție v1.10.8 ✅ — duty, cuff, jail(rcore), CCTV, radar, F6 menu" , keywords : "police cop law duty arrest handcuff spike k9 armory jail rcore cctv radar bobby_pin tracking" } ,
{ id : "wasabi_ambulance" , team : "wasabi" , r : 18 , desc : "Job ambulanță v1.14.2 ✅ — revive, heal, stretcher, death system" , keywords : "ambulance ems medic heal revive death hospital stretcher defib sedative diagnose" } ,
// === T1GER ===
{ id : "t1ger_mechanic" , team : "t1ger" , r : 16 , desc : "Job mecanic — duty, garage, repair" , keywords : "mechanic repair fix duty garage tuning" } ,
{ id : "t1ger_tuningsystem" , team : "t1ger" , r : 14 , desc : "Tuning system vehicule" , keywords : "tuning performance turbo engine suspension" } ,
{ id : "t1ger_lib" , team : "t1ger" , r : 12 , desc : "Librărie T1GER — bridge stash/target" , keywords : "library bridge t1ger" } ,
{ id : "t1ger_carlift" , team : "t1ger" , r : 8 , desc : "Elevator mașini mecanic" , keywords : "carlift elevator mechanic" } ,
{ id : "t1ger_mechanicprops" , team : "t1ger" , r : 6 , desc : "Props mecanici (stream)" , keywords : "props mechanic stream" } ,
// === RCORE ===
{ id : "rcore_fuel" , team : "rcore" , r : 14 , desc : "Fuel system — benzinării, jerry can, consum" , keywords : "fuel gas petrol pump jerry station diesel electric" } ,
{ id : "rcore_casino" , team : "rcore" , r : 14 , desc : "Casino — blackjack, poker, slots, roulette, VIP" , keywords : "casino gamble blackjack poker slots roulette lucky wheel vip chips" } ,
{ id : "rcore_casino_assets" , team : "rcore" , r : 6 , desc : "Casino assets (stream)" , keywords : "casino props assets stream" } ,
{ id : "rcore_casino_interior" , team : "rcore" , r : 6 , desc : "Casino interior MLO" , keywords : "casino interior mlo stream" } ,
{ id : "rcore_fuel_assets" , team : "rcore" , r : 6 , desc : "Fuel assets (stream)" , keywords : "fuel props assets stream" } ,
{ id : "rcore_prison" , team : "rcore" , r : 12 , desc : "Închisoare V2 — jail, prison break, solitary" , keywords : "prison jail sentence inmate break solitary community service" } ,
{ id : "rcore_doorlock" , team : "rcore" , r : 10 , desc : "Door lock — uși, business, keychain" , keywords : "door lock unlock access business keychain crafting permissions" } ,
// === KUZQUALITY ===
{ id : "kq_carheist" , team : "kq" , r : 12 , desc : "Car heist minigame" , keywords : "car heist steal minigame" } ,
{ id : "kq_dyno" , team : "kq" , r : 10 , desc : "Dyno bench — HP, torque testing" , keywords : "dyno bench test performance hp torque" } ,
{ id : "kq_wheeldamage" , team : "kq" , r : 8 , desc : "Realistic wheel damage" , keywords : "wheel damage tire burst deformation" } ,
{ id : "kq_link" , team : "kq" , r : 8 , desc : "KQ Link — update & licensing" , keywords : "kq link update license" } ,
{ id : "kq_animsuggest" , team : "kq" , r : 7 , desc : "Sugestii animații contextuale" , keywords : "animation suggest idle context" } ,
{ id : "kq_bikejump" , team : "kq" , r : 7 , desc : "Bike jump realistic" , keywords : "bike jump bmx stunt" } ,
{ id : "kq_brakeoverheat" , team : "kq" , r : 7 , desc : "Brake overheat realistic" , keywords : "brake heat smoke overheat" } ,
{ id : "kq_driftsmoke" , team : "kq" , r : 7 , desc : "Drift smoke realistic" , keywords : "drift smoke tire burnout" } ,
// === MDT / DISPATCH ===
{ id : "codem-mdt" , team : "other" , r : 10 , desc : "MDT codem — records, warrants" , keywords : "mdt codem records warrant" } ,
{ id : "codem-dispatch" , team : "other" , r : 12 , desc : "Dispatch — 911, alerts, GPS" , keywords : "dispatch 911 alert gps blip police" } ,
{ id : "codem-mdtProp" , team : "other" , r : 6 , desc : "MDT prop model (laptop)" , keywords : "mdt prop laptop model" } ,
// === ADMIN / UI ===
{ id : "luxu_admin" , team : "ui" , r : 18 , desc : "Admin panel — manage players, vehicles" , keywords : "admin panel manage player vehicle item teleport spectate ban" } ,
{ id : "mBossmenu" , team : "other" , r : 10 , desc : "Boss menu CodeM — society" , keywords : "boss society money hire fire" } ,
// === ADDONS ===
{ id : "0r_idcard" , team : "other" , r : 12 , desc : "ID card + driving license" , keywords : "id card license driving permit identity photo" } ,
{ id : "bit-driverschool" , team : "other" , r : 12 , desc : "Școală auto — test, license" , keywords : "driving school test license learn drive" } ,
{ id : "jg-dealerships" , team : "other" , r : 12 , desc : "Dealer vehicule — showroom, finance" , keywords : "dealer dealership buy car vehicle showroom finance" } ,
{ id : "svdden_banking" , team : "other" , r : 10 , desc : "Banking — ATM, transfer" , keywords : "bank atm money transfer deposit withdraw" } ,
{ id : "ac-carcontrol" , team : "other" , r : 10 , desc : "Car control — lock/engine toggle" , keywords : "car control lock engine toggle vehicle" } ,
{ id : "aty_busjob" , team : "other" , r : 9 , desc : "Job șofer autobuz" , keywords : "bus driver transport job citizen" } ,
{ id : "jo_towtruck" , team : "other" , r : 10 , desc : "Job tractări — tow truck" , keywords : "tow truck tractor transport mechanic" } ,
{ id : "ak4y-dice" , team : "other" , r : 7 , desc : "Minigame zaruri" , keywords : "dice gamble minigame" } ,
{ id : "bennylift" , team : "other" , r : 7 , desc : "Benny's car lift animation" , keywords : "benny lift car mechanic" } ,
{ id : "squidgame" , team : "other" , r : 7 , desc : "Squid game minigame" , keywords : "squid game minigame event" } ,
{ id : "VehicleDeformation" , team : "other" , r : 8 , desc : "Vehicle deformation realistic" , keywords : "vehicle deformation crash damage" } ,
// === DEPENDENCIES ===
{ id : "ox_lib" , team : "other" , r : 14 , desc : "Overextended Lib — notify, locale, UI" , keywords : "oxlib notify locale context menu ui" } ,
{ id : "oxmysql" , team : "other" , r : 14 , desc : "MySQL driver — DB queries" , keywords : "mysql database query db sql" } ,
{ id : "pma-voice" , team : "other" , r : 14 , desc : "Voice chat — proximity, calls, radio" , keywords : "voice chat proximity call radio microphone" } ,
{ id : "progressbar" , team : "other" , r : 8 , desc : "Progress bar UI" , keywords : "progress bar loading" } ,
{ id : "PolyZone" , team : "other" , r : 8 , desc : "Zone management — poly, circles" , keywords : "zone polygon circle box area" } ,
{ id : "screenshot-basic" , team : "other" , r : 8 , desc : "Screenshot capture" , keywords : "screenshot capture photo headshot" } ,
{ id : "interact-sound" , team : "other" , r : 6 , desc : "Sunete interacțiune" , keywords : "sound effect interact" } ,
{ id : "xsound" , team : "other" , r : 6 , desc : "Sound system 3D audio" , keywords : "sound music audio 3d xsound" } ,
{ id : "phone-radio" , team : "other" , r : 8 , desc : "Radio pe telefon" , keywords : "radio frequency channel" } ,
{ id : "phone-props" , team : "other" , r : 6 , desc : "Prop-uri telefon (stream)" , keywords : "phone prop model stream" } ,
{ id : "phone-recorder" , team : "other" , r : 6 , desc : "Recorder telefon" , keywords : "phone recorder video" } ,
{ id : "phone-render" , team : "other" , r : 6 , desc : "Render telefon" , keywords : "phone render camera" } ,
{ id : "connectqueue" , team : "other" , r : 8 , desc : "Queue la conectare" , keywords : "queue connect join loading" } ,
{ id : "wasabi_bridge_dep" , team : "other" , r : 0 } ,
{ id : "bob74_ipl" , team : "other" , r : 8 , desc : "IPL loader v2.6.0 — interiors, DLC maps (Casino, Tuner, Drug Wars, Mansions)" , keywords : "ipl interior loader dlc casino tuner drugwars mansions simeon criminal enterprise" } ,
{ id : "Howdy-Minigame" , team : "other" , r : 8 , desc : "Minigame hacking" , keywords : "minigame hack hacking" } ,
{ id : "mhacking" , team : "other" , r : 7 , desc : "Hacking minigame alternativ" , keywords : "hacking minigame laptop" } ,
{ id : "no-npc" , team : "other" , r : 6 , desc : "Remove NPC ambient" , keywords : "npc remove ambient ped" } ,
{ id : "minimap" , team : "stream" , r : 6 , desc : "Custom minimap" , keywords : "minimap radar custom" } ,
// === HOUSING SUB-RESOURCES ===
{ id : "housing-addons" , team : "quasar" , r : 6 , desc : "Housing addons props" , keywords : "housing addon prop furniture" } ,
{ id : "housing-island" , team : "quasar" , r : 6 , desc : "Housing island map" , keywords : "housing island cayo" } ,
{ id : "housing-sign" , team : "quasar" , r : 6 , desc : "Housing for sale signs" , keywords : "housing sign sale" } ,
] ;
// Remove hidden placeholder node
const filteredNodes = nodes . filter ( n => n . r > 0 ) ;
const links = [
// === qb-core dependencies ===
{ s : "qs-inventory" , t : "qb-core" , type : "export" , label : "GetCoreObject" } ,
{ s : "qs-vehiclekeys" , t : "qb-core" , type : "export" , label : "GetCoreObject" } ,
{ s : "qs-advancedgarages" , t : "qb-core" , type : "export" , label : "GetCoreObject" } ,
{ s : "qs-shops" , t : "qb-core" , type : "export" , label : "GetCoreObject" } ,
{ s : "qs-smartphone-pro" , t : "qb-core" , type : "export" , label : "GetCoreObject" } ,
{ s : "qs-housing" , t : "qb-core" , type : "export" , label : "GetCoreObject" } ,
{ s : "17mov_CharacterSystem" , t : "qb-core" , type : "export" , label : "GetCoreObject" } ,
{ s : "17mov-plugin-char-creator" , t : "qb-core" , type : "export" , label : "GetCoreObject" } ,
{ s : "17mov_Hud" , t : "qb-core" , type : "event" , label : "OnPlayerLoaded, OnJobUpdate" } ,
{ s : "17mov_JobCenter" , t : "qb-core" , type : "event" , label : "OnPlayerLoaded, OnJobUpdate" } ,
{ s : "17mov_Electrician" , t : "qb-core" , type : "event" , label : "PlayerLoaded" } ,
{ s : "17mov_BuilderJob" , t : "qb-core" , type : "event" , label : "PlayerLoaded" } ,
{ s : "17mov_Deliverer" , t : "qb-core" , type : "event" , label : "PlayerLoaded" } ,
{ s : "17mov_GarbageCollector" , t : "qb-core" , type : "event" , label : "PlayerLoaded" } ,
{ s : "17mov_Lumberjack" , t : "qb-core" , type : "event" , label : "PlayerLoaded" } ,
{ s : "17mov_Miner" , t : "qb-core" , type : "event" , label : "PlayerLoaded" } ,
{ s : "17mov_OilRig" , t : "qb-core" , type : "event" , label : "PlayerLoaded" } ,
{ s : "17mov_Postman" , t : "qb-core" , type : "event" , label : "PlayerLoaded" } ,
{ s : "17mov_TreasureHunter" , t : "qb-core" , type : "event" , label : "PlayerLoaded" } ,
{ s : "17mov_WindowCleaning" , t : "qb-core" , type : "event" , label : "PlayerLoaded" } ,
{ s : "t1ger_mechanic" , t : "qb-core" , type : "export" , label : "GetCoreObject" } ,
{ s : "wasabi_bridge" , t : "qb-core" , type : "export" , label : "GetCoreObject" } ,
{ s : "luxu_admin" , t : "qb-core" , type : "export" , label : "GetCoreObject" } ,
{ s : "qb-target" , t : "qb-core" , type : "export" , label : "GetCoreObject" } ,
2026-06-17 16:53:15 +03:00
{ s : "17mov_Electrician" , t : "qb-target" , type : "export" , label : "AddTargetEntity (via qtarget provide)" } ,
2026-03-29 21:41:17 +03:00
{ s : "qb-menu" , t : "qb-core" , type : "export" , label : "GetCoreObject" } ,
{ s : "qb-smallresources" , t : "qb-core" , type : "export" , label : "GetCoreObject" } ,
{ s : "qb-management" , t : "qb-core" , type : "export" , label : "GetCoreObject" } ,
{ s : "qb-weathersync" , t : "qb-core" , type : "event" , label : "OnPlayerLoaded" } ,
{ s : "0r_idcard" , t : "qb-core" , type : "export" , label : "GetCoreObject" } ,
{ s : "bit-driverschool" , t : "qb-core" , type : "export" , label : "GetCoreObject" } ,
{ s : "jg-dealerships" , t : "qb-core" , type : "export" , label : "GetCoreObject" } ,
{ s : "rcore_fuel" , t : "qb-core" , type : "event" , label : "OnPlayerLoaded, OnJobUpdate" } ,
{ s : "rcore_casino" , t : "qb-core" , type : "event" , label : "OnPlayerLoaded, OnJobUpdate" } ,
{ s : "rcore_prison" , t : "qb-core" , type : "event" , label : "OnPlayerLoaded" } ,
{ s : "rcore_doorlock" , t : "qb-core" , type : "export" , label : "GetCoreObject" } ,
{ s : "svdden_banking" , t : "qb-core" , type : "export" , label : "GetCoreObject" } ,
{ s : "ac-carcontrol" , t : "qb-core" , type : "export" , label : "GetCoreObject" } ,
{ s : "kq_carheist" , t : "qb-core" , type : "event" , label : "OnPlayerLoaded" } ,
{ s : "kq_dyno" , t : "qb-core" , type : "event" , label : "OnPlayerLoaded" } ,
{ s : "progressbar" , t : "qb-core" , type : "export" , label : "GetCoreObject" } ,
{ s : "codem-mdt" , t : "qb-core" , type : "event" , label : "OnPlayerLoaded, OnJobUpdate" } ,
{ s : "codem-dispatch" , t : "qb-core" , type : "event" , label : "OnPlayerLoaded" } ,
{ s : "mBossmenu" , t : "qb-core" , type : "export" , label : "GetCoreObject" } ,
{ s : "qb-input" , t : "qb-core" , type : "export" , label : "GetCoreObject" } ,
{ s : "qs-weed" , t : "qb-core" , type : "event" , label : "OnJobUpdate" } ,
{ s : "jo_towtruck" , t : "qb-core" , type : "export" , label : "GetCoreObject" } ,
{ s : "aty_busjob" , t : "qb-core" , type : "export" , label : "GetCoreObject" } ,
// === 17mov_Hud (notificări) ===
{ s : "qb-core" , t : "17mov_Hud" , type : "export" , label : "ShowNotification routing" } ,
{ s : "ox_lib" , t : "17mov_Hud" , type : "export" , label : "ShowNotification bridge" } ,
{ s : "0r_idcard" , t : "17mov_Hud" , type : "export" , label : "ShowNotification" } ,
{ s : "bit-driverschool" , t : "17mov_Hud" , type : "export" , label : "ShowNotification" } ,
{ s : "qb-smallresources" , t : "17mov_Hud" , type : "event" , label : "stress relief notify" } ,
{ s : "qs-inventory" , t : "17mov_Hud" , type : "event" , label : "hunger/thirst/stress" } ,
{ s : "wasabi_ambulance" , t : "17mov_Hud" , type : "event" , label : "RelieveStress, UpdateNeeds" } ,
{ s : "luxu_admin" , t : "17mov_Hud" , type : "event" , label : "UpdateNeeds admin" } ,
{ s : "jo_towtruck" , t : "17mov_Hud" , type : "export" , label : "ShowNotification" } ,
// === qs-inventory connections ===
{ s : "qb-core" , t : "qs-inventory" , type : "export" , label : "AddItem/RemoveItem bridge" } ,
{ s : "qb-smallresources" , t : "qs-inventory" , type : "export" , label : "consumables, usable items" } ,
{ s : "17mov_CharacterSystem" , t : "qs-inventory" , type : "export" , label : "setInClothing(true/false)" } ,
{ s : "luxu_admin" , t : "qs-inventory" , type : "export" , label : "item management" } ,
{ s : "mBossmenu" , t : "qs-inventory" , type : "export" , label : "society items" } ,
{ s : "qs-advancedgarages" , t : "qs-inventory" , type : "export" , label : "trunk/glovebox stash" } ,
{ s : "qs-shops" , t : "qs-inventory" , type : "export" , label : "shop items" } ,
{ s : "qs-smartphone-pro" , t : "qs-inventory" , type : "export" , label : "phone item check" } ,
{ s : "qs-vehiclekeys" , t : "qs-inventory" , type : "export" , label : "key item metadata" } ,
{ s : "rcore_casino" , t : "qs-inventory" , type : "export" , label : "chips / bar items" } ,
{ s : "rcore_fuel" , t : "qs-inventory" , type : "export" , label : "jerry can items" } ,
{ s : "rcore_prison" , t : "qs-inventory" , type : "export" , label : "prison items" } ,
{ s : "rcore_doorlock" , t : "qs-inventory" , type : "export" , label : "keychain / lockpick" } ,
{ s : "t1ger_lib" , t : "qs-inventory" , type : "export" , label : "stash AddItem mechanic" } ,
{ s : "codem-mdt" , t : "qs-inventory" , type : "export" , label : "inventory check" } ,
// === qs-vehiclekeys connections ===
{ s : "0r_idcard" , t : "qs-vehiclekeys" , type : "export" , label : "GiveKeys driving license" } ,
{ s : "bit-driverschool" , t : "qs-vehiclekeys" , type : "export" , label : "GiveKeys test auto" } ,
{ s : "ac-carcontrol" , t : "qs-vehiclekeys" , type : "export" , label : "GetVehicleKeys, SetLocked" } ,
{ s : "qs-advancedgarages" , t : "qs-vehiclekeys" , type : "export" , label : "GiveKeys/RemoveKeys" } ,
{ s : "qs-smartphone-pro" , t : "qs-vehiclekeys" , type : "export" , label : "remote lock/unlock" } ,
{ s : "rcore_fuel" , t : "qs-vehiclekeys" , type : "export" , label : "owner key check pump" } ,
{ s : "rcore_casino" , t : "qs-vehiclekeys" , type : "export" , label : "GiveKeys casino vehicle" } ,
{ s : "luxu_admin" , t : "qs-vehiclekeys" , type : "export" , label : "giveVehicleKeys admin" } ,
{ s : "wasabi_bridge" , t : "qs-vehiclekeys" , type : "export" , label : "GiveKeys police/ambulance" } ,
{ s : "qb-target" , t : "qs-vehiclekeys" , type : "export" , label : "GetVehicleKeys target" } ,
{ s : "qb-smallresources" , t : "qs-vehiclekeys" , type : "export" , label : "key check" } ,
{ s : "kq_carheist" , t : "qs-vehiclekeys" , type : "export" , label : "hotwire/keys check" } ,
// === 17mov_CharacterSystem connections ===
{ s : "17mov-plugin-char-creator" , t : "17mov_CharacterSystem" , type : "event" , label : "SaveCurrentSkin" } ,
{ s : "t1ger_mechanic" , t : "17mov_CharacterSystem" , type : "config" , label : "Config.Outfits['mechanic']" } ,
{ s : "qs-housing" , t : "17mov_CharacterSystem" , type : "event" , label : "openOutfitMenu" } ,
{ s : "qs-advancedgarages" , t : "17mov_CharacterSystem" , type : "event" , label : "openOutfitMenu" } ,
{ s : "qb-management" , t : "17mov_CharacterSystem" , type : "event" , label : "openOutfitMenu" } ,
{ s : "wasabi_ambulance" , t : "17mov_CharacterSystem" , type : "event" , label : "openMenu clothing" } ,
{ s : "qb-smallresources" , t : "17mov_CharacterSystem" , type : "event" , label : "loadOutfit (parașută)" } ,
{ s : "rcore_casino" , t : "17mov_CharacterSystem" , type : "config" , label : "casino outfit bridge" } ,
{ s : "rcore_prison" , t : "17mov_CharacterSystem" , type : "config" , label : "prison clothing bridge" } ,
// === wasabi connections ===
{ s : "wasabi_police" , t : "wasabi_bridge" , type : "dep" , label : "framework bridge" } ,
{ s : "wasabi_ambulance" , t : "wasabi_bridge" , type : "dep" , label : "framework bridge" } ,
{ s : "wasabi_police" , t : "oxmysql" , type : "dep" , label : "CCTV, radar, jail DB" } ,
{ s : "wasabi_police" , t : "qs-inventory" , type : "export" , label : "items: handcuffs, bobby_pin, tracking_bracelet" } ,
{ s : "wasabi_police" , t : "rcore_prison" , type : "config" , label : "Config.Jail.jail='rcore'" } ,
{ s : "wasabi_police" , t : "codem-mdt" , type : "event" , label : "MDT records integration" } ,
{ s : "wasabi_ambulance" , t : "oxmysql" , type : "dep" , label : "death/injury DB" } ,
{ s : "wasabi_ambulance" , t : "qs-inventory" , type : "export" , label : "medical items: defib, medikit" } ,
// === t1ger connections ===
{ s : "t1ger_mechanic" , t : "t1ger_lib" , type : "dep" , label : "GetLib core" } ,
{ s : "t1ger_tuningsystem" , t : "t1ger_lib" , type : "dep" , label : "GetLib core" } ,
{ s : "t1ger_tuningsystem" , t : "t1ger_mechanic" , type : "export" , label : "IsPlayerMechanic" } ,
{ s : "t1ger_carlift" , t : "t1ger_mechanic" , type : "config" , label : "car lift integration" } ,
{ s : "t1ger_lib" , t : "qb-target" , type : "export" , label : "target system" } ,
{ s : "t1ger_lib" , t : "qb-management" , type : "export" , label : "society billing" } ,
// === 17mov citizen jobs → JobCenter ===
{ s : "17mov_BuilderJob" , t : "17mov_JobCenter" , type : "event" , label : "job registration" } ,
{ s : "17mov_Deliverer" , t : "17mov_JobCenter" , type : "event" , label : "job registration" } ,
{ s : "17mov_Electrician" , t : "17mov_JobCenter" , type : "event" , label : "job registration" } ,
{ s : "17mov_GarbageCollector" , t : "17mov_JobCenter" , type : "event" , label : "job registration" } ,
{ s : "17mov_Lumberjack" , t : "17mov_JobCenter" , type : "event" , label : "job registration" } ,
{ s : "17mov_Miner" , t : "17mov_JobCenter" , type : "event" , label : "job registration" } ,
{ s : "17mov_OilRig" , t : "17mov_JobCenter" , type : "event" , label : "job registration" } ,
{ s : "17mov_Postman" , t : "17mov_JobCenter" , type : "event" , label : "job registration" } ,
{ s : "17mov_TreasureHunter" , t : "17mov_JobCenter" , type : "event" , label : "job registration" } ,
{ s : "17mov_WindowCleaning" , t : "17mov_JobCenter" , type : "event" , label : "job registration" } ,
// === admin connections ===
{ s : "luxu_admin" , t : "pma-voice" , type : "export" , label : "voice control mute" } ,
{ s : "luxu_admin" , t : "qs-smartphone-pro" , type : "export" , label : "phone control" } ,
{ s : "luxu_admin" , t : "oxmysql" , type : "dep" , label : "database queries" } ,
// === phone connections ===
{ s : "qs-smartphone-pro" , t : "pma-voice" , type : "export" , label : "call system voice" } ,
{ s : "phone-radio" , t : "qs-smartphone-pro" , type : "dep" , label : "radio app" } ,
{ s : "phone-radio" , t : "xsound" , type : "dep" , label : "audio playback" } ,
{ s : "phone-radio" , t : "pma-voice" , type : "export" , label : "radio voice" } ,
// === qb-management ===
{ s : "qb-management" , t : "qb-target" , type : "export" , label : "boss menu target" } ,
{ s : "qb-management" , t : "qb-input" , type : "export" , label : "input dialogs" } ,
{ s : "qb-management" , t : "qb-menu" , type : "export" , label : "context menus" } ,
{ s : "qb-menu" , t : "qb-input" , type : "dep" , label : "input integration" } ,
// === jg-dealerships ===
{ s : "jg-dealerships" , t : "oxmysql" , type : "dep" , label : "vehicle DB" } ,
{ s : "jg-dealerships" , t : "ox_lib" , type : "dep" , label : "UI/locale" } ,
// === driving school / idcard ===
{ s : "bit-driverschool" , t : "0r_idcard" , type : "export" , label : "setPlayerLicense" } ,
{ s : "0r_idcard" , t : "screenshot-basic" , type : "export" , label : "headshot capture" } ,
// === MDT / dispatch ===
{ s : "codem-dispatch" , t : "wasabi_police" , type : "event" , label : "police alerts" } ,
{ s : "codem-mdt" , t : "oxmysql" , type : "dep" , label : "records DB" } ,
{ s : "codem-mdtProp" , t : "codem-mdt" , type : "dep" , label : "prop model" } ,
// === housing ===
{ s : "qs-housing" , t : "oxmysql" , type : "dep" , label : "housing DB" } ,
{ s : "housing-addons" , t : "qs-housing" , type : "dep" , label : "addons props" } ,
{ s : "housing-island" , t : "qs-housing" , type : "dep" , label : "island map" } ,
{ s : "housing-sign" , t : "qs-housing" , type : "dep" , label : "sale signs" } ,
// === rcore ===
{ s : "rcore_fuel" , t : "oxmysql" , type : "dep" , label : "fuel DB" } ,
{ s : "rcore_casino" , t : "oxmysql" , type : "dep" , label : "casino DB" } ,
{ s : "rcore_prison" , t : "oxmysql" , type : "dep" , label : "prison DB (auto)" } ,
{ s : "rcore_doorlock" , t : "oxmysql" , type : "dep" , label : "doors DB" } ,
{ s : "rcore_prison" , t : "qb-target" , type : "export" , label : "target interactions" } ,
{ s : "rcore_prison" , t : "wasabi_police" , type : "event" , label : "jail integration" } ,
{ s : "rcore_prison" , t : "codem-dispatch" , type : "event" , label : "prison break dispatch" } ,
{ s : "rcore_doorlock" , t : "qb-target" , type : "export" , label : "door interactions" } ,
{ s : "rcore_casino_assets" , t : "rcore_casino" , type : "dep" , label : "casino props" } ,
{ s : "rcore_casino_interior" , t : "rcore_casino" , type : "dep" , label : "casino MLO" } ,
{ s : "rcore_fuel_assets" , t : "rcore_fuel" , type : "dep" , label : "fuel props" } ,
// === KQ connections ===
{ s : "kq_carheist" , t : "kq_link" , type : "dep" , label : "license & auto-update" } ,
{ s : "kq_dyno" , t : "kq_link" , type : "dep" , label : "license & auto-update" } ,
{ s : "kq_wheeldamage" , t : "kq_link" , type : "dep" , label : "license & auto-update" } ,
{ s : "kq_animsuggest" , t : "kq_link" , type : "dep" , label : "license & auto-update" } ,
{ s : "kq_bikejump" , t : "kq_link" , type : "dep" , label : "license & auto-update" } ,
{ s : "kq_brakeoverheat" , t : "kq_link" , type : "dep" , label : "license & auto-update" } ,
{ s : "kq_driftsmoke" , t : "kq_link" , type : "dep" , label : "license & auto-update" } ,
{ s : "kq_carheist" , t : "codem-dispatch" , type : "event" , label : "policeAlert la furt" } ,
{ s : "kq_dyno" , t : "t1ger_mechanic" , type : "config" , label : "job mechanic access" } ,
// === other ===
{ s : "jo_towtruck" , t : "t1ger_mechanic" , type : "config" , label : "job mechanic tow" } ,
{ s : "aty_busjob" , t : "17mov_Hud" , type : "export" , label : "ShowNotification" } ,
{ s : "svdden_banking" , t : "oxmysql" , type : "dep" , label : "banking DB" } ,
{ s : "qs-weed" , t : "qs-inventory" , type : "export" , label : "weed items" } ,
{ s : "qs-weed" , t : "oxmysql" , type : "dep" , label : "weed DB" } ,
{ s : "bob74_ipl" , t : "qb-interior" , type : "dep" , label : "IPL loading" } ,
] ;
document . getElementById ( "nodeCount" ) . textContent = filteredNodes . length ;
document . getElementById ( "linkCount" ) . textContent = links . length ;
const W = Math . max ( window . innerWidth - 360 , 600 ) , H = window . innerHeight ;
const svg = d3 . select ( "#graph" ) . attr ( "width" , W ) . attr ( "height" , H ) ;
const defs = svg . append ( "defs" ) ;
// Arrow markers per link type
Object . entries ( linkColors ) . forEach ( ( [ type , color ] ) => {
defs . append ( "marker" ) . attr ( "id" , "arrow-" + type ) . attr ( "viewBox" , "0 0 10 10" )
. attr ( "refX" , 10 ) . attr ( "refY" , 5 ) . attr ( "markerWidth" , 4 ) . attr ( "markerHeight" , 4 )
. attr ( "orient" , "auto-start-reverse" ) . attr ( "fill" , color ) . attr ( "fill-opacity" , . 6 )
. append ( "path" ) . attr ( "d" , "M 0 0 L 10 5 L 0 10 z" ) ;
} ) ;
const g = svg . append ( "g" ) ;
svg . call ( d3 . zoom ( ) . scaleExtent ( [ . 1 , 5 ] ) . on ( "zoom" , e => g . attr ( "transform" , e . transform ) ) ) ;
links . forEach ( l => { l . source = l . s ; l . target = l . t } ) ;
function nodeColor ( d ) { return teamColors [ d . team ] || "#64748b" }
const simulation = d3 . forceSimulation ( filteredNodes )
. force ( "link" , d3 . forceLink ( links ) . id ( d => d . id ) . distance ( d => {
const core = d . source . id === "qb-core" || d . target . id === "qb-core" ;
return core ? 220 : 140 ;
} ) )
. force ( "charge" , d3 . forceManyBody ( ) . strength ( d => d . id === "qb-core" ? - 1200 : - 300 ) )
. force ( "center" , d3 . forceCenter ( W / 2 , H / 2 ) )
. force ( "collision" , d3 . forceCollide ( ) . radius ( d => d . r + 6 ) )
. force ( "x" , d3 . forceX ( W / 2 ) . strength ( . 02 ) )
. force ( "y" , d3 . forceY ( H / 2 ) . strength ( . 02 ) ) ;
// Links as lines with arrow markers
const link = g . append ( "g" ) . selectAll ( "line" ) . data ( links ) . join ( "line" )
. attr ( "class" , "link" )
. attr ( "stroke" , d => linkColors [ d . type ] || "#444" )
. attr ( "stroke-width" , d => d . type === "export" ? 1 : d . type === "event" ? . 8 : . 6 )
. attr ( "marker-end" , d => "url(#arrow-" + ( d . type || "dep" ) + ")" ) ;
// Invisible wider hit area for clicking links
const linkHit = g . append ( "g" ) . selectAll ( "line" ) . data ( links ) . join ( "line" )
. attr ( "stroke" , "transparent" ) . attr ( "stroke-width" , 12 ) . attr ( "fill" , "none" )
. style ( "cursor" , "pointer" ) ;
const node = g . append ( "g" ) . selectAll ( "g" ) . data ( filteredNodes ) . join ( "g" )
. call ( d3 . drag ( )
. on ( "start" , ( e , d ) => { if ( ! e . active ) simulation . alphaTarget ( . 3 ) . restart ( ) ; d . fx = d . x ; d . fy = d . y } )
. on ( "drag" , ( e , d ) => { d . fx = e . x ; d . fy = e . y } )
. on ( "end" , ( e , d ) => { if ( ! e . active ) simulation . alphaTarget ( 0 ) ; d . fx = null ; d . fy = null } ) ) ;
node . append ( "circle" ) . attr ( "class" , "node-circle" ) . attr ( "r" , d => d . r )
. attr ( "fill" , d => nodeColor ( d ) )
. attr ( "stroke" , d => d3 . color ( nodeColor ( d ) ) . darker ( . 5 ) ) ;
const labels = g . append ( "g" ) . selectAll ( "text" ) . data ( filteredNodes ) . join ( "text" )
. attr ( "class" , "node-label" )
. attr ( "dy" , d => d . r + 13 )
. attr ( "font-size" , d => Math . max ( 8 , Math . min ( 11 , d . r * . 5 ) ) )
. text ( d => d . id ) ;
simulation . on ( "tick" , ( ) => {
// Shorten lines so arrows don't overlap circles
link . each ( function ( d ) {
const dx = d . target . x - d . source . x , dy = d . target . y - d . source . y ;
const dist = Math . sqrt ( dx * dx + dy * dy ) || 1 ;
const sr = d . source . r || 8 , tr = d . target . r || 8 ;
const sx = d . source . x + dx / dist * ( sr + 2 ) , sy = d . source . y + dy / dist * ( sr + 2 ) ;
const tx = d . target . x - dx / dist * ( tr + 6 ) , ty = d . target . y - dy / dist * ( tr + 6 ) ;
d3 . select ( this ) . attr ( "x1" , sx ) . attr ( "y1" , sy ) . attr ( "x2" , tx ) . attr ( "y2" , ty ) ;
} ) ;
linkHit . attr ( "x1" , d => d . source . x ) . attr ( "y1" , d => d . source . y )
. attr ( "x2" , d => d . target . x ) . attr ( "y2" , d => d . target . y ) ;
node . attr ( "transform" , d => ` translate( ${ d . x } , ${ d . y } ) ` ) ;
labels . attr ( "x" , d => d . x ) . attr ( "y" , d => d . y ) ;
} ) ;
// === INTERACTION: NODE CLICK ===
let selected = null ;
function selectNode ( d ) {
if ( selected === d . id ) { resetHighlight ( ) ; selected = null ; showDefault ( ) ; return }
selected = d . id ;
const connected = new Set ( [ d . id ] ) ;
const outgoing = [ ] , incoming = [ ] ;
links . forEach ( l => {
const sid = typeof l . source === "object" ? l . source . id : l . source ;
const tid = typeof l . target === "object" ? l . target . id : l . target ;
if ( sid === d . id ) { connected . add ( tid ) ; outgoing . push ( { to : tid , type : l . type , label : l . label || l . type } ) }
if ( tid === d . id ) { connected . add ( sid ) ; incoming . push ( { from : sid , type : l . type , label : l . label || l . type } ) }
} ) ;
d3 . selectAll ( ".node-circle" ) . classed ( "dimmed" , n => ! connected . has ( n . id ) ) . classed ( "highlighted" , n => n . id === d . id ) ;
d3 . selectAll ( ".node-label" ) . classed ( "dimmed" , n => ! connected . has ( n . id ) ) ;
link . classed ( "dimmed" , l => {
const s = typeof l . source === "object" ? l . source . id : l . source ;
const t = typeof l . target === "object" ? l . target . id : l . target ;
return s !== d . id && t !== d . id ;
} ) . classed ( "highlighted" , l => {
const s = typeof l . source === "object" ? l . source . id : l . source ;
const t = typeof l . target === "object" ? l . target . id : l . target ;
return s === d . id || t === d . id ;
} ) ;
const total = outgoing . length + incoming . length ;
let h = ` <h2 style="color: ${ nodeColor ( d ) } ">🧠 ${ d . id } </h2> ` ;
h += ` <p style="color:#888;margin:4px 0 8px;font-size:11px"> ${ d . desc || "" } </p> ` ;
h += ` <span class="tag" style="border-color: ${ nodeColor ( d ) } ;color: ${ nodeColor ( d ) } "> ${ teamMap [ d . team ] || d . team } </span> ` ;
h += ` <span class="tag"> ${ total } conexiuni</span> ` ;
if ( outgoing . length ) {
h += ` <h2 style="color:#f97316">→ Depinde de ( ${ outgoing . length } )</h2> ` ;
outgoing . forEach ( o => h += ` <div class="dep-item" style="color:#f97316" data-id=" ${ o . to } ">• ${ o . to } <span class="tag"> ${ o . label } </span></div> ` ) ;
}
if ( incoming . length ) {
h += ` <h2 style="color:#22d3ee">← Folosit de ( ${ incoming . length } )</h2> ` ;
incoming . forEach ( i => h += ` <div class="dep-item" style="color:#22d3ee" data-id=" ${ i . from } ">• ${ i . from } <span class="tag"> ${ i . label } </span></div> ` ) ;
}
document . getElementById ( "info" ) . innerHTML = h ;
document . querySelectorAll ( ".dep-item" ) . forEach ( el => el . addEventListener ( "click" , ( ) => {
const n = filteredNodes . find ( n => n . id === el . dataset . id ) ; if ( n ) selectNode ( n ) ;
} ) ) ;
}
node . on ( "click" , ( e , d ) => { e . stopPropagation ( ) ; selectNode ( d ) } ) ;
svg . on ( "click" , ( ) => { resetHighlight ( ) ; selected = null ; showDefault ( ) ; unpinTooltip ( ) } ) ;
function resetHighlight ( ) {
d3 . selectAll ( ".node-circle" ) . classed ( "dimmed" , false ) . classed ( "highlighted" , false ) ;
d3 . selectAll ( ".node-label" ) . classed ( "dimmed" , false ) ;
link . classed ( "dimmed" , false ) . classed ( "highlighted" , false ) ;
}
function showDefault ( ) { document . getElementById ( "info" ) . innerHTML = '<p style="color:#555">Click pe un nod sau pe o linie pentru detalii.</p>' }
// === CODE SNIPPET GENERATOR ===
function getCodeSnippet ( l ) {
const s = typeof l . source === "object" ? l . source . id : l . source ;
const t = typeof l . target === "object" ? l . target . id : l . target ;
if ( l . type === "export" ) {
if ( l . label === "GetCoreObject" ) return ` <span class='cm'>-- ${ s } obține framework-ul QBCore</span> \n <span class='kw'>local</span> QBCore = <span class='fn'>exports</span>[<span class='str'>'qb-core'</span>]:<span class='fn'>GetCoreObject</span>() <span class='cm'>-- returnează obiectul principal cu toate funcțiile QBCore</span> ` ;
if ( l . label && l . label . includes ( "ShowNotification" ) ) return ` <span class='cm'>-- ${ s } trimite notificare prin ${ t } </span> \n <span class='fn'>exports</span>[<span class='str'>' ${ t } '</span>]:<span class='fn'>ShowNotification</span>(msg, type) <span class='cm'>-- type: 'success'|'error'|'info'</span> ` ;
if ( l . label && l . label . includes ( "GiveKeys" ) ) return ` <span class='cm'>-- ${ s } dă cheile vehiculului prin ${ t } </span> \n <span class='fn'>exports</span>[<span class='str'>' ${ t } '</span>]:<span class='fn'>GiveKeys</span>(plate) <span class='cm'>-- plate = nr înmatriculare, adaugă keys in inventar</span> ` ;
if ( l . label && l . label . includes ( "RemoveKeys" ) ) return ` <span class='cm'>-- ${ s } gestionează cheile vehiculului prin ${ t } </span> \n <span class='fn'>exports</span>[<span class='str'>' ${ t } '</span>]:<span class='fn'>GiveKeys</span>(plate) <span class='cm'>-- dă cheia la player</span> \n <span class='fn'>exports</span>[<span class='str'>' ${ t } '</span>]:<span class='fn'>RemoveKeys</span>(plate) <span class='cm'>-- scoate cheia de la player</span> ` ;
if ( l . label && l . label . includes ( "GetVehicleKeys" ) ) return ` <span class='cm'>-- ${ s } verifică dacă player-ul are cheile</span> \n <span class='kw'>local</span> hasKeys = <span class='fn'>exports</span>[<span class='str'>' ${ t } '</span>]:<span class='fn'>GetVehicleKeys</span>(plate) <span class='cm'>-- returnează true/false</span> ` ;
if ( l . label && l . label . includes ( "setInClothing" ) ) return ` <span class='cm'>-- ${ s } blochează inventarul când deschide clothing</span> \n <span class='fn'>exports</span>[<span class='str'>'qs-inventory'</span>]:<span class='fn'>setInClothing</span>(<span class='kw'>true</span>) <span class='cm'>-- blochează deschiderea inventarului</span> \n <span class='cm'>-- și la închiderea meniului de clothing:</span> \n <span class='fn'>exports</span>[<span class='str'>'qs-inventory'</span>]:<span class='fn'>setInClothing</span>(<span class='kw'>false</span>) <span class='cm'>-- permite iar inventarul</span> ` ;
if ( l . label && l . label . includes ( "AddItem" ) ) return ` <span class='cm'>-- ${ s } adaugă/scoate items prin inventar</span> \n <span class='fn'>exports</span>[<span class='str'>' ${ t } '</span>]:<span class='fn'>AddItem</span>(src, item, amount) <span class='cm'>-- src=server id, item=string, amount=nr</span> \n <span class='fn'>exports</span>[<span class='str'>' ${ t } '</span>]:<span class='fn'>RemoveItem</span>(src, item, amount) <span class='cm'>-- scoate item din inventar</span> ` ;
if ( l . label && l . label . includes ( "stash" ) ) return ` <span class='cm'>-- ${ s } deschide/creează stash prin ${ t } </span> \n <span class='fn'>exports</span>[<span class='str'>' ${ t } '</span>]:<span class='fn'>OpenStash</span>(id, label, slots, weight) <span class='cm'>-- id=unic, slots=nr sloturi, weight=greutate max</span> ` ;
if ( l . label && l . label . includes ( "target" ) ) return ` <span class='cm'>-- ${ s } adaugă target zone prin ${ t } </span> \n <span class='fn'>exports</span>[<span class='str'>' ${ t } '</span>]:<span class='fn'>AddBoxZone</span>( <span class='cm'>-- creează zonă de interacțiune 3D</span> \n name, <span class='cm'>-- identificator unic zone</span> \n center, <span class='cm'>-- vector3 coordonate centru</span> \n length, width, <span class='cm'>-- dimensiuni zonă</span> \n options, <span class='cm'>-- {heading, minZ, maxZ}</span> \n targetoptions <span class='cm'>-- {icon, label, action}</span> \n ) ` ;
if ( l . label && l . label . includes ( "IsPlayerMechanic" ) ) return ` <span class='cm'>-- ${ s } verifică dacă player-ul e mecanic activ</span> \n <span class='kw'>local</span> isMech = <span class='fn'>exports</span>[<span class='str'>' ${ t } '</span>]:<span class='fn'>IsPlayerMechanic</span>() <span class='cm'>-- true dacă e on-duty ca mecanic</span> ` ;
if ( l . label && l . label . includes ( "IsPlayerTuner" ) ) return ` <span class='cm'>-- ${ s } verifică dacă player-ul e tuner activ</span> \n <span class='kw'>local</span> isTuner, shopId = <span class='fn'>exports</span>[<span class='str'>' ${ t } '</span>]:<span class='fn'>IsPlayerTuner</span>() <span class='cm'>-- returnează bool + id-ul shop-ului</span> ` ;
if ( l . label && l . label . includes ( "society" ) ) return ` <span class='cm'>-- ${ s } accesează contul society prin ${ t } </span> \n <span class='fn'>exports</span>[<span class='str'>' ${ t } '</span>]:<span class='fn'>GetAccount</span>(job) <span class='cm'>-- returnează balanța contului societății</span> \n <span class='fn'>exports</span>[<span class='str'>' ${ t } '</span>]:<span class='fn'>AddMoney</span>(job, amount) <span class='cm'>-- adaugă bani în contul societății</span> ` ;
if ( l . label && l . label . includes ( "remote lock" ) ) return ` <span class='cm'>-- ${ s } controlează vehiculul remote prin ${ t } </span> \n <span class='fn'>exports</span>[<span class='str'>' ${ t } '</span>]:<span class='fn'>SetVehicleLocked</span>(plate, <span class='kw'>true</span>) <span class='cm'>-- lock/unlock de pe telefon</span> ` ;
if ( l . label && l . label . includes ( "phone item" ) ) return ` <span class='cm'>-- ${ s } verifică dacă player-ul are telefon</span> \n <span class='kw'>local</span> hasPhone = <span class='fn'>exports</span>[<span class='str'>' ${ t } '</span>]:<span class='fn'>HasItem</span>(<span class='str'>'phone'</span>) <span class='cm'>-- bool, necesar pt a deschide telefonul</span> ` ;
if ( l . label && l . label . includes ( "key item" ) ) return ` <span class='cm'>-- ${ s } gestionează metadata cheilor</span> \n <span class='fn'>exports</span>[<span class='str'>' ${ t } '</span>]:<span class='fn'>GetItemByName</span>(<span class='str'>'vehiclekey'</span>) <span class='cm'>-- returnează item cu metadata.plate</span> ` ;
if ( l . label && l . label . includes ( "call system" ) ) return ` <span class='cm'>-- ${ s } folosește voice chat pentru apeluri</span> \n <span class='fn'>exports</span>[<span class='str'>' ${ t } '</span>]:<span class='fn'>setVoiceProperty</span>(<span class='str'>'callChannel'</span>, ch) <span class='cm'>-- setează canalul de voce pt call</span> ` ;
if ( l . label && l . label . includes ( "voice control" ) ) return ` <span class='cm'>-- ${ s } controlează voice-ul playerilor</span> \n <span class='fn'>exports</span>[<span class='str'>' ${ t } '</span>]:<span class='fn'>setPlayerMuted</span>(src, <span class='kw'>true</span>) <span class='cm'>-- mute admin</span> ` ;
if ( l . label && l . label . includes ( "phone control" ) ) return ` <span class='cm'>-- ${ s } controlează telefonul playerilor</span> \n <span class='fn'>exports</span>[<span class='str'>' ${ t } '</span>]:<span class='fn'>GetPlayerPhone</span>(src) <span class='cm'>-- acces la date telefon din admin</span> ` ;
if ( l . label && l . label . includes ( "item management" ) ) return ` <span class='cm'>-- ${ s } gestionează items prin inventar</span> \n <span class='fn'>exports</span>[<span class='str'>' ${ t } '</span>]:<span class='fn'>AddItem</span>(src, item, qty) <span class='cm'>-- admin give item</span> \n <span class='fn'>exports</span>[<span class='str'>' ${ t } '</span>]:<span class='fn'>RemoveItem</span>(src, item, qty) <span class='cm'>-- admin remove item</span> ` ;
if ( l . label && l . label . includes ( "setPlayerLicense" ) ) return ` <span class='cm'>-- ${ s } dă licență de condus prin ${ t } </span> \n <span class='fn'>exports</span>[<span class='str'>' ${ t } '</span>]:<span class='fn'>setPlayerLicense</span>(src, <span class='str'>'driver'</span>) <span class='cm'>-- acordă licența după test</span> ` ;
if ( l . label && l . label . includes ( "headshot" ) ) return ` <span class='cm'>-- ${ s } face captură foto pentru ID card</span> \n <span class='fn'>exports</span>[<span class='str'>' ${ t } '</span>]:<span class='fn'>requestScreenshotUpload</span>(url, <span class='str'>'file'</span>, cb) <span class='cm'>-- captură headshot ped</span> ` ;
if ( l . label && l . label . includes ( "consumable" ) ) return ` <span class='cm'>-- ${ s } registrează consumabile (mâncare, băutură)</span> \n <span class='fn'>exports</span>[<span class='str'>' ${ t } '</span>]:<span class='fn'>AddItem</span>(src, <span class='str'>'sandwich'</span>, 1) <span class='cm'>-- adaugă food item</span> \n <span class='cm'>-- hunger/thirst se actualizează automat prin metadata</span> ` ;
if ( l . label && l . label . includes ( "boss menu" ) ) return ` <span class='cm'>-- ${ s } accesează boss menu prin target</span> \n <span class='fn'>exports</span>[<span class='str'>' ${ t } '</span>]:<span class='fn'>AddBoxZone</span>(<span class='str'>'boss'</span>, coords, ...) <span class='cm'>-- zonă interact boss menu</span> ` ;
if ( l . label && l . label . includes ( "input" ) ) return ` <span class='cm'>-- ${ s } deschide dialog de input</span> \n <span class='fn'>exports</span>[<span class='str'>' ${ t } '</span>]:<span class='fn'>ShowInput</span>(header, inputs) <span class='cm'>-- afișează input dialog cu text fields</span> ` ;
if ( l . label && l . label . includes ( "context" ) ) return ` <span class='cm'>-- ${ s } deschide context menu</span> \n <span class='fn'>exports</span>[<span class='str'>' ${ t } '</span>]:<span class='fn'>openMenu</span>(menuData) <span class='cm'>-- afișează meniu cu opțiuni</span> ` ;
if ( l . label && l . label . includes ( "hotwire" ) ) return ` <span class='cm'>-- ${ s } verifică cheile pt hotwire</span> \n <span class='kw'>local</span> keys = <span class='fn'>exports</span>[<span class='str'>' ${ t } '</span>]:<span class='fn'>GetVehicleKeys</span>(plate) <span class='cm'>-- dacă nu are chei → hotwire minigame</span> ` ;
if ( l . label && l . label . includes ( "trunk" ) ) return ` <span class='cm'>-- ${ s } accesează portbagajul prin ${ t } </span> \n <span class='fn'>exports</span>[<span class='str'>' ${ t } '</span>]:<span class='fn'>OpenStash</span>(trunkId, label, slots, weight) <span class='cm'>-- stash trunk vehicul</span> ` ;
if ( l . label && l . label . includes ( "chips" ) ) return ` <span class='cm'>-- ${ s } gestionează chips casino prin inventar</span> \n <span class='fn'>exports</span>[<span class='str'>' ${ t } '</span>]:<span class='fn'>AddItem</span>(src, <span class='str'>'casino_chip'</span>, amount) <span class='cm'>-- adaugă chips câștigate</span> ` ;
if ( l . label && l . label . includes ( "jerry" ) ) return ` <span class='cm'>-- ${ s } verifică/consumă jerry can din inventar</span> \n <span class='fn'>exports</span>[<span class='str'>' ${ t } '</span>]:<span class='fn'>GetItemByName</span>(<span class='str'>'jerrycan'</span>) <span class='cm'>-- item cu metadata.fuel (nivel)</span> ` ;
if ( l . label && l . label . includes ( "door" ) ) return ` <span class='cm'>-- ${ s } adaugă interact pe uși</span> \n <span class='fn'>exports</span>[<span class='str'>' ${ t } '</span>]:<span class='fn'>AddTargetEntity</span>(door, options) <span class='cm'>-- target pe entity door</span> ` ;
if ( l . label && l . label . includes ( "prison items" ) ) return ` <span class='cm'>-- ${ s } gestionează items deținuți</span> \n <span class='fn'>exports</span>[<span class='str'>' ${ t } '</span>]:<span class='fn'>ClearInventory</span>(src) <span class='cm'>-- golește inventar la intrare în închisoare</span> ` ;
if ( l . label && l . label . includes ( "keychain" ) ) return ` <span class='cm'>-- ${ s } verifică lockpick/keychain din inventar</span> \n <span class='fn'>exports</span>[<span class='str'>' ${ t } '</span>]:<span class='fn'>HasItem</span>(<span class='str'>'lockpick'</span>) <span class='cm'>-- bool, necesar pt a sparge uși</span> ` ;
if ( l . label && l . label . includes ( "radio voice" ) ) return ` <span class='cm'>-- ${ s } folosește voice channel pt radio</span> \n <span class='fn'>exports</span>[<span class='str'>' ${ t } '</span>]:<span class='fn'>setVoiceProperty</span>(<span class='str'>'radioChannel'</span>, freq) <span class='cm'>-- intră pe frecvență radio</span> ` ;
if ( l . label && l . label . includes ( "weed items" ) ) return ` <span class='cm'>-- ${ s } adaugă items weed în inventar</span> \n <span class='fn'>exports</span>[<span class='str'>' ${ t } '</span>]:<span class='fn'>AddItem</span>(src, <span class='str'>'weed_brick'</span>, qty) <span class='cm'>-- adaugă produsul procesat</span> ` ;
if ( l . label && l . label . includes ( "inventory check" ) ) return ` <span class='cm'>-- ${ s } verifică inventarul playerului</span> \n <span class='fn'>exports</span>[<span class='str'>' ${ t } '</span>]:<span class='fn'>GetItemsByName</span>(src, item) <span class='cm'>-- caută items specifice în inventar</span> ` ;
return ` <span class='cm'>-- ${ s } apelează export din ${ t } </span> \n <span class='fn'>exports</span>[<span class='str'>' ${ t } '</span>]:<span class='fn'> ${ l . label || 'FunctionName' } </span>(...) <span class='cm'>-- apel cross-resource</span> ` ;
}
if ( l . type === "event" ) {
if ( l . label && l . label . includes ( "OnPlayerLoaded" ) ) return ` <span class='cm'>-- ${ s } ascultă când player-ul se încarcă</span> \n <span class='fn'>RegisterNetEvent</span>(<span class='str'>'QBCore:Client:OnPlayerLoaded'</span>) <span class='cm'>-- înregistrează event-ul</span> \n <span class='fn'>AddEventHandler</span>(<span class='str'>'QBCore:Client:OnPlayerLoaded'</span>, <span class='kw'>function</span>() <span class='cm'>-- callback la load</span> \n <span class='cm'>-- inițializare ${ s } : UI, variabile, zone, blips</span> \n <span class='kw'>end</span>) ` ;
if ( l . label && l . label . includes ( "OnJobUpdate" ) ) return ` <span class='cm'>-- ${ s } reacționează la schimbarea job-ului</span> \n <span class='fn'>RegisterNetEvent</span>(<span class='str'>'QBCore:Client:OnJobUpdate'</span>) <span class='cm'>-- se trigger automat de QBCore</span> \n <span class='fn'>AddEventHandler</span>(<span class='str'>'QBCore:Client:OnJobUpdate'</span>, <span class='kw'>function</span>(JobInfo) <span class='cm'>-- JobInfo={name,label,grade,...}</span> \n <span class='cm'>-- actualizare duty/UI pentru ${ s } : verifică dacă e on-duty</span> \n <span class='kw'>end</span>) ` ;
if ( l . label && l . label . includes ( "openOutfitMenu" ) ) return ` <span class='cm'>-- ${ s } deschide meniul de outfits din ${ t } </span> \n <span class='fn'>TriggerEvent</span>(<span class='str'>'qb-clothing:client:openOutfitMenu'</span>) <span class='cm'>-- bridge event → 17mov outfit UI</span> ` ;
if ( l . label && l . label . includes ( "openMenu" ) ) return ` <span class='cm'>-- ${ s } deschide clothing menu complet</span> \n <span class='fn'>TriggerEvent</span>(<span class='str'>'qb-clothing:client:openMenu'</span>) <span class='cm'>-- bridge → 17mov full clothing editor</span> ` ;
if ( l . label && l . label . includes ( "loadOutfit" ) ) return ` <span class='cm'>-- ${ s } aplică un outfit predefinit</span> \n <span class='fn'>TriggerEvent</span>(<span class='str'>'qb-clothing:client:loadOutfit'</span>, outfitData) <span class='cm'>-- outfitData = tabel cu componentele ped</span> ` ;
if ( l . label && l . label . includes ( "SaveCurrentSkin" ) ) return ` <span class='cm'>-- ${ s } salvează skin-ul curent în DB</span> \n <span class='fn'>TriggerEvent</span>(<span class='str'>'17mov_CharacterSystem:SaveCurrentSkin'</span>) <span class='cm'>-- salvare permanentă skin + clothing</span> ` ;
if ( l . label && l . label . includes ( "hunger" ) ) return ` <span class='cm'>-- ${ s } trimite statusuri metabolice la HUD</span> \n <span class='fn'>TriggerClientEvent</span>(<span class='str'>'hud:client:UpdateNeeds'</span>, src, hunger, thirst) <span class='cm'>-- src=player, hunger/thirst=0-100</span> ` ;
if ( l . label && l . label . includes ( "stress" ) ) return ` <span class='cm'>-- ${ s } eliberează stress la player</span> \n <span class='fn'>TriggerEvent</span>(<span class='str'>'hud:client:RelieveStress'</span>, amount) <span class='cm'>-- amount=cât stress se scade (int)</span> ` ;
if ( l . label && l . label . includes ( "jail" ) ) return ` <span class='cm'>-- ${ s } trimite player la închisoare via ${ t } </span> \n <span class='fn'>TriggerEvent</span>(<span class='str'>'police:client:JailPlayer'</span>, time) <span class='cm'>-- time=minute de pedeapsă</span> ` ;
if ( l . label && l . label . includes ( "dispatch" ) ) return ` <span class='cm'>-- ${ s } trimite alertă prin dispatch</span> \n <span class='fn'>TriggerServerEvent</span>(<span class='str'>'dispatch:server:notify'</span>, { \n message = <span class='str'>'Alert text'</span>, <span class='cm'>-- textul alertei vizibil de poliție</span> \n code = <span class='str'>'10-XX'</span>, <span class='cm'>-- codul radio (10-71, 10-50, etc)</span> \n icon = <span class='str'>'fas fa-exclamation'</span>, <span class='cm'>-- iconița din notification</span> \n coords = playerCoords <span class='cm'>-- locația pe GPS/blip</span> \n }) ` ;
if ( l . label && l . label . includes ( "policeAlert" ) ) return ` <span class='cm'>-- ${ s } trimite alertă de furt vehicul</span> \n <span class='fn'>TriggerServerEvent</span>(<span class='str'>'dispatch:server:notify'</span>, { \n message = <span class='str'>'Vehicul furat!'</span>, <span class='cm'>-- alertă vizibil pe MDT</span> \n code = <span class='str'>'10-50'</span> <span class='cm'>-- cod furt vehicul</span> \n }) ` ;
if ( l . label && l . label . includes ( "job registration" ) ) return ` <span class='cm'>-- ${ s } se înregistrează ca job în ${ t } </span> \n <span class='fn'>TriggerEvent</span>(<span class='str'>'17mov_JobCenter:RegisterJob'</span>, jobName, jobLabel) <span class='cm'>-- apare în lista de joburi</span> ` ;
if ( l . label && l . label . includes ( "police alerts" ) ) return ` <span class='cm'>-- ${ s } trimite alerte de poliție la ${ t } </span> \n <span class='fn'>TriggerServerEvent</span>(<span class='str'>'dispatch:server:notify'</span>, alertData) <span class='cm'>-- alertă dispatch cu coords + blip</span> ` ;
if ( l . label && l . label . includes ( "UpdateNeeds" ) ) return ` <span class='cm'>-- ${ s } actualizează HUD-ul cu nevoi vitale</span> \n <span class='fn'>TriggerClientEvent</span>(<span class='str'>'hud:client:UpdateNeeds'</span>, src, hunger, thirst) <span class='cm'>-- după heal/revive</span> \n <span class='fn'>TriggerEvent</span>(<span class='str'>'hud:client:RelieveStress'</span>, amount) <span class='cm'>-- după tratament medical</span> ` ;
return ` <span class='cm'>-- ${ s } trigger event spre ${ t } </span> \n <span class='fn'>TriggerEvent</span>(<span class='str'>' ${ l . label || 'eventName' } '</span>) <span class='cm'>-- event cross-resource</span> ` ;
}
if ( l . type === "config" ) {
if ( l . label && l . label . includes ( "Config.Outfits" ) ) return ` <span class='cm'>-- ${ s } citește outfitul din config-ul ${ t } </span> \n <span class='kw'>Config</span>.Outfits = { \n [<span class='str'>'mechanic'</span>] = { <span class='cm'>-- numele job-ului</span> \n <span class='cm'>-- componente ped (hat, torso, pants, shoes, etc)</span> \n male = {...}, <span class='cm'>-- outfit masculin</span> \n female = {...} <span class='cm'>-- outfit feminin</span> \n } \n } ` ;
if ( l . label && l . label . includes ( "outfit bridge" ) ) return ` <span class='cm'>-- ${ s } folosește bridge-ul de clothing din ${ t } </span> \n <span class='kw'>Config</span>.ClothingScript = <span class='str'>' ${ t } '</span> <span class='cm'>-- scriptul de clothing folosit pe server</span> ` ;
if ( l . label && l . label . includes ( "job mechanic" ) ) return ` <span class='cm'>-- ${ s } verifică job-ul de mecanic din config</span> \n <span class='kw'>Config</span>.RequiredJob = <span class='str'>'mechanic'</span> <span class='cm'>-- job necesar pentru acces la funcționalitate</span> ` ;
if ( l . label && l . label . includes ( "car lift" ) ) return ` <span class='cm'>-- ${ s } se integrează cu shop-urile din ${ t } </span> \n <span class='kw'>Config</span>.CarLiftShops = {...} <span class='cm'>-- lista shop-urilor unde e montat car lift-ul</span> ` ;
return ` <span class='cm'>-- ${ s } citește configurare din ${ t } </span> \n <span class='kw'>Config</span>. ${ l . label || 'Setting' } = ... <span class='cm'>-- valoare din config.lua</span> ` ;
}
if ( l . type === "dep" ) {
return ` <span class='cm'>-- ${ s } depinde de ${ t } (trebuie pornit înainte)</span> \n <span class='cm'>-- în fxmanifest.lua al resursei ${ s } :</span> \n <span class='fn'>dependency</span> <span class='str'>' ${ t } '</span> <span class='cm'>-- serverul pornește ${ t } automat înainte de ${ s } </span> ` ;
}
return ` <span class='cm'>-- ${ s } → ${ t } : ${ l . label || 'connection' } </span> ` ;
}
// === PINNED TOOLTIP STATE ===
let tooltipPinned = false ;
function unpinTooltip ( ) {
const tt = document . getElementById ( "tooltip" ) ;
tt . classList . remove ( "pinned" ) ;
tt . style . display = "none" ;
tooltipPinned = false ;
}
// === LINK CLICK → PIN TOOLTIP WITH CODE ===
linkHit . on ( "click" , ( e , d ) => {
e . stopPropagation ( ) ;
const tt = document . getElementById ( "tooltip" ) ;
const sid = typeof d . source === "object" ? d . source . id : d . source ;
const tid = typeof d . target === "object" ? d . target . id : d . target ;
const between = links . filter ( l => {
const s = typeof l . source === "object" ? l . source . id : l . source ;
const t = typeof l . target === "object" ? l . target . id : l . target ;
return ( s === sid && t === tid ) || ( s === tid && t === sid ) ;
} ) ;
let html = ` <span class="tt-close" onclick="unpinTooltip()">✕</span> ` ;
html += ` <b> ${ sid } ↔ ${ tid } </b><br> ` ;
html += ` <span style="color:#666;font-size:10px"> ${ between . length } conexiune(i)</span><br> ` ;
between . forEach ( l => {
const s = typeof l . source === "object" ? l . source . id : l . source ;
const color = linkColors [ l . type ] || "#666" ;
html += ` <div class="tt-row"> ${ s } → ${ s === sid ? tid : sid } <span class="tt-type" style="background: ${ color } 30;color: ${ color } ;border:1px solid ${ color } "> ${ l . type } </span> ${ l . label || "" } </div> ` ;
html += ` <div class="tt-code"> ${ getCodeSnippet ( l ) } </div> ` ;
} ) ;
tt . innerHTML = html ;
tt . style . display = "block" ;
tt . classList . add ( "pinned" ) ;
tooltipPinned = true ;
// Position near click
const x = Math . min ( e . clientX + 14 , window . innerWidth - 440 ) ;
const y = Math . min ( e . clientY - 10 , window . innerHeight - 300 ) ;
tt . style . left = x + "px" ; tt . style . top = y + "px" ;
// Also highlight in sidebar
let h = ` <h2 style="color:#c084fc">🔗 ${ sid } ↔ ${ tid } </h2> ` ;
h += ` <p style="color:#888;font-size:11px;margin-bottom:8px"> ${ between . length } conexiune(i) între aceste resurse</p> ` ;
between . forEach ( l => {
const s = typeof l . source === "object" ? l . source . id : l . source ;
const color = linkColors [ l . type ] || "#666" ;
h += ` <div class="dep-item" style="color: ${ color } "> ${ s } → ${ s === sid ? tid : sid } <span class="tag" style="border-color: ${ color } ;color: ${ color } "> ${ l . type } </span> <span class="tag"> ${ l . label || "" } </span></div> ` ;
} ) ;
document . getElementById ( "info" ) . innerHTML = h ;
// Highlight nodes
d3 . selectAll ( ".node-circle" ) . classed ( "dimmed" , n => n . id !== sid && n . id !== tid ) . classed ( "highlighted" , n => n . id === sid || n . id === tid ) ;
d3 . selectAll ( ".node-label" ) . classed ( "dimmed" , n => n . id !== sid && n . id !== tid ) ;
link . classed ( "dimmed" , l => {
const s = typeof l . source === "object" ? l . source . id : l . source ;
const t = typeof l . target === "object" ? l . target . id : l . target ;
return ! ( ( s === sid && t === tid ) || ( s === tid && t === sid ) ) ;
} ) . classed ( "highlighted" , l => {
const s = typeof l . source === "object" ? l . source . id : l . source ;
const t = typeof l . target === "object" ? l . target . id : l . target ;
return ( s === sid && t === tid ) || ( s === tid && t === sid ) ;
} ) ;
} ) ;
// === LINK HOVER TOOLTIP (only when not pinned) ===
linkHit . on ( "mouseenter" , ( e , d ) => {
if ( tooltipPinned ) return ;
const tt = document . getElementById ( "tooltip" ) ;
const sid = typeof d . source === "object" ? d . source . id : d . source ;
const tid = typeof d . target === "object" ? d . target . id : d . target ;
const between = links . filter ( l => {
const s = typeof l . source === "object" ? l . source . id : l . source ;
const t = typeof l . target === "object" ? l . target . id : l . target ;
return ( s === sid && t === tid ) || ( s === tid && t === sid ) ;
} ) ;
let html = ` <b> ${ sid } ↔ ${ tid } </b><br> ` ;
between . forEach ( l => {
const s = typeof l . source === "object" ? l . source . id : l . source ;
const color = linkColors [ l . type ] || "#666" ;
html += ` <div class="tt-row"> ${ s } → ${ s === sid ? tid : sid } <span class="tt-type" style="background: ${ color } 30;color: ${ color } ;border:1px solid ${ color } "> ${ l . type } </span> ${ l . label || "" } </div> ` ;
} ) ;
html += ` <div style="color:#555;font-size:9px;margin-top:4px">click → cod & detalii</div> ` ;
tt . innerHTML = html ; tt . style . display = "block" ;
tt . style . left = ( e . clientX + 14 ) + "px" ; tt . style . top = ( e . clientY - 10 ) + "px" ;
} ) . on ( "mousemove" , ( e ) => {
if ( tooltipPinned ) return ;
const tt = document . getElementById ( "tooltip" ) ;
tt . style . left = ( e . clientX + 14 ) + "px" ; tt . style . top = ( e . clientY - 10 ) + "px" ;
} ) . on ( "mouseleave" , ( ) => {
if ( tooltipPinned ) return ;
document . getElementById ( "tooltip" ) . style . display = "none" ;
} ) ;
// === SEARCH ===
document . getElementById ( "search" ) . addEventListener ( "input" , function ( ) {
const q = this . value . toLowerCase ( ) . trim ( ) ;
if ( ! q ) { resetHighlight ( ) ; selected = null ; return }
const matched = filteredNodes . filter ( n => n . id . toLowerCase ( ) . includes ( q ) || ( n . keywords || "" ) . includes ( q ) || ( n . desc || "" ) . toLowerCase ( ) . includes ( q ) ) ;
if ( matched . length === 1 ) { selectNode ( matched [ 0 ] ) }
else if ( matched . length > 0 ) {
const ids = new Set ( matched . map ( n => n . id ) ) ;
d3 . selectAll ( ".node-circle" ) . classed ( "dimmed" , n => ! ids . has ( n . id ) ) ;
d3 . selectAll ( ".node-label" ) . classed ( "dimmed" , n => ! ids . has ( n . id ) ) ;
link . classed ( "dimmed" , true ) ;
let h = ` <h2 style="color:#8b5cf6">🔍 ${ matched . length } rezultate pt " ${ q } "</h2> ` ;
matched . forEach ( m => h += ` <div class="dep-item" style="color:#8b5cf6" data-id=" ${ m . id } ">• ${ m . id } <span class="tag"> ${ teamMap [ m . team ] || m . team } </span></div> ` ) ;
document . getElementById ( "info" ) . innerHTML = h ;
document . querySelectorAll ( ".dep-item" ) . forEach ( el => el . addEventListener ( "click" , ( ) => {
const n = filteredNodes . find ( n => n . id === el . dataset . id ) ; if ( n ) selectNode ( n ) ;
} ) ) ;
}
} ) ;
// === TEAM FILTER ===
document . querySelectorAll ( "#teamFilter button" ) . forEach ( btn => {
btn . addEventListener ( "click" , ( ) => {
document . querySelectorAll ( "#teamFilter button" ) . forEach ( b => b . classList . remove ( "active" ) ) ;
btn . classList . add ( "active" ) ;
const team = btn . dataset . team ;
if ( team === "all" ) { resetHighlight ( ) ; selected = null ; return }
const matched = new Set ( filteredNodes . filter ( n => n . team === team ) . map ( n => n . id ) ) ;
d3 . selectAll ( ".node-circle" ) . classed ( "dimmed" , n => ! matched . has ( n . id ) ) ;
d3 . selectAll ( ".node-label" ) . classed ( "dimmed" , n => ! matched . has ( n . id ) ) ;
link . classed ( "dimmed" , l => {
const s = typeof l . source === "object" ? l . source . id : l . source ;
const t = typeof l . target === "object" ? l . target . id : l . target ;
return ! matched . has ( s ) && ! matched . has ( t ) ;
} ) . classed ( "highlighted" , false ) ;
} ) ;
} ) ;
// === NODE TOOLTIP ===
node . on ( "mouseenter" , ( e , d ) => {
const tt = document . getElementById ( "tooltip" ) ;
tt . style . display = "block" ;
const out = links . filter ( l => ( typeof l . source === "object" ? l . source . id : l . source ) === d . id ) . length ;
const inc = links . filter ( l => ( typeof l . target === "object" ? l . target . id : l . target ) === d . id ) . length ;
tt . innerHTML = ` <b style="color: ${ nodeColor ( d ) } "> ${ d . id } </b><br><span style="color:#888"> ${ d . desc || "" } </span><br><span style="color:#f97316">→ ${ out } </span> <span style="color:#22d3ee">← ${ inc } </span> <span class="tag"> ${ teamMap [ d . team ] || d . team } </span> ` ;
tt . style . left = ( e . clientX + 14 ) + "px" ; tt . style . top = ( e . clientY - 10 ) + "px" ;
} ) . on ( "mousemove" , ( e ) => {
const tt = document . getElementById ( "tooltip" ) ;
tt . style . left = ( e . clientX + 14 ) + "px" ; tt . style . top = ( e . clientY - 10 ) + "px" ;
} ) . on ( "mouseleave" , ( ) => { document . getElementById ( "tooltip" ) . style . display = "none" } ) ;
< / script >
< / body >
< / html >