Add InputDeviceIconSetSO configuration guide and related documentation

- Created a new markdown file detailing the configuration of InputDeviceIconSetSO.
- Included sections on system architecture, field explanations, image specifications, and complete workflow from setup to runtime.
- Documented the automatic device recognition logic and provided troubleshooting for common issues.
- Added references to relevant files and scripts for easier navigation.
This commit is contained in:
2026-05-23 00:10:23 +08:00
parent b7baf7ad6a
commit e879efaa89
45 changed files with 3469 additions and 63 deletions

View File

@@ -31,7 +31,8 @@
"BaseGames.Skills",
"BaseGames.World.Map",
"BaseGames.EventChain",
"BaseGames.VFX"
"BaseGames.VFX",
"Unity.InputSystem"
],
"includePlatforms": [
"Editor"

View File

@@ -37,6 +37,7 @@ namespace BaseGames.Editor
CreateAsset<StringEventChannelSO> ("Core", "EVT_SceneLoaded");
CreateAsset<VoidEventChannelSO> ("Core", "EVT_FadeInRequest");
CreateAsset<VoidEventChannelSO> ("Core", "EVT_FadeOutRequest");
CreateAsset<VoidEventChannelSO> ("Core", "EVT_SceneWorldStateRestored"); // 场景加载完毕、世界状态恢复后触发;场景物体在此订阅并应用存档状态,淡入前保证画面正确
// ── 难度 ──────────────────────────────────────────────────────────
CreateAsset<DifficultyChangedEventChannel>("Difficulty", "EVT_DifficultyChanged");
@@ -99,14 +100,18 @@ namespace BaseGames.Editor
CreateAsset<VoidEventChannelSO> ("World", "EVT_CheckpointReached");
CreateAsset<StringEventChannelSO> ("World", "EVT_DoorOpened"); // 开门/交互机关(钥匙、机关等)触发自动存档
CreateAsset<StringEventChannelSO> ("World", "EVT_ItemPickup"); // 道具/收集品获取itemId
CreateAsset<StringEventChannelSO> ("World", "EVT_CollectiblePickup"); // 关键物品拾取护符、道具等触发存档AutoSaveService / QuestManager / EventChainManager 监听
CreateAsset<StringEventChannelSO> ("World", "EVT_CollectibleSaved"); // 持久化记录收集品collectibleId
CreateAsset<StringEventChannelSO> ("World", "EVT_RoomEntered"); // 玩家进入新房间roomId
CreateAsset<StringEventChannelSO> ("World", "EVT_RegionChanged"); // 玩家首次进入新区域regionId
CreateAsset<StringEventChannelSO> ("World", "EVT_RevealRegion"); // 触发地图区域揭露regionId
CreateAsset<StringEventChannelSO> ("World", "EVT_MapUpdated"); // 房间首次探索/标注时刷新roomIdMapManager 发布MapPanel 订阅
CreateAsset<StringEventChannelSO> ("World", "EVT_ChallengeCompleted"); // 挑战房间通关challengeId
CreateAsset<StringEventChannelSO> ("World", "EVT_ChallengeFailed"); // 挑战房间失败challengeId
CreateAsset<LiquidEventChannelSO> ("World", "EVT_LiquidEntered"); // 玩家进入液体区域
CreateAsset<LiquidEventChannelSO> ("World", "EVT_LiquidExited"); // 玩家离开液体区域
CreateAsset<BaseGames.World.WorldMarkerEventChannelSO>("World", "EVT_WorldMarkerActivated"); // 导航标记点激活(地图图标显示)
CreateAsset<BaseGames.World.WorldMarkerEventChannelSO>("World", "EVT_WorldMarkerDeactivated"); // 导航标记点失活(地图图标隐藏)
// ── 对话/商店 ─────────────────────────────────────────────────────
CreateAsset<ShopPurchaseEventChannelSO> ("Dialogue", "EVT_ShopPurchase");

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 52933b4810ae6654c962a93708b64a8f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,467 @@
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.UIElements;
using BaseGames.UI;
namespace BaseGames.Editor.Input
{
/// <summary>
/// InputDeviceIconSetSO 自定义 Inspector。
///
/// 布局(从上到下):
/// ① 设备类型徽章 + 覆盖率芯片
/// ② 操作按钮工具栏(从 Action Asset 自动填充 / 打开 Studio
/// ③ 按键图标条目表Path | Icon Sprite | 48px 预览 | 删除按钮)
/// ④ + 新增条目 按钮
/// </summary>
[CustomEditor(typeof(InputDeviceIconSetSO))]
public class InputDeviceIconSetSOEditor : UnityEditor.Editor
{
private const string UssPath = "Assets/_Game/Scripts/Editor/UIToolkit/Editor.uss";
// 预设绑定路径快捷菜单(按设备类型)
private static readonly Dictionary<InputDeviceType, string[]> s_CommonPaths = new()
{
[InputDeviceType.KeyboardMouse] = new[]
{
"<Keyboard>/e", "<Keyboard>/f", "<Keyboard>/r", "<Keyboard>/space",
"<Keyboard>/enter", "<Keyboard>/escape", "<Keyboard>/shift",
"<Keyboard>/ctrl", "<Keyboard>/tab", "<Keyboard>/q", "<Keyboard>/g",
"<Mouse>/leftButton", "<Mouse>/rightButton", "<Mouse>/middleButton"
},
[InputDeviceType.XboxController] = new[]
{
"<Gamepad>/buttonSouth", "<Gamepad>/buttonNorth",
"<Gamepad>/buttonEast", "<Gamepad>/buttonWest",
"<Gamepad>/leftShoulder", "<Gamepad>/rightShoulder",
"<Gamepad>/leftTrigger", "<Gamepad>/rightTrigger",
"<Gamepad>/start", "<Gamepad>/select",
"<Gamepad>/leftStickPress", "<Gamepad>/rightStickPress"
},
[InputDeviceType.PlayStationController] = new[]
{
"<Gamepad>/buttonSouth", "<Gamepad>/buttonNorth",
"<Gamepad>/buttonEast", "<Gamepad>/buttonWest",
"<Gamepad>/leftShoulder", "<Gamepad>/rightShoulder",
"<Gamepad>/leftTrigger", "<Gamepad>/rightTrigger",
"<Gamepad>/start", "<Gamepad>/select"
},
[InputDeviceType.SwitchController] = new[]
{
"<Gamepad>/buttonSouth", "<Gamepad>/buttonNorth",
"<Gamepad>/buttonEast", "<Gamepad>/buttonWest",
"<Gamepad>/leftShoulder", "<Gamepad>/rightShoulder",
"<Gamepad>/leftTrigger", "<Gamepad>/rightTrigger",
"<Gamepad>/start", "<Gamepad>/select"
}
};
// ── 状态 ──────────────────────────────────────────────────────────────
private SerializedProperty _entriesProp;
private VisualElement _root;
private VisualElement _tableContainer;
private Label _coverageLabel;
// ── 生命周期 ──────────────────────────────────────────────────────────
public override VisualElement CreateInspectorGUI()
{
_entriesProp = serializedObject.FindProperty("_entries");
_root = new VisualElement();
var uss = AssetDatabase.LoadAssetAtPath<StyleSheet>(UssPath);
if (uss != null) _root.styleSheets.Add(uss);
BuildHeader();
BuildToolbar();
BuildTable();
BuildAddButton();
return _root;
}
// ── 头部 ─────────────────────────────────────────────────────────────
private void BuildHeader()
{
var header = new VisualElement();
header.style.flexDirection = FlexDirection.Row;
header.style.alignItems = Align.Center;
header.style.paddingLeft = 4;
header.style.paddingRight = 4;
header.style.paddingTop = 6;
header.style.paddingBottom = 6;
header.style.borderBottomWidth = 1;
header.style.borderBottomColor = new StyleColor(new Color(0.5f, 0.5f, 0.5f, 0.25f));
header.style.marginBottom = 6;
// 设备类型字段
var deviceProp = serializedObject.FindProperty("_deviceType");
var deviceField = new PropertyField(deviceProp, "设备类型");
deviceField.style.flexGrow = 1;
deviceField.RegisterValueChangeCallback(_ => RefreshCoverage());
header.Add(deviceField);
// 覆盖率徽章
_coverageLabel = new Label();
_coverageLabel.AddToClassList("status-chip--ok");
_coverageLabel.style.fontSize = 10;
_coverageLabel.style.marginLeft = 8;
RefreshCoverage();
header.Add(_coverageLabel);
_root.Add(header);
}
// ── 工具栏 ────────────────────────────────────────────────────────────
private void BuildToolbar()
{
var bar = new VisualElement();
bar.AddToClassList("editor-toolbar");
bar.style.marginBottom = 6;
// 从 InputActionAsset 自动填充路径
var btnAutoFill = new Button(AutoFillFromActionAsset)
{
text = "⬇ 从 Action Asset 填充路径",
tooltip = "扫描 InputReaderSO自动为该设备控制方案添加所有绑定路径Sprite 留空需手动指定)"
};
btnAutoFill.AddToClassList("wizard-factory-btn");
bar.Add(btnAutoFill);
// 清空 null Sprite 条目
var btnClean = new Button(RemoveEmptySpriteEntries)
{
text = "🧹 清理空 Sprite",
tooltip = "移除 Sprite 为空的条目"
};
bar.Add(btnClean);
// 打开 Studio
var btnStudio = new Button(() => InputIconStudioWindow.Open())
{
text = "🎨 打开 Icon Studio",
tooltip = "打开完整的按键图标管理工作台"
};
btnStudio.AddToClassList("wizard-jump-btn");
btnStudio.style.marginLeft = 8;
bar.Add(btnStudio);
_root.Add(bar);
}
// ── 条目表 ────────────────────────────────────────────────────────────
private void BuildTable()
{
// 表头
var tableHead = new VisualElement();
tableHead.style.flexDirection = FlexDirection.Row;
tableHead.style.paddingLeft = 4;
tableHead.style.paddingRight = 4;
tableHead.style.paddingTop = 3;
tableHead.style.paddingBottom = 3;
tableHead.style.borderBottomWidth = 1;
tableHead.style.borderBottomColor = new StyleColor(new Color(0.5f, 0.5f, 0.5f, 0.3f));
tableHead.style.backgroundColor = new StyleColor(new Color(0.5f, 0.5f, 0.5f, 0.08f));
AddHeaderCell(tableHead, "绑定路径", flexGrow: 1f);
AddHeaderCell(tableHead, "图标", flexGrow: 0.8f);
AddHeaderCell(tableHead, "预览", width: 52f);
AddHeaderCell(tableHead, "", width: 24f);
_root.Add(tableHead);
// 条目容器
_tableContainer = new VisualElement();
_root.Add(_tableContainer);
RebuildRows();
}
private void RebuildRows()
{
_tableContainer.Clear();
serializedObject.Update();
for (int i = 0; i < _entriesProp.arraySize; i++)
_tableContainer.Add(BuildRow(i));
RefreshCoverage();
}
private VisualElement BuildRow(int index)
{
var entryProp = _entriesProp.GetArrayElementAtIndex(index);
var pathProp = entryProp.FindPropertyRelative("BindingPath");
var iconProp = entryProp.FindPropertyRelative("Icon");
var row = new VisualElement();
row.style.flexDirection = FlexDirection.Row;
row.style.alignItems = Align.Center;
row.style.paddingTop = 2;
row.style.paddingBottom = 2;
row.style.paddingLeft = 4;
row.style.paddingRight = 4;
row.style.borderBottomWidth = 1;
row.style.borderBottomColor = new StyleColor(new Color(0.5f, 0.5f, 0.5f, 0.12f));
row.AddToClassList("list-item");
// ── 绑定路径 ──────────────────────────────────────────────────
var pathField = new TextField { value = pathProp.stringValue, isDelayed = true };
pathField.style.flexGrow = 1;
pathField.style.flexShrink = 1;
pathField.style.fontSize = 10;
pathField.RegisterValueChangedCallback(evt =>
{
serializedObject.Update();
_entriesProp.GetArrayElementAtIndex(index)
.FindPropertyRelative("BindingPath").stringValue = evt.newValue;
serializedObject.ApplyModifiedProperties();
RefreshCoverage();
});
// 路径快捷菜单按钮
var pathRow = new VisualElement();
pathRow.style.flexDirection = FlexDirection.Row;
pathRow.style.flexGrow = 1;
pathRow.style.alignItems = Align.Center;
pathField.style.flexGrow = 1;
pathRow.Add(pathField);
var menuBtn = new Button(() =>
{
var menu = new GenericMenu();
var so = target as InputDeviceIconSetSO;
if (so != null && s_CommonPaths.TryGetValue(so.DeviceType, out var paths))
{
foreach (var p in paths)
{
var captured = p;
menu.AddItem(new GUIContent(p), false, () =>
{
serializedObject.Update();
_entriesProp.GetArrayElementAtIndex(index)
.FindPropertyRelative("BindingPath").stringValue = captured;
serializedObject.ApplyModifiedProperties();
pathField.value = captured;
RefreshCoverage();
});
}
}
menu.ShowAsContext();
}) { text = "▾" };
menuBtn.style.width = 20;
menuBtn.style.height = 18;
menuBtn.style.fontSize = 9;
menuBtn.style.paddingLeft = menuBtn.style.paddingRight = 0;
menuBtn.tooltip = "常用路径快捷选择";
pathRow.Add(menuBtn);
row.Add(pathRow);
// ── Icon Sprite ───────────────────────────────────────────────
var iconField = new ObjectField { objectType = typeof(Sprite), allowSceneObjects = false };
iconField.value = iconProp.objectReferenceValue as Sprite;
iconField.style.flexGrow = 0.8f;
iconField.style.marginLeft = 4;
iconField.RegisterValueChangedCallback(evt =>
{
serializedObject.Update();
_entriesProp.GetArrayElementAtIndex(index)
.FindPropertyRelative("Icon").objectReferenceValue = evt.newValue;
serializedObject.ApplyModifiedProperties();
UpdatePreviewInRow(row, evt.newValue as Sprite);
RefreshCoverage();
});
row.Add(iconField);
// ── 预览缩略图 ────────────────────────────────────────────────
var preview = new Image { name = "icon-preview" };
preview.style.width = 48;
preview.style.height = 48;
preview.style.flexShrink = 0;
preview.style.marginLeft = 4;
preview.style.borderTopLeftRadius = 3;
preview.style.borderTopRightRadius = 3;
preview.style.borderBottomLeftRadius = 3;
preview.style.borderBottomRightRadius = 3;
preview.style.backgroundColor = new StyleColor(new Color(0f, 0f, 0f, 0.25f));
preview.scaleMode = ScaleMode.ScaleToFit;
if (iconProp.objectReferenceValue is Sprite spr)
preview.sprite = spr;
row.Add(preview);
// ── 删除按钮 ──────────────────────────────────────────────────
var delBtn = new Button(() =>
{
serializedObject.Update();
_entriesProp.DeleteArrayElementAtIndex(index);
serializedObject.ApplyModifiedProperties();
RebuildRows();
}) { text = "✕" };
delBtn.style.width = 24;
delBtn.style.height = 24;
delBtn.style.marginLeft = 4;
delBtn.style.flexShrink = 0;
delBtn.AddToClassList("action-button--danger");
delBtn.tooltip = "删除此条目";
row.Add(delBtn);
return row;
}
private void BuildAddButton()
{
var addBtn = new Button(() =>
{
serializedObject.Update();
_entriesProp.InsertArrayElementAtIndex(_entriesProp.arraySize);
var newEntry = _entriesProp.GetArrayElementAtIndex(_entriesProp.arraySize - 1);
newEntry.FindPropertyRelative("BindingPath").stringValue = string.Empty;
newEntry.FindPropertyRelative("Icon").objectReferenceValue = null;
serializedObject.ApplyModifiedProperties();
RebuildRows();
}) { text = "+ 新增条目" };
addBtn.style.marginTop = 6;
addBtn.style.marginLeft = 4;
addBtn.style.marginBottom = 4;
addBtn.AddToClassList("wizard-factory-btn");
_root.Add(addBtn);
}
// ── 自动填充 ──────────────────────────────────────────────────────────
private void AutoFillFromActionAsset()
{
var so = target as InputDeviceIconSetSO;
if (so == null) return;
// 查找项目中的 InputReaderSO获取 InputActionAsset
var readerGuids = AssetDatabase.FindAssets("t:InputReaderSO");
InputActionAsset actionAsset = null;
foreach (var g in readerGuids)
{
var path = AssetDatabase.GUIDToAssetPath(g);
var reader = AssetDatabase.LoadAssetAtPath<ScriptableObject>(path);
if (reader == null) continue;
var so2 = new SerializedObject(reader);
var prop = so2.FindProperty("_inputActions");
if (prop?.objectReferenceValue is InputActionAsset asset)
{
actionAsset = asset;
break;
}
}
if (actionAsset == null)
{
EditorUtility.DisplayDialog("未找到 InputActionAsset",
"无法在项目中找到 InputReaderSO 或其引用的 InputActionAsset。\n请手动填写绑定路径。", "确定");
return;
}
string scheme = so.DeviceType == InputDeviceType.KeyboardMouse ? "Keyboard&Mouse" : "Gamepad";
serializedObject.Update();
// 收集已有路径,避免重复
var existingPaths = new HashSet<string>();
for (int i = 0; i < _entriesProp.arraySize; i++)
{
var p = _entriesProp.GetArrayElementAtIndex(i).FindPropertyRelative("BindingPath").stringValue;
if (!string.IsNullOrEmpty(p)) existingPaths.Add(p);
}
int added = 0;
foreach (var action in actionAsset)
{
foreach (var binding in action.bindings)
{
if (binding.isComposite) continue;
if (!string.IsNullOrEmpty(scheme)
&& !string.IsNullOrEmpty(binding.groups)
&& !binding.groups.Contains(scheme, System.StringComparison.OrdinalIgnoreCase))
continue;
var effectivePath = binding.effectivePath;
if (string.IsNullOrEmpty(effectivePath)) continue;
if (existingPaths.Contains(effectivePath)) continue;
existingPaths.Add(effectivePath);
_entriesProp.InsertArrayElementAtIndex(_entriesProp.arraySize);
var newEntry = _entriesProp.GetArrayElementAtIndex(_entriesProp.arraySize - 1);
newEntry.FindPropertyRelative("BindingPath").stringValue = effectivePath;
newEntry.FindPropertyRelative("Icon").objectReferenceValue = null;
added++;
}
}
serializedObject.ApplyModifiedProperties();
RebuildRows();
if (added == 0)
EditorUtility.DisplayDialog("填充完成", "所有路径已存在,无新增条目。", "确定");
else
Debug.Log($"[InputIconStudio] 自动填充了 {added} 个绑定路径Sprite 需手动指定)。");
}
private void RemoveEmptySpriteEntries()
{
serializedObject.Update();
int removed = 0;
for (int i = _entriesProp.arraySize - 1; i >= 0; i--)
{
var icon = _entriesProp.GetArrayElementAtIndex(i).FindPropertyRelative("Icon");
if (icon.objectReferenceValue == null)
{
_entriesProp.DeleteArrayElementAtIndex(i);
removed++;
}
}
serializedObject.ApplyModifiedProperties();
RebuildRows();
Debug.Log($"[InputIconStudio] 清理了 {removed} 个空 Sprite 条目。");
}
// ── 工具方法 ──────────────────────────────────────────────────────────
private void RefreshCoverage()
{
if (_coverageLabel == null || _entriesProp == null) return;
serializedObject.Update();
int total = _entriesProp.arraySize;
int hasIcon = 0;
for (int i = 0; i < total; i++)
{
if (_entriesProp.GetArrayElementAtIndex(i)
.FindPropertyRelative("Icon").objectReferenceValue != null)
hasIcon++;
}
_coverageLabel.text = $"覆盖: {hasIcon}/{total}";
bool allOk = total > 0 && hasIcon == total;
_coverageLabel.EnableInClassList("status-chip--ok", allOk);
_coverageLabel.EnableInClassList("status-chip--missing", !allOk);
}
private static void UpdatePreviewInRow(VisualElement row, Sprite sprite)
{
var preview = row.Q<Image>("icon-preview");
if (preview == null) return;
preview.sprite = sprite;
}
private static void AddHeaderCell(VisualElement parent, string text, float? flexGrow = null, float? width = null)
{
var lbl = new Label(text);
lbl.style.fontSize = 10;
lbl.style.opacity = 0.6f;
lbl.style.unityFontStyleAndWeight = FontStyle.Bold;
if (flexGrow.HasValue) lbl.style.flexGrow = flexGrow.Value;
if (width.HasValue) lbl.style.width = width.Value;
lbl.style.paddingRight = 4;
parent.Add(lbl);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f5106bbd57a6fb242b364657a739a8b6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f6534cbe9abbd954bb1ece4cbb2d7747
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -259,3 +259,113 @@
background-color: rgba(200, 80, 80, 0.20);
border-color: rgba(220, 100, 100, 0.80);
}
/* ── Input Icon Studio 专用样式 ─────────────────────────────
用于 InputIconStudioWindow 和 InputDeviceIconSetSOEditor */
/* 图标缩略图32px 正方形,带圆角 + 深色背景ScaleToFit*/
.icon-thumbnail {
width: 32px;
height: 32px;
border-radius: 3px;
background-color: rgba(0, 0, 0, 0.22);
flex-shrink: 0;
margin-left: 4px;
}
/* 大图预览64px用于当前 Action 图标展示)*/
.icon-preview-large {
width: 64px;
height: 64px;
border-radius: 4px;
background-color: rgba(0, 0, 0, 0.25);
border-width: 1px;
border-color: rgba(128, 128, 128, 0.22);
flex-shrink: 0;
margin-left: 12px;
}
/* 覆盖率指示点:绿(已配置) */
.coverage-dot--ok {
color: rgba(60, 200, 90, 1.0);
font-size: 10px;
width: 14px;
flex-shrink: 0;
}
/* 覆盖率指示点:红(未配置) */
.coverage-dot--missing {
color: rgba(210, 65, 65, 1.0);
font-size: 10px;
width: 14px;
flex-shrink: 0;
}
/* 交互提示模拟预览容器(仿 InteractPromptWidget 外观)*/
.prompt-preview {
flex-direction: row;
align-items: center;
align-self: flex-start;
padding: 8px 14px 8px 12px;
margin-top: 4px;
margin-left: 8px;
border-radius: 6px;
background-color: rgba(20, 20, 26, 0.85);
border-width: 1px;
border-color: rgba(130, 130, 155, 0.35);
}
/* 模拟预览中的按键图标方框 */
.prompt-key-box {
width: 32px;
height: 32px;
border-radius: 4px;
background-color: rgba(50, 50, 64, 0.90);
border-width: 1px;
border-color: rgba(160, 160, 185, 0.50);
justify-content: center;
align-items: center;
flex-shrink: 0;
margin-right: 10px;
}
/* Action 列表行Input Icon Studio 左列) */
.action-list-row {
flex-direction: row;
align-items: center;
padding: 5px 8px 5px 10px;
margin-bottom: 1px;
border-radius: 0;
border-width: 0;
}
.action-list-row:hover {
background-color: rgba(128, 128, 128, 0.10);
}
.action-list-row--selected {
background-color: rgba(90, 140, 220, 0.20);
}
/* 设备徽章(四色:键鼠蓝 / Xbox绿 / PS深蓝 / Switch红 */
.device-badge {
font-size: 10px;
padding: 2px 7px;
border-radius: 4px;
border-width: 1px;
-unity-font-style: bold;
}
.device-badge--kbm {
background-color: rgba(50, 100, 180, 0.50);
border-color: rgba(90, 150, 240, 0.60);
}
.device-badge--xbox {
background-color: rgba(25, 130, 40, 0.50);
border-color: rgba(60, 185, 80, 0.60);
}
.device-badge--ps {
background-color: rgba(25, 65, 165, 0.50);
border-color: rgba(60, 110, 230, 0.60);
}
.device-badge--switch {
background-color: rgba(190, 40, 50, 0.50);
border-color: rgba(230, 80, 90, 0.60);
}