fix: fe – only scroll to bottom on initial page load, not on tab switch

This commit is contained in:
marko-kraemer 2025-03-31 20:39:14 -07:00
parent 110df43921
commit c4cb7bc920
6 changed files with 257 additions and 440 deletions

2
backend/.gitignore vendored
View File

@ -170,5 +170,7 @@ state.json
/workspace/* /workspace/*
/workspace/** /workspace/**
# SQLite # SQLite
*.db *.db

View File

@ -1,57 +1,35 @@
# Modern Web Application Template # Brand Identity Business Card
A clean, responsive web application template built with HTML, CSS, and JavaScript. This is a modern, interactive business card design implemented with HTML, CSS, and JavaScript.
## Features ## Features
- Responsive design that works on all devices - Interactive flip animation to show front and back of the card
- Light/dark theme toggle with local storage persistence - Color theme customization with predefined color options
- Interactive demo section - Responsive design that works on different screen sizes
- Modern UI with smooth animations - Modern, clean aesthetic with attention to typography and spacing
- Easy to customize and extend - Social media icons and contact information
## Getting Started ## Usage
1. Clone or download this repository 1. Open `index.html` in any modern web browser
2. Open `index.html` in your browser 2. Click the "Flip Card" button to see both sides of the business card
3. Start customizing the template for your needs 3. Use the color dots below to change the card's color theme
4. You can also double-click on the card to flip it
## Structure
- `index.html` - Main HTML structure
- `styles.css` - All styling and responsive design
- `script.js` - JavaScript functionality
## Customization ## Customization
### Colors To customize this business card for your own use:
You can easily change the color scheme by modifying the CSS variables in the `:root` selector in `styles.css`: 1. Edit the HTML to change the name, title, and contact information
2. Modify the logo and branding elements in the CSS
3. Add or remove social media icons as needed
4. Adjust the color palette by changing the color option values
```css ## Technologies Used
:root {
--primary-color: #3498db;
--secondary-color: #2ecc71;
/* other variables */
}
```
### Adding Pages - HTML5
- CSS3 (with variables, flexbox, and transitions)
To add new pages, create additional HTML files following the same structure as `index.html`. - JavaScript
- Font Awesome for icons
### Extending Functionality - Google Fonts (Montserrat and Playfair Display)
The JavaScript file is organized to make it easy to add new features. Simply add your code to `script.js` or create new JS files and link them in the HTML.
## Browser Support
This template works in all modern browsers including:
- Chrome
- Firefox
- Safari
- Edge
## License
This template is available under the MIT License.

View File

@ -3,97 +3,54 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Modern Web Application</title> <title>Brand Identity Business Card</title>
<link rel="stylesheet" href="styles.css"> <link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600;700&family=Playfair+Display:wght@400;700&display=swap" rel="stylesheet">
</head> </head>
<body> <body>
<header> <div class="container">
<div class="container"> <div class="card-container">
<div class="logo"> <div class="card front">
<h1>WebApp</h1> <div class="logo">
<div class="logo-symbol">A</div>
<div class="logo-text">ACME</div>
</div>
<div class="tagline">Creative Solutions</div>
<div class="pattern"></div>
</div> </div>
<nav> <div class="card back">
<ul> <div class="info">
<li><a href="#" class="active">Home</a></li> <h2 class="name">ALEX JOHNSON</h2>
<li><a href="#">Features</a></li> <p class="title">Creative Director</p>
<li><a href="#">Pricing</a></li> <div class="divider"></div>
<li><a href="#">Contact</a></li> <div class="contact">
</ul> <p><i class="fas fa-phone"></i> (555) 123-4567</p>
</nav> <p><i class="fas fa-envelope"></i> alex@acmecreative.com</p>
<div class="theme-toggle"> <p><i class="fas fa-map-marker-alt"></i> 123 Design Street, Creative City</p>
<button id="themeToggle"><i class="fas fa-moon"></i></button> <p><i class="fas fa-globe"></i> www.acmecreative.com</p>
</div>
</div>
<div class="social-icons">
<i class="fab fa-linkedin"></i>
<i class="fab fa-twitter"></i>
<i class="fab fa-instagram"></i>
<i class="fab fa-behance"></i>
</div>
</div> </div>
</div> </div>
</header>
<main> <div class="controls">
<section class="hero"> <button id="flip-btn">Flip Card</button>
<div class="container"> <div class="color-options">
<h1>Welcome to Our Web Application</h1> <div class="color-option" data-color="#1a237e" style="background-color: #1a237e;"></div>
<p>A modern, responsive web application template to kickstart your projects.</p> <div class="color-option" data-color="#006064" style="background-color: #006064;"></div>
<div class="cta"> <div class="color-option" data-color="#4a148c" style="background-color: #4a148c;"></div>
<button class="btn primary">Get Started</button> <div class="color-option" data-color="#880e4f" style="background-color: #880e4f;"></div>
<button class="btn secondary">Learn More</button> <div class="color-option" data-color="#bf360c" style="background-color: #bf360c;"></div>
</div>
</div>
</section>
<section class="features">
<div class="container">
<h2>Key Features</h2>
<div class="feature-grid">
<div class="feature-card">
<i class="fas fa-mobile-alt"></i>
<h3>Responsive Design</h3>
<p>Looks great on all devices, from mobile to desktop.</p>
</div>
<div class="feature-card">
<i class="fas fa-bolt"></i>
<h3>Fast Performance</h3>
<p>Optimized for speed and efficiency.</p>
</div>
<div class="feature-card">
<i class="fas fa-palette"></i>
<h3>Modern UI</h3>
<p>Clean and intuitive user interface.</p>
</div>
<div class="feature-card">
<i class="fas fa-code"></i>
<h3>Easy to Customize</h3>
<p>Simple structure that's easy to modify and extend.</p>
</div>
</div>
</div>
</section>
<section class="demo">
<div class="container">
<h2>Interactive Demo</h2>
<div class="demo-container">
<div class="controls">
<input type="text" id="demoInput" placeholder="Enter some text...">
<button id="demoButton" class="btn primary">Submit</button>
</div>
<div id="message" class="message">
Your message will appear here.
</div>
</div>
</div>
</section>
</main>
<footer>
<div class="container">
<p>&copy; 2023 WebApp. All rights reserved.</p>
<div class="social-links">
<a href="#"><i class="fab fa-twitter"></i></a>
<a href="#"><i class="fab fa-facebook"></i></a>
<a href="#"><i class="fab fa-instagram"></i></a>
<a href="#"><i class="fab fa-github"></i></a>
</div> </div>
</div> </div>
</footer> </div>
<script src="script.js"></script> <script src="script.js"></script>
</body> </body>

View File

@ -1,62 +1,26 @@
// Wait for the DOM to be fully loaded
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
// Theme toggle functionality const cardContainer = document.querySelector('.card-container');
const themeToggle = document.getElementById('themeToggle'); const flipBtn = document.getElementById('flip-btn');
const body = document.body; const colorOptions = document.querySelectorAll('.color-option');
// Check if user has a preferred theme // Set initial primary color
const savedTheme = localStorage.getItem('theme'); document.documentElement.style.setProperty('--primary-color', '#1a237e');
if (savedTheme === 'dark') {
body.classList.add('dark-mode');
themeToggle.innerHTML = '<i class="fas fa-sun"></i>';
}
themeToggle.addEventListener('click', function() { // Flip card functionality
body.classList.toggle('dark-mode'); flipBtn.addEventListener('click', function() {
cardContainer.classList.toggle('flipped');
if (body.classList.contains('dark-mode')) {
themeToggle.innerHTML = '<i class="fas fa-sun"></i>';
localStorage.setItem('theme', 'dark');
} else {
themeToggle.innerHTML = '<i class="fas fa-moon"></i>';
localStorage.setItem('theme', 'light');
}
}); });
// Demo functionality // Color change functionality
const demoInput = document.getElementById('demoInput'); colorOptions.forEach(option => {
const demoButton = document.getElementById('demoButton'); option.addEventListener('click', function() {
const message = document.getElementById('message'); const color = this.getAttribute('data-color');
document.documentElement.style.setProperty('--primary-color', color);
demoButton.addEventListener('click', function() {
if (demoInput.value.trim() !== '') {
message.style.display = 'block';
message.textContent = demoInput.value;
message.style.backgroundColor = body.classList.contains('dark-mode') ? '#34495e' : '#f5f5f5';
message.style.color = body.classList.contains('dark-mode') ? '#f4f4f4' : '#333';
demoInput.value = '';
} else {
alert('Please enter some text first!');
}
});
// Allow pressing Enter to submit
demoInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
demoButton.click();
}
});
// Add animation to feature cards
const featureCards = document.querySelectorAll('.feature-card');
featureCards.forEach(card => {
card.addEventListener('mouseenter', function() {
this.style.transform = 'translateY(-10px)';
}); });
});
card.addEventListener('mouseleave', function() { // Double click to flip card
this.style.transform = 'translateY(0)'; cardContainer.addEventListener('dblclick', function() {
}); this.classList.toggle('flipped');
}); });
}); });

View File

@ -1,18 +1,3 @@
/* Base styles */
:root {
--primary-color: #3498db;
--secondary-color: #2ecc71;
--text-color: #333;
--bg-color: #f9f9f9;
--card-bg: #fff;
--header-bg: #fff;
--footer-bg: #333;
--footer-text: #fff;
--border-radius: 8px;
--box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
--transition: all 0.3s ease;
}
* { * {
margin: 0; margin: 0;
padding: 0; padding: 0;
@ -20,302 +5,226 @@
} }
body { body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; font-family: 'Montserrat', sans-serif;
line-height: 1.6; background-color: #f5f5f5;
color: var(--text-color); display: flex;
background-color: var(--bg-color); justify-content: center;
transition: var(--transition); align-items: center;
min-height: 100vh;
color: #333;
} }
.container { .container {
width: 90%;
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
/* Header styles */
header {
background-color: var(--header-bg);
box-shadow: var(--box-shadow);
padding: 1rem 0;
position: sticky;
top: 0;
z-index: 100;
}
header .container {
display: flex; display: flex;
justify-content: space-between; flex-direction: column;
align-items: center; align-items: center;
gap: 30px;
} }
.logo h1 { .card-container {
font-size: 1.8rem; width: 350px;
color: var(--primary-color); height: 200px;
perspective: 1000px;
} }
nav ul { .card {
display: flex; position: absolute;
list-style: none; width: 100%;
height: 100%;
transform-style: preserve-3d;
transition: transform 0.8s;
border-radius: 10px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
padding: 20px;
} }
nav ul li { .card.front {
margin-left: 1.5rem; background-color: var(--primary-color, #1a237e);
}
nav ul li a {
text-decoration: none;
color: var(--text-color);
font-weight: 500;
transition: var(--transition);
}
nav ul li a:hover, nav ul li a.active {
color: var(--primary-color);
}
/* Button styles */
.btn {
padding: 0.8rem 1.5rem;
border: none;
border-radius: var(--border-radius);
font-weight: 600;
cursor: pointer;
transition: var(--transition);
}
.primary {
background-color: var(--primary-color);
color: white; color: white;
}
.primary:hover {
background-color: #2980b9;
}
.secondary {
background-color: transparent;
border: 2px solid var(--primary-color);
color: var(--primary-color);
}
.secondary:hover {
background-color: var(--primary-color);
color: white;
}
/* Hero section */
.hero {
padding: 5rem 0;
text-align: center;
}
.hero h1 {
font-size: 2.5rem;
margin-bottom: 1rem;
}
.hero p {
font-size: 1.2rem;
max-width: 700px;
margin: 0 auto 2rem;
color: #666;
}
.cta {
display: flex; display: flex;
gap: 1rem; flex-direction: column;
justify-content: space-between;
backface-visibility: hidden;
}
.card.back {
background-color: white;
transform: rotateY(180deg);
backface-visibility: hidden;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.card-container.flipped .card.front {
transform: rotateY(180deg);
}
.card-container.flipped .card.back {
transform: rotateY(0deg);
}
.logo {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 10px;
}
.logo-symbol {
width: 40px;
height: 40px;
background-color: white;
color: var(--primary-color, #1a237e);
border-radius: 50%;
display: flex;
justify-content: center; justify-content: center;
align-items: center;
font-weight: bold;
font-size: 24px;
font-family: 'Playfair Display', serif;
} }
/* Features section */ .logo-text {
.features { font-size: 24px;
padding: 5rem 0; font-weight: 700;
background-color: var(--card-bg); letter-spacing: 2px;
} }
.features h2 { .tagline {
text-align: center; font-size: 14px;
margin-bottom: 3rem; font-weight: 300;
font-size: 2rem; letter-spacing: 1px;
margin-top: -5px;
} }
.feature-grid { .pattern {
display: grid; position: absolute;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); bottom: 0;
gap: 2rem; right: 0;
width: 150px;
height: 80px;
background: linear-gradient(135deg, transparent 25%, rgba(255, 255, 255, 0.1) 25%,
rgba(255, 255, 255, 0.1) 50%, transparent 50%,
transparent 75%, rgba(255, 255, 255, 0.1) 75%);
background-size: 20px 20px;
border-radius: 0 0 10px 0;
} }
.feature-card { .info {
background-color: var(--card-bg); padding: 10px 0;
padding: 2rem;
border-radius: var(--border-radius);
box-shadow: var(--box-shadow);
text-align: center;
transition: var(--transition);
} }
.feature-card:hover { .name {
transform: translateY(-5px); font-size: 18px;
font-weight: 600;
color: var(--primary-color, #1a237e);
letter-spacing: 1px;
} }
.feature-card i { .title {
font-size: 2.5rem; font-size: 14px;
color: var(--primary-color); color: #666;
margin-bottom: 1rem; margin-bottom: 15px;
} }
.feature-card h3 { .divider {
margin-bottom: 1rem; width: 40px;
height: 3px;
background-color: var(--primary-color, #1a237e);
margin-bottom: 15px;
} }
/* Demo section */ .contact {
.demo { font-size: 12px;
padding: 5rem 0; line-height: 1.6;
} }
.demo h2 { .contact p {
text-align: center; display: flex;
margin-bottom: 3rem; align-items: center;
font-size: 2rem; gap: 8px;
} }
.demo-container { .contact i {
max-width: 600px; color: var(--primary-color, #1a237e);
margin: 0 auto; width: 15px;
background-color: var(--card-bg); }
padding: 2rem;
border-radius: var(--border-radius); .social-icons {
box-shadow: var(--box-shadow); display: flex;
gap: 15px;
margin-top: 10px;
justify-content: flex-end;
padding: 0 10px 10px 0;
}
.social-icons i {
color: var(--primary-color, #1a237e);
font-size: 16px;
cursor: pointer;
transition: transform 0.3s;
}
.social-icons i:hover {
transform: translateY(-3px);
} }
.controls { .controls {
display: flex; display: flex;
gap: 10px; flex-direction: column;
margin-bottom: 1rem;
justify-content: center;
}
input {
padding: 0.7rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
}
.message {
padding: 1rem;
background-color: #f5f5f5;
border-radius: 4px;
margin-top: 1rem;
}
/* Footer styles */
footer {
background-color: var(--footer-bg);
color: var(--footer-text);
padding: 2rem 0;
margin-top: 3rem;
}
footer .container {
display: flex;
justify-content: space-between;
align-items: center; align-items: center;
gap: 15px;
} }
.social-links { #flip-btn {
display: flex; background-color: var(--primary-color, #1a237e);
gap: 1rem; color: white;
} border: none;
padding: 10px 20px;
.social-links a { border-radius: 5px;
color: var(--footer-text); cursor: pointer;
font-size: 1.2rem; font-family: 'Montserrat', sans-serif;
transition: var(--transition); font-weight: 500;
}
.social-links a:hover {
color: var(--primary-color);
}
.theme-toggle {
margin-top: 1.5rem;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
}
#themeToggle {
width: 40px;
height: 40px;
border-radius: 50%;
font-size: 1.2rem;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
}
#message {
margin-top: 1.5rem;
padding: 1rem;
border-radius: 4px;
display: none;
transition: background-color 0.3s; transition: background-color 0.3s;
} }
/* Dark mode styles */ #flip-btn:hover {
body.dark-mode { background-color: #0d1642;
background-color: #1a1a1a;
color: #f4f4f4;
} }
body.dark-mode .container { .color-options {
background-color: #2c3e50; display: flex;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3); gap: 10px;
} }
body.dark-mode h1 { .color-option {
color: #3498db; width: 25px;
height: 25px;
border-radius: 50%;
cursor: pointer;
transition: transform 0.3s;
border: 2px solid white;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
} }
body.dark-mode input { .color-option:hover {
background-color: #34495e; transform: scale(1.1);
color: white;
border-color: #2c3e50;
} }
body.dark-mode button { @media (max-width: 400px) {
background-color: #e74c3c; .card-container {
} width: 300px;
height: 180px;
body.dark-mode button:hover {
background-color: #c0392b;
}
/* Responsive styles */
@media (max-width: 768px) {
nav ul {
display: none;
} }
.cta { .logo-symbol {
flex-direction: column; width: 35px;
height: 35px;
font-size: 20px;
} }
.hero h1 { .logo-text {
font-size: 2rem; font-size: 20px;
}
.feature-grid {
grid-template-columns: 1fr;
}
footer .container {
flex-direction: column;
gap: 1rem;
} }
} }

View File

@ -54,6 +54,7 @@ export default function ThreadPage({ params }: { params: Promise<ThreadParams> }
const [showScrollButton, setShowScrollButton] = useState(false); const [showScrollButton, setShowScrollButton] = useState(false);
const [buttonOpacity, setButtonOpacity] = useState(0); const [buttonOpacity, setButtonOpacity] = useState(0);
const [userHasScrolled, setUserHasScrolled] = useState(false); const [userHasScrolled, setUserHasScrolled] = useState(false);
const hasInitiallyScrolled = useRef<boolean>(false);
const handleStreamAgent = useCallback(async (runId: string) => { const handleStreamAgent = useCallback(async (runId: string) => {
// Clean up any existing stream // Clean up any existing stream
@ -183,6 +184,12 @@ export default function ThreadPage({ params }: { params: Promise<ThreadParams> }
const messagesData = await getMessages(threadId) as unknown as ApiMessage[]; const messagesData = await getMessages(threadId) as unknown as ApiMessage[];
if (isMounted) { if (isMounted) {
setMessages(messagesData); setMessages(messagesData);
// Only scroll to bottom on initial page load
if (!hasInitiallyScrolled.current) {
scrollToBottom('auto');
hasInitiallyScrolled.current = true;
}
} }
// Check for active agent runs // Check for active agent runs
@ -403,10 +410,10 @@ export default function ThreadPage({ params }: { params: Promise<ThreadParams> }
useEffect(() => { useEffect(() => {
const isNewUserMessage = messages.length > 0 && messages[messages.length - 1]?.role === 'user'; const isNewUserMessage = messages.length > 0 && messages[messages.length - 1]?.role === 'user';
if (isNewUserMessage || agentStatus === 'running') { if ((isNewUserMessage || agentStatus === 'running') && !userHasScrolled) {
scrollToBottom(); scrollToBottom();
} }
}, [messages, agentStatus]); }, [messages, agentStatus, userHasScrolled]);
// Make sure clicking the scroll button scrolls to bottom // Make sure clicking the scroll button scrolls to bottom
const handleScrollButtonClick = () => { const handleScrollButtonClick = () => {