fix: font loading and chat scrolling issues
- fix: Replace corrupted Merriweather fonts with valid WOFF2 files from @fontsource - fix: Use CSS @font-face instead of next/font/local for Merriweather (Next.js 16 compatibility) - fix: Add smart scroll detection to ChatWindow (respects manual scrolling) - feat: Update app title to "Brachnha Insight" - feat: Update app description Co-Authored-By: Claude (glm-4.7) <noreply@anthropic.com>
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
36
src/app/fonts.css
Normal file
36
src/app/fonts.css
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/* Merriweather Font Faces */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Merriweather';
|
||||||
|
src: url('/fonts/Merriweather-Light.woff2') format('woff2');
|
||||||
|
font-weight: 300;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Merriweather';
|
||||||
|
src: url('/fonts/Merriweather-Regular.woff2') format('woff2');
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Merriweather';
|
||||||
|
src: url('/fonts/Merriweather-Bold.woff2') format('woff2');
|
||||||
|
font-weight: 700;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Merriweather';
|
||||||
|
src: url('/fonts/Merriweather-Black.woff2') format('woff2');
|
||||||
|
font-weight: 900;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--font-merriweather: 'Merriweather', serif;
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import localFont from "next/font/local";
|
import localFont from "next/font/local";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
import "./fonts.css";
|
||||||
import { OfflineIndicator } from "../components/features/common";
|
import { OfflineIndicator } from "../components/features/common";
|
||||||
import { InstallPrompt } from "../components/features/pwa/install-prompt";
|
import { InstallPrompt } from "../components/features/pwa/install-prompt";
|
||||||
import { ThemeProvider } from "@/components/theme-provider";
|
import { ThemeProvider } from "@/components/theme-provider";
|
||||||
@@ -11,20 +12,9 @@ const inter = localFont({
|
|||||||
display: "swap",
|
display: "swap",
|
||||||
});
|
});
|
||||||
|
|
||||||
const merriweather = localFont({
|
|
||||||
src: [
|
|
||||||
{ path: "../../public/fonts/Merriweather-Light.woff2", weight: "300", style: "normal" },
|
|
||||||
{ path: "../../public/fonts/Merriweather-Regular.woff2", weight: "400", style: "normal" },
|
|
||||||
{ path: "../../public/fonts/Merriweather-Bold.woff2", weight: "700", style: "normal" },
|
|
||||||
{ path: "../../public/fonts/Merriweather-Black.woff2", weight: "900", style: "normal" },
|
|
||||||
],
|
|
||||||
variable: "--font-merriweather",
|
|
||||||
display: "swap",
|
|
||||||
});
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Test01 - Local-First Venting",
|
title: "Brachnha Insight",
|
||||||
description: "Transform your struggles into personal branding content with AI-powered journaling",
|
description: "Transform what in your mind into a powerful notes",
|
||||||
// Story 3.4: PWA metadata
|
// Story 3.4: PWA metadata
|
||||||
manifest: "/manifest.webmanifest", // Next.js 14+ convention
|
manifest: "/manifest.webmanifest", // Next.js 14+ convention
|
||||||
themeColor: "#64748B",
|
themeColor: "#64748B",
|
||||||
@@ -52,7 +42,7 @@ export default function RootLayout({
|
|||||||
return (
|
return (
|
||||||
<html lang="en" suppressHydrationWarning>
|
<html lang="en" suppressHydrationWarning>
|
||||||
<body
|
<body
|
||||||
className={`${inter.variable} ${merriweather.variable} font-sans antialiased`}
|
className={`${inter.variable} font-sans antialiased`}
|
||||||
>
|
>
|
||||||
<ThemeProvider
|
<ThemeProvider
|
||||||
attribute="class"
|
attribute="class"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { ChatBubble } from './chat-bubble';
|
import { ChatBubble } from './chat-bubble';
|
||||||
import { TypingIndicator } from './typing-indicator';
|
import { TypingIndicator } from './typing-indicator';
|
||||||
import { useChatStore } from '@/store/use-chat';
|
import { useChatStore } from '@/store/use-chat';
|
||||||
@@ -9,11 +9,39 @@ import { BookOpen, Sparkles } from 'lucide-react';
|
|||||||
export function ChatWindow() {
|
export function ChatWindow() {
|
||||||
const { messages, isTyping } = useChatStore();
|
const { messages, isTyping } = useChatStore();
|
||||||
const bottomRef = useRef<HTMLDivElement>(null);
|
const bottomRef = useRef<HTMLDivElement>(null);
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [isUserScrolling, setIsUserScrolling] = useState(false);
|
||||||
|
|
||||||
// Auto-scroll to bottom
|
// Auto-scroll to bottom only when new messages arrive
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!isUserScrolling) {
|
||||||
bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
|
bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||||
}, [messages, isTyping]);
|
}
|
||||||
|
}, [messages.length, isUserScrolling]);
|
||||||
|
|
||||||
|
// Detect when user is manually scrolling
|
||||||
|
useEffect(() => {
|
||||||
|
const container = containerRef.current;
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const handleScroll = () => {
|
||||||
|
const { scrollTop, scrollHeight, clientHeight } = container;
|
||||||
|
const isAtBottom = scrollHeight - scrollTop - clientHeight < 100;
|
||||||
|
|
||||||
|
// If user scrolls up from bottom, disable auto-scroll
|
||||||
|
if (!isAtBottom && !isUserScrolling) {
|
||||||
|
setIsUserScrolling(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If user scrolls back near bottom, re-enable auto-scroll
|
||||||
|
if (isAtBottom && isUserScrolling) {
|
||||||
|
setIsUserScrolling(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
container.addEventListener('scroll', handleScroll);
|
||||||
|
return () => container.removeEventListener('scroll', handleScroll);
|
||||||
|
}, [isUserScrolling]);
|
||||||
|
|
||||||
if (!messages || messages.length === 0) {
|
if (!messages || messages.length === 0) {
|
||||||
return (
|
return (
|
||||||
@@ -38,7 +66,7 @@ export function ChatWindow() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex-1 overflow-y-auto px-4 py-6 scroll-smooth">
|
<div ref={containerRef} className="h-full flex-1 overflow-y-auto px-4 py-6 scroll-smooth">
|
||||||
<div className="max-w-3xl mx-auto space-y-4">
|
<div className="max-w-3xl mx-auto space-y-4">
|
||||||
{messages.map((msg) => (
|
{messages.map((msg) => (
|
||||||
<ChatBubble key={msg.id} role={msg.role} content={msg.content} />
|
<ChatBubble key={msg.id} role={msg.role} content={msg.content} />
|
||||||
|
|||||||
Reference in New Issue
Block a user