// frontend\src\components\ui\FinishedDownloadsCardsView.tsx
'use client'

import {
  memo,
  useCallback,
  useEffect,
  useMemo,
  useRef,
  useState,
  type CSSProperties,
  type ReactNode,
} from 'react'
import Card from './Card'
import Button from './Button'
import type {
  RecordJob,
  FinishedPostworkSummary,
  GalleryModelFlags,
  InlinePlayState
} from '../../types'
import FinishedVideoPreview from './FinishedVideoPreview'
import SwipeCard, { type SwipeCardHandle } from './SwipeCard'
import RecordJobActions from './RecordJobActions'
import TagOverflowRow from './TagOverflowRow'
import PreviewScrubber from './PreviewScrubber'
import { isHotName, stripHotPrefix } from './hotName'
import type { FinishedDownloadsTeaserState } from './teaserPlayback'
import {
  shouldAnimateTeaser,
  shouldEnableTeaserAudio,
} from './teaserPlayback'
import { SpeakerWaveIcon } from '@heroicons/react/24/solid'
import {
  buildSpriteFrameStyle,
  parseJobMeta,
  readPreviewSpriteInfo,
  scrubProgressRatioFromIndex,
} from './previewSprite'
import {
  buildVideoHeatmapSegments,
  heatmapDurationForJob,
} from './videoHeatmap'
import ModelGenderIcon from './ModelGenderIcon'
import { parseFinishedTags, resolveFinishedModelFlags } from './finishedModelFlags'
import CountryFlag, { resolveFinishedCountry } from './CountryFlag'
import {
  type FinishedSelectionStore,
} from './finishedSelectionStore'
import SpritePreloader from './SpritePreloader'
import FinishedDownloadsDesktopCard from './FinishedDownloadsDesktopCard'
import PreviewRatingOverlay from './PreviewRatingOverlay'
import PreviewMetaOverlay from './PreviewMetaOverlay'
import DeletingPreviewOverlay from './DeletingPreviewOverlay'
import { autoTagsForJob, mergeTags } from './autoTags'
import { useFinishedDownloadsAutoGridPageSize } from './useFinishedDownloadsAutoPageSize'


type SwipeRefs = {
  current: Map<string, SwipeCardHandle>
}

type Props = {
  rows: RecordJob[]
  isLoading?: boolean
  isSmall: boolean
  selectionStore: FinishedSelectionStore
  selectionModeActive: boolean
  onToggleSelected: (job: RecordJob) => void
  onToggleSelectAllPage: (checked: boolean) => void
  onSelectOnly: (job: RecordJob) => void

  blurPreviews?: boolean
  durations: Record<string, number>
  inlinePlay: InlinePlayState
  teaserState: FinishedDownloadsTeaserState

  deletingKeys: Set<string>
  deletedSuccessKeys: Set<string>
  keptSuccessKeys: Set<string>
  keepingKeys: Set<string>
  removingKeys: Set<string>

  swipeRefs: SwipeRefs

  assetNonce?: number
  assetNonceForJob?: (job: RecordJob) => number

  jobForDetails: (job: RecordJob) => RecordJob
  handleScrubberClickIndex: (job: RecordJob, segmentIndex: number, segmentCount: number) => void

  // helpers
  keyFor: (j: RecordJob) => string
  baseName: (p: string) => string
  modelNameFromOutput: (output?: string) => string
  runtimeOf: (job: RecordJob) => string
  sizeBytesOf: (job: RecordJob) => number | null
  formatBytes: (bytes?: number | null) => string
  lower: (s: string) => string

  // callbacks/actions
  onHoverPreviewKeyChange?: (key: string | null) => void
  onOpenPlayer: (job: RecordJob) => void
  openPlayer: (job: RecordJob) => void
  onOpenPlayerAt?: (job: RecordJob, seconds: number) => void
  startInlineAt?: (key: string, seconds: number, domId: string) => void
  startInline: (key: string) => void
  tryAutoplayInline: (domId: string) => boolean
  registerTeaserHost: (key: string) => (el: HTMLDivElement | null) => void

  handleDuration: (job: RecordJob, seconds: number) => void
  handleResolution: (job: RecordJob, w: number, h: number) => void
  resolutionLabelOf: (job: RecordJob) => string

  deleteVideo: (
    job: RecordJob,
    opts?: { alreadyRemoved?: boolean; optimisticRemove?: boolean }
  ) => Promise<boolean>
  keepVideo: (job: RecordJob) => Promise<boolean>

  releasePlayingFile: (file: string, opts?: { close?: boolean }) => Promise<void>

  modelsByKey: Record<string, GalleryModelFlags>
  activeTagSet: Set<string>
  onToggleTagFilter: (tag: string) => void
  onToggleHot?: (job: RecordJob) => void | Promise<void>
  onToggleFavorite?: (job: RecordJob) => void | Promise<void>
  onToggleLike?: (job: RecordJob) => void | Promise<void>
  onToggleWatch?: (job: RecordJob) => void | Promise<void>
  onSplit?: (job: RecordJob) => void | Promise<void>
  onAddToDownloads?: (job: RecordJob) => void | Promise<boolean>

  postworkByFile: Record<string, FinishedPostworkSummary>
  getPostworkSummaryForOutput: (
    output: string | undefined,
    summaries: Record<string, FinishedPostworkSummary>
  ) => FinishedPostworkSummary | undefined

  renderPostworkBadge?: (badge?: FinishedPostworkSummary) => ReactNode

  forcePreviewMuted?: boolean
  mobileTeaserAudioUnlocked?: boolean
  onUnlockMobileTeaserAudio?: () => void
  renderIntegrityBadge?: (job: RecordJob) => ReactNode
  renderRatingOverlay?: (job: RecordJob) => ReactNode
  onRecommendedPageSizeChange?: (size: number) => void
  suspendAutoPageSize?: boolean
}

function CardBlurWrapper({
  blurred,
  animateUnblurOnMount,
  children,
}: {
  blurred?: boolean
  animateUnblurOnMount?: boolean
  children: ReactNode
}) {
  const [entered, setEntered] = useState(!animateUnblurOnMount)

  useEffect(() => {
    if (!animateUnblurOnMount) return
    const raf = requestAnimationFrame(() => {
      setEntered(true)
    })
    return () => cancelAnimationFrame(raf)
  }, [animateUnblurOnMount])

  const className = [
    'relative will-change-[filter,transform] transition-[filter,transform,opacity] duration-180 ease-out',
    blurred
      ? 'blur-[1.5px] saturate-90 scale-[0.995]'
      : entered
        ? 'blur-0 saturate-100 scale-100'
        : 'blur-[1.5px] saturate-90 scale-[0.995]',
  ]
    .filter(Boolean)
    .join(' ')

  return <div className={className}>{children}</div>
}

function PromoteToFrontWrapper({
  animateOnMount,
  children,
}: {
  animateOnMount?: boolean
  children: ReactNode
}) {
  const [entered, setEntered] = useState(!animateOnMount)

  useEffect(() => {
    if (!animateOnMount) return
    const raf = requestAnimationFrame(() => {
      setEntered(true)
    })
    return () => cancelAnimationFrame(raf)
  }, [animateOnMount])

  return (
    <div
      className="will-change-[transform,opacity] transition-[transform,opacity] duration-160 ease-out motion-reduce:transition-none"
      style={{
        transform: entered
          ? 'translateY(0px) scale(1) translateZ(0)'
          : 'translateY(-15px) scale(0.97) translateZ(0)',
        opacity: entered ? 1 : 0.92,
        transformOrigin: 'top center',
      }}
    >
      {children}
    </div>
  )
}

function LoadingCardSkeleton({ blurred = false }: { blurred?: boolean }) {
  return (
    <div
      className={[
        'group relative overflow-hidden rounded-lg outline-1 outline-black/5 dark:-outline-offset-1 dark:outline-white/10',
        'bg-white dark:bg-gray-900/40',
        blurred ? 'blur-[1.5px] saturate-90 scale-[0.995]' : '',
      ].join(' ')}
      aria-hidden="true"
    >
      <Card noBodyPadding className="overflow-hidden bg-transparent">
        <div className="animate-pulse">
          {/* Preview */}
          <div className="relative aspect-video rounded-t-lg bg-gray-200/80 dark:bg-white/10">
            <div className="absolute inset-0 bg-gradient-to-br from-white/20 via-transparent to-black/5 dark:from-white/5 dark:to-white/10" />

            {/* Meta-Overlay unten rechts */}
            <div className="absolute right-2 bottom-2 z-10">
              <div className="h-4 w-28 rounded bg-black/25 dark:bg-black/35" />
            </div>
          </div>

          {/* Footer / Meta */}
          <div className="relative min-h-[112px] overflow-hidden border-t border-black/5 bg-white px-4 py-3 dark:border-white/10 dark:bg-gray-900">
            <div className="flex items-start justify-between gap-3">
              <div className="min-w-0 flex-1">
                <div className="h-4 w-32 rounded bg-gray-200 dark:bg-white/10" />
                <div className="mt-2 h-3 w-48 rounded bg-gray-100 dark:bg-white/5" />
              </div>

              <div className="shrink-0 flex items-center gap-1.5 pt-0.5">
                <div className="size-4 rounded-full bg-gray-200 dark:bg-white/10" />
                <div className="size-4 rounded-full bg-gray-200 dark:bg-white/10" />
                <div className="size-4 rounded-full bg-gray-200 dark:bg-white/10" />
              </div>
            </div>

            <div className="mt-3 rounded-md bg-gray-50/70 p-1 ring-1 ring-black/5 dark:bg-white/5 dark:ring-white/10">
              <div className="flex items-center gap-1.5">
                <div className="h-8 flex-1 rounded bg-gray-200 dark:bg-white/10" />
                <div className="h-8 w-10 rounded bg-gray-200 dark:bg-white/10" />
                <div className="h-8 w-10 rounded bg-gray-200 dark:bg-white/10" />
                <div className="h-8 w-10 rounded bg-gray-200 dark:bg-white/10" />
              </div>
            </div>

            <div className="mt-3 flex flex-wrap gap-2">
              <div className="h-5 w-16 rounded-full bg-gray-100 dark:bg-white/5" />
              <div className="h-5 w-20 rounded-full bg-gray-100 dark:bg-white/5" />
              <div className="h-5 w-14 rounded-full bg-gray-100 dark:bg-white/5" />
            </div>
          </div>
        </div>
      </Card>
    </div>
  )
}

function MobileLoadingStack({
  stackPeekOffsetPx,
  stackExtraTopPx,
}: {
  stackPeekOffsetPx: number
  stackExtraTopPx: number
}) {
  const backDepths = [2, 1]

  return (
    <div className="relative mx-auto w-full max-w-[560px] overflow-y-visible" style={{ overflowX: 'clip' }}>
      <div
        className="relative overflow-visible"
        style={{
          paddingTop: stackExtraTopPx > 0 ? `${stackExtraTopPx}px` : undefined,
        }}
      >
        {backDepths.map((depth) => {
          const y = -(depth * stackPeekOffsetPx)
          const scale = 1 - depth * 0.03
          const opacity = 1 - depth * 0.14

          return (
            <div
              key={`loading-back-${depth}`}
              className="absolute inset-x-0 top-0 pointer-events-none will-change-[transform,opacity]"
              style={{
                zIndex: 20 - depth,
                transform: `translateY(${y}px) scale(${scale}) translateZ(0)`,
                opacity,
                transformOrigin: 'top center',
              }}
              aria-hidden="true"
            >
              <div className="relative">
                <LoadingCardSkeleton blurred />
                <div className="absolute inset-0 rounded-lg bg-white/8 dark:bg-black/8" />
              </div>
            </div>
          )
        })}
      </div>

      <div className="relative touch-pan-y" style={{ zIndex: 30 }}>
        <LoadingCardSkeleton />
        <div className="absolute inset-0 grid place-items-center pointer-events-none">
          <div className="flex items-center gap-2 rounded-lg border border-gray-200/80 bg-white/88 px-3 py-2 shadow-sm backdrop-blur-sm dark:border-white/10 dark:bg-gray-900/82">
            <div className="h-4 w-4 animate-spin rounded-full border-2 border-gray-300 border-t-transparent dark:border-white/20 dark:border-t-transparent" />
            <div className="text-xs font-medium text-gray-700 dark:text-gray-200">Lade…</div>
          </div>
        </div>
      </div>
    </div>
  )
}

function FinishedDownloadsCardsView({
  rows,
  isSmall,
  selectionStore,
  selectionModeActive,
  onToggleSelected,
  isLoading,
  blurPreviews,
  durations,
  teaserState,
  inlinePlay,
  deletingKeys,
  deletedSuccessKeys,
  keptSuccessKeys,
  keepingKeys,
  removingKeys,

  swipeRefs,
  assetNonce,
  assetNonceForJob,
  jobForDetails,
  handleScrubberClickIndex,

  keyFor,
  baseName,
  modelNameFromOutput,
  runtimeOf,
  sizeBytesOf,
  formatBytes,
  lower,

  onHoverPreviewKeyChange,
  onOpenPlayer,
  openPlayer,
  startInlineAt,
  startInline,
  tryAutoplayInline,
  registerTeaserHost,
  handleDuration,
  handleResolution,
  resolutionLabelOf,

  deleteVideo,
  keepVideo,

  modelsByKey,
  activeTagSet,
  onToggleTagFilter,
  
  onToggleHot,
  onToggleFavorite,
  onToggleLike,
  onToggleWatch,
  onSplit,
  onAddToDownloads,
  postworkByFile,
  getPostworkSummaryForOutput,
  renderPostworkBadge,
  forcePreviewMuted,
  mobileTeaserAudioUnlocked,
  onUnlockMobileTeaserAudio,
  renderIntegrityBadge,
  renderRatingOverlay,
  onRecommendedPageSizeChange,
  suspendAutoPageSize,
}: Props) {

  const rootRef = useRef<HTMLDivElement | null>(null)
  const desktopGridMinCardWidth = 300
  const desktopGridGapPx = 12
  const estimateDesktopCardHeight = useCallback(
    ({ itemWidth }: { itemWidth: number }) => {
      return Math.round(itemWidth * 9 / 16 + 150)
    },
    []
  )

  useFinishedDownloadsAutoGridPageSize({
    rootRef,
    enabled: !isSmall && !suspendAutoPageSize,
    minItemWidth: desktopGridMinCardWidth,
    gapPx: desktopGridGapPx,
    fallbackItemHeight: estimateDesktopCardHeight,
    onRecommendedPageSizeChange,
  })

  const footerTapRef = useRef<Record<string, boolean>>({})

  const teaserPlayFnsRef = useRef<Record<string, (() => void) | null>>({})

  const [speedHoldKey, setSpeedHoldKey] = useState<string | null>(null)

  const registerTeaserPlayForKey = useCallback(
    (key: string) => (fn: (() => void) | null) => {
      if (fn) teaserPlayFnsRef.current[key] = fn
      else delete teaserPlayFnsRef.current[key]
    },
    []
  )

  const parseMeta = useCallback((j: RecordJob): any | null => {
    return parseJobMeta((j as any)?.meta)
  }, [])

  const [scrubActiveByKey, setScrubActiveByKey] = useState<Record<string, number | undefined>>({})
  const [scrubHoveringByKey, setScrubHoveringByKey] = useState<Record<string, boolean | undefined>>({})

  const [hoveredThumbKey, setHoveredThumbKey] = useState<string | null>(null)

  const setScrubActiveIndex = useCallback((key: string, index: number | undefined) => {
    setScrubActiveByKey((prev) => {
      if (index === undefined) {
        if (!(key in prev)) return prev
        const next = { ...prev }
        delete next[key]
        return next
      }

      if (prev[key] === index) return prev
      return { ...prev, [key]: index }
    })
  }, [])

  const clearScrubActiveIndex = useCallback((key: string) => {
    setScrubActiveIndex(key, undefined)
  }, [setScrubActiveIndex])

  const setScrubHovering = useCallback((key: string, hovering: boolean | undefined) => {
    setScrubHoveringByKey((prev) => {
      if (hovering === undefined) {
        if (!(key in prev)) return prev
        const next = { ...prev }
        delete next[key]
        return next
      }
      if (prev[key] === hovering) return prev
      return { ...prev, [key]: hovering }
    })
  }, [])

  const renderCardItem = (
    j: RecordJob,
    opts?: {
      forceStill?: boolean
      disableInline?: boolean
      disablePreviewHover?: boolean
      isDecorative?: boolean
      forceLoadStill?: boolean
      mobileStackTopOnlyVideo?: boolean
      disableScrubber?: boolean
      blur?: boolean
      animateUnblurOnMount?: boolean
      preloadTeaserWhenStill?: boolean
      renderRole?: 'top' | 'back' | 'preload' | 'desktop'
    }
  ) => {
    const k = keyFor(j)
    const rowAssetNonce = assetNonceForJob?.(j) ?? assetNonce ?? 0

    const handleSelectOrOpen = () => {
      if (selectionModeActive) {
        onToggleSelected(j)
        return true
      }
      return false
    }

    const realInlineActive = inlinePlay?.key === k
    const inlineActive = opts?.disableInline ? false : realInlineActive

    const allowSound = shouldEnableTeaserAudio({
      state: teaserState,
      itemKey: k,
      inlineActive,
      disabled: Boolean(opts?.forceStill),
    })

    const isMobileTopCard = Boolean(isSmall && opts?.mobileStackTopOnlyVideo)

    const previewMuted = forcePreviewMuted
      ? true
      : (
          isSmall && opts?.mobileStackTopOnlyVideo
            ? teaserAudioEnabledForKey !== k
            : !allowSound
        )

    const inlineNonce = inlineActive ? inlinePlay?.nonce ?? 0 : 0
    const forceLoadStill = Boolean(opts?.forceLoadStill)

    // Mobile-Stack:
    // - TopCard: bei hover/all immer Teaser animieren
    // - Backcards/Preloadcards: immer still
    const allowTeaserAnimation =
      Boolean(opts?.forceStill)
        ? false
        : opts?.mobileStackTopOnlyVideo
          ? teaserState.mode !== 'still'
          : shouldAnimateTeaser({
              state: teaserState,
              itemKey: k,
              disabled: false,
            })

    const deleteDone = deletedSuccessKeys.has(k)
    const keepDone = keptSuccessKeys.has(k)
    const keepBusy = keepingKeys.has(k)
    const busy = deletingKeys.has(k) || deleteDone || keepBusy || keepDone || removingKeys.has(k)

    const model = modelNameFromOutput(j.output)
    const fileRaw = baseName(j.output || '')
    const livePostwork = getPostworkSummaryForOutput(j.output, postworkByFile)

    const { flags } = resolveFinishedModelFlags(
      j,
      modelsByKey,
      modelNameFromOutput,
      lower
    )

    const scrubActiveIndex = scrubActiveByKey[k]
    const scrubHovering = scrubHoveringByKey[k] === true

    const sprite = readPreviewSpriteInfo((j as any)?.meta, {
      fallbackPath: flags?.previewScrubberPath,
      fallbackCount: flags?.previewScrubberCount,
      versionFallback: rowAssetNonce,
    })

    const spriteUrl = sprite.url
    const hasScrubberUi = sprite.hasScrubberUi
    const hasSpriteScrubber = sprite.hasSpriteScrubber
    const scrubberCount = hasScrubberUi ? sprite.count : 0
    const scrubberStepSeconds = hasScrubberUi ? sprite.stepSeconds : 0
    const heatmapDuration = heatmapDurationForJob(j, durations[k] ?? (j as any)?.durationSeconds)
    const heatmapSegments = buildVideoHeatmapSegments(j, heatmapDuration)

    const teaserPreloadEnabled =
      opts?.mobileStackTopOnlyVideo
        ? false
        : (opts?.preloadTeaserWhenStill ? true : !isSmall)

    const shouldPreloadSprite =
      Boolean(teaserPreloadEnabled && hasSpriteScrubber && spriteUrl)

    const spriteScrubbingActive =
      hasSpriteScrubber && scrubHovering && typeof scrubActiveIndex === 'number'

    const scrubProgressRatio = scrubProgressRatioFromIndex(scrubActiveIndex, scrubberCount)

    const spriteFrameStyle = buildSpriteFrameStyle({
      spriteUrl,
      spriteCols: sprite.cols,
      spriteRows: sprite.rows,
      spriteCount: sprite.count,
      activeIndex: scrubActiveIndex,
    })

    const isHot = isHotName(fileRaw)
    const isFav = Boolean(flags?.favorite)
    const isLiked = flags?.liked === true
    const isWatching = Boolean(flags?.watching)

    const tags = mergeTags(
      parseFinishedTags(flags?.tags),
      autoTagsForJob(j)
    )

    const modelCountry = resolveFinishedCountry(flags, j)

    const dur = runtimeOf(j)
    const size = formatBytes(sizeBytesOf(j))

    const meta = parseMeta(j)

    const resObj = (() => {
      const w = meta?.media?.video?.width ?? meta?.file?.video?.width
      const h = meta?.media?.video?.height ?? meta?.file?.video?.height

      if (
        typeof w === 'number' && Number.isFinite(w) && w > 0 &&
        typeof h === 'number' && Number.isFinite(h) && h > 0
      ) {
        return { w, h }
      }

      return null
    })()

    const resLabelRaw = resolutionLabelOf(j)
    const resLabel = resLabelRaw && resLabelRaw !== '—' ? resLabelRaw : ''

    const inlineDomId = `inline-prev-${encodeURIComponent(k)}`

    const previewRenderRole = opts?.renderRole ?? (isSmall ? 'top' : 'desktop')

    const showMobileAudioButton =
      Boolean(onUnlockMobileTeaserAudio) &&
      isMobileTopCard &&
      !inlineActive &&
      !opts?.forceStill &&
      allowTeaserAnimation &&
      audioButtonDismissedForKey !== k

    // ✅ Shell an Gallery angelehnt
    const shellCls = [
      'group relative rounded-lg overflow-visible outline-1 outline-black/5 dark:-outline-offset-1 dark:outline-white/10',
      'bg-white dark:bg-gray-900/40',
      'transition-all duration-200',
      !isSmall && 'hover:-translate-y-0.5 hover:shadow-md dark:hover:shadow-none',
      'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 dark:focus-visible:outline-indigo-500',
      busy && 'pointer-events-none opacity-70',
      deletingKeys.has(k) && 'ring-1 ring-red-300 bg-red-50/60 dark:bg-red-500/10 dark:ring-red-500/30',
      deleteDone && 'ring-1 ring-emerald-300 bg-emerald-50/60 dark:bg-emerald-500/10 dark:ring-emerald-500/30',
      (keepBusy || keepDone) && 'ring-1 ring-emerald-300 bg-emerald-50/60 dark:bg-emerald-500/10 dark:ring-emerald-500/30',
      removingKeys.has(k) && 'opacity-0 translate-y-2 scale-[0.98]',
    ]
      .filter(Boolean)
      .join(' ')

    const cardInner = (
      <div
        role="button"
        tabIndex={0}
        data-finished-hover-key={k}
        className={shellCls}
        onClick={(e) => {
          e.preventDefault()
          e.stopPropagation()

          if (!isSmall) {
            openPlayer(j)
          }
        }}
        onMouseEnter={
          isSmall
            ? undefined
            : () => {
                onHoverPreviewKeyChange?.(k)
              }
        }
        onMouseLeave={
          isSmall
            ? undefined
            : () => {
                onHoverPreviewKeyChange?.(null)
                setHoveredThumbKey((prev) => (prev === k ? null : prev))
                clearScrubActiveIndex(k)
                setScrubHovering(k, false)
              }
        }
        onKeyDown={(e) => {
          if (e.key === 'Enter' || e.key === ' ') {
            e.preventDefault()

            if (handleSelectOrOpen()) return
            onOpenPlayer(j)
          }
        }}
      >
        <CardBlurWrapper
          blurred={opts?.blur}
          animateUnblurOnMount={opts?.animateUnblurOnMount}
        >
          {/* Card shell keeps backgrounds consistent */}
          <Card noBodyPadding className="overflow-hidden bg-transparent">
            {/* Preview */}
            <div
              id={inlineDomId}
              ref={
                opts?.disableInline || opts?.isDecorative
                  ? undefined
                  : registerTeaserHost(k)
              }
              className="group/thumb relative aspect-video rounded-t-lg bg-black/5 dark:bg-white/5"
              onMouseEnter={
                isSmall
                  ? undefined
                  : () => {
                      setHoveredThumbKey(k)
                    }
              }
              onMouseLeave={() => {
                setHoveredThumbKey((prev) => (prev === k ? null : prev))
                clearScrubActiveIndex(k)
                setScrubHovering(k, false)
              }}
              onClick={(e) => {
                e.preventDefault()
                e.stopPropagation()

                if (isSmall || opts?.disableInline) return
                startInline(k)
              }}
            >
              {/* media */}
              <div className="absolute inset-0 overflow-hidden rounded-t-lg">
                {isSmall ? (
                  !inlineActive && renderRatingOverlay ? (
                    <div
                      className="pointer-events-auto absolute bottom-2 left-2 z-[45]"
                      onClick={(e) => {
                        e.preventDefault()
                        e.stopPropagation()
                      }}
                      onPointerDown={(e) => {
                        e.stopPropagation()
                      }}
                      onTouchStart={(e) => {
                        e.stopPropagation()
                      }}
                    >
                      {renderRatingOverlay(j)}
                    </div>
                  ) : null
                ) : (
                  <PreviewRatingOverlay hidden={inlineActive}>
                    {renderRatingOverlay ? renderRatingOverlay(j) : null}
                  </PreviewRatingOverlay>
                )}

                <PreviewMetaOverlay
                  hidden={inlineActive}
                  dur={dur}
                  resObj={resObj}
                  resLabel={resLabel}
                  size={size}
                />

                <FinishedVideoPreview
                  job={j}
                  getFileName={(p) => stripHotPrefix(baseName(p))}
                  className="h-full w-full"
                  variant="fill"
                  durationSeconds={durations[k] ?? (j as any)?.meta?.file?.durationSeconds}
                  onDuration={handleDuration}
                  onResolution={handleResolution}
                  showPopover={false}
                  blur={inlineActive ? false : Boolean(blurPreviews)}
                  animated={allowTeaserAnimation && !spriteScrubbingActive}
                  animatedMode="teaser"
                  animatedTrigger="always"
                  inlineVideo={!opts?.disableInline && inlineActive ? 'always' : false}
                  inlineNonce={inlineNonce}
                  inlineControls={realInlineActive}
                  inlineLoop={false}
                  muted={previewMuted}
                  popoverMuted={previewMuted}
                  assetNonce={rowAssetNonce}
                  alwaysLoadStill={forceLoadStill}
                  teaserPreloadEnabled={teaserPreloadEnabled}
                  teaserPreloadRootMargin={
                    opts?.preloadTeaserWhenStill
                      ? '1200px 0px'
                      : (isSmall ? '900px 0px' : '700px 0px')
                  }
                  scrubProgressRatio={hasSpriteScrubber ? undefined : scrubProgressRatio}
                  preferScrubProgress={
                    !hasSpriteScrubber && scrubHovering && typeof scrubActiveIndex === 'number'
                  }
                  forceActive={Boolean(isSmall && opts?.mobileStackTopOnlyVideo && !opts?.forceStill)}
                  activationNonce={
                    previewRenderRole === 'top'
                      ? mobileTopActivationNonce
                      : 0
                  }
                  registerTeaserPlay={registerTeaserPlayForKey(k)}
                  holdSpeedActive={speedHoldKey === k}
                />

                {showMobileAudioButton ? (
                  <button
                    type="button"
                    className="
                      absolute top-2 right-2 z-[35] inline-flex items-center gap-1.5 rounded-full
                      px-2.5 py-1.5 text-[11px] font-medium backdrop-blur transition
                      bg-white/95 text-gray-900 ring-1 ring-gray-200 shadow-sm hover:bg-white
                      dark:bg-black/70 dark:text-white dark:ring-white/10 dark:hover:bg-black/80
                      focus:outline-none focus:ring-2 focus:ring-indigo-500
                    "
                    onClick={(e) => {
                      e.preventDefault()
                      e.stopPropagation()

                      setAudioButtonDismissedForKey(k)
                      setTeaserAudioEnabledForKey(k)
                      onUnlockMobileTeaserAudio?.()

                      teaserPlayFnsRef.current[k]?.()
                    }}
                    onMouseDown={(e) => e.stopPropagation()}
                    onTouchStart={(e) => e.stopPropagation()}
                    aria-label="Teaser mit Ton abspielen"
                    title="Ton aktivieren"
                  >
                    <SpeakerWaveIcon className="size-3.5 shrink-0" />
                    <span>Ton aktivieren</span>
                  </button>
                ) : null}

                <SpritePreloader
                  src={spriteUrl}
                  enabled={shouldPreloadSprite}
                />

                {/* ✅ Scrub-Frame Overlay via Sprite (kein Request pro Move) */}
                {hasSpriteScrubber && spriteFrameStyle && !inlineActive ? (
                  <div className="absolute inset-x-0 top-0 bottom-[6px] z-[5]" aria-hidden="true">
                    <div className="h-full w-full" style={spriteFrameStyle} />
                  </div>
                ) : null}

                {/* ✅ stashapp-artiger Hover-Scrubber (wie GalleryView) */}
                {!opts?.isDecorative &&
                  !opts?.disableScrubber &&
                  !inlineActive &&
                  scrubberCount > 1 &&
                  hoveredThumbKey === k ? (
                    <div
                      className="absolute inset-x-0 bottom-0 z-30 pointer-events-none opacity-100 transition-opacity duration-150"
                      onClick={(e) => e.stopPropagation()}
                      onMouseDown={(e) => e.stopPropagation()}
                      onMouseEnter={() => setScrubHovering(k, true)}
                      onMouseLeave={() => {
                        setScrubHovering(k, false)
                        setScrubActiveIndex(k, undefined)
                      }}
                    >
                      <PreviewScrubber
                        className="pointer-events-auto px-1"
                        imageCount={scrubberCount}
                        activeIndex={scrubActiveIndex}
                        onActiveIndexChange={(idx) => setScrubActiveIndex(k, idx)}
                        onIndexClick={(index) => {
                          if (isSmall || opts?.disableInline) {
                            handleScrubberClickIndex(j, index, scrubberCount)
                            return
                          }

                          const seconds = scrubberStepSeconds > 0 ? index * scrubberStepSeconds : 0

                          if (startInlineAt) {
                            startInlineAt(k, seconds, inlineDomId)
                            requestAnimationFrame(() => {
                              if (!tryAutoplayInline(inlineDomId)) {
                                requestAnimationFrame(() => tryAutoplayInline(inlineDomId))
                              }
                            })
                            return
                          }

                          startInline(k)
                          requestAnimationFrame(() => {
                            if (!tryAutoplayInline(inlineDomId)) {
                              requestAnimationFrame(() => tryAutoplayInline(inlineDomId))
                            }
                          })
                        }}
                        stepSeconds={scrubberStepSeconds}
                        durationSeconds={heatmapDuration}
                        heatmapSegments={heatmapSegments}
                      />
                    </div>
                  ) : null}

                  {deletingKeys.has(k) || deleteDone ? (
                    <DeletingPreviewOverlay done={deleteDone} />
                  ) : keepBusy || keepDone ? (
                    <DeletingPreviewOverlay
                      done={keepDone}
                      label={keepDone ? 'Behalten' : 'Wird behalten'}
                    />
                  ) : null}
              </div>
            </div>

            {/* Footer / Meta (wie Gallery strukturiert) */}
            <div
              className="relative min-h-[112px] overflow-visible px-4 py-3 border-t border-black/5 dark:border-white/10 bg-white dark:bg-gray-900"
              onPointerDownCapture={() => {
                footerTapRef.current[k] = true
              }}
              onClick={(e) => {
                e.preventDefault()
                e.stopPropagation()

                if (isSmall) return

                if (selectionModeActive) {
                  onToggleSelected(j)
                  return
                }

                openPlayer(j)
              }}
            >
              <div className="flex min-h-6 items-center justify-between gap-2">
                <div className="min-w-0 flex-1">
                  <div className="flex min-h-6 items-center justify-between gap-2 min-w-0">
                    <div className="min-w-0 flex items-center gap-1.5">
                      <div className="min-w-0 truncate text-sm font-semibold text-gray-900 dark:text-white">
                        {model}
                      </div>

                      <ModelGenderIcon
                        gender={flags?.gender}
                        className="shrink-0"
                      />

                      <CountryFlag
                        country={modelCountry}
                        size="sm"
                        className="shrink-0"
                      />
                    </div>

                    {(renderIntegrityBadge || (renderPostworkBadge && livePostwork)) ? (
                      <div className="relative z-20 shrink-0 flex h-6 items-center gap-2">
                        {renderIntegrityBadge ? renderIntegrityBadge(j) : null}
                        {renderPostworkBadge && livePostwork ? renderPostworkBadge(livePostwork) : null}
                      </div>
                    ) : null}
                  </div>

                  <div className="mt-0.5 flex items-start gap-2 min-w-0">
                    {isHot ? (
                      <span className="shrink-0 self-start rounded bg-amber-500/15 px-1.5 py-0.5 text-[11px] leading-none font-semibold text-amber-800 dark:text-amber-300">
                        HOT
                      </span>
                    ) : null}

                    <span className="min-w-0 truncate text-xs text-gray-500 dark:text-gray-400">
                      {stripHotPrefix(fileRaw) || '—'}
                    </span>
                  </div>
                </div>
              </div>

              {/* Meta + Actions (nicht im Video) */}
              <div
                className="mt-2"
                onClick={(e) => e.stopPropagation()}
                onMouseDown={(e) => e.stopPropagation()}
              >
                {/* Actions: volle Breite */}
                <div className="w-full">
                  <div className="w-full rounded-md bg-gray-50/70 p-1 ring-1 ring-black/5 dark:bg-white/5 dark:ring-white/10">
                    <RecordJobActions
                      job={jobForDetails(j)}
                      variant="table"
                      busy={busy}
                      collapseToMenu
                      compact={false}
                      isHot={isHot}
                      isFavorite={isFav}
                      isLiked={isLiked}
                      isWatching={isWatching}
                      onToggleWatch={onToggleWatch}
                      onToggleFavorite={onToggleFavorite}
                      onToggleLike={onToggleLike}
                      onToggleHot={onToggleHot}
                      onKeep={keepVideo}
                      onDelete={deleteVideo}
                      onSplit={onSplit}
                      onAddToDownloads={onAddToDownloads}
                      order={['watch', 'favorite', 'like', 'hot', 'keep', 'delete', 'split', 'details', 'add', 'training']}
                      className="w-full gap-1.5"
                    />
                  </div>
                </div>
              </div>

              {/* Tags */}
              <div className="mt-2" onClick={(e) => e.stopPropagation()} onMouseDown={(e) => e.stopPropagation()}>
                <TagOverflowRow
                  rowKey={k}
                  tags={tags}
                  activeTagSet={activeTagSet}
                  lower={lower}
                  onToggleTagFilter={onToggleTagFilter}
                />
              </div>
            </div>
          </Card>
        </CardBlurWrapper>
      </div>
    )

    return {
      k,
      j,
      busy,
      isHot,
      inlineDomId,
      cardInner,
      inlineActive,
      allowTeaserAnimation,
    }
  }

  const [skippedMobileKeys, setSkippedMobileKeys] = useState<string[]>([])
  const mobileStackFrameRef = useRef<HTMLDivElement | null>(null)
  const mobileStackHeightTimerRef = useRef<number | null>(null)
  const [mobileStackMinHeight, setMobileStackMinHeight] = useState(0)

  const lockMobileStackHeight = useCallback((durationMs = 520) => {
    if (!isSmall) return

    const height = mobileStackFrameRef.current?.getBoundingClientRect().height ?? 0
    if (height > 0) {
      setMobileStackMinHeight(Math.ceil(height))
    }

    if (mobileStackHeightTimerRef.current != null) {
      window.clearTimeout(mobileStackHeightTimerRef.current)
    }

    mobileStackHeightTimerRef.current = window.setTimeout(() => {
      setMobileStackMinHeight(0)
      mobileStackHeightTimerRef.current = null
    }, durationMs)
  }, [isSmall])

  useEffect(() => {
    return () => {
      if (mobileStackHeightTimerRef.current != null) {
        window.clearTimeout(mobileStackHeightTimerRef.current)
        mobileStackHeightTimerRef.current = null
      }
    }
  }, [])

  const mobileOrderedRows = useMemo(() => {
    if (!isSmall || skippedMobileKeys.length === 0) return rows

    const skippedSet = new Set(skippedMobileKeys)

    const front = rows.filter((row) => !skippedSet.has(keyFor(row)))

    const byKey = new Map(rows.map((row) => [keyFor(row), row] as const))

    const back = skippedMobileKeys
      .map((key) => byKey.get(key))
      .filter((row): row is RecordJob => Boolean(row))

    return [...front, ...back]
  }, [isSmall, rows, skippedMobileKeys, keyFor])

  const skipTopMobileCard = useCallback(() => {
    if (!isSmall || mobileOrderedRows.length <= 1) return

    const topRow = mobileOrderedRows[0]
    if (!topRow) return

    const topKey = keyFor(topRow)

    lockMobileStackHeight()
    setSpeedHoldKey((prev) => (prev === topKey ? null : prev))

    setSkippedMobileKeys((prev) => [
      ...prev.filter((key) => key !== topKey),
      topKey,
    ])
  }, [isSmall, mobileOrderedRows, keyFor, lockMobileStackHeight])
  
  const mobileStackDepth = 3

  const mobileLoadedStackRows = isSmall
    ? mobileOrderedRows.slice(0, mobileStackDepth)
    : []

  const mobileVisibleStackRows = mobileLoadedStackRows
  const mobilePreloadedRow = null
  const mobileTopRow = mobileVisibleStackRows[0] ?? null

  const mobileTopKey = isSmall && mobileVisibleStackRows[0]
    ? keyFor(mobileVisibleStackRows[0])
    : ''

  const [mobileTopActivationNonce, setMobileTopActivationNonce] = useState(0)

  const [mobileTopTeaserEnabled, setMobileTopTeaserEnabled] = useState(false)
  const mobileTopTeaserEnableTimerRef = useRef<number | null>(null)

  const [mobileTopTapEnabled, setMobileTopTapEnabled] = useState(true)
  const mobileTopTapEnableTimerRef = useRef<number | null>(null)
  const [audioButtonDismissedForKey, setAudioButtonDismissedForKey] = useState<string | null>(null)
  const [teaserAudioEnabledForKey, setTeaserAudioEnabledForKey] = useState<string | null>(null)

  useEffect(() => {
    if (!isSmall) {
      setSkippedMobileKeys([])
      return
    }

    const validKeys = new Set(rows.map((row) => keyFor(row)))

    setSkippedMobileKeys((prev) => {
      const next = prev.filter((key) => validKeys.has(key))
      return next.length === prev.length ? prev : next
    })
  }, [isSmall, rows, keyFor])

  useEffect(() => {
    if (!isSmall) return
    if (!mobileTopKey) return
    if (teaserAudioEnabledForKey !== mobileTopKey) return
    if (!mobileTeaserAudioUnlocked) return

    requestAnimationFrame(() => {
      requestAnimationFrame(() => {
        teaserPlayFnsRef.current[mobileTopKey]?.()
      })
    })
  }, [isSmall, mobileTopKey, teaserAudioEnabledForKey, mobileTeaserAudioUnlocked])

  useEffect(() => {
    if (mobileTopTeaserEnableTimerRef.current != null) {
      window.clearTimeout(mobileTopTeaserEnableTimerRef.current)
      mobileTopTeaserEnableTimerRef.current = null
    }

    if (mobileTopTapEnableTimerRef.current != null) {
      window.clearTimeout(mobileTopTapEnableTimerRef.current)
      mobileTopTapEnableTimerRef.current = null
    }

    setAudioButtonDismissedForKey(null)
    setTeaserAudioEnabledForKey(null)

    if (!isSmall || !mobileTopKey) {
      setMobileTopTeaserEnabled(true)
      setMobileTopTapEnabled(true)
      return
    }

    // neue Top-Card kurz still + taps sperren
    setMobileTopTeaserEnabled(false)
    setMobileTopTapEnabled(false)

    mobileTopTeaserEnableTimerRef.current = window.setTimeout(() => {
      setMobileTopTeaserEnabled(true)

      requestAnimationFrame(() => {
        requestAnimationFrame(() => {
          setMobileTopActivationNonce((n) => n + 1)
        })
      })

      mobileTopTeaserEnableTimerRef.current = null
    }, 220)

    // Taps etwas später wieder erlauben
    mobileTopTapEnableTimerRef.current = window.setTimeout(() => {
      setMobileTopTapEnabled(true)
      mobileTopTapEnableTimerRef.current = null
    }, 520)

    return () => {
      if (mobileTopTeaserEnableTimerRef.current != null) {
        window.clearTimeout(mobileTopTeaserEnableTimerRef.current)
        mobileTopTeaserEnableTimerRef.current = null
      }
      if (mobileTopTapEnableTimerRef.current != null) {
        window.clearTimeout(mobileTopTapEnableTimerRef.current)
        mobileTopTapEnableTimerRef.current = null
      }
    }
  }, [isSmall, mobileTopKey])

  // größerer Peek-Offset für stärkeren Stack-Effekt
  const stackPeekOffsetPx = 15

  // weil wir nach OBEN stacken, brauchen wir oben Platz
  const stackExtraTopPx = Math.max(0, Math.min(mobileVisibleStackRows.length, mobileStackDepth) - 1)
  const mobileStackShellStyle = useMemo(() => ({
    overflowX: 'clip',
    overflowAnchor: 'none',
    minHeight: mobileStackMinHeight > 0 ? `${mobileStackMinHeight}px` : undefined,
  }) as CSSProperties, [mobileStackMinHeight])
  

  return (
    <div ref={rootRef} className="relative">
      {!isSmall ? (
        <div
          className="grid gap-3"
          style={{
            gridTemplateColumns: `repeat(auto-fill, minmax(min(100%, ${desktopGridMinCardWidth}px), 1fr))`,
          }}
        >
          {rows.map((j) => {
            const k = keyFor(j)

            return (
              <FinishedDownloadsDesktopCard
                key={`${k}__desktop`}
                job={j}
                selectionStore={selectionStore}
                selectionModeActive={selectionModeActive}
                isDeleting={deletingKeys.has(k)}
                isDeleted={deletedSuccessKeys.has(k)}
                isKept={keptSuccessKeys.has(k)}
                isKeeping={keepingKeys.has(k)}
                isRemoving={removingKeys.has(k)}
                postworkBadge={getPostworkSummaryForOutput(j.output, postworkByFile)}
                blurPreviews={blurPreviews}
                durationSeconds={durations[k]}
                teaserState={teaserState}
                inlinePlay={inlinePlay}
                assetNonce={assetNonceForJob?.(j) ?? assetNonce}
                forcePreviewMuted={forcePreviewMuted}
                keyFor={keyFor}
                baseName={baseName}
                modelNameFromOutput={modelNameFromOutput}
                runtimeOf={runtimeOf}
                sizeBytesOf={sizeBytesOf}
                formatBytes={formatBytes}
                lower={lower}
                jobForDetails={jobForDetails}
                handleScrubberClickIndex={handleScrubberClickIndex}
                handleDuration={handleDuration}
                handleResolution={handleResolution}
                resolutionLabelOf={resolutionLabelOf}
                onToggleSelected={onToggleSelected}
                onHoverPreviewKeyChange={onHoverPreviewKeyChange}
                onOpenPlayer={onOpenPlayer}
                openPlayer={openPlayer}
                startInline={startInline}
                registerTeaserHost={registerTeaserHost}
                modelsByKey={modelsByKey}
                activeTagSet={activeTagSet}
                onToggleTagFilter={onToggleTagFilter}
                onToggleHot={onToggleHot}
                onToggleFavorite={onToggleFavorite}
                onToggleLike={onToggleLike}
                onToggleWatch={onToggleWatch}
                onSplit={onSplit}
                onAddToDownloads={onAddToDownloads}
                deleteVideo={deleteVideo}
                keepVideo={keepVideo}
                renderPostworkBadge={renderPostworkBadge}
                renderIntegrityBadge={renderIntegrityBadge}
                renderRatingOverlay={renderRatingOverlay}
              />
            )
          })}
        </div>
      ) : (
        <div className="relative overflow-y-visible touch-pan-y" style={{ overflowX: 'clip' }}>
          {mobileOrderedRows.length === 0 ? (
              isLoading ? (
                <MobileLoadingStack
                  stackPeekOffsetPx={stackPeekOffsetPx}
                  stackExtraTopPx={stackExtraTopPx}
                />
              ) : null
            ) : (
              <div
                ref={mobileStackFrameRef}
                className="relative mx-auto w-full max-w-[560px] overflow-y-visible"
                style={mobileStackShellStyle}
              >
                {/* feste Höhe für den Stapel (damit die unteren Karten sichtbar “rausgucken”) */}
                <div
                  className="relative overflow-visible"
                  style={{
                    paddingTop: stackExtraTopPx > 0 ? `${stackExtraTopPx}px` : undefined,
                  }}
                >
                  {(() => {
                    const visible = mobileVisibleStackRows
                    const topRow = visible[0]
                    const backRows = visible.slice(1)
                    const preloadRow = mobilePreloadedRow

                    return (
                      <>
                      {/* Hintere Karten zuerst (absolut, dekorativ) */}
                      {backRows
                        .map((j, backIdx) => {
                          const idx = backIdx + 1 // 1,2...
                          const { k, cardInner } = renderCardItem(j, {
                            forceStill: true,
                            disableInline: true,
                            disablePreviewHover: true,
                            isDecorative: true,
                            forceLoadStill: true,
                            blur: true,
                            preloadTeaserWhenStill: true,
                            renderRole: 'back',
                          })

                          const depth = idx
                          const y = -(depth * stackPeekOffsetPx)
                          const scale = 1 - depth * 0.03
                          const opacity = 1 - depth * 0.14

                          return (
                            <div
                              key={`${k}__back_${depth}`}
                              className="absolute inset-x-0 top-0 pointer-events-none will-change-[transform,opacity] transition-[transform,opacity] duration-220 ease-out motion-reduce:transition-none"
                              style={{
                                zIndex: 20 - depth,
                                transform: `translateY(${y}px) scale(${scale}) translateZ(0)`,
                                opacity,
                                transformOrigin: 'top center',
                              }}
                              aria-hidden="true"
                            >
                              <div className="relative">
                                {cardInner}
                                <div className="absolute inset-0 rounded-lg bg-white/8 dark:bg-black/8" />
                              </div>
                            </div>
                          )
                        })
                        .reverse()}

                        {preloadRow ? (() => {
                          const { k, cardInner } = renderCardItem(preloadRow, {
                            forceStill: true,
                            disableInline: true,
                            disablePreviewHover: true,
                            isDecorative: true,
                            forceLoadStill: true,
                            preloadTeaserWhenStill: true,
                            renderRole: 'preload',
                          })

                          return (
                            <div
                              key={`${k}__preload`}
                              className="absolute inset-x-0 top-0 pointer-events-none"
                              style={{
                                zIndex: 1,
                                opacity: 0,
                                transform: `translateY(${-(mobileStackDepth * stackPeekOffsetPx)}px) scale(${1 - mobileStackDepth * 0.03}) translateZ(0)`,
                                transformOrigin: 'top center',
                              }}
                              aria-hidden="true"
                            >
                              {cardInner}
                            </div>
                          )
                        })() : null}

                        {/* Oberste Karte im Flow -> bestimmt die echte Höhe */}
                        {topRow ? (() => {
                          const j = topRow
                          const { k, busy, isHot, cardInner, inlineDomId, inlineActive, allowTeaserAnimation } = renderCardItem(j, {
                            forceLoadStill: false,
                            forceStill: !mobileTopTeaserEnabled,
                            mobileStackTopOnlyVideo: true,
                            disableScrubber: true,
                            animateUnblurOnMount: true,
                            renderRole: 'top',
                          })

                          const topCardSelectionModeActive = selectionModeActive

                          const canSpeedHold =
                            !inlineActive &&
                            !busy &&
                            (
                              allowTeaserAnimation ||
                              (topCardSelectionModeActive && teaserState.mode !== 'still')
                            )

                          return (
                            <div
                              key={`${k}__top`}
                              className="relative touch-pan-y"
                              style={{ zIndex: 30 }}
                            >
                              <PromoteToFrontWrapper animateOnMount>
                                <SwipeCard
                                  ref={(h) => {
                                    if (h) swipeRefs.current.set(k, h)
                                    else swipeRefs.current.delete(k)
                                  }}
                                  enabled
                                  disabled={busy}
                                  ignoreFromBottomPx={110}
                                  doubleTapMs={360}
                                  doubleTapMaxMovePx={48}
                                  pressDelayMs={220}
                                  enablePressHold={canSpeedHold}
                                  onDoubleTap={async () => {
                                    if (isHot) return
                                    await onToggleHot?.(j)
                                  }}
                                  onTap={() => {
                                    if (!mobileTopTapEnabled) return

                                    if (topCardSelectionModeActive) {
                                      onToggleSelected(j)
                                      return
                                    }

                                    if (footerTapRef.current[k]) {
                                      delete footerTapRef.current[k]
                                      openPlayer(j)
                                      return
                                    }

                                    startInline(k)
                                    requestAnimationFrame(() => {
                                      if (!tryAutoplayInline(inlineDomId)) {
                                        requestAnimationFrame(() => tryAutoplayInline(inlineDomId))
                                      }
                                    })
                                  }}
                                  onSwipeLeft={() => {
                                    lockMobileStackHeight()
                                    setMobileTopTeaserEnabled(false)
                                    void deleteVideo(j, { optimisticRemove: true })
                                    return true
                                  }}
                                  onSwipeRight={async () => {
                                    lockMobileStackHeight()
                                    setMobileTopTeaserEnabled(false)
                                    return await keepVideo(j)
                                  }}
                                  onPressStart={(info) => {
                                    if (info.pointerType === 'mouse' && info.button !== 0) return
                                    if (!canSpeedHold) return

                                    setMobileTopTeaserEnabled(true)
                                    setSpeedHoldKey(k)
                                  }}
                                  onPressEnd={() => {
                                    setSpeedHoldKey((prev) => (prev === k ? null : prev))
                                  }}
                                >
                                  {cardInner}
                                </SwipeCard>
                              </PromoteToFrontWrapper>
                            </div>
                          )
                        })() : null}
                      </>
                    )
                  })()}
                </div>
              </div>
            )}

            {mobileTopRow ? (
              <div className="mt-3 space-y-2">
                {mobileOrderedRows.length > 1 ? (
                  <Button
                    size="sm"
                    variant="soft"
                    onClick={skipTopMobileCard}
                    className="w-full"
                  >
                    Überspringen
                  </Button>
                ) : null}
              </div>
            ) : null}
        </div>
      )}

      {!isSmall && isLoading && rows.length === 0 ? (
        <div className="absolute inset-0 z-20 grid place-items-center rounded-lg bg-white/50 backdrop-blur-[2px] dark:bg-gray-950/40">
          <div className="flex items-center gap-3 rounded-lg border border-gray-200 bg-white/80 px-3 py-2 shadow-sm dark:border-white/10 dark:bg-gray-900/70">
            <div className="h-5 w-5 animate-spin rounded-full border-2 border-gray-300 border-t-transparent dark:border-white/20 dark:border-t-transparent" />
            <div className="text-sm font-medium text-gray-800 dark:text-gray-100">Lade…</div>
          </div>
        </div>
      ) : null}
    </div>
  )
}

const MemoFinishedDownloadsCardsView = memo(FinishedDownloadsCardsView)
export default MemoFinishedDownloadsCardsView
