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/**
# SQLite
*.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
- Responsive design that works on all devices
- Light/dark theme toggle with local storage persistence
- Interactive demo section
- Modern UI with smooth animations
- Easy to customize and extend
- Interactive flip animation to show front and back of the card
- Color theme customization with predefined color options
- Responsive design that works on different screen sizes
- Modern, clean aesthetic with attention to typography and spacing
- Social media icons and contact information
## Getting Started
## Usage
1. Clone or download this repository
2. Open `index.html` in your browser
3. Start customizing the template for your needs
## Structure
- `index.html` - Main HTML structure
- `styles.css` - All styling and responsive design
- `script.js` - JavaScript functionality
1. Open `index.html` in any modern web browser
2. Click the "Flip Card" button to see both sides of the business card
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
## 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
:root {
--primary-color: #3498db;
--secondary-color: #2ecc71;
/* other variables */
}
```
## Technologies Used
### Adding Pages
To add new pages, create additional HTML files following the same structure as `index.html`.
### Extending Functionality
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.
- HTML5
- CSS3 (with variables, flexbox, and transitions)
- JavaScript
- Font Awesome for icons
- Google Fonts (Montserrat and Playfair Display)

View File

@ -3,97 +3,54 @@
<head>
<meta charset="UTF-8">
<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="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>
<body>
<header>
<div class="container">
<div class="card-container">
<div class="card front">
<div class="logo">
<h1>WebApp</h1>
<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 class="card back">
<div class="info">
<h2 class="name">ALEX JOHNSON</h2>
<p class="title">Creative Director</p>
<div class="divider"></div>
<div class="contact">
<p><i class="fas fa-phone"></i> (555) 123-4567</p>
<p><i class="fas fa-envelope"></i> alex@acmecreative.com</p>
<p><i class="fas fa-map-marker-alt"></i> 123 Design Street, Creative City</p>
<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>
<nav>
<ul>
<li><a href="#" class="active">Home</a></li>
<li><a href="#">Features</a></li>
<li><a href="#">Pricing</a></li>
<li><a href="#">Contact</a></li>
</ul>
</nav>
<div class="theme-toggle">
<button id="themeToggle"><i class="fas fa-moon"></i></button>
</div>
</div>
</header>
<main>
<section class="hero">
<div class="container">
<h1>Welcome to Our Web Application</h1>
<p>A modern, responsive web application template to kickstart your projects.</p>
<div class="cta">
<button class="btn primary">Get Started</button>
<button class="btn secondary">Learn More</button>
</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.
<button id="flip-btn">Flip Card</button>
<div class="color-options">
<div class="color-option" data-color="#1a237e" style="background-color: #1a237e;"></div>
<div class="color-option" data-color="#006064" style="background-color: #006064;"></div>
<div class="color-option" data-color="#4a148c" style="background-color: #4a148c;"></div>
<div class="color-option" data-color="#880e4f" style="background-color: #880e4f;"></div>
<div class="color-option" data-color="#bf360c" style="background-color: #bf360c;"></div>
</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>
</footer>
<script src="script.js"></script>
</body>

View File

@ -1,62 +1,26 @@
// Wait for the DOM to be fully loaded
document.addEventListener('DOMContentLoaded', function() {
// Theme toggle functionality
const themeToggle = document.getElementById('themeToggle');
const body = document.body;
const cardContainer = document.querySelector('.card-container');
const flipBtn = document.getElementById('flip-btn');
const colorOptions = document.querySelectorAll('.color-option');
// Check if user has a preferred theme
const savedTheme = localStorage.getItem('theme');
if (savedTheme === 'dark') {
body.classList.add('dark-mode');
themeToggle.innerHTML = '<i class="fas fa-sun"></i>';
}
// Set initial primary color
document.documentElement.style.setProperty('--primary-color', '#1a237e');
themeToggle.addEventListener('click', function() {
body.classList.toggle('dark-mode');
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');
}
// Flip card functionality
flipBtn.addEventListener('click', function() {
cardContainer.classList.toggle('flipped');
});
// Demo functionality
const demoInput = document.getElementById('demoInput');
const demoButton = document.getElementById('demoButton');
const message = document.getElementById('message');
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!');
}
// Color change functionality
colorOptions.forEach(option => {
option.addEventListener('click', function() {
const color = this.getAttribute('data-color');
document.documentElement.style.setProperty('--primary-color', color);
});
});
// 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() {
this.style.transform = 'translateY(0)';
});
// Double click to flip card
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;
padding: 0;
@ -20,302 +5,226 @@
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: var(--text-color);
background-color: var(--bg-color);
transition: var(--transition);
font-family: 'Montserrat', sans-serif;
background-color: #f5f5f5;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
color: #333;
}
.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;
justify-content: space-between;
flex-direction: column;
align-items: center;
gap: 30px;
}
.logo h1 {
font-size: 1.8rem;
color: var(--primary-color);
.card-container {
width: 350px;
height: 200px;
perspective: 1000px;
}
nav ul {
display: flex;
list-style: none;
.card {
position: absolute;
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 {
margin-left: 1.5rem;
}
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);
.card.front {
background-color: var(--primary-color, #1a237e);
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;
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;
align-items: center;
font-weight: bold;
font-size: 24px;
font-family: 'Playfair Display', serif;
}
/* Features section */
.features {
padding: 5rem 0;
background-color: var(--card-bg);
.logo-text {
font-size: 24px;
font-weight: 700;
letter-spacing: 2px;
}
.features h2 {
text-align: center;
margin-bottom: 3rem;
font-size: 2rem;
.tagline {
font-size: 14px;
font-weight: 300;
letter-spacing: 1px;
margin-top: -5px;
}
.feature-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 2rem;
.pattern {
position: absolute;
bottom: 0;
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 {
background-color: var(--card-bg);
padding: 2rem;
border-radius: var(--border-radius);
box-shadow: var(--box-shadow);
text-align: center;
transition: var(--transition);
.info {
padding: 10px 0;
}
.feature-card:hover {
transform: translateY(-5px);
.name {
font-size: 18px;
font-weight: 600;
color: var(--primary-color, #1a237e);
letter-spacing: 1px;
}
.feature-card i {
font-size: 2.5rem;
color: var(--primary-color);
margin-bottom: 1rem;
.title {
font-size: 14px;
color: #666;
margin-bottom: 15px;
}
.feature-card h3 {
margin-bottom: 1rem;
.divider {
width: 40px;
height: 3px;
background-color: var(--primary-color, #1a237e);
margin-bottom: 15px;
}
/* Demo section */
.demo {
padding: 5rem 0;
.contact {
font-size: 12px;
line-height: 1.6;
}
.demo h2 {
text-align: center;
margin-bottom: 3rem;
font-size: 2rem;
.contact p {
display: flex;
align-items: center;
gap: 8px;
}
.demo-container {
max-width: 600px;
margin: 0 auto;
background-color: var(--card-bg);
padding: 2rem;
border-radius: var(--border-radius);
box-shadow: var(--box-shadow);
.contact i {
color: var(--primary-color, #1a237e);
width: 15px;
}
.social-icons {
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 {
display: flex;
gap: 10px;
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;
flex-direction: column;
align-items: center;
gap: 15px;
}
.social-links {
display: flex;
gap: 1rem;
}
.social-links a {
color: var(--footer-text);
font-size: 1.2rem;
transition: var(--transition);
}
.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;
#flip-btn {
background-color: var(--primary-color, #1a237e);
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
font-family: 'Montserrat', sans-serif;
font-weight: 500;
transition: background-color 0.3s;
}
/* Dark mode styles */
body.dark-mode {
background-color: #1a1a1a;
color: #f4f4f4;
#flip-btn:hover {
background-color: #0d1642;
}
body.dark-mode .container {
background-color: #2c3e50;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
.color-options {
display: flex;
gap: 10px;
}
body.dark-mode h1 {
color: #3498db;
.color-option {
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 {
background-color: #34495e;
color: white;
border-color: #2c3e50;
.color-option:hover {
transform: scale(1.1);
}
body.dark-mode button {
background-color: #e74c3c;
}
body.dark-mode button:hover {
background-color: #c0392b;
}
/* Responsive styles */
@media (max-width: 768px) {
nav ul {
display: none;
@media (max-width: 400px) {
.card-container {
width: 300px;
height: 180px;
}
.cta {
flex-direction: column;
.logo-symbol {
width: 35px;
height: 35px;
font-size: 20px;
}
.hero h1 {
font-size: 2rem;
}
.feature-grid {
grid-template-columns: 1fr;
}
footer .container {
flex-direction: column;
gap: 1rem;
.logo-text {
font-size: 20px;
}
}

View File

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