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",
|
"blockeddoor" => "blocked-door",
|
||||||
"exteriordoor" => "exterior-door",
|
"exteriordoor" => "exterior-door",
|
||||||
"stairs" or "stair" or "stairsup" or "stairsdown" => "stairs",
|
"stairs" or "stair" or "stairsup" or "stairsdown" => "stairs",
|
||||||
"passage" => "passage",
|
"passage" or "openpassage" => "passage",
|
||||||
|
"hiddenpassage" or "secretpassage" => "secret-route",
|
||||||
_ => "other"
|
_ => "other"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -220,6 +221,7 @@
|
|||||||
data-location-id="@block.LocationID"
|
data-location-id="@block.LocationID"
|
||||||
data-location-name="@block.LocationName"
|
data-location-name="@block.LocationName"
|
||||||
data-location-path="@block.LocationPath"
|
data-location-path="@block.LocationPath"
|
||||||
|
data-location-title="@block.LocationPath"
|
||||||
title="@block.LocationPath">
|
title="@block.LocationPath">
|
||||||
<span class="floor-plan-block-label">@block.DisplayLabel</span>
|
<span class="floor-plan-block-label">@block.DisplayLabel</span>
|
||||||
<span class="floor-plan-resize-handle" data-resize-handle aria-hidden="true"></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.locationId = block.locationID || "";
|
||||||
button.dataset.locationName = block.locationName || "";
|
button.dataset.locationName = block.locationName || "";
|
||||||
button.dataset.locationPath = block.locationPath || "";
|
button.dataset.locationPath = block.locationPath || "";
|
||||||
|
button.dataset.locationTitle = block.locationPath || "";
|
||||||
button.title = 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.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";
|
button.querySelector(".floor-plan-block-label").textContent = block.displayLabel || block.locationName || "New block";
|
||||||
@ -1655,6 +1658,26 @@
|
|||||||
return ["hiddenpassage", "openpassage", "secretpassage"].includes(normalizeTransitionType(type));
|
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) {
|
function blocksTouchAlongEdge(a, b) {
|
||||||
const verticalTouch = a.x + a.w === b.x || b.x + b.w === a.x;
|
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;
|
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 };
|
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) {
|
function sharedTransitionEdge(fromBlocks, toBlocks) {
|
||||||
let best = null;
|
let best = null;
|
||||||
fromBlocks.forEach(from => {
|
fromBlocks.forEach(from => {
|
||||||
@ -1931,9 +1992,21 @@
|
|||||||
|
|
||||||
function positionTransitions(activePanel, visibleBlocks) {
|
function positionTransitions(activePanel, visibleBlocks) {
|
||||||
const markerPositions = [];
|
const markerPositions = [];
|
||||||
|
activePanel.querySelectorAll("[data-secret-route]").forEach(route => route.remove());
|
||||||
activePanel.querySelectorAll("[data-transition-marker]").forEach(marker => {
|
activePanel.querySelectorAll("[data-transition-marker]").forEach(marker => {
|
||||||
const fromBlocks = visibleBlocks.filter(item => item.block.dataset.locationId === marker.dataset.fromLocationId);
|
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 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)) {
|
if (fromBlocks.length === 0 || (!isStairTransition(marker.dataset.transitionKind) && toBlocks.length === 0)) {
|
||||||
marker.hidden = true;
|
marker.hidden = true;
|
||||||
return;
|
return;
|
||||||
@ -1943,8 +2016,6 @@
|
|||||||
? positionWithinLocation(fromBlocks, marker.dataset.positionWithinLocation)
|
? positionWithinLocation(fromBlocks, marker.dataset.positionWithinLocation)
|
||||||
: null;
|
: null;
|
||||||
const sharedEdge = stairPlacement ? null : sharedTransitionEdge(fromBlocks, toBlocks);
|
const sharedEdge = stairPlacement ? null : sharedTransitionEdge(fromBlocks, toBlocks);
|
||||||
const fromCenter = locationCenter(fromBlocks);
|
|
||||||
const toCenter = locationCenter(toBlocks);
|
|
||||||
marker.hidden = false;
|
marker.hidden = false;
|
||||||
marker.classList.toggle("is-shared-edge", !!sharedEdge);
|
marker.classList.toggle("is-shared-edge", !!sharedEdge);
|
||||||
marker.classList.toggle("is-inside-location", !!stairPlacement);
|
marker.classList.toggle("is-inside-location", !!stairPlacement);
|
||||||
@ -2005,6 +2076,7 @@
|
|||||||
if (!activePanel) return;
|
if (!activePanel) return;
|
||||||
const visibleBlocks = refreshSharedWalls(activePanel);
|
const visibleBlocks = refreshSharedWalls(activePanel);
|
||||||
positionTransitions(activePanel, visibleBlocks);
|
positionTransitions(activePanel, visibleBlocks);
|
||||||
|
refreshHiddenLocationStyles(visibleBlocks);
|
||||||
let overlaps = false;
|
let overlaps = false;
|
||||||
visibleBlocks.forEach(a => {
|
visibleBlocks.forEach(a => {
|
||||||
a.block.classList.remove("overlap");
|
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);
|
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 {
|
.floor-plan-transition-marker {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
--transition-marker-ink: #12332c;
|
--transition-marker-ink: #12332c;
|
||||||
@ -5151,6 +5191,24 @@ body.dragging-location [data-drag-type="location"] {
|
|||||||
color: #f1f6f3;
|
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,
|
[data-bs-theme="dark"] .floor-plan-coordinate-label,
|
||||||
.dark .floor-plan-coordinate-label {
|
.dark .floor-plan-coordinate-label {
|
||||||
color: rgba(244, 234, 220, .46);
|
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