fix dossier and shadows for scrollable dialogues

Этот коммит содержится в:
Constantin Rusu 2025-01-28 00:45:38 +00:00
родитель a457733ce9
Коммит 444400bddf
6 изменённых файлов: 166 добавлений и 80 удалений

Просмотреть файл

@ -54,8 +54,8 @@ export const DossierPanel = ({ entries, choices = [] }: DossierPanelProps) => {
{t('dossier.button')}
</Button>
</SheetTrigger>
<SheetContent className="w-[95vw] sm:w-[90vw] lg:w-[45vw] bg-[#1a1a1a] border-gray-700 text-white overflow-hidden p-8 pt-10 !max-w-[100vw]">
<SheetHeader className="mb-6">
<SheetContent className="w-[95vw] sm:w-[90vw] lg:w-[45vw] bg-[#1a1a1a] border-gray-700 text-white overflow-hidden p-2 sm:p-8 pt-10 !max-w-[100vw] flex flex-col">
<SheetHeader className="mb-6 flex-none">
<SheetTitle className="text-yellow-500 relative">
<span className="absolute -top-6 left-0 text-xs text-red-500 tracking-wider font-mono">
{t('dossier.clearanceRequired')}
@ -64,13 +64,14 @@ export const DossierPanel = ({ entries, choices = [] }: DossierPanelProps) => {
</SheetTitle>
</SheetHeader>
<div className="bg-gray-800/30 p-6 rounded-md border border-gray-700 mb-6">
<MetricsDisplay choices={choices} className="pl-0" />
</div>
<ScrollArea className="flex-1 min-h-0">
<div className="space-y-6 pb-4 px-2 sm:px-4">
<div className="bg-gray-800/30 p-4 sm:p-6 rounded-md border border-gray-700">
<MetricsDisplay choices={choices} className="pl-0" />
</div>
<Separator className="my-6 bg-gray-700" />
<ScrollArea className="h-[calc(100vh-320px)] pr-4">
<div className="space-y-6 pb-16">
<Separator className="bg-gray-700" />
{entries.length === 0 ? (
<p className="text-gray-400 italic">{t('dossier.noIntelligence')}</p>
) : (
@ -80,7 +81,7 @@ export const DossierPanel = ({ entries, choices = [] }: DossierPanelProps) => {
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: index * 0.1 }}
className="space-y-4 relative bg-gray-800/30 p-6 rounded-md border border-gray-700"
className="space-y-4 relative bg-gray-800/30 p-4 sm:p-6 rounded-md border border-gray-700"
>
<div>
<h3 className="text-yellow-500 font-semibold flex items-center gap-3">

Просмотреть файл

@ -66,19 +66,21 @@ export const EndGameDialog = ({ onContinue, startFade }: EndGameDialogProps) =>
return (
<Dialog open={open}>
<DialogPortal>
<DialogOverlay className="z-[45]" />
<DialogContent
className={cn(
"bg-black/95 text-white border-emerald-900/50 max-w-2xl [&>button]:hidden",
"z-[50] fixed left-[50%] top-[50%] grid w-full translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg"
)}
onPointerDownOutside={(e) => e.preventDefault()}
onEscapeKeyDown={(e) => e.preventDefault()}
>
<DialogOverlay className={cn(
"bg-black/95 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
startFade && "animate-fade-out"
)} />
<DialogContent className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-emerald-900/50 bg-black/95 p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=open]:slide-in-from-left-1/2 sm:rounded-lg",
startFade && "animate-fade-out",
"max-h-[90vh] overflow-y-auto scrollbar-thin scrollbar-thumb-emerald-500/20 scrollbar-track-transparent",
"relative after:absolute after:bottom-0 after:left-0 after:right-0 after:h-16 after:bg-gradient-to-t after:from-black/95 after:to-transparent after:pointer-events-none after:z-50"
)}>
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.8, ease: "easeOut" }}
className="relative z-10 pb-16"
>
<DialogHeader>
<DialogTitle className="text-emerald-500 text-2xl mb-6">

Просмотреть файл

@ -21,9 +21,12 @@
inset 0 0 60px rgba(0, 0, 0, 0.6);
width: 100%;
max-width: 800px;
max-height: 80vh;
margin: 0 auto;
position: relative;
color: #e8e8e8;
display: flex;
flex-direction: column;
}
@media (min-width: 640px) {
@ -92,12 +95,56 @@
white-space: pre-wrap;
line-height: 1.5;
text-shadow: 0 0 1px rgba(255, 255, 255, 0.1);
overflow-y: auto;
flex: 1;
padding-right: 0.5rem;
position: relative;
-webkit-overflow-scrolling: touch;
max-height: calc(80vh - 12rem);
}
/* Gradient container */
.memo-gradient {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 60px;
background: linear-gradient(to top, #1a1715 10%, rgba(26, 23, 21, 0.8) 40%, transparent 100%);
pointer-events: none;
opacity: 0;
transition: opacity 0.3s ease;
z-index: 10;
}
.memo-gradient.show {
opacity: 0.95;
}
/* Custom scrollbar for WebKit browsers */
.memo-body::-webkit-scrollbar {
width: 8px;
}
.memo-body::-webkit-scrollbar-track {
background: transparent;
}
.memo-body::-webkit-scrollbar-thumb {
background-color: rgba(255, 255, 255, 0.2);
border-radius: 4px;
}
/* Show scrollbar on hover */
.memo-body:hover::-webkit-scrollbar-thumb {
background-color: rgba(255, 255, 255, 0.3);
}
/* Remove the old gradient and padding styles */
.memo-body p {
margin-bottom: 1.5rem;
}
.memo-body p:last-child {
margin-bottom: 0;
margin-bottom: 1rem;
}

Просмотреть файл

@ -1,7 +1,8 @@
import React from 'react';
import React, { useEffect, useState, useRef, useCallback } from 'react';
import './ExpertMemo.css';
import { useTranslation } from 'react-i18next';
import { BriefingAudio } from './BriefingAudio';
import { cn } from '@/lib/utils';
interface ExpertMemoProps {
from: string;
@ -16,6 +17,31 @@ export const ExpertMemo: React.FC<ExpertMemoProps> = ({ from, subject, children,
const { t } = useTranslation();
const highlightColor = isAlert ? 'text-red-500' : 'text-yellow-500';
const memoClass = isAlert ? 'expert-memo alert' : 'expert-memo';
const [showGradient, setShowGradient] = useState(false);
const memoBodyRef = useRef<HTMLDivElement>(null);
const checkScroll = useCallback(() => {
const element = memoBodyRef.current;
if (element) {
const hasOverflow = element.scrollHeight > element.clientHeight;
const isAtBottom = Math.abs(element.scrollHeight - element.clientHeight - element.scrollTop) < 1;
setShowGradient(hasOverflow && !isAtBottom);
}
}, []);
useEffect(() => {
const element = memoBodyRef.current;
if (element) {
checkScroll();
element.addEventListener('scroll', checkScroll);
window.addEventListener('resize', checkScroll);
return () => {
element.removeEventListener('scroll', checkScroll);
window.removeEventListener('resize', checkScroll);
};
}
}, [checkScroll]);
// Function to wrap text content in paragraph tags
const formatContent = (content: React.ReactNode) => {
@ -53,9 +79,10 @@ export const ExpertMemo: React.FC<ExpertMemoProps> = ({ from, subject, children,
</div>
)}
</div>
<div className="memo-body text-gray-300">
<div ref={memoBodyRef} className="memo-body">
{formatContent(children)}
</div>
<div className={cn("memo-gradient", showGradient && "show")} />
</div>
);
};

Просмотреть файл

@ -24,48 +24,54 @@ export const IntroDialog = ({ onStartAudio }: IntroDialogProps) => {
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent className="bg-black/90 text-white border-gray-700 max-w-2xl">
<DialogHeader>
<DialogTitle className="text-yellow-500 text-2xl mb-6">
{t('intro.title')}
</DialogTitle>
<div className="space-y-6 text-gray-200">
<div className="flex items-center gap-4">
<div className="text-4xl">🎯</div>
<p className="text-lg font-medium">
{t('intro.mission')}
</p>
</div>
<p className="text-base">
{t('intro.explanation')}
</p>
<p className="text-base">
{t('intro.howToPlay.description')}
</p>
<p className="text-yellow-500 text-sm">
{t('intro.reminder')}
</p>
</div>
</DialogHeader>
<DialogContent
className="bg-black text-white border-gray-700 max-w-2xl max-h-[85vh] overflow-y-auto"
>
<div className="relative flex-1 min-h-0">
<div className="space-y-6">
<DialogHeader>
<DialogTitle className="text-yellow-500 text-2xl mb-6">
{t('intro.title')}
</DialogTitle>
<div className="space-y-6 text-gray-200">
<div className="flex items-center gap-4">
<div className="text-4xl">🎯</div>
<p className="text-lg font-medium">
{t('intro.mission')}
</p>
</div>
<p className="text-base">
{t('intro.explanation')}
</p>
<p className="text-base">
{t('intro.howToPlay.description')}
</p>
<p className="text-yellow-500 text-sm">
{t('intro.reminder')}
</p>
</div>
</DialogHeader>
<div className="flex flex-col items-center gap-6 mt-8">
<div className="flex items-center gap-2 self-start">
<LanguageSwitcher />
<span className="text-xs text-gray-400">
{t('languageSwitcher.hint')}
</span>
<div className="flex flex-col items-center gap-6 mt-8">
<div className="flex items-center gap-2 self-start">
<LanguageSwitcher />
<span className="text-xs text-gray-400">
{t('languageSwitcher.hint')}
</span>
</div>
<Button
onClick={handleBeginSimulation}
className="bg-yellow-500 hover:bg-yellow-600 text-black font-semibold w-full py-6 text-lg"
>
{t('buttons.beginSimulation')}
</Button>
</div>
</div>
<Button
onClick={handleBeginSimulation}
className="bg-yellow-500 hover:bg-yellow-600 text-black font-semibold w-full py-6 text-lg"
>
{t('buttons.beginSimulation')}
</Button>
</div>
</DialogContent>
</Dialog>

Просмотреть файл

@ -32,25 +32,28 @@ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
))
>(({ className, children, ...props }, ref) => {
return (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 flex flex-col w-full max-w-lg translate-x-[-50%] translate-y-[-50%] border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=open]:slide-in-from-left-1/2 sm:rounded-lg",
"max-h-[85vh] overflow-y-auto scrollbar-thin scrollbar-thumb-gray-400 scrollbar-track-transparent",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
);
})
DialogContent.displayName = DialogPrimitive.Content.displayName
const DialogHeader = ({