Detective Board UI update (#90591)

## About The Pull Request

I've updated the UI of the detective board to make it look more
detective-like. Also, it is not necessary to specify the name and
description of the evidence to be attached. If it is not specified,
these fields are taken from the item.



https://github.com/user-attachments/assets/51419478-5fa9-43d4-a903-992ab4f0c91d

(The detective smoked a cigar and is now coughing)

Before:

![old](https://github.com/user-attachments/assets/b900b488-8271-40df-ac7c-18b60c8f6bf5)
After:

![new](https://github.com/user-attachments/assets/6c9d19e7-d94b-4003-81b1-54d781fb62bc)

## Why It's Good For The Game

Better UI/UX

## Changelog

🆑 FeudeyTF
qol: Better UI/UX for detective's board
/🆑
This commit is contained in:
FeudeyTF
2025-04-16 18:00:43 -07:00
committed by GitHub
parent abcfcda51f
commit 85e547ab0d
5 changed files with 162 additions and 22 deletions
@@ -52,12 +52,10 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/detectiveboard, 32)
attaching_evidence = TRUE
var/name = tgui_input_text(user, "Please enter the evidence name", "Detective's Board", max_length = MAX_NAME_LEN)
if(!name)
attaching_evidence = FALSE
return
name = item.name
var/desc = tgui_input_text(user, "Please enter the evidence description", "Detective's Board", max_length = MAX_DESC_LEN)
if(!desc)
attaching_evidence = FALSE
return
desc = item.desc
if(!user.transferItemToLoc(item, src))
attaching_evidence = FALSE
@@ -38,6 +38,8 @@ export function Evidence(props: EvidenceProps) {
null,
);
const randomRotation = Math.random() * 2 - 1;
function handleMouseDown(args) {
if (canDrag) {
setDragging(true);
@@ -74,9 +76,6 @@ export function Evidence(props: EvidenceProps) {
};
}, [dragging]);
function getPinPositionByPosition(evidence: Position) {
return { x: evidence.x + 15, y: evidence.y + 45 };
}
useEffect(() => {
if (!dragging) {
return;
@@ -85,13 +84,16 @@ export function Evidence(props: EvidenceProps) {
const onMouseMove = (args: MouseEvent) => {
if (canDrag) {
if (lastMousePosition) {
const newX = dragPosition.x - (lastMousePosition.x - args.screenX);
const newY = dragPosition.y - (lastMousePosition.y - args.screenY);
setDragPosition({
x: dragPosition.x - (lastMousePosition.x - args.screenX),
y: dragPosition.y - (lastMousePosition.y - args.screenY),
x: newX,
y: newY,
});
props.onMoving(evidence, {
x: dragPosition.x - (lastMousePosition.x - args.screenX),
y: dragPosition.y - (lastMousePosition.y - args.screenY),
x: newX,
y: newY,
});
}
@@ -107,10 +109,15 @@ export function Evidence(props: EvidenceProps) {
return (
<Box
className={dragging && 'Evidence--dragging'}
position="absolute"
left={`${dragPosition.x}px`}
top={`${dragPosition.y}px`}
onMouseDown={handleMouseDown}
style={{
transform: !dragging ? `rotate(${randomRotation}deg)` : undefined,
zIndex: 1,
}}
>
<Stack vertical>
<Stack.Item>
@@ -1,5 +1,6 @@
import { useEffect, useState } from 'react';
import { Box, Stack } from 'tgui-core/components';
import { classes } from 'tgui-core/react';
import { DataEvidence } from './DataTypes';
@@ -48,7 +49,10 @@ export function Pin(props: PinProps) {
<Stack>
<Stack.Item>
<Box
className="Evidence__Pin"
className={classes([
'Evidence__Pin',
creatingRope && 'Evidence__Pin--connecting',
])}
textAlign="center"
onMouseDown={handleMouseDown}
onMouseUp={(args) => onMouseUp(evidence, args)}
@@ -291,20 +291,20 @@ export function DetectiveBoard(props) {
connections={retrieveConnections(
movingEvidenceConnections,
)}
zLayer={99}
zLayer={1}
/>
)}
{connection && (
<Connections
lineWidth={5}
connections={[connection]}
zLayer={99}
zLayer={1}
/>
)}
<Connections
lineWidth={5}
connections={connections}
zLayer={99}
zLayer={1}
/>
{item?.evidences?.map((evidence, index) => (
<Evidence
@@ -13,6 +13,7 @@
transition-property: background-color, transform;
transition-duration: var(--transition-time-medium);
}
.BoardTab__Text {
margin-right: var(--space-m);
}
@@ -39,36 +40,124 @@
.Evidence__Pin {
box-shadow: var(--shadow-glow-small) hsla(0, 0%, 0%, 0.33);
background-color: var(--color-red);
background-color: hsl(0, 65%, 40%);
border-radius: var(--border-radius-circular);
width: 15px;
height: 15px;
width: 16px;
height: 16px;
transition:
transform 0.2s ease,
box-shadow 0.2s ease;
border: 1px solid hsla(0, 0%, 0%, 0.3);
background-image: radial-gradient(
circle at 30% 30%,
rgba(255, 255, 255, 0.5) 0%,
rgba(255, 255, 255, 0) 60%
);
}
.Evidence__Pin:hover {
transform: scale(1.2);
cursor: var(--cursor-pointer);
box-shadow: 0 0 12px hsla(0, 72%, 50%, 0.7);
}
.Evidence__Pin--connecting {
background-color: hsl(0, 82%, 60%);
box-shadow: 0 0 15px hsla(0, 72%, 50%, 0.9);
transform: scale(1.3);
}
.Evidence__Box {
cursor: var(--cursor-pointer);
box-shadow: var(--shadow-glow-medium) hsla(0, 0%, 0%, 0.4);
box-shadow: 0 4px 12px hsla(0, 0%, 0%, 0.6);
padding: var(--space-m);
color: var(--color-black);
min-width: 200px;
max-width: 300px;
background-color: hsl(0, 0%, 100%);
border: var(--border-thickness-small) solid hsl(0, 0%, 50%);
background-color: hsl(46, 39%, 94%);
border: 2px solid hsl(35, 30%, 50%);
user-select: none;
-ms-user-select: none;
text-wrap: wrap;
position: relative;
background-image: repeating-linear-gradient(
0deg,
rgba(0, 0, 0, 0.06) 0px,
rgba(0, 0, 0, 0.06) 1px,
transparent 1px,
transparent 22px
);
transition: all 0.3s ease;
}
.Evidence__Box:hover {
box-shadow: 0 8px 16px hsla(0, 0%, 0%, 0.7);
transform: translateY(-4px) rotate(0.5deg);
border-color: hsl(35, 40%, 40%);
transition: transform 0.3s ease;
}
.Evidence--dragging .Evidence__Box {
animation: card-sway 1s ease-in-out infinite;
box-shadow: 0 12px 24px hsla(0, 0%, 0%, 0.7);
transform: translateY(-8px) rotate(1deg) translateX(10px);
transition: all 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
border-color: hsl(35, 40%, 30%);
z-index: 100;
}
.Evidence__Box::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(
135deg,
rgba(255, 255, 255, 0.3) 0%,
rgba(255, 255, 255, 0) 50%
);
pointer-events: none;
}
.Evidence__Box::before {
content: '';
position: absolute;
top: 0;
right: 0;
width: 20px;
height: 20px;
background: linear-gradient(
135deg,
transparent 0%,
transparent 50%,
rgba(0, 0, 0, 0.05) 50%,
rgba(0, 0, 0, 0.05) 100%
);
pointer-events: none;
}
.Evidence__Box__TextBox {
border-top: var(--border-thickness-tiny) solid hsl(0, 0%, 92%);
border-top: var(--border-thickness-tiny) dashed hsl(36, 29%, 80%);
text-wrap: wrap;
padding: var(--space-m) 0;
margin-top: var(--space-m);
max-width: 240px;
text-align: center;
font-family: 'Courier New', Courier, monospace;
color: #333;
&.title {
border-top: none;
margin-top: 0;
font-weight: bold;
font-size: 1.1em;
color: #22131b;
text-transform: uppercase;
letter-spacing: 1px;
text-align: center;
text-shadow: 1px 1px 0 rgba(255, 255, 255, 0.5);
}
}
@@ -78,6 +167,16 @@
padding: var(--space-m);
overflow: hidden;
height: 95%;
background-image: radial-gradient(
circle,
rgba(0, 0, 0, 0.1) 1px,
transparent 1px
),
radial-gradient(circle, rgba(0, 0, 0, 0.1) 1px, transparent 1px);
background-size: 16px 16px;
background-position:
0 0,
8px 8px;
}
.Evidence__Icon {
@@ -85,6 +184,14 @@
margin-top: var(--space-m);
border: var(--border-thickness-large) solid hsl(0, 0%, 88%);
width: 100%;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
transition: transform 0.2s ease;
filter: sepia(20%);
}
.Evidence__Icon:hover {
transform: scale(1.02);
filter: sepia(0%);
}
@each $color-name, $color-value in colors.$color-map {
@@ -100,3 +207,27 @@
);
}
}
@keyframes float {
0% {
transform: translateY(0px);
}
50% {
transform: translateY(-5px);
}
100% {
transform: translateY(0px);
}
}
@keyframes card-sway {
0% {
transform: rotate(-1deg) translateX(0);
}
50% {
transform: rotate(1deg) translateX(5px);
}
100% {
transform: rotate(-1deg) translateX(0);
}
}