グカネータ改良 (#371) #375

マージ済み
みてるぞ が 7 個のコミットを feature/371 から main へマージ 2026-06-17 01:04:58 +09:00
コミット cda90b76d2 の変更だけを表示してゐます - すべてのコミットを表示
+99 -187
ファイルの表示
@@ -2753,26 +2753,13 @@ const GekanatorBackdrop: FC<{
nextThumbnails)
const [flipVisualSeed, setFlipVisualSeed] = useState (visualSeed)
const [isFlippingTiles, setIsFlippingTiles] = useState (false)
const [isCrossfading, setIsCrossfading] = useState (false)
const isLeavingGuessBackdrop = displayedBackdropMode === 'guess' && backdropMode !== 'guess'
const renderBackdropMode = isLeavingGuessBackdrop ? backdropMode : displayedBackdropMode
const renderWinningRunCount = (isLeavingGuessBackdrop
? winningRunQuestionCount
: displayedWinningRunCount)
const renderThumbnails = isLeavingGuessBackdrop ? nextThumbnails : displayedThumbnails
const renderIsCrossfading = isCrossfading && !(isLeavingGuessBackdrop)
const renderedSettings = settingsForMode (renderBackdropMode)
const renderedSettings = settingsForMode (displayedBackdropMode)
const renderedTileCount = renderedSettings.columns * renderedSettings.rows
const renderedScale = scaleForMode (renderBackdropMode, renderWinningRunCount)
const renderedScale = scaleForMode (displayedBackdropMode, displayedWinningRunCount)
const isGuessPresentation = backdropMode === 'guess'
const crossfadeDuration = motionMode === 'calm' ? .95 : .75
const isGuessPresentation =
backdropMode === 'guess' || displayedBackdropMode === 'guess'
useEffect (() => {
guessAnimationControlsRef.current.forEach (control => control.stop ())
@@ -2866,7 +2853,6 @@ const GekanatorBackdrop: FC<{
{
applyDirection ()
setIsFlippingTiles (false)
setIsCrossfading (false)
setFlipVisualSeed (visualSeed)
return
}
@@ -2874,7 +2860,6 @@ const GekanatorBackdrop: FC<{
if (backdropMode === 'guess' && guessThumbnail)
{
setIsFlippingTiles (false)
setIsCrossfading (false)
setDisplayedBackdropMode ('guess')
setDisplayedWinningRunCount (winningRunQuestionCount)
setDisplayedThumbnails (nextThumbnails)
@@ -2894,7 +2879,6 @@ const GekanatorBackdrop: FC<{
setFromThumbnails (nextThumbnails)
setToThumbnails (nextThumbnails)
setIsFlippingTiles (false)
setIsCrossfading (false)
setFlipVisualSeed (visualSeed)
return
}
@@ -2903,23 +2887,6 @@ const GekanatorBackdrop: FC<{
{
applyDirection ()
setIsFlippingTiles (false)
setIsCrossfading (false)
setFlipVisualSeed (visualSeed)
return
}
if (displayedBackdropMode === 'guess' && backdropMode !== 'guess')
{
applyDirection ()
x.set (0)
y.set (0)
setIsFlippingTiles (false)
setIsCrossfading (false)
setDisplayedBackdropMode (backdropMode)
setDisplayedWinningRunCount (winningRunQuestionCount)
setDisplayedThumbnails (nextThumbnails)
setFromThumbnails (nextThumbnails)
setToThumbnails (nextThumbnails)
setFlipVisualSeed (visualSeed)
return
}
@@ -2942,10 +2909,7 @@ const GekanatorBackdrop: FC<{
setFromThumbnails (currentThumbnails)
setToThumbnails (nextThumbnails)
const shouldCrossfade =
displayedBackdropMode === 'winning_run' && backdropMode === 'normal'
setIsCrossfading (shouldCrossfade)
setIsFlippingTiles (!(shouldCrossfade))
setIsFlippingTiles (true)
flipTimerRef.current = window.setTimeout (() => {
setDisplayedBackdropMode (backdropMode)
@@ -2954,11 +2918,10 @@ const GekanatorBackdrop: FC<{
setFromThumbnails (nextThumbnails)
setToThumbnails (nextThumbnails)
setIsFlippingTiles (false)
setIsCrossfading (false)
applyDirection ()
setFlipVisualSeed (visualSeed)
flipTimerRef.current = null
}, (shouldCrossfade ? crossfadeDuration : tileFlipDuration) * 1000)
}, tileFlipDuration * 1000)
return () => {
if (flipTimerRef.current !== null)
@@ -2978,126 +2941,10 @@ const GekanatorBackdrop: FC<{
visualSeed,
activeDirection,
winningRunQuestionCount,
crossfadeDuration,
tileFlipDuration,
x,
y])
const renderTileSet = (
{ mode,
thumbnails,
settings,
tileCount,
scale,
opacity,
withFlip }: { mode: 'normal' | 'winning_run' | 'guess'
thumbnails: string[]
settings: { columns: number; rows: number; opacity: number }
tileCount: number
scale: number
opacity?: number
withFlip?: boolean }) => {
const guessModeFlg = mode === 'guess'
return (
<motion.div
className="absolute inset-0"
initial={guessModeFlg
? false
: (opacity == null
? undefined
: { opacity: opacity === 0 ? 1 : 0, scale, x: '0%', y: '0%' })}
animate={{ opacity: opacity ?? 1,
scale,
x: guessModeFlg ? guessFocusOffset.x : '0%',
y: guessModeFlg ? guessFocusOffset.y : '0%' }}
transition={guessModeFlg
? { duration: 0 }
: (mode === 'winning_run'
? { duration: crossfadeDuration, ease: [.16, 1, .3, 1] }
: { duration: .2 })}>
{Array.from ({ length: 9 }, (_, duplicate) => {
const column = duplicate % 3
const row = Math.floor (duplicate / 3)
return (
<motion.div
key={`${ mode }:${ duplicate }`}
className="absolute grid overflow-hidden"
layout={mode === 'winning_run'}
style={{
left: `${ column * 33.333333 }%`,
top: `${ row * 33.333333 }%`,
width: '33.333333%',
height: '33.333333%',
gridTemplateColumns:
`repeat(${ settings.columns }, minmax(0, 1fr))`,
gridTemplateRows:
`repeat(${ settings.rows }, minmax(0, 1fr))` }}
transition={{ duration: tileFlipDuration, ease: 'easeInOut' }}>
{Array.from ({ length: tileCount }, (_, index) => {
const currentThumbnail =
thumbnails[index % Math.max (thumbnails.length, 1)]
const frontThumbnail =
withFlip
? fromThumbnails[index % Math.max (fromThumbnails.length, 1)]
: currentThumbnail
const backThumbnail =
withFlip
? toThumbnails[index % Math.max (toThumbnails.length, 1)]
: currentThumbnail
const thumbnail = currentThumbnail
if (!(thumbnail) || !(frontThumbnail) || !(backThumbnail))
return null
return (
<motion.div
key={`${ mode }:${ duplicate }:${ index }`}
className="relative overflow-hidden"
layout={mode === 'winning_run'}
transition={{ duration: tileFlipDuration, ease: 'easeInOut' }}
style={{ perspective: 1600 }}>
{(mode !== 'normal' || !(withFlip))
? (
<img
src={['intro', 'end'].includes (phase)
? mascotAsset
: thumbnail}
alt=""
className="absolute inset-0 h-full w-full object-cover"
style={{ opacity: settings.opacity }}/>)
: (
<motion.div
className="absolute inset-0"
initial={{ rotateY: 0 }}
animate={{ rotateY: 180 }}
transition={{
duration: tileFlipDuration,
ease: 'easeInOut' }}
style={{ transformStyle: 'preserve-3d' }}>
<img
src={backThumbnail}
alt=""
className="absolute inset-0 h-full w-full object-cover"
style={{
backfaceVisibility: 'hidden',
opacity: settings.opacity,
transform: 'rotateY(180deg)' }}/>
<img
src={frontThumbnail}
alt=""
className="absolute inset-0 h-full w-full object-cover"
style={{
backfaceVisibility: 'hidden',
opacity: settings.opacity }}/>
</motion.div>)}
</motion.div>)
})}
</motion.div>)
})}
</motion.div>)
}
if (motionMode === 'off' || nextThumbnails.length === 0)
return (
<div className="absolute inset-0 bg-gradient-to-br from-yellow-50 via-white
@@ -3113,34 +2960,99 @@ const GekanatorBackdrop: FC<{
width: 'calc(max(100vw, 100vh) * 3)',
height: 'calc(max(100vw, 100vh) * 3)' }}>
<motion.div
className="relative h-full w-full">
{renderIsCrossfading
? (
<>
{renderTileSet ({
mode: displayedBackdropMode,
thumbnails: fromThumbnails,
settings: settingsForMode (displayedBackdropMode),
tileCount: (settingsForMode (displayedBackdropMode).columns
* settingsForMode (displayedBackdropMode).rows),
scale: scaleForMode (displayedBackdropMode, displayedWinningRunCount),
opacity: 0 })}
{renderTileSet ({
mode: backdropMode,
thumbnails: toThumbnails,
settings: targetSettings,
tileCount: targetTileCount,
scale: scaleForMode (backdropMode, winningRunQuestionCount),
opacity: 1 })}
</>)
: (
renderTileSet ({
mode: renderBackdropMode,
thumbnails: renderThumbnails,
settings: renderedSettings,
tileCount: renderedTileCount,
scale: renderedScale,
withFlip: isFlippingTiles && !(isLeavingGuessBackdrop) }))}
className="relative h-full w-full"
animate={{ scale: renderedScale,
x: displayedBackdropMode === 'guess' ? guessFocusOffset.x : '0%',
y: displayedBackdropMode === 'guess' ? guessFocusOffset.y : '0%' }}
transition={(displayedBackdropMode === 'winning_run'
|| displayedBackdropMode === 'guess')
? { duration: motionMode === 'calm' ? .95 : .75,
ease: [.16, 1, .3, 1] }
: { duration: .2 }}>
{Array.from ({ length: 9 }, (_, duplicate) => {
const column = duplicate % 3
const row = Math.floor (duplicate / 3)
return (
<motion.div
key={duplicate}
className="absolute grid overflow-hidden"
layout={displayedBackdropMode !== 'normal'}
style={{
left: `${ column * 33.333333 }%`,
top: `${ row * 33.333333 }%`,
width: '33.333333%',
height: '33.333333%',
gridTemplateColumns:
`repeat(${ renderedSettings.columns }, minmax(0, 1fr))`,
gridTemplateRows:
`repeat(${ renderedSettings.rows }, minmax(0, 1fr))` }}
transition={{ duration: tileFlipDuration, ease: 'easeInOut' }}>
{Array.from ({ length: renderedTileCount }, (_, index) => {
const currentThumbnail =
displayedThumbnails[
index % Math.max (displayedThumbnails.length, 1)]
const frontThumbnail =
isFlippingTiles
? fromThumbnails[index % Math.max (fromThumbnails.length, 1)]
: currentThumbnail
const backThumbnail =
isFlippingTiles
? toThumbnails[index % Math.max (toThumbnails.length, 1)]
: currentThumbnail
const thumbnail =
displayedBackdropMode === 'winning_run'
|| displayedBackdropMode === 'guess'
? nextThumbnails[index % Math.max (nextThumbnails.length, 1)]
: currentThumbnail
if (!(thumbnail) || !(frontThumbnail) || !(backThumbnail))
return null
return (
<motion.div
key={`${ duplicate }:${ index }`}
className="relative overflow-hidden"
layout={displayedBackdropMode !== 'normal'}
transition={{ duration: tileFlipDuration, ease: 'easeInOut' }}
style={{ perspective: 1600 }}>
{(displayedBackdropMode !== 'normal' || !(isFlippingTiles))
? (
<img
src={['intro', 'end'].includes (phase)
? mascotAsset
: thumbnail}
alt=""
className="absolute inset-0 h-full w-full object-cover"
style={{ opacity: renderedSettings.opacity }}/>)
: (
<motion.div
className="absolute inset-0"
initial={{ rotateY: 0 }}
animate={{ rotateY: 180 }}
transition={{
duration: tileFlipDuration,
ease: 'easeInOut' }}
style={{ transformStyle: 'preserve-3d' }}>
<img
src={backThumbnail}
alt=""
className="absolute inset-0 h-full w-full object-cover"
style={{
backfaceVisibility: 'hidden',
opacity: renderedSettings.opacity,
transform: 'rotateY(180deg)' }}/>
<img
src={frontThumbnail}
alt=""
className="absolute inset-0 h-full w-full object-cover"
style={{
backfaceVisibility: 'hidden',
opacity: renderedSettings.opacity }}/>
</motion.div>)}
</motion.div>)
})}
</motion.div>)
})}
</motion.div>
</motion.div>
</div>