mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-28 22:31:22 -05:00
Build Analyzer: New weapon "info cards" style
This commit is contained in:
@@ -14,28 +14,6 @@
|
||||
gap: var(--s-8);
|
||||
}
|
||||
|
||||
.analyzer__weapon-info-badges {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
color: var(--text-lighter);
|
||||
font-size: var(--fonts-xxs);
|
||||
font-weight: var(--semi-bold);
|
||||
gap: var(--s-2);
|
||||
}
|
||||
|
||||
.analyzer__weapon-info-badge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 2px solid var(--border);
|
||||
border-radius: var(--rounded);
|
||||
background-color: var(--bg-darker);
|
||||
gap: var(--s-2);
|
||||
padding-block: var(--s-1);
|
||||
padding-inline: var(--s-2);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.analyzer__effects-selector {
|
||||
display: grid;
|
||||
gap: var(--s-2);
|
||||
@@ -78,17 +56,35 @@
|
||||
|
||||
.analyzer__summary {
|
||||
border-radius: var(--rounded);
|
||||
background-color: var(--bg-lighter);
|
||||
background-color: var(--bg-light-variation);
|
||||
font-size: var(--fonts-md);
|
||||
font-weight: var(--bold);
|
||||
padding-block: var(--s-2);
|
||||
padding-inline: var(--s-3);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.analyzer__details:has(.analyzer__stat-card-highlighted) .analyzer__summary {
|
||||
background-color: var(--theme-transparent);
|
||||
}
|
||||
|
||||
.analyzer__weapon-info-badge {
|
||||
display: inline-flex;
|
||||
font-size: var(--fonts-xs);
|
||||
gap: var(--s-2);
|
||||
align-items: center;
|
||||
background-color: var(--bg-darker);
|
||||
border-radius: var(--rounded);
|
||||
padding: var(--s-0-5) var(--s-3);
|
||||
margin-inline-start: auto;
|
||||
/** Decided to do it absolutely because
|
||||
summary really dislikes becoming a flex
|
||||
(arrow disappears) */
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 6.5px;
|
||||
}
|
||||
|
||||
.analyzer__stat-collection {
|
||||
display: grid;
|
||||
gap: var(--s-2);
|
||||
@@ -102,7 +98,7 @@
|
||||
justify-content: space-between;
|
||||
padding: var(--s-2);
|
||||
border-radius: var(--rounded);
|
||||
background-color: var(--bg-darker);
|
||||
background-color: var(--bg-lighter);
|
||||
gap: var(--s-4);
|
||||
}
|
||||
|
||||
@@ -175,7 +171,7 @@
|
||||
width: 100%;
|
||||
padding: var(--s-3);
|
||||
border-radius: var(--rounded);
|
||||
background-color: var(--bg-darker);
|
||||
background-color: var(--bg-lighter);
|
||||
margin-block-start: var(--s-4);
|
||||
padding-block: var(--s-2);
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import { type SendouRouteHandle } from "~/utils/remix";
|
||||
import { makeTitle } from "~/utils/strings";
|
||||
import {
|
||||
ANALYZER_URL,
|
||||
mainWeaponImageUrl,
|
||||
navIconUrl,
|
||||
objectDamageCalculatorPage,
|
||||
specialWeaponImageUrl,
|
||||
@@ -180,7 +181,6 @@ export default function BuildAnalyzerPage() {
|
||||
clearsInputOnFocus
|
||||
/>
|
||||
</div>
|
||||
<WeaponInfoBadges analyzed={analyzed} />
|
||||
</div>
|
||||
<div className="stack md items-center">
|
||||
<AbilitiesSelector
|
||||
@@ -216,12 +216,38 @@ export default function BuildAnalyzerPage() {
|
||||
</div>
|
||||
<div className="stack md">
|
||||
{mainWeaponCategoryItems.length > 0 && (
|
||||
<StatCategory title={t("analyzer:stat.category.main")}>
|
||||
<StatCategory
|
||||
title={t("analyzer:stat.category.main")}
|
||||
summaryRightContent={
|
||||
<div className="analyzer__weapon-info-badge">
|
||||
<Image
|
||||
path={mainWeaponImageUrl(mainWeaponId)}
|
||||
width={24}
|
||||
height={24}
|
||||
alt={t(`weapons:MAIN_${mainWeaponId}`)}
|
||||
/>
|
||||
{t(`weapons:MAIN_${mainWeaponId}`)}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{mainWeaponCategoryItems}
|
||||
</StatCategory>
|
||||
)}
|
||||
|
||||
<StatCategory title={t("analyzer:stat.category.sub")}>
|
||||
<StatCategory
|
||||
title={t("analyzer:stat.category.sub")}
|
||||
summaryRightContent={
|
||||
<div className="analyzer__weapon-info-badge">
|
||||
<Image
|
||||
path={subWeaponImageUrl(analyzed.weapon.subWeaponSplId)}
|
||||
width={24}
|
||||
height={24}
|
||||
alt={t(`weapons:SUB_${analyzed.weapon.subWeaponSplId}`)}
|
||||
/>
|
||||
{t(`weapons:SUB_${analyzed.weapon.subWeaponSplId}`)}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<StatCard
|
||||
abilityPoints={abilityPoints}
|
||||
stat={analyzed.stats.subWeaponInkConsumptionPercentage}
|
||||
@@ -297,7 +323,24 @@ export default function BuildAnalyzerPage() {
|
||||
)}
|
||||
</StatCategory>
|
||||
|
||||
<StatCategory title={t("analyzer:stat.category.special")}>
|
||||
<StatCategory
|
||||
title={t("analyzer:stat.category.special")}
|
||||
summaryRightContent={
|
||||
<div className="analyzer__weapon-info-badge">
|
||||
<Image
|
||||
path={specialWeaponImageUrl(
|
||||
analyzed.weapon.specialWeaponSplId
|
||||
)}
|
||||
width={24}
|
||||
height={24}
|
||||
alt={t(
|
||||
`weapons:SPECIAL_${analyzed.weapon.specialWeaponSplId}`
|
||||
)}
|
||||
/>
|
||||
{t(`weapons:SPECIAL_${analyzed.weapon.specialWeaponSplId}`)}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<StatCard
|
||||
abilityPoints={abilityPoints}
|
||||
stat={analyzed.stats.specialPoint}
|
||||
@@ -578,6 +621,11 @@ export default function BuildAnalyzerPage() {
|
||||
)}
|
||||
|
||||
<StatCategory title={t("analyzer:stat.category.movement")}>
|
||||
<StatCard
|
||||
title={t("analyzer:attribute.weight")}
|
||||
abilityPoints={abilityPoints}
|
||||
stat={t(`analyzer:attribute.weight.${analyzed.weapon.speedType}`)}
|
||||
/>
|
||||
<StatCard
|
||||
abilityPoints={abilityPoints}
|
||||
stat={analyzed.stats.swimSpeed}
|
||||
@@ -700,37 +748,6 @@ export default function BuildAnalyzerPage() {
|
||||
);
|
||||
}
|
||||
|
||||
function WeaponInfoBadges({ analyzed }: { analyzed: AnalyzedBuild }) {
|
||||
const { t } = useTranslation(["weapons", "analyzer"]);
|
||||
|
||||
return (
|
||||
<div className="analyzer__weapon-info-badges">
|
||||
<div className="analyzer__weapon-info-badge">
|
||||
<Image
|
||||
path={subWeaponImageUrl(analyzed.weapon.subWeaponSplId)}
|
||||
width={20}
|
||||
height={20}
|
||||
alt={t(`weapons:SUB_${analyzed.weapon.subWeaponSplId}`)}
|
||||
/>
|
||||
{t(`weapons:SUB_${analyzed.weapon.subWeaponSplId}`)}
|
||||
</div>
|
||||
<div className="analyzer__weapon-info-badge">
|
||||
{t("analyzer:attribute.weight")}{" "}
|
||||
{t(`analyzer:attribute.weight.${analyzed.weapon.speedType}`)}
|
||||
</div>
|
||||
<div className="analyzer__weapon-info-badge">
|
||||
<Image
|
||||
path={specialWeaponImageUrl(analyzed.weapon.specialWeaponSplId)}
|
||||
width={20}
|
||||
height={20}
|
||||
alt={t(`weapons:SPECIAL_${analyzed.weapon.specialWeaponSplId}`)}
|
||||
/>
|
||||
{t(`weapons:SPECIAL_${analyzed.weapon.specialWeaponSplId}`)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function EffectsSelector({
|
||||
build,
|
||||
effects,
|
||||
@@ -880,15 +897,20 @@ function StatCategory({
|
||||
children,
|
||||
containerClassName = "analyzer__stat-collection",
|
||||
textBelow,
|
||||
summaryRightContent,
|
||||
}: {
|
||||
title: string;
|
||||
children: React.ReactNode;
|
||||
containerClassName?: string;
|
||||
textBelow?: string;
|
||||
summaryRightContent?: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<details className="analyzer__details">
|
||||
<summary className="analyzer__summary">{title}</summary>
|
||||
<summary className="analyzer__summary">
|
||||
{title}
|
||||
{summaryRightContent}
|
||||
</summary>
|
||||
<div className={containerClassName}>{children}</div>
|
||||
{textBelow && (
|
||||
<div className="analyzer__stat-category-explanation">{textBelow}</div>
|
||||
@@ -905,16 +927,17 @@ function StatCard({
|
||||
abilityPoints,
|
||||
}: {
|
||||
title: string;
|
||||
stat: Stat | Stat<string> | number;
|
||||
stat: Stat | Stat<string> | number | string;
|
||||
suffix?: string;
|
||||
popoverInfo?: string;
|
||||
abilityPoints: AbilityPoints;
|
||||
}) {
|
||||
const { t } = useTranslation("analyzer");
|
||||
|
||||
const baseValue = typeof stat === "number" ? stat : stat.baseValue;
|
||||
const isStaticValue = typeof stat === "number" || typeof stat === "string";
|
||||
const baseValue = isStaticValue ? stat : stat.baseValue;
|
||||
const showBuildValue = () => {
|
||||
if (typeof stat === "number") return false;
|
||||
if (isStaticValue) return false;
|
||||
|
||||
// slightly hacky but handles the edge case
|
||||
// where baseValue === value which can happen when
|
||||
@@ -971,9 +994,7 @@ function StatCard({
|
||||
</div>
|
||||
</div>
|
||||
<div className="analyzer__stat-card__ability-container">
|
||||
{typeof stat !== "number" && (
|
||||
<ModifiedByAbilities abilities={stat.modifiedBy} />
|
||||
)}
|
||||
{!isStaticValue && <ModifiedByAbilities abilities={stat.modifiedBy} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -1079,7 +1100,7 @@ function ConsumptionTable({
|
||||
<tbody>
|
||||
{new Array(maxSubsToUse + 1).fill(null).map((_, subsUsed) => {
|
||||
return (
|
||||
<tr key={subsUsed} className="bg-darker-important">
|
||||
<tr key={subsUsed}>
|
||||
<td>×{subsUsed}</td>
|
||||
{options
|
||||
.filter((opt) => opt.subsUsed === subsUsed)
|
||||
|
||||
@@ -59,12 +59,6 @@
|
||||
font-weight: var(--semi-bold);
|
||||
}
|
||||
|
||||
.plus__comment {
|
||||
min-width: auto;
|
||||
overflow-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.plus__comment-time {
|
||||
color: var(--text-lighter);
|
||||
font-size: var(--fonts-xxs);
|
||||
|
||||
@@ -50,10 +50,6 @@
|
||||
fill: var(--theme-warning);
|
||||
}
|
||||
|
||||
.bg-darker-important {
|
||||
background-color: var(--bg-darker) !important;
|
||||
}
|
||||
|
||||
.bg-transparent-important {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
html {
|
||||
--bg: #ebebf0;
|
||||
--bg-darker: #f8f8f8;
|
||||
--bg-lighter: #fff;
|
||||
--bg-lighter: rgb(250 250 250);
|
||||
--bg-lightest: #fff;
|
||||
--bg-light-variation: #fff;
|
||||
--bg-lighter-transparent: hsla(225deg 100% 88% / 50%);
|
||||
--bg-darker-very-transparent: rgb(187 208 219 / 29.5%);
|
||||
--bg-darker-transparent: hsla(202deg 90% 90% / 65%);
|
||||
@@ -90,6 +91,7 @@ html.dark {
|
||||
--bg-darker: #0a092d;
|
||||
--bg-lighter: rgb(169 138 255 / 10%);
|
||||
--bg-lighter-transparent: rgb(64 67 108 / 50%);
|
||||
--bg-light-variation: #a98aff30;
|
||||
--bg-lightest: rgb(169 138 255 / 30%);
|
||||
--bg-darker-very-transparent: hsla(237.3deg 42.3% 26.6% / 50%);
|
||||
--bg-darker-transparent: #0a092dce;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"attribute.weight": "Vægt: ",
|
||||
"attribute.weight": "Vægt",
|
||||
"attribute.weight.Fast": "Let",
|
||||
"attribute.weight.Slow": "Tung",
|
||||
"attribute.weight.Normal": "Normal",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"attribute.weight": "Gewicht:",
|
||||
"attribute.weight": "Gewicht",
|
||||
"attribute.weight.Fast": "Leicht",
|
||||
"attribute.weight.Slow": "Schwer",
|
||||
"attribute.weight.Normal": "Normal",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"attribute.weight": "Weight:",
|
||||
"attribute.weight": "Weight",
|
||||
"attribute.weight.Fast": "Light",
|
||||
"attribute.weight.Slow": "Heavy",
|
||||
"attribute.weight.Normal": "Normal",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"attribute.weight": "Peso:",
|
||||
"attribute.weight": "Peso",
|
||||
"attribute.weight.Fast": "Ligero",
|
||||
"attribute.weight.Slow": "Pesado",
|
||||
"attribute.weight.Normal": "Normal",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"attribute.weight": "Peso:",
|
||||
"attribute.weight": "Peso",
|
||||
"attribute.weight.Fast": "Ligero",
|
||||
"attribute.weight.Slow": "Pesado",
|
||||
"attribute.weight.Normal": "Normal",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"attribute.weight": "Poids :",
|
||||
"attribute.weight": "Poids",
|
||||
"attribute.weight.Fast": "Léger",
|
||||
"attribute.weight.Slow": "Lourd",
|
||||
"attribute.weight.Normal": "Moyen",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"attribute.weight": "Peso:",
|
||||
"attribute.weight": "Peso",
|
||||
"attribute.weight.Fast": "Leggero",
|
||||
"attribute.weight.Slow": "Pesante",
|
||||
"attribute.weight.Normal": "Normale",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"attribute.weight": "重さ:",
|
||||
"attribute.weight": "重さ",
|
||||
"attribute.weight.Fast": "軽量級",
|
||||
"attribute.weight.Slow": "重量級",
|
||||
"attribute.weight.Normal": "中量級",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"attribute.weight": "Gewicht:",
|
||||
"attribute.weight": "Gewicht",
|
||||
"attribute.weight.Fast": "Licht",
|
||||
"attribute.weight.Slow": "Zwaar",
|
||||
"attribute.weight.Normal": "Normaal",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"attribute.weight": "Весовая категория:",
|
||||
"attribute.weight": "Весовая категория",
|
||||
"attribute.weight.Fast": "Лёгкая",
|
||||
"attribute.weight.Slow": "Тяжелая",
|
||||
"attribute.weight.Normal": "Средняя",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"attribute.weight": "量级:",
|
||||
"attribute.weight": "量级",
|
||||
"attribute.weight.Fast": "轻量级",
|
||||
"attribute.weight.Slow": "重量级",
|
||||
"attribute.weight.Normal": "中量级",
|
||||
|
||||
Reference in New Issue
Block a user