From 3b2bd1a2c7d5e740b515b090cecc917933e2bc43 Mon Sep 17 00:00:00 2001 From: Mosmain Date: Sun, 14 Jun 2026 22:34:12 +0300 Subject: [PATCH 1/3] Enhance map extraction mechanics and introduce transit faction - Added a new 'transit' faction for map-to-map transit points, allowing for better faction management and rendering. - Updated extraction data across multiple maps to include transit points, ensuring accurate representation in the game. - Replaced PNG icons with inline SVGs for faction icons, improving scalability and color customization. - Introduced quick filter components for layer visibility settings, enhancing user experience. - Updated localization files to include new transit-related terms in both English and Russian. - Refactored related components and styles to accommodate the new transit functionality. --- CLAUDE.md | 40 +++++---- .../public/icons/extracts/extract_pmc.png | Bin 1611 -> 0 bytes .../public/icons/extracts/extract_scav.png | Bin 1594 -> 0 bytes .../public/icons/extracts/extract_shared.png | Bin 1598 -> 0 bytes apps/client/src/features/i18n/locales/en.json | 60 ++++++++++---- apps/client/src/features/i18n/locales/ru.json | 60 ++++++++++---- .../src/features/map/components/LayerRail.vue | 5 ++ .../src/features/map/components/MapView.vue | 3 + .../map/composables/useDevCoordsCopy.ts | 51 ++++++++++++ apps/client/src/features/map/data/extracts.ts | 10 +++ .../features/map/data/extracts/bigmap.json | 51 +++++++++--- .../map/data/extracts/factory4_day.json | 40 ++++++--- .../map/data/extracts/interchange.json | 61 ++++++++++---- .../map/data/extracts/laboratory.json | 51 +++++++----- .../map/data/extracts/lighthouse.json | 35 +++++++- .../map/data/extracts/rezervbase.json | 35 +++++++- .../features/map/data/extracts/sandbox.json | 17 +++- .../features/map/data/extracts/shoreline.json | 29 ++++++- .../map/data/extracts/tarkovstreets.json | 35 +++++++- .../src/features/map/data/extracts/woods.json | 77 ++++++++++++------ .../src/features/map/layers/extracts/icon.ts | 67 +++++++++------ .../src/features/map/layers/extracts/index.ts | 2 + .../features/map/layers/extracts/tooltip.ts | 18 ++-- .../map/layers/extracts/useExtractsLayer.ts | 6 +- .../src/features/map/layers/registry.ts | 3 + apps/client/src/features/map/store.ts | 4 +- .../sections/ExtractFactionFilter.vue | 38 +++++++++ .../settings/sections/ExtractsSection.vue | 29 ++----- apps/client/src/styles.css | 28 ++----- packages/shared/src/maps.ts | 5 +- 30 files changed, 622 insertions(+), 238 deletions(-) delete mode 100644 apps/client/public/icons/extracts/extract_pmc.png delete mode 100644 apps/client/public/icons/extracts/extract_scav.png delete mode 100644 apps/client/public/icons/extracts/extract_shared.png create mode 100644 apps/client/src/features/map/composables/useDevCoordsCopy.ts create mode 100644 apps/client/src/features/settings/sections/ExtractFactionFilter.vue diff --git a/CLAUDE.md b/CLAUDE.md index d968e57..e16eab9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -150,7 +150,10 @@ and `extractNames..` under no strings — adding a language is a locale-only change. `factions` is an array because one physical exit can serve multiple sides -(PMC + Scav share the same door). At render time the client **merges +(PMC + Scav share the same door). `transit` is a fourth pseudo-faction for +map-to-map transit points — not a real side (PMC-only mechanic), but riding +the faction pipeline gives icons/tooltips/filter/auto-hide for free; the +destination map lives only in the i18n display name, not in the data. At render time the client **merges co-located extracts** that round to the same `(x, z)` bucket — Customs has dorms V-Ex (PMC) and old road gate (Scav) ~1m apart, they become a single composite marker. See "Map rendering layers" below for how that composite @@ -190,7 +193,11 @@ key back in the running app. (derived from registry `subgroup`: today only `player`, with `loot`/`quests` dimmed future icons). Clicking a category icon opens a flyout listing its layers: each row has a visibility `ToggleSwitch`, layer name, and a gear button - that expands that layer's settings component inline. Floor stepper at the rail's + that expands that layer's settings component inline. A layer may also register + a `filterComponent` — quick controls rendered always-expanded under its row + while the layer is visible, no gear click (extracts: the faction toggles, with + factions that have no exits on the current map auto-hidden via + `factionsForMap`). Floor stepper at the rail's bottom (multi-floor maps only) is a vertical ▲/level/▼. Rail hides when the overlay is click-through-locked. When locked on a multi-floor map, a compact read-only floor chip appears bottom-left. Flyouts position relative to the rail @@ -237,24 +244,21 @@ vertical span is pushed out to the rail's right edge (RAIL_GAP px beyond). Measu each frame via `.layer-rail` rect so arrows flow dynamically as the rail animates in/out on lock transitions. -Extract markers are `L.divIcon` instances (not `L.icon`) — the composite -PNG split via CSS `clip-path: polygon(...)` is built inline as HTML. -Single-faction = one ``; 2 or 3 factions = stacked ``s with `/`- -diagonal clip slices (area-balanced thirds for the 3-faction case). The -icon reacts to the faction filter dynamically: turning Scav off on a -PMC+Scav exit re-renders the icon as a clean PMC. Tailwind's preflight -gives `` `max-width: 100%` which collapses the icon to 0 inside -Leaflet's positioned wrapper, so `styles.css` keeps an override: - -``` -.leaflet-extracts-pane .extract-icon-divicon img { - max-width: none !important; max-height: none !important; -} -``` +Extract markers are `L.divIcon` instances (not `L.icon`) — the icon is +**inline SVG generated in `icon.ts`** (one hand-drawn shield+runner shape, +interior recoloured per faction from `FACTION_COLORS`; no static image +assets, nothing under `public/icons/`). Inline rather than `` +because an external SVG can't be recoloured from outside. Single-faction = +one ``; 2 or 3 factions = stacked recoloured copies with `/`-diagonal +CSS `clip-path` slices (area-balanced thirds for the 3-faction case; ≥4 +co-located kinds show the first three per `FACTION_ORDER`). The icon +reacts to the faction filter dynamically: turning Scav off on a PMC+Scav +exit re-renders the icon as a clean PMC. Tooltip is multi-row (one row per distinct name), each row carries a -faction-coloured left stripe via `.extract-tooltip-row--{pmc|scav|shared}` -or `.extract-tooltip-row--multi` when factions share a name. `direction: +faction-coloured left stripe — the colour is inlined from `FACTION_COLORS` +in `tooltip.ts` (no per-faction CSS classes); rows where multiple factions +share a name keep the neutral stripe from the base CSS rule. `direction: 'top'` keeps the tooltip above the icon; `tooltipAnchor` inside the icon's top edge gives a few pixels of overlap for visual cohesion. diff --git a/apps/client/public/icons/extracts/extract_pmc.png b/apps/client/public/icons/extracts/extract_pmc.png deleted file mode 100644 index b6d33bb919fe53be8c1670e2e9e52bbf08231b54..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1611 zcmV-R2DJH!P)@o^xSr5U3JAwovJ!EBmO~Y-q)2cGarVG z?8}!g(UrO@EmB%(am`ce03f^)xf2HEV*1EUnI+JG%mJ{RV6bI8k)a?U~k6=n(b= z{sw$uBIwo(4+p-f0DD&gMGfcpo8d!&S{4Ico$8Um45uHMN@emu;3K0zKHdUUXjKmn z1OB~hm|NO#E#=xWN+6Z`hQ>VLntMBRKgIW~_{oWpQX|m&* zD^r}oN~-`r(BY9b0sgTK$lq502=Td`$v|DpfdMhP@58td$=GO-vBhb1!zlun*d*&1d15icwoRen#fq*UZ9!H zYyjM^iXH%`FlRkIrE$>ucaM!ZSV`dxl;RD0n*Up{Gdf2Sx&FMV!(}xYK8hduUhXMDhC?JJ6 zA8+dCLBQ*JbMTSDhV!j9LT?>lao}sy90P>z6+mO_f#!7pZ5s$W?-G1C%_NB-;hGi! z?HdY;6;T`)^|mE}TN{jY!iEGTjR{H~OCC=do1-q@&v4o|%^w0sV~YY8N$U>S{REu0 zud*9!$IJ<$a;jx%>((vQu3Z~mUS2W)aRG7DRR4AkKL=J5(?UKzK4{*&IUYQCpa;tFrhsU-Ibb0_ z_25nbovU;^MD z_|G8Ep|Kr1b|6!xOtODxNB~L^TO7-qH!tUUJ+KCJ6xcW0DYZ~aqD23t{s0)$<*pIAZOQ-u002ov JPDHLkV1g2j{jLB2 diff --git a/apps/client/public/icons/extracts/extract_scav.png b/apps/client/public/icons/extracts/extract_scav.png deleted file mode 100644 index e2ead3af8283daab1025688dc51e7fb1fa9521d1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1594 zcmV-A2F3Y_P)wHQK*=ygv zeM=*>PUw!%1C#5f&~gBAg~&)`=U^UjgRihy6YuU| zpLzyXu^8~KeuQ~#2@^PC@?p;#@FA`?zG8f;uUcP{N>vzE6f^UzNX8`hg}ezc1L_bPAfi=2F-j#ZA#$Qc1Aj3eF#Zo5R%7~|DG}yM_s<2;q*Pt9|A{XlL8k>V+?FG0jKv>He>ym zIYAVzwM_`jCLC58-i3ZXnZ@~3KPxh|nW=!~ifDJgfVt#J^$9)86B^_r6iXK{Z^gaG z0jp*%`wL?EQb<9K&DwQW4|yucX48PPC3f-U(%u#HD+?|ToGhjs*v^k}Vc-qj<=!oq z*~NDNG%g?m&u(QxV0Sk{(}L3KpHsRyi4ZxgTwe0uLURuuHrC3;k==Pf(3{dFP`+! z66r%%{C6oWFRsbQ)fJh33O8A@WLULom1>{m7ok+n^l%XLW9Zu3w{J0U;6V8K`AKWM zydmYnSjWtgb8z#*JR4$s-kFhv`uXI2AK}A?$Cxo=@bTkE73^IA>73X;EQ#R<3VnjE z-MMoIwQJWFfg_9*fVhCT?Ua9O!_R?Lm{fs^s6%BM>Ik4HzzR4j)#XbGin~xxP`OM!0|fz6$h%P*2r6{MWz)z(4SxLB8|v zUAuN6Q>ILksb+{zND)i7a+o(S=Xw<=w}nQ2`k$G!fthCzx~S*=^5qMrPMwMvF=8;B zr1ZRCAxR-4lge+a1I|dXb|4Ld#CEv$3kUQ z)?@;cwyW_f|Gs+lD(7$&=trRe+V$20W^nQ94 sKr-okYXi#)^v$J0^Mncu(SNDG09DKIKT%AqOaK4?07*qoM6N<$g5S^fO#lD@ diff --git a/apps/client/public/icons/extracts/extract_shared.png b/apps/client/public/icons/extracts/extract_shared.png deleted file mode 100644 index 84d0da90c1bb934ea1c6081e4714bb6946b54832..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1598 zcmV-E2EqA>P)1Xt(;&Cvu2SY!c=I9OUmBXEA#@T zXiy6^MT-mrG)d98A{tlT91j0*Y`~!K`Su*1vCH##?%m}^?wmWn+jnF4IS0W?&#FF%jgr=A*zIPB$?vf651eE6;(33~`-}4;}U~;M_d-m*l<~R-Cy!gK+TArt(mHSu^X96d4Ix z8w)yo#3XgN=nyC%Kn9vU2efRtsIRV|w{A6Ir*-@UXw)bfWX4QTwOx!iHi1^I&v)ieD>NgVsXYQ6Se$ph+nX^hqwNZssr?rWIi;p`?(717+ zQ)Tk_#p=Ebm8?u_69M-@XYB!jd3!JD)6YOZmzV_j%2m+l(e4%oqDB+Bx77;z_!AQVZ{MyD z5ESI>r{Dj;v-n=U+Tg%-4w12m?sUpF=W&i}VzEDLm}^*z#RPX)i$NmQVv5&Zas4G- zxCkmM7g@jXJ8jKuf*3yYyX)Thl_VeAwQOt>OFunWTL9=%jgJ9uOK}y8q8^|4$%lZC z9+Q)g#UkphnxRX6F*vZJUMvt^O9o}V1=^nvDme+dT-$dcqRfPa$#C%-KozG!6DG)? zi+&?nVAWz zwO=+JGX}IP6V%Y?9q{L0s4b_b=ZbQenkG6raXyCf8bO6vLC-!Xz4s9q7>KN_EOd8w>%m?JFo*uD7-Q+&e?w7ob2DONV_~sa z-?_QDm^g7_zr_Ie(7Y&KhODeUU%(u2Lh9Z7dlD)?^hf1s$oz8=xh(Q$a&E>9DxqEtgkdu>x@nRoKa&T~PZ*AAh3EUR|gpkn%;2FkP8x*y*wIMk<86$X1 zYc)J0BLkhCoqBimCihA*n;2tz_2Rp(u1>4rdZ69hJIT!A0AsjIr1;j0Q&HnblCOvZ wJ;LqaR^!wTAe?+vT5NI`a_{3_N_ScR0#j_3R!G-h@&Et;07*qoM6N<$f(e!P>Hq)$ diff --git a/apps/client/src/features/i18n/locales/en.json b/apps/client/src/features/i18n/locales/en.json index 0531ebf..c31fcc6 100644 --- a/apps/client/src/features/i18n/locales/en.json +++ b/apps/client/src/features/i18n/locales/en.json @@ -41,7 +41,8 @@ "factions": { "pmc": "PMC", "scav": "Scav", - "shared": "Shared" + "shared": "Shared", + "transit": "Transits" }, "paths": { "heading": "Game integration", @@ -174,7 +175,6 @@ "railroad_to_port": "Railroad to Port", "trailer_park_workers_shack": "Trailer Park Workers' Shack", "railroad_to_tarkov": "Railroad to Tarkov", - "ruaf_roadblock_2": "RUAF Roadblock", "warehouse_17": "Warehouse 17", "factory_shacks": "Factory Shacks", "warehouse_4": "Warehouse 4", @@ -185,21 +185,28 @@ "scav_checkpoint": "Scav Checkpoint", "boiler_room_basement_co_op": "Boiler Room Basement (Co-op)", "smugglers_boat": "Smugglers' Boat", - "smugglers_bunker_zb_1012": "Smugglers' Bunker (ZB-1012)" + "smugglers_bunker_zb_1012": "Smugglers' Bunker (ZB-1012)", + "transit_reserve": "Transit to Reserve", + "transit_factory": "Transit to Factory", + "transit_interchange": "Transit to Interchange", + "transit_shoreline": "Transit to Shoreline" }, "factory4_day": { "cellars": "Cellars", "gate_3": "Gate 3", - "gate_3_2": "Gate 3", "camera_bunker_door": "Camera Bunker Door", "office_window": "Office Window", "gate_0": "Gate 0", "med_tent_gate": "Med Tent Gate", "courtyard_gate": "Courtyard Gate", - "smugglers_passage": "Smugglers' Passage" + "smugglers_passage": "Smugglers' Passage", + "transit_woods": "Transit to Woods", + "transit_customs": "Transit to Customs", + "transit_lab": "Transit to The Lab" }, "woods": { - "friendship_bridge_co_op": "Friendship Bridge (Co-Op)", + "scav_bridge": "Scav Bridge", + "scav_bridge_co_op": "Scav Bridge (Co-op)", "ruaf_gate": "RUAF Gate", "zb_016": "ZB-016", "zb_014": "ZB-014", @@ -213,12 +220,14 @@ "mountain_stash": "Mountain Stash", "eastern_rocks": "Eastern Rocks", "old_railway_depot": "Old Railway Depot", - "un_roadblock_2": "UN Roadblock", "ruaf_roadblock": "RUAF Roadblock", - "outskirts_2": "Outskirts", "northern_un_roadblock": "Northern UN Roadblock", "power_line_passage_flare": "Power Line Passage (Flare)", - "railway_bridge_to_tarkov": "Railway Bridge to Tarkov" + "railway_bridge_to_tarkov": "Railway Bridge to Tarkov", + "transit_factory": "Transit to Factory", + "transit_reserve": "Transit to Reserve", + "transit_lighthouse": "Transit to Lighthouse", + "transit_customs": "Transit to Customs" }, "shoreline": { "road_to_north_v_ex": "Road to North V-Ex", @@ -233,7 +242,10 @@ "smugglers_path_co_op": "Smugglers' Path (Co-op)", "pier_boat": "Pier Boat", "climber_s_trail": "Climber's Trail", - "mountain_bunker": "Mountain Bunker" + "mountain_bunker": "Mountain Bunker", + "transit_lighthouse": "Transit to Lighthouse", + "transit_terminal": "Transit to Terminal", + "transit_labyrinth": "Transit to Labyrinth?" }, "rezervbase": { "armored_train": "Armored Train", @@ -246,15 +258,21 @@ "heating_pipe": "Heating Pipe", "depot_hermetic_door": "Depot Hermetic Door", "checkpoint_fence": "Checkpoint Fence", - "exit_to_woods": "Exit to Woods" + "exit_to_woods": "Exit to Woods", + "transit_customs": "Transit to Customs", + "transit_woods": "Transit to Woods", + "transit_lighthouse": "Transit to Lighthouse" }, "interchange": { "emercom_checkpoint": "Emercom Checkpoint", "railway_exfil": "Railway Exfil", "power_station_v_ex": "Power Station V-Ex", "scav_camp_co_op": "Scav Camp (Co-Op)", - "hole_in_the_fence": "Hole in the Fence", - "saferoom_exfil": "Saferoom Exfil" + "saferoom_exfil": "Saferoom Exfil", + "smugglers_tunnel": "Smugglers' Tunnel", + "path_to_river": "Path to River", + "transit_customs": "Transit to Customs", + "transit_streets": "Transit to Streets of Tarkov" }, "lighthouse": { "armored_train": "Armored Train", @@ -268,7 +286,10 @@ "hideout_under_the_landing_stage": "Hideout Under the Landing Stage", "scav_hideout_at_the_grotto": "Scav Hideout at the Grotto", "industrial_zone_gates": "Industrial Zone Gates", - "passage_by_the_lake": "Passage by the Lake" + "passage_by_the_lake": "Passage by the Lake", + "transit_shoreline": "Transit to Shoreline", + "transit_reserve": "Transit to Reserve", + "transit_woods": "Transit to Woods" }, "tarkovstreets": { "entrance_to_catacombs": "Entrance to Catacombs", @@ -287,7 +308,10 @@ "klimov_street_flare": "Klimov Street (Flare)", "pinewood_basement_co_op": "Pinewood Basement (Co-Op)", "expo_checkpoint": "Expo Checkpoint", - "smugglers_basement": "Smugglers' Basement" + "smugglers_basement": "Smugglers' Basement", + "transit_ground_zero": "Transit to Ground Zero", + "transit_interchange": "Transit to Interchange", + "transit_lab": "Transit to The Lab" }, "laboratory": { "medical_block_elevator": "Medical Block Elevator", @@ -296,7 +320,8 @@ "ventilation_shaft": "Ventilation Shaft", "sewage_conduit": "Sewage Conduit", "parking_gate": "Parking Gate", - "hangar_gate": "Hangar Gate" + "hangar_gate": "Hangar Gate", + "transit_streets": "Transit to Streets of Tarkov" }, "sandbox": { "police_cordon_v_ex": "Police Cordon V-Ex", @@ -304,7 +329,8 @@ "scav_checkpoint_co_op": "Scav Checkpoint (Co-op)", "nakatani_basement_stairs": "Nakatani Basement Stairs", "mira_ave_flare": "Mira Ave (Flare)", - "tartowers_sales_office": "Tartowers Sales Office" + "tartowers_sales_office": "Tartowers Sales Office", + "transit_streets": "Transit to Streets of Tarkov" } }, "mapNames": { diff --git a/apps/client/src/features/i18n/locales/ru.json b/apps/client/src/features/i18n/locales/ru.json index e524c8b..7ac7323 100644 --- a/apps/client/src/features/i18n/locales/ru.json +++ b/apps/client/src/features/i18n/locales/ru.json @@ -41,7 +41,8 @@ "factions": { "pmc": "ЧВК", "scav": "Дикие", - "shared": "Общие" + "shared": "Общие", + "transit": "Переходы" }, "paths": { "heading": "Интеграция с игрой", @@ -174,7 +175,6 @@ "railroad_to_port": "ЖД к порту", "trailer_park_workers_shack": "Времянка у трейл. парка", "railroad_to_tarkov": "ЖД до Таркова", - "ruaf_roadblock_2": "Блокпост ВС РФ", "warehouse_17": "Склад 17", "factory_shacks": "Заводские времянки", "warehouse_4": "Склад 4", @@ -185,21 +185,28 @@ "scav_checkpoint": "КПП Диких", "boiler_room_basement_co_op": "Подвал котельной (Совм.)", "smugglers_boat": "Лодка Контрабандистов", - "smugglers_bunker_zb_1012": "Бункер Контрабандистов (ЗБ-1012)" + "smugglers_bunker_zb_1012": "Бункер Контрабандистов (ЗБ-1012)", + "transit_reserve": "Переход на Резерв", + "transit_factory": "Переход на Завод", + "transit_interchange": "Переход на Развязку", + "transit_shoreline": "Переход на Берег" }, "factory4_day": { "cellars": "Подвалы", "gate_3": "Ворота 3", - "gate_3_2": "Ворота 3", "camera_bunker_door": "Бункерная дверь с камерой", "office_window": "Офисное окно", "gate_0": "Ворота 0", "med_tent_gate": "Ворота у медицинской палатки", "courtyard_gate": "Ворота во дворе", - "smugglers_passage": "Проход Контрабандистов" + "smugglers_passage": "Проход Контрабандистов", + "transit_woods": "Переход на Лес", + "transit_customs": "Переход на Таможню", + "transit_lab": "Переход на Лабораторию" }, "woods": { - "friendship_bridge_co_op": "Мост дружбы (Совм.)", + "scav_bridge": "Дикий мост", + "scav_bridge_co_op": "Дикий мост (Совм.)", "ruaf_gate": "Ворота ВС РФ", "zb_016": "ЗБ-016", "zb_014": "ЗБ-014", @@ -213,12 +220,14 @@ "mountain_stash": "Горный тайник", "eastern_rocks": "Восточные скалы", "old_railway_depot": "Старое депо", - "un_roadblock_2": "Южный пост ООН", "ruaf_roadblock": "Блокпост ВС РФ", - "outskirts_2": "Окраины", "northern_un_roadblock": "Северный пост ООН", "power_line_passage_flare": "Проход у ЛЭП (Сигнал)", - "railway_bridge_to_tarkov": "ЖД-мост к Таркову" + "railway_bridge_to_tarkov": "ЖД-мост к Таркову", + "transit_factory": "Переход на Завод", + "transit_reserve": "Переход на Резерв", + "transit_lighthouse": "Переход на Маяк", + "transit_customs": "Переход на Таможню" }, "shoreline": { "road_to_north_v_ex": "А-Выход дорога на север", @@ -233,7 +242,10 @@ "smugglers_path_co_op": "Путь контрабандиста (Совм.)", "pier_boat": "Лодка на причале", "climber_s_trail": "Тропа альпиниста", - "mountain_bunker": "Бункер в горе" + "mountain_bunker": "Бункер в горе", + "transit_lighthouse": "Переход на Маяк", + "transit_terminal": "Переход на Терминал", + "transit_labyrinth": "Переход на Лабиринт?" }, "rezervbase": { "armored_train": "Бронепоезд", @@ -246,15 +258,21 @@ "heating_pipe": "Трубопровод отопления", "depot_hermetic_door": "Гермодверь к депо", "checkpoint_fence": "Забор у КПП", - "exit_to_woods": "Выход в сторону Леса" + "exit_to_woods": "Выход в сторону Леса", + "transit_customs": "Переход на Таможню", + "transit_woods": "Переход на Лес", + "transit_lighthouse": "Переход на Маяк" }, "interchange": { "emercom_checkpoint": "КПП МЧС", "railway_exfil": "Выход на железной дороге", "power_station_v_ex": "А-Выход у ТЭЦ", "scav_camp_co_op": "Лагерь Диких (Совм.)", - "hole_in_the_fence": "Дыра в заборе", - "saferoom_exfil": "Безопасная комната" + "saferoom_exfil": "Безопасная комната", + "smugglers_tunnel": "Тропа контрабандистов", + "path_to_river": "Тропа к реке", + "transit_customs": "Переход на Таможню", + "transit_streets": "Переход на Улицы Таркова" }, "lighthouse": { "armored_train": "Бронепоезд", @@ -268,7 +286,10 @@ "hideout_under_the_landing_stage": "Схрон под дебаркадером", "scav_hideout_at_the_grotto": "Укрытие в гроте", "industrial_zone_gates": "Ворота промзоны", - "passage_by_the_lake": "Проход у озера" + "passage_by_the_lake": "Проход у озера", + "transit_shoreline": "Переход на Берег", + "transit_reserve": "Переход на Резерв", + "transit_woods": "Переход на Лес" }, "tarkovstreets": { "entrance_to_catacombs": "Вход в катакомбы", @@ -287,7 +308,10 @@ "klimov_street_flare": "Улица Климова (Сигнал)", "pinewood_basement_co_op": "Подвал в Pinewood (Совм.)", "expo_checkpoint": "КПП у выставочного центра", - "smugglers_basement": "Подвал Контрабандистов" + "smugglers_basement": "Подвал Контрабандистов", + "transit_ground_zero": "Переход на Эпицентр", + "transit_interchange": "Переход на Развязку", + "transit_lab": "Переход на Лабораторию" }, "laboratory": { "medical_block_elevator": "Лифт медблока", @@ -296,7 +320,8 @@ "ventilation_shaft": "Вентиляционная шахта", "sewage_conduit": "Накопительный коллектор", "parking_gate": "Парковка", - "hangar_gate": "Ангар" + "hangar_gate": "Ангар", + "transit_streets": "Переход на Улицы Таркова" }, "sandbox": { "police_cordon_v_ex": "А-Выход полицейский кордон", @@ -304,7 +329,8 @@ "scav_checkpoint_co_op": "Блокпост Диких (Совм.)", "nakatani_basement_stairs": "Спуск в подвал в Nakatani", "mira_ave_flare": "Проспект Мира (Сигнал)", - "tartowers_sales_office": "Офис продаж Tartowers" + "tartowers_sales_office": "Офис продаж Tartowers", + "transit_streets": "Переход на Улицы Таркова" } }, "mapNames": { diff --git a/apps/client/src/features/map/components/LayerRail.vue b/apps/client/src/features/map/components/LayerRail.vue index 418a804..23ab825 100644 --- a/apps/client/src/features/map/components/LayerRail.vue +++ b/apps/client/src/features/map/components/LayerRail.vue @@ -219,6 +219,11 @@ function stepFloor(delta: number): void { + +
+ +
diff --git a/apps/client/src/features/map/components/MapView.vue b/apps/client/src/features/map/components/MapView.vue index 35343b8..6234255 100644 --- a/apps/client/src/features/map/components/MapView.vue +++ b/apps/client/src/features/map/components/MapView.vue @@ -1,5 +1,6 @@ + + diff --git a/apps/client/src/features/settings/sections/ExtractsSection.vue b/apps/client/src/features/settings/sections/ExtractsSection.vue index 188e7c0..72e80ae 100644 --- a/apps/client/src/features/settings/sections/ExtractsSection.vue +++ b/apps/client/src/features/settings/sections/ExtractsSection.vue @@ -1,20 +1,13 @@ diff --git a/apps/client/src/features/map/layers/extracts/tooltip.ts b/apps/client/src/features/map/layers/extracts/tooltip.ts index d7b67a8..56aab48 100644 --- a/apps/client/src/features/map/layers/extracts/tooltip.ts +++ b/apps/client/src/features/map/layers/extracts/tooltip.ts @@ -25,6 +25,31 @@ function escapeHtml(s: string): string { * Co-located extracts with different names (Customs' Dorms V-Ex + Old Road * Gate) give multiple rows, each tagged by its own faction colour. */ +/** + * Plain-text accessible name for a marker (WCAG 4.1.2 / 2.4.4) — Leaflet makes + * each extract a focusable role="button", so it needs a name. One clause per + * distinct name with its faction(s): "Dorms V-Ex (PMC); Old Road Gate (Scav)". + */ +export function buildAriaLabel( + entries: ReadonlyArray, + factionLabel: (f: FactionKey) => string, +): string { + const byName = new Map(); + const order: string[] = []; + for (const e of entries) { + let bucket = byName.get(e.name); + if (!bucket) { + bucket = []; + byName.set(e.name, bucket); + order.push(e.name); + } + bucket.push(e.faction); + } + return order + .map((name) => `${name} (${byName.get(name)!.map(factionLabel).join(', ')})`) + .join('; '); +} + export function buildTooltipHtml(entries: ReadonlyArray): string { const factionsByName = new Map(); // Preserve first-seen name order so the layout matches FACTION_ORDER input. diff --git a/apps/client/src/features/map/layers/extracts/useExtractsLayer.ts b/apps/client/src/features/map/layers/extracts/useExtractsLayer.ts index 1f243e2..30cd221 100644 --- a/apps/client/src/features/map/layers/extracts/useExtractsLayer.ts +++ b/apps/client/src/features/map/layers/extracts/useExtractsLayer.ts @@ -1,7 +1,7 @@ import L, { type Marker, type LayerGroup } from 'leaflet'; import { FACTION_COLORS } from '@shared/maps'; import { makeIcon } from './icon'; -import { buildTooltipHtml, sortedEntries, type ExtractEntry } from './tooltip'; +import { buildAriaLabel, buildTooltipHtml, sortedEntries, type ExtractEntry } from './tooltip'; import { createEdgeIndicators, type EdgeArrow } from './useEdgeIndicators'; import { extractsForMap } from '@/features/map/data/extracts'; import { useMapSettingsStore } from '@/features/map/store'; @@ -70,6 +70,9 @@ export function useExtractsLayer(ctx: MapLayerContext): void { const filtered = effectiveEntries(entry); if (filtered.length > 0) { const factions = filtered.map((e) => e.faction); + // Accessible name for Leaflet's role="button" marker (WCAG 4.1.2). + // Set on options so _initIcon re-applies it on every setIcon rebuild. + entry.marker.options.title = buildAriaLabel(filtered, (f) => t(`factions.${f}`)); entry.marker.setIcon(makeIcon(factions)); entry.marker.unbindTooltip(); entry.marker.bindTooltip(buildTooltipHtml(filtered), { diff --git a/apps/client/src/features/overlay/components/MapSwitchMenu.vue b/apps/client/src/features/overlay/components/MapSwitchMenu.vue index b9426ac..1d10c01 100644 --- a/apps/client/src/features/overlay/components/MapSwitchMenu.vue +++ b/apps/client/src/features/overlay/components/MapSwitchMenu.vue @@ -3,6 +3,8 @@ import { VISIBLE_MAP_CODES, type TarkovMapCode } from '@shared/maps'; import { useMapSettingsStore } from '@/features/map/store'; import { useMapI18n } from '@/features/map/composables/useMapI18n'; +const { t } = useI18n(); + // Right-click-the-map-name → quick map switcher. Same teleported-popup pattern // as MapQuickMenu (open(x,y) / outside-click / Esc / blur dismissal). const { mapCode } = storeToRefs(useMapSettingsStore()); @@ -63,6 +65,7 @@ defineExpose({ open, close }); class="fixed z-[2000] max-h-[80vh] w-52 origin-top-left overflow-y-auto rounded-md border border-surface-700 bg-surface-900/95 p-1 shadow-xl backdrop-blur" :style="{ left: position.x + 'px', top: position.y + 'px' }" role="menu" + :aria-label="t('a11y.selectMap')" >