Spaces:
Sleeping
Sleeping
Refactor App and Chat components for improved layout and user experience
Browse files- Updated the App component to enhance layout responsiveness and added flex properties for better alignment.
- Improved the Chat component's structure for consistent styling and overflow handling.
- Introduced thin scrollbar styles in global CSS for a more modern look.
- Enhanced FileManager component with minimum height and improved button styles for better usability.
- app/frontend/src/App.js +64 -58
- app/frontend/src/components/Chat.js +40 -38
- app/frontend/src/components/FileManager.js +9 -8
- app/frontend/src/globals.css +54 -0
app/frontend/src/App.js
CHANGED
|
@@ -58,8 +58,8 @@ function App() {
|
|
| 58 |
|
| 59 |
return (
|
| 60 |
<ThemeProvider defaultTheme="light">
|
| 61 |
-
<div className="min-h-screen bg-background text-foreground transition-colors duration-300">
|
| 62 |
-
<header className="bg-primary text-primary-foreground py-4 px-6 shadow-md">
|
| 63 |
<div className="container mx-auto flex items-center justify-between">
|
| 64 |
<div className="flex items-center gap-2">
|
| 65 |
<span role="img" aria-label="brain" className="text-2xl">π§ </span>
|
|
@@ -68,65 +68,71 @@ function App() {
|
|
| 68 |
<ThemeToggle />
|
| 69 |
</div>
|
| 70 |
</header>
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
{!showUploadForm && uploadedFiles.length > 0 && (
|
| 80 |
-
<div className="rounded-lg border border-border bg-card p-6 shadow-sm transition-colors duration-300">
|
| 81 |
-
<FileManager
|
| 82 |
-
files={uploadedFiles}
|
| 83 |
-
activeFileIndex={activeFileIndex}
|
| 84 |
-
onSelectFile={handleSelectFile}
|
| 85 |
-
onUploadNew={handleUploadNew}
|
| 86 |
/>
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
<button
|
| 107 |
-
key={idx}
|
| 108 |
-
className="text-xs bg-secondary text-secondary-foreground px-3 py-2 rounded-full hover:bg-secondary/80 transition-colors duration-200 flex items-center gap-1"
|
| 109 |
-
onClick={() => handleQuestionSelect(question)}
|
| 110 |
-
>
|
| 111 |
-
<span role="img" aria-label="question" className="text-xs">β</span>
|
| 112 |
-
{question}
|
| 113 |
-
</button>
|
| 114 |
-
))}
|
| 115 |
-
</div>
|
| 116 |
</div>
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
</main>
|
| 129 |
-
|
|
|
|
| 130 |
<p>Made with <span role="img" aria-label="heart" className="text-red-500">β€οΈ</span> and Shadcn/UI</p>
|
| 131 |
</footer>
|
| 132 |
</div>
|
|
|
|
| 58 |
|
| 59 |
return (
|
| 60 |
<ThemeProvider defaultTheme="light">
|
| 61 |
+
<div className="min-h-screen bg-background text-foreground transition-colors duration-300 flex flex-col">
|
| 62 |
+
<header className="bg-primary text-primary-foreground py-4 px-6 shadow-md w-full">
|
| 63 |
<div className="container mx-auto flex items-center justify-between">
|
| 64 |
<div className="flex items-center gap-2">
|
| 65 |
<span role="img" aria-label="brain" className="text-2xl">π§ </span>
|
|
|
|
| 68 |
<ThemeToggle />
|
| 69 |
</div>
|
| 70 |
</header>
|
| 71 |
+
|
| 72 |
+
<main className="container mx-auto py-6 px-4 flex-1 flex items-center justify-center">
|
| 73 |
+
<div className="w-full max-w-4xl">
|
| 74 |
+
{showUploadForm && (
|
| 75 |
+
<FileUpload
|
| 76 |
+
sessionId={sessionId}
|
| 77 |
+
onUploadSuccess={handleFileUploadSuccess}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
/>
|
| 79 |
+
)}
|
| 80 |
+
|
| 81 |
+
{!showUploadForm && uploadedFiles.length > 0 && (
|
| 82 |
+
<div className="rounded-lg border border-border bg-card p-6 shadow-sm transition-colors duration-300 w-full">
|
| 83 |
+
<FileManager
|
| 84 |
+
files={uploadedFiles}
|
| 85 |
+
activeFileIndex={activeFileIndex}
|
| 86 |
+
onSelectFile={handleSelectFile}
|
| 87 |
+
onUploadNew={handleUploadNew}
|
| 88 |
+
/>
|
| 89 |
+
|
| 90 |
+
<div className="rounded-md bg-muted p-4 mb-4 transition-colors duration-300 min-h-[100px]">
|
| 91 |
+
<h3 className="font-medium mb-2 flex items-center gap-2">
|
| 92 |
+
<span role="img" aria-label="document" className="text-xl flex-shrink-0">
|
| 93 |
+
{activeFile.name.toLowerCase().endsWith('.pdf') ? 'π' : 'π'}
|
| 94 |
+
</span>
|
| 95 |
+
<span className="truncate">File: <span className="font-bold">{activeFile.name}</span></span>
|
| 96 |
+
</h3>
|
| 97 |
+
<p className="text-sm text-muted-foreground line-clamp-3">{activeFile.description}</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
</div>
|
| 99 |
+
|
| 100 |
+
<div className="min-h-[100px] mb-4">
|
| 101 |
+
{activeFile.suggestedQuestions && activeFile.suggestedQuestions.length > 0 && (
|
| 102 |
+
<div className="space-y-2 bg-card p-4 rounded-md border border-border transition-colors duration-300 h-full">
|
| 103 |
+
<h4 className="text-sm font-medium flex items-center gap-2">
|
| 104 |
+
<span role="img" aria-label="lightbulb" className="text-xl flex-shrink-0">π‘</span>
|
| 105 |
+
<span>Suggested questions:</span>
|
| 106 |
+
</h4>
|
| 107 |
+
<div className="flex flex-wrap gap-2 overflow-y-auto max-h-[100px] pb-1">
|
| 108 |
+
{activeFile.suggestedQuestions.map((question, idx) => (
|
| 109 |
+
<button
|
| 110 |
+
key={idx}
|
| 111 |
+
className="text-xs bg-secondary text-secondary-foreground px-3 py-2 rounded-full hover:bg-secondary/80 transition-colors duration-200 flex items-center gap-1 flex-shrink-0"
|
| 112 |
+
onClick={() => handleQuestionSelect(question)}
|
| 113 |
+
>
|
| 114 |
+
<span role="img" aria-label="question" className="text-xs flex-shrink-0">β</span>
|
| 115 |
+
<span className="truncate max-w-[200px]">{question}</span>
|
| 116 |
+
</button>
|
| 117 |
+
))}
|
| 118 |
+
</div>
|
| 119 |
+
</div>
|
| 120 |
+
)}
|
| 121 |
+
</div>
|
| 122 |
+
|
| 123 |
+
<Chat
|
| 124 |
+
sessionId={activeFile.sessionId}
|
| 125 |
+
docDescription={activeFile.description}
|
| 126 |
+
suggestedQuestions={activeFile.suggestedQuestions}
|
| 127 |
+
selectedQuestion={selectedQuestion}
|
| 128 |
+
onQuestionSelected={() => setSelectedQuestion('')}
|
| 129 |
+
/>
|
| 130 |
+
</div>
|
| 131 |
+
)}
|
| 132 |
+
</div>
|
| 133 |
</main>
|
| 134 |
+
|
| 135 |
+
<footer className="w-full py-4 px-6 text-center text-sm text-muted-foreground">
|
| 136 |
<p>Made with <span role="img" aria-label="heart" className="text-red-500">β€οΈ</span> and Shadcn/UI</p>
|
| 137 |
</footer>
|
| 138 |
</div>
|
app/frontend/src/components/Chat.js
CHANGED
|
@@ -148,8 +148,8 @@ const Chat = ({ sessionId, docDescription, suggestedQuestions, selectedQuestion,
|
|
| 148 |
);
|
| 149 |
|
| 150 |
return (
|
| 151 |
-
<div className="flex
|
| 152 |
-
<div className="flex-1 overflow-y-auto p-4">
|
| 153 |
{messages.length === 0 ? (
|
| 154 |
<div className="flex h-full flex-col items-center justify-center text-center">
|
| 155 |
<div className="mb-4 rounded-full bg-primary/10 p-6 transition-all duration-300">
|
|
@@ -162,45 +162,47 @@ const Chat = ({ sessionId, docDescription, suggestedQuestions, selectedQuestion,
|
|
| 162 |
</p>
|
| 163 |
</div>
|
| 164 |
) : (
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
key={index}
|
| 168 |
-
className={`mb-4 flex ${message.sender === 'user' ? 'justify-end' : 'justify-start'}`}
|
| 169 |
-
>
|
| 170 |
<div
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
${message.sender === 'user'
|
| 174 |
-
? 'bg-primary text-primary-foreground'
|
| 175 |
-
: message.isError
|
| 176 |
-
? 'bg-destructive text-destructive-foreground'
|
| 177 |
-
: 'bg-secondary text-secondary-foreground'
|
| 178 |
-
}
|
| 179 |
-
transition-all duration-200
|
| 180 |
-
`}
|
| 181 |
>
|
| 182 |
-
<
|
| 183 |
-
{
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
? '
|
| 187 |
-
:
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 195 |
</div>
|
| 196 |
</div>
|
| 197 |
-
|
| 198 |
-
|
| 199 |
)}
|
| 200 |
{isLoading && (
|
| 201 |
-
<div className="mb-4 flex justify-start">
|
| 202 |
<div className="flex items-start gap-2 max-w-[80%] rounded-lg px-4 py-3 bg-secondary text-secondary-foreground shadow-sm">
|
| 203 |
-
<span className="mt-1 text-lg">π€</span>
|
| 204 |
<div className="flex items-center space-x-2">
|
| 205 |
<div className="h-2 w-2 animate-bounce rounded-full bg-primary"></div>
|
| 206 |
<div className="h-2 w-2 animate-bounce rounded-full bg-primary" style={{ animationDelay: '0.2s' }}></div>
|
|
@@ -212,8 +214,8 @@ const Chat = ({ sessionId, docDescription, suggestedQuestions, selectedQuestion,
|
|
| 212 |
<div ref={messagesEndRef} />
|
| 213 |
</div>
|
| 214 |
|
| 215 |
-
<form onSubmit={handleSubmit} className="border-t border-border p-4 transition-colors duration-300">
|
| 216 |
-
<div className="flex space-x-2">
|
| 217 |
<Input
|
| 218 |
id="chat-input"
|
| 219 |
ref={inputRef}
|
|
@@ -227,10 +229,10 @@ const Chat = ({ sessionId, docDescription, suggestedQuestions, selectedQuestion,
|
|
| 227 |
<Button
|
| 228 |
type="submit"
|
| 229 |
disabled={isLoading || !input.trim()}
|
| 230 |
-
className="transition-colors duration-300 flex items-center gap-2"
|
| 231 |
>
|
| 232 |
<span>Send</span>
|
| 233 |
-
<span role="img" aria-label="send">π€</span>
|
| 234 |
</Button>
|
| 235 |
</div>
|
| 236 |
</form>
|
|
|
|
| 148 |
);
|
| 149 |
|
| 150 |
return (
|
| 151 |
+
<div className="flex flex-col rounded-lg border border-border bg-card/50 transition-colors duration-300 h-[600px]">
|
| 152 |
+
<div className="flex-1 overflow-y-auto p-4 w-full">
|
| 153 |
{messages.length === 0 ? (
|
| 154 |
<div className="flex h-full flex-col items-center justify-center text-center">
|
| 155 |
<div className="mb-4 rounded-full bg-primary/10 p-6 transition-all duration-300">
|
|
|
|
| 162 |
</p>
|
| 163 |
</div>
|
| 164 |
) : (
|
| 165 |
+
<div className="w-full">
|
| 166 |
+
{messages.map((message, index) => (
|
|
|
|
|
|
|
|
|
|
| 167 |
<div
|
| 168 |
+
key={index}
|
| 169 |
+
className={`mb-4 flex ${message.sender === 'user' ? 'justify-end' : 'justify-start'} w-full`}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 170 |
>
|
| 171 |
+
<div
|
| 172 |
+
className={`
|
| 173 |
+
flex items-start gap-2 max-w-[80%] rounded-lg px-4 py-3 shadow-sm
|
| 174 |
+
${message.sender === 'user'
|
| 175 |
+
? 'bg-primary text-primary-foreground'
|
| 176 |
+
: message.isError
|
| 177 |
+
? 'bg-destructive text-destructive-foreground'
|
| 178 |
+
: 'bg-secondary text-secondary-foreground'
|
| 179 |
+
}
|
| 180 |
+
transition-all duration-200
|
| 181 |
+
`}
|
| 182 |
+
>
|
| 183 |
+
<span className="mt-1 text-lg flex-shrink-0">
|
| 184 |
+
{message.sender === 'user'
|
| 185 |
+
? 'π€'
|
| 186 |
+
: message.isError
|
| 187 |
+
? 'β οΈ'
|
| 188 |
+
: 'π€'}
|
| 189 |
+
</span>
|
| 190 |
+
<div className={`text-sm ${message.sender === 'ai' ? 'markdown-content' : ''} overflow-hidden`}>
|
| 191 |
+
{message.sender === 'user' ? (
|
| 192 |
+
message.text
|
| 193 |
+
) : (
|
| 194 |
+
<MarkdownContent content={message.text} />
|
| 195 |
+
)}
|
| 196 |
+
</div>
|
| 197 |
</div>
|
| 198 |
</div>
|
| 199 |
+
))}
|
| 200 |
+
</div>
|
| 201 |
)}
|
| 202 |
{isLoading && (
|
| 203 |
+
<div className="mb-4 flex justify-start w-full">
|
| 204 |
<div className="flex items-start gap-2 max-w-[80%] rounded-lg px-4 py-3 bg-secondary text-secondary-foreground shadow-sm">
|
| 205 |
+
<span className="mt-1 text-lg flex-shrink-0">π€</span>
|
| 206 |
<div className="flex items-center space-x-2">
|
| 207 |
<div className="h-2 w-2 animate-bounce rounded-full bg-primary"></div>
|
| 208 |
<div className="h-2 w-2 animate-bounce rounded-full bg-primary" style={{ animationDelay: '0.2s' }}></div>
|
|
|
|
| 214 |
<div ref={messagesEndRef} />
|
| 215 |
</div>
|
| 216 |
|
| 217 |
+
<form onSubmit={handleSubmit} className="border-t border-border p-4 transition-colors duration-300 w-full">
|
| 218 |
+
<div className="flex space-x-2 w-full">
|
| 219 |
<Input
|
| 220 |
id="chat-input"
|
| 221 |
ref={inputRef}
|
|
|
|
| 229 |
<Button
|
| 230 |
type="submit"
|
| 231 |
disabled={isLoading || !input.trim()}
|
| 232 |
+
className="transition-colors duration-300 flex items-center gap-2 flex-shrink-0"
|
| 233 |
>
|
| 234 |
<span>Send</span>
|
| 235 |
+
<span role="img" aria-label="send" className="flex-shrink-0">π€</span>
|
| 236 |
</Button>
|
| 237 |
</div>
|
| 238 |
</form>
|
app/frontend/src/components/FileManager.js
CHANGED
|
@@ -5,10 +5,10 @@ const FileManager = ({ files, activeFileIndex, onSelectFile, onUploadNew }) => {
|
|
| 5 |
if (!files.length) return null;
|
| 6 |
|
| 7 |
return (
|
| 8 |
-
<div className="mb-6">
|
| 9 |
<div className="flex justify-between items-center mb-3">
|
| 10 |
<h3 className="text-md font-medium flex items-center gap-2">
|
| 11 |
-
<span role="img" aria-label="files" className="text-lg">π</span>
|
| 12 |
<span>Your Documents</span>
|
| 13 |
</h3>
|
| 14 |
<Button
|
|
@@ -17,28 +17,29 @@ const FileManager = ({ files, activeFileIndex, onSelectFile, onUploadNew }) => {
|
|
| 17 |
variant="outline"
|
| 18 |
className="flex items-center gap-1 text-xs"
|
| 19 |
>
|
| 20 |
-
<span role="img" aria-label="upload">π</span>
|
| 21 |
<span>Upload New</span>
|
| 22 |
</Button>
|
| 23 |
</div>
|
| 24 |
|
| 25 |
-
<div className="overflow-x-auto">
|
| 26 |
-
<div className="flex gap-2 pb-
|
| 27 |
{files.map((file, index) => (
|
| 28 |
<button
|
| 29 |
key={index}
|
| 30 |
onClick={() => onSelectFile(index)}
|
| 31 |
className={`
|
| 32 |
-
px-3 py-2 rounded-md text-sm whitespace-nowrap flex items-center gap-1.5 min-w-0 transition-colors
|
| 33 |
${activeFileIndex === index
|
| 34 |
? 'bg-primary text-primary-foreground'
|
| 35 |
: 'bg-secondary/70 hover:bg-secondary text-secondary-foreground'}
|
| 36 |
`}
|
|
|
|
| 37 |
>
|
| 38 |
-
<span role="img" aria-label="document" className="flex-
|
| 39 |
{file.name.toLowerCase().endsWith('.pdf') ? 'π' : 'π'}
|
| 40 |
</span>
|
| 41 |
-
<span className="truncate
|
| 42 |
</button>
|
| 43 |
))}
|
| 44 |
</div>
|
|
|
|
| 5 |
if (!files.length) return null;
|
| 6 |
|
| 7 |
return (
|
| 8 |
+
<div className="mb-6 min-h-[80px]">
|
| 9 |
<div className="flex justify-between items-center mb-3">
|
| 10 |
<h3 className="text-md font-medium flex items-center gap-2">
|
| 11 |
+
<span role="img" aria-label="files" className="text-lg flex-shrink-0">π</span>
|
| 12 |
<span>Your Documents</span>
|
| 13 |
</h3>
|
| 14 |
<Button
|
|
|
|
| 17 |
variant="outline"
|
| 18 |
className="flex items-center gap-1 text-xs"
|
| 19 |
>
|
| 20 |
+
<span role="img" aria-label="upload" className="flex-shrink-0">π</span>
|
| 21 |
<span>Upload New</span>
|
| 22 |
</Button>
|
| 23 |
</div>
|
| 24 |
|
| 25 |
+
<div className="overflow-x-auto pb-2 scrollbar-thin">
|
| 26 |
+
<div className="flex gap-2 pb-1 min-w-[300px]">
|
| 27 |
{files.map((file, index) => (
|
| 28 |
<button
|
| 29 |
key={index}
|
| 30 |
onClick={() => onSelectFile(index)}
|
| 31 |
className={`
|
| 32 |
+
px-3 py-2 rounded-md text-sm whitespace-nowrap flex items-center gap-1.5 min-w-0 flex-shrink-0 transition-colors
|
| 33 |
${activeFileIndex === index
|
| 34 |
? 'bg-primary text-primary-foreground'
|
| 35 |
: 'bg-secondary/70 hover:bg-secondary text-secondary-foreground'}
|
| 36 |
`}
|
| 37 |
+
style={{ minWidth: '120px', maxWidth: '200px' }}
|
| 38 |
>
|
| 39 |
+
<span role="img" aria-label="document" className="flex-shrink-0">
|
| 40 |
{file.name.toLowerCase().endsWith('.pdf') ? 'π' : 'π'}
|
| 41 |
</span>
|
| 42 |
+
<span className="truncate">{file.name}</span>
|
| 43 |
</button>
|
| 44 |
))}
|
| 45 |
</div>
|
app/frontend/src/globals.css
CHANGED
|
@@ -165,4 +165,58 @@
|
|
| 165 |
max-width: 100%;
|
| 166 |
height: auto;
|
| 167 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
}
|
|
|
|
| 165 |
max-width: 100%;
|
| 166 |
height: auto;
|
| 167 |
}
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
/* Thin scrollbar style */
|
| 171 |
+
.scrollbar-thin::-webkit-scrollbar {
|
| 172 |
+
width: 4px;
|
| 173 |
+
height: 4px;
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
.scrollbar-thin::-webkit-scrollbar-track {
|
| 177 |
+
@apply bg-secondary/30 rounded-full;
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
.scrollbar-thin::-webkit-scrollbar-thumb {
|
| 181 |
+
@apply bg-primary/30 rounded-full transition-colors duration-300;
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
.scrollbar-thin::-webkit-scrollbar-thumb:hover {
|
| 185 |
+
@apply bg-primary/50;
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
/* Make all scrollable areas use the thin scrollbar */
|
| 189 |
+
.overflow-y-auto,
|
| 190 |
+
.overflow-x-auto {
|
| 191 |
+
@apply scrollbar-thin;
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
/* Additional content height limits */
|
| 195 |
+
.line-clamp-3 {
|
| 196 |
+
display: -webkit-box;
|
| 197 |
+
-webkit-line-clamp: 3;
|
| 198 |
+
-webkit-box-orient: vertical;
|
| 199 |
+
overflow: hidden;
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
/* Fix layout shifts with transitions */
|
| 203 |
+
.max-w-md,
|
| 204 |
+
.max-w-4xl,
|
| 205 |
+
.container {
|
| 206 |
+
width: 100%;
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
/* Ensure fixed layout for markdown content */
|
| 210 |
+
.markdown-content img,
|
| 211 |
+
.markdown-content pre,
|
| 212 |
+
.markdown-content table {
|
| 213 |
+
max-width: 100%;
|
| 214 |
+
width: auto !important;
|
| 215 |
+
overflow-x: auto;
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
/* Prevent layout shifts when switching files */
|
| 219 |
+
button, a {
|
| 220 |
+
backface-visibility: hidden;
|
| 221 |
+
transform: translateZ(0);
|
| 222 |
}
|