Implemented the Secret Passages + Hidden Room detection pass.
Changed: [Edit.cshtml (line 36)](C:/Source/PlotLine/PlotLine/Views/FloorPlans/Edit.cshtml:36)Secret Passage and Hidden Passage now render as dashed route overlays instead of wall markers. Hidden Door remains a wall/shared-edge marker and follows existing adjacency rules. Hidden locations are detected from existing transition data: visible entrances count, windows/hidden doors/secret passages/hidden passages/blocked doors do not. Hidden room tooltip now includes Hidden location: no visible entrance. [site.css (line 4793)](C:/Source/PlotLine/PlotLine/wwwroot/css/site.css:4793)Added subtle hidden-location hatch styling. Added dashed secret-route styling with dark-theme support. [site.min.css](C:/Source/PlotLine/PlotLine/wwwroot/css/site.min.css)Synced minified CSS. No database changes. No stored procedure changes. No Features concept added.
This commit is contained in:
parent
1786d8c7b1
commit
2cf3c7da3a
@ -32,7 +32,8 @@
|
||||
"blockeddoor" => "blocked-door",
|
||||
"exteriordoor" => "exterior-door",
|
||||
"stairs" or "stair" or "stairsup" or "stairsdown" => "stairs",
|
||||
"passage" => "passage",
|
||||
"passage" or "openpassage" => "passage",
|
||||
"hiddenpassage" or "secretpassage" => "secret-route",
|
||||
_ => "other"
|
||||
};
|
||||
}
|
||||
@ -220,6 +221,7 @@
|
||||
data-location-id="@block.LocationID"
|
||||
data-location-name="@block.LocationName"
|
||||
data-location-path="@block.LocationPath"
|
||||
data-location-title="@block.LocationPath"
|
||||
title="@block.LocationPath">
|
||||
<span class="floor-plan-block-label">@block.DisplayLabel</span>
|
||||
<span class="floor-plan-resize-handle" data-resize-handle aria-hidden="true"></span>
|
||||
@ -1530,6 +1532,7 @@
|
||||
button.dataset.locationId = block.locationID || "";
|
||||
button.dataset.locationName = block.locationName || "";
|
||||
button.dataset.locationPath = block.locationPath || "";
|
||||
button.dataset.locationTitle = block.locationPath || "";
|
||||
button.title = block.locationPath || "";
|
||||
button.innerHTML = `<span class="floor-plan-block-label"></span><span class="floor-plan-resize-handle" data-resize-handle aria-hidden="true"></span>`;
|
||||
button.querySelector(".floor-plan-block-label").textContent = block.displayLabel || block.locationName || "New block";
|
||||
@ -1655,6 +1658,26 @@
|
||||
return ["hiddenpassage", "openpassage", "secretpassage"].includes(normalizeTransitionType(type));
|
||||
}
|
||||
|
||||
function isSecretRouteTransition(type) {
|
||||
return ["hiddenpassage", "secretpassage"].includes(normalizeTransitionType(type));
|
||||
}
|
||||
|
||||
function isVisibleEntranceTransition(type) {
|
||||
return [
|
||||
"door",
|
||||
"doubledoor",
|
||||
"frenchdoor",
|
||||
"frenchdoors",
|
||||
"slidingdoor",
|
||||
"slidingdoors",
|
||||
"exteriordoor",
|
||||
"archway",
|
||||
"openpassage",
|
||||
"stairsup",
|
||||
"stairsdown"
|
||||
].includes(normalizeTransitionType(type));
|
||||
}
|
||||
|
||||
function blocksTouchAlongEdge(a, b) {
|
||||
const verticalTouch = a.x + a.w === b.x || b.x + b.w === a.x;
|
||||
const verticalOverlap = a.y < b.y + b.h && b.y < a.y + a.h;
|
||||
@ -1874,6 +1897,44 @@
|
||||
return { x: (minX + maxX) / 2, y: (minY + maxY) / 2 };
|
||||
}
|
||||
|
||||
function refreshHiddenLocationStyles(visibleBlocks) {
|
||||
const visibleEntranceLocationIds = new Set();
|
||||
editor.querySelectorAll("[data-transition-marker]").forEach(marker => {
|
||||
if (!isVisibleEntranceTransition(marker.dataset.transitionKind)) return;
|
||||
if (marker.dataset.fromLocationId) visibleEntranceLocationIds.add(marker.dataset.fromLocationId);
|
||||
if (marker.dataset.toLocationId) visibleEntranceLocationIds.add(marker.dataset.toLocationId);
|
||||
});
|
||||
|
||||
visibleBlocks.forEach(item => {
|
||||
const locationId = item.block.dataset.locationId;
|
||||
const isHidden = !!locationId && !visibleEntranceLocationIds.has(locationId);
|
||||
item.block.classList.toggle("is-hidden-location", isHidden);
|
||||
const baseTitle = item.block.dataset.locationTitle || item.block.dataset.locationPath || item.block.dataset.locationName || "";
|
||||
item.block.title = isHidden
|
||||
? `${baseTitle}${baseTitle ? "\n" : ""}Hidden location: no visible entrance`
|
||||
: baseTitle;
|
||||
});
|
||||
}
|
||||
|
||||
function renderSecretRoute(activePanel, marker, fromCenter, toCenter) {
|
||||
if (!fromCenter || !toCenter) return;
|
||||
const dx = toCenter.x - fromCenter.x;
|
||||
const dy = toCenter.y - fromCenter.y;
|
||||
const length = Math.sqrt(dx * dx + dy * dy);
|
||||
if (length <= 0) return;
|
||||
|
||||
const route = document.createElement("span");
|
||||
route.className = "floor-plan-secret-route";
|
||||
route.dataset.secretRoute = marker.dataset.transitionMarker || "";
|
||||
route.style.setProperty("--route-x", fromCenter.x);
|
||||
route.style.setProperty("--route-y", fromCenter.y);
|
||||
route.style.setProperty("--route-length", length);
|
||||
route.style.setProperty("--route-angle", `${Math.atan2(dy, dx)}rad`);
|
||||
route.title = marker.title || "";
|
||||
route.setAttribute("aria-hidden", "true");
|
||||
activePanel.querySelector("[data-floor-grid]")?.append(route);
|
||||
}
|
||||
|
||||
function sharedTransitionEdge(fromBlocks, toBlocks) {
|
||||
let best = null;
|
||||
fromBlocks.forEach(from => {
|
||||
@ -1931,9 +1992,21 @@
|
||||
|
||||
function positionTransitions(activePanel, visibleBlocks) {
|
||||
const markerPositions = [];
|
||||
activePanel.querySelectorAll("[data-secret-route]").forEach(route => route.remove());
|
||||
activePanel.querySelectorAll("[data-transition-marker]").forEach(marker => {
|
||||
const fromBlocks = visibleBlocks.filter(item => item.block.dataset.locationId === marker.dataset.fromLocationId);
|
||||
const toBlocks = visibleBlocks.filter(item => item.block.dataset.locationId === marker.dataset.toLocationId);
|
||||
const fromCenter = locationCenter(fromBlocks);
|
||||
const toCenter = locationCenter(toBlocks);
|
||||
|
||||
if (isSecretRouteTransition(marker.dataset.transitionKind)) {
|
||||
marker.hidden = true;
|
||||
if (fromCenter && toCenter) {
|
||||
renderSecretRoute(activePanel, marker, fromCenter, toCenter);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (fromBlocks.length === 0 || (!isStairTransition(marker.dataset.transitionKind) && toBlocks.length === 0)) {
|
||||
marker.hidden = true;
|
||||
return;
|
||||
@ -1943,8 +2016,6 @@
|
||||
? positionWithinLocation(fromBlocks, marker.dataset.positionWithinLocation)
|
||||
: null;
|
||||
const sharedEdge = stairPlacement ? null : sharedTransitionEdge(fromBlocks, toBlocks);
|
||||
const fromCenter = locationCenter(fromBlocks);
|
||||
const toCenter = locationCenter(toBlocks);
|
||||
marker.hidden = false;
|
||||
marker.classList.toggle("is-shared-edge", !!sharedEdge);
|
||||
marker.classList.toggle("is-inside-location", !!stairPlacement);
|
||||
@ -2005,6 +2076,7 @@
|
||||
if (!activePanel) return;
|
||||
const visibleBlocks = refreshSharedWalls(activePanel);
|
||||
positionTransitions(activePanel, visibleBlocks);
|
||||
refreshHiddenLocationStyles(visibleBlocks);
|
||||
let overlaps = false;
|
||||
visibleBlocks.forEach(a => {
|
||||
a.block.classList.remove("overlap");
|
||||
|
||||
@ -4790,6 +4790,46 @@ body.dragging-location [data-drag-type="location"] {
|
||||
box-shadow: inset 0 0 0 2px rgba(180, 85, 68, .18);
|
||||
}
|
||||
|
||||
.floor-plan-block.is-hidden-location {
|
||||
background-image: repeating-linear-gradient(135deg, rgba(18, 51, 44, .08) 0, rgba(18, 51, 44, .08) 3px, transparent 3px, transparent 9px);
|
||||
box-shadow: inset 0 0 0 1px rgba(43, 54, 49, .18);
|
||||
}
|
||||
|
||||
.floor-plan-block.is-hidden-location.selected {
|
||||
box-shadow: inset 0 0 0 1px rgba(43, 54, 49, .18), 0 0 0 3px rgba(47, 111, 99, .28);
|
||||
}
|
||||
|
||||
.floor-plan-block.is-hidden-location.overlap {
|
||||
box-shadow: inset 0 0 0 2px rgba(180, 85, 68, .18), inset 0 0 0 1px rgba(43, 54, 49, .18);
|
||||
}
|
||||
|
||||
.floor-plan-secret-route {
|
||||
border-top: 2px dashed rgba(69, 55, 42, .58);
|
||||
height: 0;
|
||||
left: calc(var(--route-x, 0) * var(--cell-size));
|
||||
opacity: .72;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
top: calc(var(--route-y, 0) * var(--cell-size));
|
||||
transform: rotate(var(--route-angle, 0));
|
||||
transform-origin: 0 50%;
|
||||
width: calc(var(--route-length, 0) * var(--cell-size));
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.floor-plan-secret-route::after {
|
||||
background: rgba(255, 250, 242, .9);
|
||||
border: 1px solid rgba(69, 55, 42, .5);
|
||||
border-radius: 50%;
|
||||
content: "";
|
||||
height: 7px;
|
||||
left: 50%;
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
transform: translateX(-50%);
|
||||
width: 7px;
|
||||
}
|
||||
|
||||
.floor-plan-transition-marker {
|
||||
align-items: center;
|
||||
--transition-marker-ink: #12332c;
|
||||
@ -5151,6 +5191,24 @@ body.dragging-location [data-drag-type="location"] {
|
||||
color: #f1f6f3;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .floor-plan-block.is-hidden-location,
|
||||
.dark .floor-plan-block.is-hidden-location {
|
||||
background-image: repeating-linear-gradient(135deg, rgba(244, 234, 220, .09) 0, rgba(244, 234, 220, .09) 3px, transparent 3px, transparent 9px);
|
||||
box-shadow: inset 0 0 0 1px rgba(244, 234, 220, .2);
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .floor-plan-secret-route,
|
||||
.dark .floor-plan-secret-route {
|
||||
border-top-color: rgba(244, 204, 160, .58);
|
||||
opacity: .76;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .floor-plan-secret-route::after,
|
||||
.dark .floor-plan-secret-route::after {
|
||||
background: rgba(42, 35, 30, .95);
|
||||
border-color: rgba(244, 204, 160, .55);
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .floor-plan-coordinate-label,
|
||||
.dark .floor-plan-coordinate-label {
|
||||
color: rgba(244, 234, 220, .46);
|
||||
|
||||
2
PlotLine/wwwroot/css/site.min.css
vendored
2
PlotLine/wwwroot/css/site.min.css
vendored
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user