Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "showdex",
"version": "1.3.0",
"name": "olrics-showdex",
"version": "1.0.2",
"description": "Pokémon Showdown extension that harnesses the power of parabolic calculus to strategically extract your opponents' Elo.",
"author": "Keith Choison <keith@tize.io>",
"license": "AGPL-3.0",
Expand Down
204 changes: 204 additions & 0 deletions src/pages/Calcdex/Calcdex.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,210 @@
@include spacing.mt(12px);
}

.validationPanel {
@include spacing.mt(12px);
@include spacing.p($a: 10px);
border-radius: 10px;
box-shadow: 0 6px 18px color.alpha(colors.$black, 0.16);

[data-showdex-scheme='light'] & {
background-color: color.alpha(colors.$white, 0.7);
}

[data-showdex-scheme='dark'] & {
background-color: color.alpha(colors.$gray-darkest, 0.68);
box-shadow: 0 6px 18px color.alpha(colors.$black, 0.4);
}
}

.validationHeader {
@include flex.row($align: center, $justify: space-between);
@include spacing.mb(8px);
}

.validationTitle {
@include font.primary(600);
font-size: 12px;
letter-spacing: 0.02em;

[data-showdex-scheme='dark'] & {
color: colors.$white;
}
}

.validationSummary {
@include font.primary(600);
font-size: 11px;
padding: 2px 6px;
border-radius: 999px;

[data-showdex-scheme='light'] & {
background-color: color.alpha(colors.$gray-light, 0.4);
color: colors.$gray-darkest;
}

[data-showdex-scheme='dark'] & {
background-color: color.alpha(colors.$gray-dark, 0.6);
color: colors.$white;
}

&.validationHasFails {
[data-showdex-scheme='light'] & {
background-color: color.alpha(colors.$red, 0.12);
color: colors.$red;
}

[data-showdex-scheme='dark'] & {
background-color: color.alpha(colors.$red, 0.2);
color: color.alpha(colors.$red, 0.95);
}
}
}

.validationBaseline {
@include flex.row($align: center);
gap: 4px;
@include spacing.mb(8px);
}

.validationBaselineItem {
width: 18px;
height: 18px;
@include flex.row-center;
border-radius: 3px;
font-size: 9px;

[data-showdex-scheme='light'] & {
background-color: color.alpha(colors.$white, 0.8);
}

[data-showdex-scheme='dark'] & {
background-color: color.alpha(colors.$gray-darker, 0.8);
}
}

.validationIcon {
font-size: 10px;
}

.validationTypeWindow {
& + & {
@include spacing.mt(8px);
}
}

.validationTypeWindowLabel {
@include font.primary(500);
font-size: 10px;
letter-spacing: 0.03em;
text-transform: uppercase;
@include spacing.mb(4px);

[data-showdex-scheme='light'] & {
color: color.alpha(colors.$gray-darkest, 0.7);
}

[data-showdex-scheme='dark'] & {
color: color.alpha(colors.$white, 0.6);
}
}

.validationTypeGrid {
display: flex;
flex-wrap: wrap;
gap: 4px;
}

.validationTypeIcon {
position: relative;
width: 36px;
height: 36px;
@include flex.row-center;
border-radius: 6px;
overflow: hidden;

[data-showdex-scheme='light'] & {
background-color: color.alpha(colors.$white, 0.7);
box-shadow: inset 0 0 0 1px color.alpha(colors.$gray-light, 0.4);
}

[data-showdex-scheme='dark'] & {
background-color: color.alpha(colors.$gray-darker, 0.6);
box-shadow: inset 0 0 0 1px color.alpha(colors.$black, 0.3);
}

// highlight the type icon
:global(.container) {
width: 100%;
height: 100%;
@include flex.row-center;
opacity: 0.7;
}
}

.validationTypeCount {
position: absolute;
bottom: -2px;
right: 0;
@include font.primary(600);
font-size: 9px;
padding: 0 2px;
border-radius: 2px 0 4px 0;
min-width: 14px;
text-align: center;

[data-showdex-scheme='light'] & {
background-color: color.alpha(colors.$gray-dark, 0.6);
color: colors.$white;
}

[data-showdex-scheme='dark'] & {
background-color: color.alpha(colors.$black, 0.5);
color: colors.$white;
}
}

.validationDouble {
&:hover .validationDoubleMarker {
opacity: 1;
}
}

.validationDoubleMarker {
position: absolute;
top: -1px;
left: 1px;
@include font.primary(600);
font-size: 8px;
color: colors.$red;
opacity: 0;
@include transition.apply(opacity);
}

.validationPass {
[data-showdex-scheme='light'] & {
background-color: color.alpha(colors.$green, 0.12);
box-shadow: inset 0 0 0 1px color.alpha(colors.$green, 0.3);
}

[data-showdex-scheme='dark'] & {
background-color: color.alpha(colors.$green, 0.15);
box-shadow: inset 0 0 0 1px color.alpha(colors.$green, 0.4);
}
}

.validationFail {
[data-showdex-scheme='light'] & {
background-color: color.alpha(colors.$red, 0.15);
box-shadow: inset 0 0 0 1px color.alpha(colors.$red, 0.4);
}

[data-showdex-scheme='dark'] & {
background-color: color.alpha(colors.$red, 0.18);
box-shadow: inset 0 0 0 1px color.alpha(colors.$red, 0.4);
}
}

.overlayCloseButton {
@include position.abs($t: 8px, $r: 8px);
@include flex.row-center;
Expand Down
3 changes: 3 additions & 0 deletions src/pages/Calcdex/Calcdex.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
import { findPlayerTitle } from '@showdex/utils/app';
import { useMobileViewport, useRandomUuid } from '@showdex/utils/hooks';
import styles from './Calcdex.module.scss';
import { RandomBattleValidationPanel } from './RandomBattleValidationPanel';

export interface CalcdexProps {
onUserPopup?: (username?: string) => void;
Expand Down Expand Up @@ -197,6 +198,8 @@ export const Calcdex = ({
/>
}

<RandomBattleValidationPanel />

<PiconRackSortableContext playerKey={topKey}>
<PlayerCalc
className={styles.playerCalc}
Expand Down
137 changes: 137 additions & 0 deletions src/pages/Calcdex/RandomBattleValidationPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import * as React from 'react';
import cx from 'classnames';
import { PokeType } from '@showdex/components/app';
import { useRandomBattlesValidation } from '@showdex/utils/hooks';
import styles from './Calcdex.module.scss';

const parseTypeFromCheckId = (checkId: string): Showdown.TypeName | null => {
const typeMatch = checkId.match(/(?:type-count|type-weak|type-double-weak)-(.+)$/);
if (!typeMatch) return null;

const typeId = typeMatch[1];
// Map the type names directly since they're already normalized
const typeNameMap: Record<string, Showdown.TypeName> = {
normal: 'Normal',
fighting: 'Fighting',
flying: 'Flying',
poison: 'Poison',
ground: 'Ground',
rock: 'Rock',
bug: 'Bug',
ghost: 'Ghost',
steel: 'Steel',
fire: 'Fire',
water: 'Water',
grass: 'Grass',
electric: 'Electric',
psychic: 'Psychic',
ice: 'Ice',
dragon: 'Dragon',
dark: 'Dark',
fairy: 'Fairy',
};

return typeNameMap[typeId] || null;
};

export const RandomBattleValidationPanel = (): JSX.Element => {
const validation = useRandomBattlesValidation();

if (!validation?.active || !validation?.checks?.length) {
return null;
}

const typeChecks = validation.checks.filter((c) => c.group === 'type-count');
const weakChecks = validation.checks.filter((c) => c.group === 'type-weakness');
const doubleWeakChecks = validation.checks.filter((c) => c.group === 'type-double-weakness');
const freezeDryCheck = validation.checks.find((c) => c.id === 'freeze-dry-weakness');

return (
<section className={styles.validationPanel} aria-label="Random Battles validation">
<header className={styles.validationHeader}>
<div className={styles.validationTitle}>Random Battles</div>
</header>

{typeChecks.length > 0 && (
<div className={styles.validationTypeWindow}>
<div className={styles.validationTypeWindowLabel}>Types</div>
<div className={styles.validationTypeGrid}>
{typeChecks.map((check) => {
const typeName = parseTypeFromCheckId(check.id);

return (
<div
key={check.id}
className={cx(
styles.validationTypeIcon,
check.ok ? styles.validationPass : styles.validationFail,
)}
title={`${check.label}: ${check.count}/${check.limit}`}
>
{typeName && <PokeType type={typeName} containerSize="xs" highlight={false} />}
<span className={styles.validationTypeCount}>{check.count}</span>
</div>
);
})}
</div>
</div>
)}

{(weakChecks.length > 0 || doubleWeakChecks.length > 0) && (
<div className={styles.validationTypeWindow}>
<div className={styles.validationTypeWindowLabel}>Weaknesses</div>
<div className={styles.validationTypeGrid}>
{doubleWeakChecks.map((check) => {
const typeName = parseTypeFromCheckId(check.id);

return (
<div
key={check.id}
className={cx(
styles.validationTypeIcon,
check.ok ? styles.validationPass : styles.validationFail,
styles.validationDouble,
)}
title={`2x ${check.label}: ${check.count}/${check.limit}`}
>
{typeName && <PokeType type={typeName} containerSize="xs" highlight={false} />}
<span className={styles.validationTypeCount}>{check.count}</span>
<span className={styles.validationDoubleMarker}>2×</span>
</div>
);
})}
{weakChecks.map((check) => {
const typeName = parseTypeFromCheckId(check.id);

return (
<div
key={check.id}
className={cx(
styles.validationTypeIcon,
check.ok ? styles.validationPass : styles.validationFail,
)}
title={`${check.label}: ${check.count}/${check.limit}`}
>
{typeName && <PokeType type={typeName} containerSize="xs" highlight={false} />}
<span className={styles.validationTypeCount}>{check.count}</span>
</div>
);
})}
{freezeDryCheck && freezeDryCheck.count > 0 && (
<div
className={cx(
styles.validationTypeIcon,
freezeDryCheck.ok ? styles.validationPass : styles.validationFail,
)}
title={`${freezeDryCheck.label}: ${freezeDryCheck.count}/${freezeDryCheck.limit}`}
>
<PokeType type="Ice" containerSize="xs" highlight={false} />
<span className={styles.validationTypeCount}>{freezeDryCheck.count}</span>
</div>
)}
</div>
</div>
)}
</section>
);
};
1 change: 1 addition & 0 deletions src/pages/Calcdex/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ export * from './CalcdexPreactBattleSide';
export * from './CalcdexPreactBattleTimerButton';
export * from './CalcdexPreactBootstrapper';
export * from './CalcdexPreactPanel';
export * from './RandomBattleValidationPanel';
export * from './CalcdexRenderer';
Loading