diff --git a/components/CharacterGrid/index.tsx b/components/CharacterGrid/index.tsx
index 3532caf0..d4c72e6e 100644
--- a/components/CharacterGrid/index.tsx
+++ b/components/CharacterGrid/index.tsx
@@ -57,11 +57,16 @@ const CharacterGrid = (props: Props) => {
})
const [errorMessage, setErrorMessage] = useState('')
- // Create a temporary state to store previous character uncap values
+ // Create a temporary state to store previous weapon uncap values and transcendence stages
const [previousUncapValues, setPreviousUncapValues] = useState<{
[key: number]: number | undefined
}>({})
+ const [previousTranscendenceStages, setPreviousTranscendenceStages] =
+ useState<{
+ [key: number]: number | undefined
+ }>({})
+
// Set the editable flag only on first load
useEffect(() => {
// If user is logged in and matches
@@ -269,6 +274,7 @@ const CharacterGrid = (props: Props) => {
// Note: Saves, but debouncing is not working properly
async function saveUncap(id: string, position: number, uncapLevel: number) {
storePreviousUncapValue(position)
+ storePreviousTranscendenceStage(position)
try {
if (uncapLevel != previousUncapValues[position])
@@ -280,11 +286,17 @@ const CharacterGrid = (props: Props) => {
// Revert optimistic UI
updateUncapLevel(position, previousUncapValues[position])
+ updateTranscendenceStage(position, previousTranscendenceStages[position])
// Remove optimistic key
- let newPreviousValues = { ...previousUncapValues }
- delete newPreviousValues[position]
- setPreviousUncapValues(newPreviousValues)
+ let newPreviousTranscendenceStages = { ...previousTranscendenceStages }
+ let newPreviousUncapValues = { ...previousUncapValues }
+
+ delete newPreviousTranscendenceStages[position]
+ delete newPreviousUncapValues[position]
+
+ setPreviousTranscendenceStages(newPreviousTranscendenceStages)
+ setPreviousUncapValues(newPreviousUncapValues)
}
}
@@ -298,21 +310,25 @@ const CharacterGrid = (props: Props) => {
accountState.account.user &&
party.user.id === accountState.account.user.id
) {
- memoizeAction(id, position, uncapLevel)
+ memoizeUncapAction(id, position, uncapLevel)
// Optimistically update UI
updateUncapLevel(position, uncapLevel)
+
+ if (uncapLevel < 6) {
+ updateTranscendenceStage(position, 0)
+ }
}
}
- const memoizeAction = useCallback(
+ const memoizeUncapAction = useCallback(
(id: string, position: number, uncapLevel: number) => {
- debouncedAction(id, position, uncapLevel)
+ debouncedUncapAction(id, position, uncapLevel)
},
[props, previousUncapValues]
)
- const debouncedAction = useMemo(
+ const debouncedUncapAction = useMemo(
() =>
debounce((id, position, number) => {
saveUncap(id, position, number)
@@ -341,6 +357,106 @@ const CharacterGrid = (props: Props) => {
}
}
+ // Methods: Updating transcendence stage
+ // Note: Saves, but debouncing is not working properly
+ async function saveTranscendence(
+ id: string,
+ position: number,
+ stage: number
+ ) {
+ storePreviousUncapValue(position)
+ storePreviousTranscendenceStage(position)
+
+ const payload = {
+ character: {
+ uncap_level: stage > 0 ? 6 : 5,
+ transcendence_step: stage,
+ },
+ }
+
+ try {
+ if (stage != previousTranscendenceStages[position])
+ await api.endpoints.grid_characters
+ .update(id, payload)
+ .then((response) => {
+ storeGridCharacter(response.data)
+ })
+ } catch (error) {
+ console.error(error)
+
+ // Revert optimistic UI
+ updateUncapLevel(position, previousUncapValues[position])
+ updateTranscendenceStage(position, previousTranscendenceStages[position])
+
+ // Remove optimistic key
+ let newPreviousTranscendenceStages = { ...previousTranscendenceStages }
+ let newPreviousUncapValues = { ...previousUncapValues }
+
+ delete newPreviousTranscendenceStages[position]
+ delete newPreviousUncapValues[position]
+
+ setPreviousTranscendenceStages(newPreviousTranscendenceStages)
+ setPreviousUncapValues(newPreviousUncapValues)
+ }
+ }
+
+ function initiateTranscendenceUpdate(
+ id: string,
+ position: number,
+ stage: number
+ ) {
+ if (
+ party.user &&
+ accountState.account.user &&
+ party.user.id === accountState.account.user.id
+ ) {
+ memoizeTranscendenceAction(id, position, stage)
+
+ // Optimistically update UI
+ updateTranscendenceStage(position, stage)
+
+ if (stage > 0) {
+ updateUncapLevel(position, 6)
+ }
+ }
+ }
+
+ const memoizeTranscendenceAction = useCallback(
+ (id: string, position: number, stage: number) => {
+ debouncedTranscendenceAction(id, position, stage)
+ },
+ [props, previousTranscendenceStages]
+ )
+
+ const debouncedTranscendenceAction = useMemo(
+ () =>
+ debounce((id, position, number) => {
+ saveTranscendence(id, position, number)
+ }, 500),
+ [props, saveTranscendence]
+ )
+
+ const updateTranscendenceStage = (
+ position: number,
+ stage: number | undefined
+ ) => {
+ const character = appState.grid.characters[position]
+ if (character && stage !== undefined) {
+ character.transcendence_step = stage
+ appState.grid.characters[position] = character
+ }
+ }
+
+ function storePreviousTranscendenceStage(position: number) {
+ // Save the current value in case of an unexpected result
+ let newPreviousValues = { ...previousUncapValues }
+
+ if (grid.characters[position]) {
+ newPreviousValues[position] = grid.characters[position]?.uncap_level
+ setPreviousTranscendenceStages(newPreviousValues)
+ }
+ }
+
function cancelAlert() {
setErrorMessage('')
}
@@ -380,6 +496,7 @@ const CharacterGrid = (props: Props) => {
position={i}
updateObject={receiveCharacterFromSearch}
updateUncap={initiateUncapUpdate}
+ updateTranscendence={initiateTranscendenceUpdate}
removeCharacter={removeCharacter}
/>
diff --git a/components/CharacterUnit/index.tsx b/components/CharacterUnit/index.tsx
index 5f046605..d813b877 100644
--- a/components/CharacterUnit/index.tsx
+++ b/components/CharacterUnit/index.tsx
@@ -40,6 +40,7 @@ interface Props {
removeCharacter: (id: string) => void
updateObject: (object: SearchableObject, position: number) => void
updateUncap: (id: string, position: number, uncap: number) => void
+ updateTranscendence: (id: string, position: number, stage: number) => void
}
const CharacterUnit = ({
@@ -49,6 +50,7 @@ const CharacterUnit = ({
removeCharacter: sendCharacterToRemove,
updateObject,
updateUncap,
+ updateTranscendence,
}: Props) => {
// Translations and locale
const { t } = useTranslation('common')
@@ -156,6 +158,10 @@ const CharacterUnit = ({
if (gridCharacter) updateUncap(gridCharacter.id, position, uncap)
}
+ function passTranscendenceData(stage: number) {
+ if (gridCharacter) updateTranscendence(gridCharacter.id, position, stage)
+ }
+
function removeCharacter() {
if (gridCharacter) sendCharacterToRemove(gridCharacter.id)
}
@@ -169,8 +175,8 @@ const CharacterUnit = ({
// Change the image based on the uncap level
let suffix = '01'
- if (gridCharacter.uncap_level == 6) suffix = '04'
- else if (gridCharacter.uncap_level == 5) suffix = '03'
+ if (gridCharacter.transcendence_step > 0) suffix = '04'
+ else if (gridCharacter.uncap_level >= 5) suffix = '03'
else if (gridCharacter.uncap_level > 2) suffix = '02'
// Special casing for Lyria (and Young Cat eventually)
@@ -280,7 +286,11 @@ const CharacterUnit = ({
}
const image = (
-
+
![{character?.name[locale]}]()
) : (
diff --git a/components/ExtraSummons/index.tsx b/components/ExtraSummons/index.tsx
index ea3f7993..58383ede 100644
--- a/components/ExtraSummons/index.tsx
+++ b/components/ExtraSummons/index.tsx
@@ -14,6 +14,7 @@ interface Props {
removeSummon: (id: string) => void
updateObject: (object: SearchableObject, position: number) => void
updateUncap: (id: string, position: number, uncap: number) => void
+ updateTranscendence: (id: string, position: number, stage: number) => void
}
const ExtraSummons = (props: Props) => {
@@ -36,6 +37,7 @@ const ExtraSummons = (props: Props) => {
gridSummon={props.grid[props.offset + i]}
updateObject={props.updateObject}
updateUncap={props.updateUncap}
+ updateTranscendence={props.updateTranscendence}
/>
)
diff --git a/components/Popover/index.scss b/components/Popover/index.scss
new file mode 100644
index 00000000..d89267b7
--- /dev/null
+++ b/components/Popover/index.scss
@@ -0,0 +1,9 @@
+.Popover {
+ animation: scaleIn $duration-zoom ease-out;
+ background: var(--dialog-bg);
+ border-radius: $card-corner;
+ border: 0.5px solid rgba(0, 0, 0, 0.18);
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.24);
+ transform-origin: var(--radix-popover-content-transform-origin);
+ outline: none;
+}
diff --git a/components/Popover/index.tsx b/components/Popover/index.tsx
new file mode 100644
index 00000000..b38e4985
--- /dev/null
+++ b/components/Popover/index.tsx
@@ -0,0 +1,37 @@
+import React, { PropsWithChildren } from 'react'
+import classnames from 'classnames'
+import * as PopoverPrimitive from '@radix-ui/react-popover'
+
+import './index.scss'
+
+interface Props
+ extends React.DetailedHTMLProps<
+ React.DialogHTMLAttributes
,
+ HTMLDivElement
+ > {}
+
+export const Popover = PopoverPrimitive.Root
+export const PopoverAnchor = PopoverPrimitive.Anchor
+export const PopoverTrigger = PopoverPrimitive.Trigger
+
+export const PopoverContent = React.forwardRef(
+ ({ children, ...props }: PropsWithChildren, forwardedRef) => {
+ const classes = classnames(props.className, {
+ Popover: true,
+ })
+
+ return (
+
+
+ {children}
+
+
+
+ )
+ }
+)
diff --git a/components/SummonGrid/index.tsx b/components/SummonGrid/index.tsx
index 0bf300e9..0da1200e 100644
--- a/components/SummonGrid/index.tsx
+++ b/components/SummonGrid/index.tsx
@@ -42,10 +42,14 @@ const SummonGrid = (props: Props) => {
const { party, grid } = useSnapshot(appState)
const [slug, setSlug] = useState()
- // Create a temporary state to store previous weapon uncap value
+ // Create a temporary state to store previous weapon uncap values and transcendence stages
const [previousUncapValues, setPreviousUncapValues] = useState<{
[key: number]: number
}>({})
+ const [previousTranscendenceStages, setPreviousTranscendenceStages] =
+ useState<{
+ [key: number]: number
+ }>({})
// Set the editable flag only on first load
useEffect(() => {
@@ -155,6 +159,7 @@ const SummonGrid = (props: Props) => {
// Note: Saves, but debouncing is not working properly
async function saveUncap(id: string, position: number, uncapLevel: number) {
storePreviousUncapValue(position)
+ storePreviousTranscendenceStage(position)
try {
if (uncapLevel != previousUncapValues[position])
@@ -166,11 +171,17 @@ const SummonGrid = (props: Props) => {
// Revert optimistic UI
updateUncapLevel(position, previousUncapValues[position])
+ updateTranscendenceStage(position, previousTranscendenceStages[position])
// Remove optimistic key
- let newPreviousValues = { ...previousUncapValues }
- delete newPreviousValues[position]
- setPreviousUncapValues(newPreviousValues)
+ let newPreviousTranscendenceStages = { ...previousTranscendenceStages }
+ let newPreviousUncapValues = { ...previousUncapValues }
+
+ delete newPreviousTranscendenceStages[position]
+ delete newPreviousUncapValues[position]
+
+ setPreviousTranscendenceStages(newPreviousTranscendenceStages)
+ setPreviousUncapValues(newPreviousUncapValues)
}
}
@@ -184,21 +195,25 @@ const SummonGrid = (props: Props) => {
accountState.account.user &&
party.user.id === accountState.account.user.id
) {
- memoizeAction(id, position, uncapLevel)
+ memoizeUncapAction(id, position, uncapLevel)
// Optimistically update UI
updateUncapLevel(position, uncapLevel)
+
+ if (uncapLevel < 6) {
+ updateTranscendenceStage(position, 0)
+ }
}
}
- const memoizeAction = useCallback(
+ const memoizeUncapAction = useCallback(
(id: string, position: number, uncapLevel: number) => {
- debouncedAction(id, position, uncapLevel)
+ debouncedUncapAction(id, position, uncapLevel)
},
[props, previousUncapValues]
)
- const debouncedAction = useMemo(
+ const debouncedUncapAction = useMemo(
() =>
debounce((id, position, number) => {
saveUncap(id, position, number)
@@ -237,6 +252,116 @@ const SummonGrid = (props: Props) => {
setPreviousUncapValues(newPreviousValues)
}
+ // Methods: Updating transcendence stage
+ // Note: Saves, but debouncing is not working properly
+ async function saveTranscendence(
+ id: string,
+ position: number,
+ stage: number
+ ) {
+ storePreviousUncapValue(position)
+ storePreviousTranscendenceStage(position)
+
+ const payload = {
+ summon: {
+ uncap_level: stage > 0 ? 6 : 5,
+ transcendence_step: stage,
+ },
+ }
+
+ try {
+ if (stage != previousTranscendenceStages[position])
+ await api.endpoints.grid_summons
+ .update(id, payload)
+ .then((response) => {
+ storeGridSummon(response.data.grid_summon)
+ })
+ } catch (error) {
+ console.error(error)
+
+ // Revert optimistic UI
+ updateUncapLevel(position, previousUncapValues[position])
+ updateTranscendenceStage(position, previousTranscendenceStages[position])
+
+ // Remove optimistic key
+ let newPreviousTranscendenceStages = { ...previousTranscendenceStages }
+ let newPreviousUncapValues = { ...previousUncapValues }
+
+ delete newPreviousTranscendenceStages[position]
+ delete newPreviousUncapValues[position]
+
+ setPreviousTranscendenceStages(newPreviousTranscendenceStages)
+ setPreviousUncapValues(newPreviousUncapValues)
+ }
+ }
+
+ function initiateTranscendenceUpdate(
+ id: string,
+ position: number,
+ stage: number
+ ) {
+ if (
+ party.user &&
+ accountState.account.user &&
+ party.user.id === accountState.account.user.id
+ ) {
+ memoizeTranscendenceAction(id, position, stage)
+
+ // Optimistically update UI
+ updateTranscendenceStage(position, stage)
+
+ if (stage > 0) {
+ updateUncapLevel(position, 6)
+ }
+ }
+ }
+
+ const memoizeTranscendenceAction = useCallback(
+ (id: string, position: number, stage: number) => {
+ debouncedTranscendenceAction(id, position, stage)
+ },
+ [props, previousTranscendenceStages]
+ )
+
+ const debouncedTranscendenceAction = useMemo(
+ () =>
+ debounce((id, position, number) => {
+ saveTranscendence(id, position, number)
+ }, 500),
+ [props, saveTranscendence]
+ )
+
+ const updateTranscendenceStage = (position: number, stage: number) => {
+ if (appState.grid.summons.mainSummon && position == -1)
+ appState.grid.summons.mainSummon.transcendence_step = stage
+ else if (appState.grid.summons.friendSummon && position == 6)
+ appState.grid.summons.friendSummon.transcendence_step = stage
+ else {
+ const summon = appState.grid.summons.allSummons[position]
+ if (summon) {
+ summon.transcendence_step = stage
+ appState.grid.summons.allSummons[position] = summon
+ }
+ }
+ }
+
+ function storePreviousTranscendenceStage(position: number) {
+ // Save the current value in case of an unexpected result
+ let newPreviousValues = { ...previousUncapValues }
+
+ if (appState.grid.summons.mainSummon && position == -1)
+ newPreviousValues[position] = appState.grid.summons.mainSummon.uncap_level
+ else if (appState.grid.summons.friendSummon && position == 6)
+ newPreviousValues[position] =
+ appState.grid.summons.friendSummon.uncap_level
+ else {
+ const summon = appState.grid.summons.allSummons[position]
+ newPreviousValues[position] = summon ? summon.uncap_level : 0
+ }
+
+ setPreviousUncapValues(newPreviousValues)
+ }
+
async function removeSummon(id: string) {
try {
const response = await api.endpoints.grid_summons.destroy({ id: id })
@@ -267,6 +392,7 @@ const SummonGrid = (props: Props) => {
removeSummon={removeSummon}
updateObject={receiveSummonFromSearch}
updateUncap={initiateUncapUpdate}
+ updateTranscendence={initiateTranscendenceUpdate}
/>
)
@@ -280,11 +406,14 @@ const SummonGrid = (props: Props) => {
key="grid_friend_summon"
position={6}
unitType={2}
+ removeSummon={removeSummon}
updateObject={receiveSummonFromSearch}
updateUncap={initiateUncapUpdate}
+ updateTranscendence={initiateTranscendenceUpdate}
/>
)
+
const summonGridElement = (
{t('summons.summons')}
@@ -300,6 +429,7 @@ const SummonGrid = (props: Props) => {
removeSummon={removeSummon}
updateObject={receiveSummonFromSearch}
updateUncap={initiateUncapUpdate}
+ updateTranscendence={initiateTranscendenceUpdate}
/>
)
@@ -307,6 +437,7 @@ const SummonGrid = (props: Props) => {
)
+
const subAuraSummonElement = (
{
removeSummon={removeSummon}
updateObject={receiveSummonFromSearch}
updateUncap={initiateUncapUpdate}
+ updateTranscendence={initiateTranscendenceUpdate}
/>
)
+
return (
diff --git a/components/SummonUnit/index.tsx b/components/SummonUnit/index.tsx
index 48847211..9da0b566 100644
--- a/components/SummonUnit/index.tsx
+++ b/components/SummonUnit/index.tsx
@@ -29,6 +29,7 @@ interface Props {
removeSummon: (id: string) => void
updateObject: (object: SearchableObject, position: number) => void
updateUncap: (id: string, position: number, uncap: number) => void
+ updateTranscendence: (id: string, position: number, stage: number) => void
}
const SummonUnit = ({
@@ -39,6 +40,7 @@ const SummonUnit = ({
removeSummon: sendSummonToRemove,
updateObject,
updateUncap,
+ updateTranscendence,
}: Props) => {
// Translations and locale
const { t } = useTranslation('common')
@@ -105,6 +107,10 @@ const SummonUnit = ({
if (gridSummon) updateUncap(gridSummon.id, position, uncap)
}
+ function passTranscendenceData(stage: number) {
+ if (gridSummon) updateTranscendence(gridSummon.id, position, stage)
+ }
+
function removeSummon() {
if (gridSummon) sendSummonToRemove(gridSummon.id)
}
@@ -133,11 +139,14 @@ const SummonUnit = ({
]
let suffix = ''
- if (
+ if (gridSummon.object.uncap.xlb && gridSummon.uncap_level == 6) {
+ suffix = '_03'
+ } else if (
upgradedSummons.indexOf(summon.granblue_id.toString()) != -1 &&
gridSummon.uncap_level == 5
- )
+ ) {
suffix = '_02'
+ }
// Generate the correct source for the summon
if (unitType == 0 || unitType == 2)
@@ -243,8 +252,13 @@ const SummonUnit = ({
type="summon"
ulb={gridSummon.object.uncap.ulb || false}
flb={gridSummon.object.uncap.flb || false}
+ xlb={gridSummon.object.uncap.xlb || false}
+ editable={editable}
uncapLevel={gridSummon.uncap_level}
+ transcendenceStage={gridSummon.transcendence_step}
+ position={gridSummon.position}
updateUncap={passUncapData}
+ updateTranscendence={passTranscendenceData}
special={false}
/>
) : (
diff --git a/components/TranscendenceFragment/index.scss b/components/TranscendenceFragment/index.scss
new file mode 100644
index 00000000..bab27223
--- /dev/null
+++ b/components/TranscendenceFragment/index.scss
@@ -0,0 +1,83 @@
+.Fragment {
+ $degrees: 72deg;
+
+ $origWidth: 29px;
+ $origHeight: 54px;
+
+ $scaledWidth: 12px;
+ $scaledHeight: calc(($scaledWidth / $origWidth) * $origHeight);
+
+ $scale: 1.2;
+
+ @include hidpiImage(
+ '/icons/transcendence/interactive/interactive-piece',
+ png,
+ $scaledWidth,
+ $scaledHeight
+ );
+
+ position: absolute;
+ z-index: 32;
+
+ aspect-ratio: 29 / 54;
+ height: $scaledHeight;
+ width: $scaledWidth;
+ opacity: 0;
+
+ &:hover {
+ cursor: pointer;
+ }
+
+ &.Visible {
+ opacity: 1;
+ }
+
+ &.Stage1 {
+ top: 3px;
+ left: 18px;
+
+ // &:hover {
+ // transform: scale($scale, $scale) translateY(-2px);
+ // }
+ }
+
+ &.Stage2 {
+ top: 10px;
+ left: 27px;
+ transform: rotate($degrees);
+
+ // &:hover {
+ // transform: rotate($degrees) scale($scale, $scale) translateY(-2px);
+ // }
+ }
+
+ &.Stage3 {
+ top: 21px;
+ left: 24px;
+ transform: rotate($degrees * 2);
+
+ // &:hover {
+ // transform: rotate($degrees * 2) scale($scale, $scale) translateY(-1px);
+ // }
+ }
+
+ &.Stage4 {
+ top: 21px;
+ left: 12px;
+ transform: rotate($degrees * 3);
+
+ // &:hover {
+ // transform: rotate($degrees * 3) scale($scale, $scale) translateY(-1px);
+ // }
+ }
+
+ &.Stage5 {
+ top: 10px;
+ left: 8px;
+ transform: rotate($degrees * 4);
+
+ // &:hover {
+ // transform: rotate($degrees * 4) scale($scale, $scale) translateY(-1px);
+ // }
+ }
+}
diff --git a/components/TranscendenceFragment/index.tsx b/components/TranscendenceFragment/index.tsx
new file mode 100644
index 00000000..0daaef18
--- /dev/null
+++ b/components/TranscendenceFragment/index.tsx
@@ -0,0 +1,49 @@
+import React from 'react'
+import classnames from 'classnames'
+
+import './index.scss'
+
+interface Props {
+ stage: number
+ interactive: boolean
+ visible: boolean
+ onClick?: (index: number) => void
+ onHover?: (index: number) => void
+}
+
+const TranscendenceFragment = ({
+ interactive,
+ stage,
+ visible,
+ onClick,
+ onHover,
+}: Props) => {
+ const classes = classnames({
+ Fragment: true,
+ Visible: visible,
+ Stage1: stage === 1,
+ Stage2: stage === 2,
+ Stage3: stage === 3,
+ Stage4: stage === 4,
+ Stage5: stage === 5,
+ })
+
+ function handleClick() {
+ if (interactive && onClick) onClick(stage)
+ }
+
+ function handleHover() {
+ if (interactive && onHover) onHover(stage)
+ }
+
+ return (
+
+ )
+}
+
+TranscendenceFragment.defaultProps = {
+ interactive: false,
+ visible: false,
+}
+
+export default TranscendenceFragment
diff --git a/components/TranscendencePopover/index.scss b/components/TranscendencePopover/index.scss
new file mode 100644
index 00000000..e54cec64
--- /dev/null
+++ b/components/TranscendencePopover/index.scss
@@ -0,0 +1,25 @@
+.Transcendence.Popover {
+ align-items: center;
+ flex-direction: column;
+ gap: $unit-half;
+ display: flex;
+ width: $unit-10x;
+ height: $unit-10x;
+ padding: $unit;
+ justify-content: center;
+ z-index: 32;
+
+ &.open {
+ opacity: 1;
+ display: flex;
+ }
+
+ h4 {
+ font-size: $font-small;
+ font-weight: $medium;
+ }
+
+ .Pending {
+ color: $yellow;
+ }
+}
diff --git a/components/TranscendencePopover/index.tsx b/components/TranscendencePopover/index.tsx
new file mode 100644
index 00000000..4284db55
--- /dev/null
+++ b/components/TranscendencePopover/index.tsx
@@ -0,0 +1,87 @@
+import React, { PropsWithChildren, useEffect, useState } from 'react'
+import { useTranslation } from 'next-i18next'
+import classNames from 'classnames'
+
+import { Popover, PopoverAnchor, PopoverContent } from '~components/Popover'
+import TranscendenceStar from '~components/TranscendenceStar'
+
+import './index.scss'
+
+interface Props
+ extends React.DetailedHTMLProps<
+ React.DialogHTMLAttributes
,
+ HTMLDivElement
+ > {
+ open: boolean
+ stage: number
+ onOpenChange?: (open: boolean) => void
+ sendValue?: (stage: number) => void
+}
+
+const TranscendencePopover = ({
+ children,
+ open: popoverOpen,
+ stage,
+ tabIndex,
+ onOpenChange,
+ sendValue,
+}: PropsWithChildren) => {
+ const { t } = useTranslation('common')
+
+ const [open, setOpen] = useState(false)
+
+ const [currentStage, setCurrentStage] = useState(0)
+
+ const popoverRef = React.createRef()
+
+ const classes = classNames({
+ Transcendence: true,
+ })
+
+ const levelClasses = classNames({
+ Pending: stage != currentStage,
+ })
+
+ useEffect(() => {
+ if (open) popoverRef.current?.focus()
+ }, [])
+
+ useEffect(() => {
+ setCurrentStage(stage)
+ }, [stage])
+
+ useEffect(() => {
+ setOpen(popoverOpen)
+ }, [popoverOpen])
+
+ function handleFragmentClicked(newStage: number) {
+ setCurrentStage(newStage)
+ if (sendValue) sendValue(newStage)
+ }
+
+ function handleFragmentHovered(newStage: number) {
+ setCurrentStage(newStage)
+ }
+
+ return (
+
+ {children}
+
+
+
+ {t('level')}
+ {200 + 10 * currentStage}
+
+
+
+ )
+}
+
+export default TranscendencePopover
diff --git a/components/TranscendenceStar/index.scss b/components/TranscendenceStar/index.scss
new file mode 100644
index 00000000..ed84a405
--- /dev/null
+++ b/components/TranscendenceStar/index.scss
@@ -0,0 +1,108 @@
+.TranscendenceStar {
+ $size: 18px;
+ position: relative;
+
+ &:hover {
+ transform: scale(1.2);
+ }
+
+ &.Immutable {
+ pointer-events: none;
+ }
+
+ &.Empty {
+ @include hidpiImage('/icons/transcendence/0/stage-0', png, $size, $size);
+ }
+
+ &.Stage1 {
+ @include hidpiImage('/icons/transcendence/1/stage-1', png, $size, $size);
+ }
+
+ &.Stage2 {
+ @include hidpiImage('/icons/transcendence/2/stage-2', png, $size, $size);
+ }
+
+ &.Stage3 {
+ @include hidpiImage('/icons/transcendence/3/stage-3', png, $size, $size);
+ }
+
+ &.Stage4 {
+ @include hidpiImage('/icons/transcendence/4/stage-4', png, $size, $size);
+ }
+
+ &.Stage5 {
+ @include hidpiImage('/icons/transcendence/5/stage-5', png, $size, $size);
+ }
+
+ .Figure {
+ $size: 18px;
+ background-repeat: no-repeat;
+ background-size: 54px 54px;
+ display: block;
+ height: $size;
+ width: $size;
+
+ &.Interactive.Base {
+ $size: $unit-6x;
+
+ @include hidpiImage(
+ '/icons/transcendence/interactive/interactive-base',
+ png,
+ $size,
+ $size
+ );
+
+ height: $size;
+ width: $size;
+
+ &:hover {
+ cursor: pointer;
+ transform: none;
+ }
+ }
+
+ &:hover {
+ transform: scale(1.2);
+ }
+
+ &.Stage1 {
+ background-image: url('/icons/transcendence/1/step-1@3x.png');
+
+ &:hover {
+ background-image: url('/icons/transcendence/1/step-1-hover@3x.png');
+ }
+ }
+
+ &.Stage2 {
+ background-image: url('/icons/transcendence/2/step-2@3x.png');
+
+ &:hover {
+ background-image: url('/icons/transcendence/2/step-2-hover@3x.png');
+ }
+ }
+
+ &.Stage3 {
+ background-image: url('/icons/transcendence/3/step-3@3x.png');
+
+ &:hover {
+ background-image: url('/icons/transcendence/3/step-3-hover@3x.png');
+ }
+ }
+
+ &.Stage4 {
+ background-image: url('/icons/transcendence/4/step-4@3x.png');
+
+ &:hover {
+ background-image: url('/icons/transcendence/4/step-4-hover@3x.png');
+ }
+ }
+
+ &.Stage5 {
+ background-image: url('/icons/transcendence/5/step-5@3x.png');
+
+ &:hover {
+ background-image: url('/icons/transcendence/5/step-5-hover@3x.png');
+ }
+ }
+ }
+}
diff --git a/components/TranscendenceStar/index.tsx b/components/TranscendenceStar/index.tsx
new file mode 100644
index 00000000..161be68b
--- /dev/null
+++ b/components/TranscendenceStar/index.tsx
@@ -0,0 +1,118 @@
+import React, { useEffect, useState } from 'react'
+import classnames from 'classnames'
+
+import TranscendenceFragment from '~components/TranscendenceFragment'
+import './index.scss'
+
+interface Props
+ extends React.DetailedHTMLProps<
+ React.DialogHTMLAttributes,
+ HTMLDivElement
+ > {
+ className?: string
+ stage: number
+ editable: boolean
+ interactive: boolean
+ onStarClick?: () => void
+ onFragmentClick?: (newStage: number) => void
+ onFragmentHover?: (newStage: number) => void
+}
+
+const NUM_FRAGMENTS = 5
+
+const TranscendenceStar = ({
+ className,
+ interactive,
+ stage,
+ editable,
+ tabIndex,
+ onStarClick,
+ onFragmentClick,
+ onFragmentHover,
+}: Props) => {
+ const [visibleStage, setVisibleStage] = useState(0)
+ const [currentStage, setCurrentStage] = useState(0)
+ const [immutable, setImmutable] = useState(false)
+
+ // Classes
+ const starClasses = classnames({
+ TranscendenceStar: true,
+ Immutable: immutable,
+ Empty: stage === 0,
+ Stage1: stage === 1,
+ Stage2: stage === 2,
+ Stage3: stage === 3,
+ Stage4: stage === 4,
+ Stage5: stage === 5,
+ })
+
+ const baseImageClasses = classnames(className, {
+ Figure: true,
+ })
+
+ useEffect(() => {
+ setVisibleStage(stage)
+ setCurrentStage(stage)
+ }, [stage])
+
+ function handleClick() {
+ if (onStarClick) {
+ onStarClick()
+ }
+ }
+
+ function handleFragmentClick(index: number) {
+ let newStage = index
+ if (index === currentStage) newStage = 0
+
+ setVisibleStage(newStage)
+ setCurrentStage(newStage)
+ if (onFragmentClick) onFragmentClick(newStage)
+ }
+
+ function handleFragmentHover(index: number) {
+ setVisibleStage(index)
+ if (onFragmentHover) onFragmentHover(index)
+ }
+
+ function handleMouseLeave() {
+ setVisibleStage(currentStage)
+ if (onFragmentHover) onFragmentHover(currentStage)
+ }
+
+ return (
+ {}}
+ onMouseLeave={interactive ? handleMouseLeave : () => {}}
+ tabIndex={tabIndex}
+ >
+
+ {[...Array(NUM_FRAGMENTS)].map((e, i) => {
+ const loopStage = i + 1
+ return interactive ? (
+
+ ) : (
+ ''
+ )
+ })}
+
+
+
+ )
+}
+
+TranscendenceStar.defaultProps = {
+ stage: 0,
+ editable: false,
+ interactive: false,
+}
+
+export default TranscendenceStar
diff --git a/components/UncapIndicator/index.scss b/components/UncapIndicator/index.scss
index 5b174197..42e1dcd8 100644
--- a/components/UncapIndicator/index.scss
+++ b/components/UncapIndicator/index.scss
@@ -1,3 +1,7 @@
+.Uncap {
+ position: relative;
+}
+
.UncapIndicator {
display: flex;
flex-direction: row;
diff --git a/components/UncapIndicator/index.tsx b/components/UncapIndicator/index.tsx
index b8ab47ec..647a0a7d 100644
--- a/components/UncapIndicator/index.tsx
+++ b/components/UncapIndicator/index.tsx
@@ -1,5 +1,7 @@
-import React from 'react'
+import React, { useState } from 'react'
import UncapStar from '~components/UncapStar'
+import TranscendencePopover from '~components/TranscendencePopover'
+import TranscendenceStar from '~components/TranscendenceStar'
import './index.scss'
@@ -7,14 +9,22 @@ interface Props {
type: 'character' | 'weapon' | 'summon'
rarity?: number
uncapLevel?: number
+ position: number
+ transcendenceStage?: number
+ editable: boolean
flb: boolean
ulb: boolean
+ xlb?: boolean
special: boolean
updateUncap?: (index: number) => void
+ updateTranscendence?: (index: number) => void
}
const UncapIndicator = (props: Props) => {
const numStars = setNumStars()
+
+ const [popoverOpen, setPopoverOpen] = useState(false)
+
function setNumStars() {
let numStars
@@ -37,7 +47,9 @@ const UncapIndicator = (props: Props) => {
}
}
} else {
- if (props.ulb) {
+ if (props.xlb) {
+ numStars = 6
+ } else if (props.ulb) {
numStars = 5
} else if (props.flb) {
numStars = 4
@@ -56,14 +68,41 @@ const UncapIndicator = (props: Props) => {
}
}
+ function togglePopover(open: boolean) {
+ setPopoverOpen(open)
+ }
+
+ function sendTranscendenceStage(stage: number) {
+ if (props.updateTranscendence) props.updateTranscendence(stage)
+ togglePopover(false)
+ }
+
const transcendence = (i: number) => {
- return (
- = props.uncapLevel : false}
+ const tabIndex = props.position * 7 + i + 1
+ return props.type === 'character' || props.type === 'summon' ? (
+
+ togglePopover(true)}
+ />
+
+ ) : (
+
)
}
@@ -76,7 +115,8 @@ const UncapIndicator = (props: Props) => {
empty={props.uncapLevel != null ? i >= props.uncapLevel : false}
key={`star_${i}`}
index={i}
- onClick={toggleStar}
+ onStarClick={toggleStar}
+ tabIndex={props.position * 7 + i + 1}
/>
)
}
@@ -89,7 +129,8 @@ const UncapIndicator = (props: Props) => {
empty={props.uncapLevel != null ? i >= props.uncapLevel : false}
key={`star_${i}`}
index={i}
- onClick={toggleStar}
+ onStarClick={toggleStar}
+ tabIndex={props.position * 7 + i + 1}
/>
)
}
@@ -101,29 +142,38 @@ const UncapIndicator = (props: Props) => {
empty={props.uncapLevel != null ? i >= props.uncapLevel : false}
key={`star_${i}`}
index={i}
- onClick={toggleStar}
+ onStarClick={toggleStar}
+ tabIndex={props.position * 7 + i + 1}
/>
)
}
return (
-
- {Array.from(Array(numStars)).map((x, i) => {
- if (props.type === 'character' && i > 4) {
- if (props.special) return ulb(i)
- else return transcendence(i)
- } else if (
- (props.special && props.type === 'character' && i == 3) ||
- (props.type === 'character' && i == 4) ||
- (props.type !== 'character' && i > 2)
- ) {
- return flb(i)
- } else {
- return mlb(i)
- }
- })}
-
+
+
+ {Array.from(Array(numStars)).map((x, i) => {
+ if (props.type === 'character' && i > 4) {
+ if (props.special) return ulb(i)
+ else return transcendence(i)
+ } else if (props.type === 'summon' && i > 4) {
+ return transcendence(i)
+ } else if (
+ (props.special && props.type === 'character' && i == 3) ||
+ (props.type === 'character' && i == 4) ||
+ (props.type !== 'character' && i > 2)
+ ) {
+ return flb(i)
+ } else {
+ return mlb(i)
+ }
+ })}
+
+
)
}
+UncapIndicator.defaultProps = {
+ editable: false,
+}
+
export default UncapIndicator
diff --git a/components/UncapStar/index.tsx b/components/UncapStar/index.tsx
index 9b0969f7..5393a494 100644
--- a/components/UncapStar/index.tsx
+++ b/components/UncapStar/index.tsx
@@ -3,13 +3,17 @@ import classnames from 'classnames'
import './index.scss'
-interface Props {
+interface Props
+ extends React.DetailedHTMLProps<
+ React.DialogHTMLAttributes,
+ HTMLDivElement
+ > {
empty: boolean
special: boolean
flb: boolean
ulb: boolean
index: number
- onClick: (index: number, empty: boolean) => void
+ onStarClick: (index: number, empty: boolean) => void
}
const UncapStar = (props: Props) => {
@@ -23,10 +27,12 @@ const UncapStar = (props: Props) => {
})
function clicked() {
- props.onClick(props.index, props.empty)
+ props.onStarClick(props.index, props.empty)
}
- return
+ return (
+
+ )
}
UncapStar.defaultProps = {
diff --git a/components/WeaponUnit/index.tsx b/components/WeaponUnit/index.tsx
index 978c7710..8149f6a6 100644
--- a/components/WeaponUnit/index.tsx
+++ b/components/WeaponUnit/index.tsx
@@ -548,6 +548,7 @@ const WeaponUnit = ({
ulb={gridWeapon.object.uncap.ulb || false}
flb={gridWeapon.object.uncap.flb || false}
uncapLevel={gridWeapon.uncap_level}
+ position={gridWeapon.position}
updateUncap={passUncapData}
special={false}
/>
diff --git a/package-lock.json b/package-lock.json
index 520ded8a..2f4f81e5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,6 +10,7 @@
"@radix-ui/react-dialog": "^1.0.2",
"@radix-ui/react-dropdown-menu": "^2.0.1",
"@radix-ui/react-hover-card": "^1.0.2",
+ "@radix-ui/react-popover": "^1.0.3",
"@radix-ui/react-select": "^1.1.2",
"@radix-ui/react-switch": "^1.0.1",
"@radix-ui/react-toast": "^1.1.2",
@@ -2325,6 +2326,55 @@
"react-dom": "^16.8 || ^17.0 || ^18.0"
}
},
+ "node_modules/@radix-ui/react-popover": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.0.3.tgz",
+ "integrity": "sha512-YwedSukfWsyJs3/yP3yXUq44k4/JBe3jqU63Z8v2i19qZZ3dsx32oma17ztgclWPNuqp3A+Xa9UiDlZHyVX8Vg==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/primitive": "1.0.0",
+ "@radix-ui/react-compose-refs": "1.0.0",
+ "@radix-ui/react-context": "1.0.0",
+ "@radix-ui/react-dismissable-layer": "1.0.2",
+ "@radix-ui/react-focus-guards": "1.0.0",
+ "@radix-ui/react-focus-scope": "1.0.1",
+ "@radix-ui/react-id": "1.0.0",
+ "@radix-ui/react-popper": "1.1.0",
+ "@radix-ui/react-portal": "1.0.1",
+ "@radix-ui/react-presence": "1.0.0",
+ "@radix-ui/react-primitive": "1.0.1",
+ "@radix-ui/react-slot": "1.0.1",
+ "@radix-ui/react-use-controllable-state": "1.0.0",
+ "aria-hidden": "^1.1.1",
+ "react-remove-scroll": "2.5.5"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0",
+ "react-dom": "^16.8 || ^17.0 || ^18.0"
+ }
+ },
+ "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-popper": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.1.0.tgz",
+ "integrity": "sha512-07U7jpI0dZcLRAxT7L9qs6HecSoPhDSJybF7mEGHJDBDv+ZoGCvIlva0s+WxMXwJEav+ckX3hAlXBtnHmuvlCQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.13.10",
+ "@floating-ui/react-dom": "0.7.2",
+ "@radix-ui/react-arrow": "1.0.1",
+ "@radix-ui/react-compose-refs": "1.0.0",
+ "@radix-ui/react-context": "1.0.0",
+ "@radix-ui/react-primitive": "1.0.1",
+ "@radix-ui/react-use-callback-ref": "1.0.0",
+ "@radix-ui/react-use-layout-effect": "1.0.0",
+ "@radix-ui/react-use-rect": "1.0.0",
+ "@radix-ui/react-use-size": "1.0.0",
+ "@radix-ui/rect": "1.0.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0",
+ "react-dom": "^16.8 || ^17.0 || ^18.0"
+ }
+ },
"node_modules/@radix-ui/react-popper": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.0.1.tgz",
@@ -8809,6 +8859,49 @@
"react-remove-scroll": "2.5.5"
}
},
+ "@radix-ui/react-popover": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.0.3.tgz",
+ "integrity": "sha512-YwedSukfWsyJs3/yP3yXUq44k4/JBe3jqU63Z8v2i19qZZ3dsx32oma17ztgclWPNuqp3A+Xa9UiDlZHyVX8Vg==",
+ "requires": {
+ "@babel/runtime": "^7.13.10",
+ "@radix-ui/primitive": "1.0.0",
+ "@radix-ui/react-compose-refs": "1.0.0",
+ "@radix-ui/react-context": "1.0.0",
+ "@radix-ui/react-dismissable-layer": "1.0.2",
+ "@radix-ui/react-focus-guards": "1.0.0",
+ "@radix-ui/react-focus-scope": "1.0.1",
+ "@radix-ui/react-id": "1.0.0",
+ "@radix-ui/react-popper": "1.1.0",
+ "@radix-ui/react-portal": "1.0.1",
+ "@radix-ui/react-presence": "1.0.0",
+ "@radix-ui/react-primitive": "1.0.1",
+ "@radix-ui/react-slot": "1.0.1",
+ "@radix-ui/react-use-controllable-state": "1.0.0",
+ "aria-hidden": "^1.1.1",
+ "react-remove-scroll": "2.5.5"
+ },
+ "dependencies": {
+ "@radix-ui/react-popper": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.1.0.tgz",
+ "integrity": "sha512-07U7jpI0dZcLRAxT7L9qs6HecSoPhDSJybF7mEGHJDBDv+ZoGCvIlva0s+WxMXwJEav+ckX3hAlXBtnHmuvlCQ==",
+ "requires": {
+ "@babel/runtime": "^7.13.10",
+ "@floating-ui/react-dom": "0.7.2",
+ "@radix-ui/react-arrow": "1.0.1",
+ "@radix-ui/react-compose-refs": "1.0.0",
+ "@radix-ui/react-context": "1.0.0",
+ "@radix-ui/react-primitive": "1.0.1",
+ "@radix-ui/react-use-callback-ref": "1.0.0",
+ "@radix-ui/react-use-layout-effect": "1.0.0",
+ "@radix-ui/react-use-rect": "1.0.0",
+ "@radix-ui/react-use-size": "1.0.0",
+ "@radix-ui/rect": "1.0.0"
+ }
+ }
+ }
+ },
"@radix-ui/react-popper": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.0.1.tgz",
diff --git a/package.json b/package.json
index 4771dae9..0d880a01 100644
--- a/package.json
+++ b/package.json
@@ -15,6 +15,7 @@
"@radix-ui/react-dialog": "^1.0.2",
"@radix-ui/react-dropdown-menu": "^2.0.1",
"@radix-ui/react-hover-card": "^1.0.2",
+ "@radix-ui/react-popover": "^1.0.3",
"@radix-ui/react-select": "^1.1.2",
"@radix-ui/react-switch": "^1.0.1",
"@radix-ui/react-toast": "^1.1.2",
diff --git a/public/icons/transcendence/0/stage-0.png b/public/icons/transcendence/0/stage-0.png
new file mode 100644
index 00000000..96795401
Binary files /dev/null and b/public/icons/transcendence/0/stage-0.png differ
diff --git a/public/icons/transcendence/0/stage-0@2x.png b/public/icons/transcendence/0/stage-0@2x.png
new file mode 100644
index 00000000..db9a33bf
Binary files /dev/null and b/public/icons/transcendence/0/stage-0@2x.png differ
diff --git a/public/icons/transcendence/0/stage-0@3x.png b/public/icons/transcendence/0/stage-0@3x.png
new file mode 100644
index 00000000..b32db530
Binary files /dev/null and b/public/icons/transcendence/0/stage-0@3x.png differ
diff --git a/public/icons/transcendence/1/stage-1-hover.png b/public/icons/transcendence/1/stage-1-hover.png
new file mode 100644
index 00000000..042e6316
Binary files /dev/null and b/public/icons/transcendence/1/stage-1-hover.png differ
diff --git a/public/icons/transcendence/1/stage-1-hover@2x.png b/public/icons/transcendence/1/stage-1-hover@2x.png
new file mode 100644
index 00000000..b79738e3
Binary files /dev/null and b/public/icons/transcendence/1/stage-1-hover@2x.png differ
diff --git a/public/icons/transcendence/1/stage-1-hover@3x.png b/public/icons/transcendence/1/stage-1-hover@3x.png
new file mode 100644
index 00000000..15764200
Binary files /dev/null and b/public/icons/transcendence/1/stage-1-hover@3x.png differ
diff --git a/public/icons/transcendence/1/stage-1.png b/public/icons/transcendence/1/stage-1.png
new file mode 100644
index 00000000..fe78d181
Binary files /dev/null and b/public/icons/transcendence/1/stage-1.png differ
diff --git a/public/icons/transcendence/1/stage-1@2x.png b/public/icons/transcendence/1/stage-1@2x.png
new file mode 100644
index 00000000..febf6ceb
Binary files /dev/null and b/public/icons/transcendence/1/stage-1@2x.png differ
diff --git a/public/icons/transcendence/1/stage-1@3x.png b/public/icons/transcendence/1/stage-1@3x.png
new file mode 100644
index 00000000..c93a7856
Binary files /dev/null and b/public/icons/transcendence/1/stage-1@3x.png differ
diff --git a/public/icons/transcendence/2/stage-2-hover.png b/public/icons/transcendence/2/stage-2-hover.png
new file mode 100644
index 00000000..e71be7c4
Binary files /dev/null and b/public/icons/transcendence/2/stage-2-hover.png differ
diff --git a/public/icons/transcendence/2/stage-2-hover@2x.png b/public/icons/transcendence/2/stage-2-hover@2x.png
new file mode 100644
index 00000000..f302baf7
Binary files /dev/null and b/public/icons/transcendence/2/stage-2-hover@2x.png differ
diff --git a/public/icons/transcendence/2/stage-2-hover@3x.png b/public/icons/transcendence/2/stage-2-hover@3x.png
new file mode 100644
index 00000000..02a10b51
Binary files /dev/null and b/public/icons/transcendence/2/stage-2-hover@3x.png differ
diff --git a/public/icons/transcendence/2/stage-2.png b/public/icons/transcendence/2/stage-2.png
new file mode 100644
index 00000000..5824a4b0
Binary files /dev/null and b/public/icons/transcendence/2/stage-2.png differ
diff --git a/public/icons/transcendence/2/stage-2@2x.png b/public/icons/transcendence/2/stage-2@2x.png
new file mode 100644
index 00000000..6f023ae8
Binary files /dev/null and b/public/icons/transcendence/2/stage-2@2x.png differ
diff --git a/public/icons/transcendence/2/stage-2@3x.png b/public/icons/transcendence/2/stage-2@3x.png
new file mode 100644
index 00000000..14826056
Binary files /dev/null and b/public/icons/transcendence/2/stage-2@3x.png differ
diff --git a/public/icons/transcendence/3/stage-3-hover.png b/public/icons/transcendence/3/stage-3-hover.png
new file mode 100644
index 00000000..53e2c47d
Binary files /dev/null and b/public/icons/transcendence/3/stage-3-hover.png differ
diff --git a/public/icons/transcendence/3/stage-3-hover@2x.png b/public/icons/transcendence/3/stage-3-hover@2x.png
new file mode 100644
index 00000000..84d43cb0
Binary files /dev/null and b/public/icons/transcendence/3/stage-3-hover@2x.png differ
diff --git a/public/icons/transcendence/3/stage-3-hover@3x.png b/public/icons/transcendence/3/stage-3-hover@3x.png
new file mode 100644
index 00000000..165ba6be
Binary files /dev/null and b/public/icons/transcendence/3/stage-3-hover@3x.png differ
diff --git a/public/icons/transcendence/3/stage-3.png b/public/icons/transcendence/3/stage-3.png
new file mode 100644
index 00000000..cd840c42
Binary files /dev/null and b/public/icons/transcendence/3/stage-3.png differ
diff --git a/public/icons/transcendence/3/stage-3@2x.png b/public/icons/transcendence/3/stage-3@2x.png
new file mode 100644
index 00000000..ee2f52a0
Binary files /dev/null and b/public/icons/transcendence/3/stage-3@2x.png differ
diff --git a/public/icons/transcendence/3/stage-3@3x.png b/public/icons/transcendence/3/stage-3@3x.png
new file mode 100644
index 00000000..221e86b0
Binary files /dev/null and b/public/icons/transcendence/3/stage-3@3x.png differ
diff --git a/public/icons/transcendence/4/stage-4-hover.png b/public/icons/transcendence/4/stage-4-hover.png
new file mode 100644
index 00000000..e304b9e8
Binary files /dev/null and b/public/icons/transcendence/4/stage-4-hover.png differ
diff --git a/public/icons/transcendence/4/stage-4-hover@2x.png b/public/icons/transcendence/4/stage-4-hover@2x.png
new file mode 100644
index 00000000..13701c76
Binary files /dev/null and b/public/icons/transcendence/4/stage-4-hover@2x.png differ
diff --git a/public/icons/transcendence/4/stage-4-hover@3x.png b/public/icons/transcendence/4/stage-4-hover@3x.png
new file mode 100644
index 00000000..e3bc9599
Binary files /dev/null and b/public/icons/transcendence/4/stage-4-hover@3x.png differ
diff --git a/public/icons/transcendence/4/stage-4.png b/public/icons/transcendence/4/stage-4.png
new file mode 100644
index 00000000..5e65155e
Binary files /dev/null and b/public/icons/transcendence/4/stage-4.png differ
diff --git a/public/icons/transcendence/4/stage-4@2x.png b/public/icons/transcendence/4/stage-4@2x.png
new file mode 100644
index 00000000..b5b726b4
Binary files /dev/null and b/public/icons/transcendence/4/stage-4@2x.png differ
diff --git a/public/icons/transcendence/4/stage-4@3x.png b/public/icons/transcendence/4/stage-4@3x.png
new file mode 100644
index 00000000..142753ee
Binary files /dev/null and b/public/icons/transcendence/4/stage-4@3x.png differ
diff --git a/public/icons/transcendence/5/stage-5-hover.png b/public/icons/transcendence/5/stage-5-hover.png
new file mode 100644
index 00000000..01adb1b1
Binary files /dev/null and b/public/icons/transcendence/5/stage-5-hover.png differ
diff --git a/public/icons/transcendence/5/stage-5-hover@2x.png b/public/icons/transcendence/5/stage-5-hover@2x.png
new file mode 100644
index 00000000..2026dd64
Binary files /dev/null and b/public/icons/transcendence/5/stage-5-hover@2x.png differ
diff --git a/public/icons/transcendence/5/stage-5-hover@3x.png b/public/icons/transcendence/5/stage-5-hover@3x.png
new file mode 100644
index 00000000..d5b644ac
Binary files /dev/null and b/public/icons/transcendence/5/stage-5-hover@3x.png differ
diff --git a/public/icons/transcendence/5/stage-5.png b/public/icons/transcendence/5/stage-5.png
new file mode 100644
index 00000000..a99ad6fe
Binary files /dev/null and b/public/icons/transcendence/5/stage-5.png differ
diff --git a/public/icons/transcendence/5/stage-5@2x.png b/public/icons/transcendence/5/stage-5@2x.png
new file mode 100644
index 00000000..14551b35
Binary files /dev/null and b/public/icons/transcendence/5/stage-5@2x.png differ
diff --git a/public/icons/transcendence/5/stage-5@3x.png b/public/icons/transcendence/5/stage-5@3x.png
new file mode 100644
index 00000000..64d7a506
Binary files /dev/null and b/public/icons/transcendence/5/stage-5@3x.png differ
diff --git a/public/icons/transcendence/interactive/interactive-base.png b/public/icons/transcendence/interactive/interactive-base.png
new file mode 100644
index 00000000..0fb4ae3c
Binary files /dev/null and b/public/icons/transcendence/interactive/interactive-base.png differ
diff --git a/public/icons/transcendence/interactive/interactive-base@2x.png b/public/icons/transcendence/interactive/interactive-base@2x.png
new file mode 100644
index 00000000..e2aab19b
Binary files /dev/null and b/public/icons/transcendence/interactive/interactive-base@2x.png differ
diff --git a/public/icons/transcendence/interactive/interactive-base@3x.png b/public/icons/transcendence/interactive/interactive-base@3x.png
new file mode 100644
index 00000000..7df350cb
Binary files /dev/null and b/public/icons/transcendence/interactive/interactive-base@3x.png differ
diff --git a/public/icons/transcendence/interactive/interactive-piece.png b/public/icons/transcendence/interactive/interactive-piece.png
new file mode 100644
index 00000000..06d240b2
Binary files /dev/null and b/public/icons/transcendence/interactive/interactive-piece.png differ
diff --git a/public/icons/transcendence/interactive/interactive-piece@2x.png b/public/icons/transcendence/interactive/interactive-piece@2x.png
new file mode 100644
index 00000000..d2585a80
Binary files /dev/null and b/public/icons/transcendence/interactive/interactive-piece@2x.png differ
diff --git a/public/icons/transcendence/interactive/interactive-piece@3x.png b/public/icons/transcendence/interactive/interactive-piece@3x.png
new file mode 100644
index 00000000..d9106def
Binary files /dev/null and b/public/icons/transcendence/interactive/interactive-piece@3x.png differ
diff --git a/public/locales/en/common.json b/public/locales/en/common.json
index 77f417db..e97072a2 100644
--- a/public/locales/en/common.json
+++ b/public/locales/en/common.json
@@ -370,5 +370,6 @@
"no_raid": "No raid",
"no_user": "Anonymous",
"no_job": "No class",
- "no_value": "No value"
+ "no_value": "No value",
+ "level": "Level"
}
diff --git a/public/locales/ja/common.json b/public/locales/ja/common.json
index 14570323..c0a355a6 100644
--- a/public/locales/ja/common.json
+++ b/public/locales/ja/common.json
@@ -371,5 +371,6 @@
"no_raid": "マルチなし",
"no_user": "無名",
"no_job": "ジョブなし",
- "no_value": "値なし"
+ "no_value": "値なし",
+ "level": "レベル"
}
diff --git a/styles/globals.scss b/styles/globals.scss
index b0fb3efa..31f35573 100644
--- a/styles/globals.scss
+++ b/styles/globals.scss
@@ -321,3 +321,38 @@ i.tag {
backdrop-filter: blur(5px) saturate(100%) brightness(80%) opacity(1);
}
}
+
+@keyframes scaleIn {
+ 0% {
+ opacity: 0;
+ transform: scale(0);
+ }
+ 20% {
+ opacity: 0.2;
+ transform: scale(0.4);
+ }
+ 40% {
+ opacity: 0.4;
+ transform: scale(0.8);
+ }
+ 60% {
+ opacity: 0.6;
+ transform: scale(1);
+ }
+ 70% {
+ opacity: 0.8;
+ transform: scale(1.1);
+ }
+ 80% {
+ opacity: 0.8;
+ transform: scale(1);
+ }
+ 90% {
+ opacity: 0.8;
+ transform: scale(0.95);
+ }
+ 100% {
+ opacity: 1;
+ transform: scale(1);
+ }
+}
diff --git a/styles/mixins.scss b/styles/mixins.scss
index e39df63d..0fc2db64 100644
--- a/styles/mixins.scss
+++ b/styles/mixins.scss
@@ -26,3 +26,36 @@
}
}
}
+
+@mixin hidpiImage(
+ $image,
+ $extension,
+ $width,
+ $height,
+ $position: center,
+ $repeat: no-repeat
+) {
+ background: url($image + '.' + $extension) $repeat $position;
+
+ @media screen and (-webkit-min-device-pixel-ratio: 2),
+ screen and (min--moz-device-pixel-ratio: 2),
+ screen and (-moz-min-device-pixel-ratio: 2),
+ screen and (-o-min-device-pixel-ratio: 2/1),
+ screen and (min-device-pixel-ratio: 2),
+ screen and (min-resolution: 192dpi),
+ screen and (min-resolution: 2dppx) {
+ background: url($image + '@2x' + '.' + $extension) $repeat $position;
+ background-size: $width $height;
+ }
+
+ @media screen and (-webkit-min-device-pixel-ratio: 3),
+ screen and (min--moz-device-pixel-ratio: 3),
+ screen and (-moz-min-device-pixel-ratio: 3),
+ screen and (-o-min-device-pixel-ratio: 3/1),
+ screen and (min-device-pixel-ratio: 3),
+ screen and (min-resolution: 216dpi),
+ screen and (min-resolution: 3dppx) {
+ background: url($image + '@3x' + '.' + $extension) $repeat $position;
+ background-size: $width $height;
+ }
+}
diff --git a/styles/variables.scss b/styles/variables.scss
index d7a2f265..76e3cc4d 100644
--- a/styles/variables.scss
+++ b/styles/variables.scss
@@ -26,6 +26,7 @@ $unit-half: calc($unit / 2);
$unit-2x: $unit * 2;
$unit-3x: $unit * 3;
$unit-4x: $unit * 4;
+$unit-5x: $unit * 5;
$unit-6x: $unit * 6;
$unit-8x: $unit * 8;
$unit-10x: $unit * 10;
diff --git a/types/GridCharacter.d.ts b/types/GridCharacter.d.ts
index 90905bb2..efb82041 100644
--- a/types/GridCharacter.d.ts
+++ b/types/GridCharacter.d.ts
@@ -3,6 +3,7 @@ interface GridCharacter {
position: number
object: Character
uncap_level: number
+ transcendence_step: number
over_mastery: CharacterOverMastery
aetherial_mastery: ExtendedMastery
awakening: {
diff --git a/types/GridSummon.d.ts b/types/GridSummon.d.ts
index bfde1535..e8b14831 100644
--- a/types/GridSummon.d.ts
+++ b/types/GridSummon.d.ts
@@ -5,4 +5,5 @@ interface GridSummon {
position: number
object: Summon
uncap_level: number
+ transcendence_step: number
}
diff --git a/types/Summon.d.ts b/types/Summon.d.ts
index c71c62ce..0f6de332 100644
--- a/types/Summon.d.ts
+++ b/types/Summon.d.ts
@@ -15,16 +15,19 @@ interface Summon {
max_hp: number
max_hp_flb: number
max_hp_ulb: number
+ max_hp_xlb: number
}
atk: {
min_atk: number
max_atk: number
max_atk_flb: number
max_atk_ulb: number
+ max_atk_xlb: number
}
uncap: {
flb: boolean
ulb: boolean
+ xlb: boolean
}
position?: number
}
diff --git a/utils/api.tsx b/utils/api.tsx
index 0acb1d40..352f60ed 100644
--- a/utils/api.tsx
+++ b/utils/api.tsx
@@ -138,7 +138,8 @@ class Api {
return axios.post(resourceUrl, {
[resource]: {
id: id,
- uncap_level: value
+ uncap_level: value,
+ transcendence_step: 0
}
})
}