diff --git a/internal/generator/html.go b/internal/generator/html.go index 5fa19ec..58f261e 100644 --- a/internal/generator/html.go +++ b/internal/generator/html.go @@ -374,11 +374,19 @@ func (g *HTMLGenerator) GenerateLandingPage(wikiPages []WikiPageInfo, newestBlog } } + // Use default OpenGraph image for all pages + ogImage := g.siteURL + g.defaultImage + + description := "Welcome to " + g.siteName + if newestBlogItem != nil || newestArticleItem != nil { + description = "Latest content from " + g.siteName + } + data := PageData{ Title: "Home", - Description: "Welcome to " + g.siteName, + Description: description, CanonicalURL: g.siteURL + "/", - OGImage: g.siteURL + g.defaultImage, + OGImage: ogImage, OGType: "website", SiteName: g.siteName, SiteURL: g.siteURL, @@ -437,16 +445,21 @@ func (g *HTMLGenerator) GenerateWikiPage(wiki *nostr.WikiEvent, wikiPages []Wiki description := wiki.Summary if description == "" { - description = wiki.Title + description = wiki.Title + " - Wiki article from " + g.siteName + } else { + description = description + " - " + g.siteName + " Wiki" } canonicalURL := g.siteURL + "/wiki/" + wiki.DTag + // Use default OpenGraph image for all pages + ogImage := g.siteURL + g.defaultImage + data := PageData{ Title: wiki.Title, Description: description, CanonicalURL: canonicalURL, - OGImage: g.siteURL + g.defaultImage, + OGImage: ogImage, OGType: "article", SiteName: g.siteName, SiteURL: g.siteURL, @@ -496,11 +509,20 @@ func (g *HTMLGenerator) GenerateBlogPage(blogIndex *nostr.IndexEvent, blogItems ctx := context.Background() profiles := g.fetchProfilesBatch(ctx, pubkeys) + // Use default OpenGraph image for all pages + ogImage := g.siteURL + g.defaultImage + + // Use blog index title if available, otherwise default to "Blog" + title := blogIndex.Title + if title == "" { + title = "Blog" + } + data := PageData{ - Title: "Blog", + Title: title, Description: description, CanonicalURL: canonicalURL, - OGImage: g.siteURL + g.defaultImage, + OGImage: ogImage, OGType: "website", SiteName: g.siteName, SiteURL: g.siteURL, @@ -575,11 +597,14 @@ func (g *HTMLGenerator) GenerateArticlesPage(articleItems []ArticleItemInfo, fee ctx := context.Background() profiles := g.fetchProfilesBatch(ctx, pubkeys) + // Use default OpenGraph image for all pages + ogImage := g.siteURL + g.defaultImage + data := PageData{ Title: "Articles", Description: description, CanonicalURL: canonicalURL, - OGImage: g.siteURL + g.defaultImage, + OGImage: ogImage, OGType: "website", SiteName: g.siteName, SiteURL: g.siteURL, @@ -626,6 +651,11 @@ func (g *HTMLGenerator) GenerateWikiIndexPage(wikiIndex *nostr.IndexEvent, wikiP canonicalURL := g.siteURL + "/wiki" + // Enhanced description for wiki index + if description == "" { + description = "Browse wiki documentation and articles from " + g.siteName + } + data := PageData{ Title: "Wiki", Description: description, @@ -664,11 +694,19 @@ func (g *HTMLGenerator) GenerateEBooksPage(ebooks []EBookInfo, feedItems []FeedI ctx := context.Background() profiles := g.fetchProfilesBatch(ctx, pubkeys) + // Use default OpenGraph image for all pages + ogImage := g.siteURL + g.defaultImage + + description := "Browse top-level publications (index events) from Nostr" + if len(formattedEBooks) > 0 { + description = fmt.Sprintf("Browse %d e-books and publications from Nostr", len(formattedEBooks)) + } + data := PageData{ Title: "E-Books", - Description: "Browse top-level publications (index events) from Nostr", + Description: description, CanonicalURL: canonicalURL, - OGImage: g.siteURL + g.defaultImage, + OGImage: ogImage, OGType: "website", SiteName: g.siteName, SiteURL: g.siteURL, @@ -710,10 +748,15 @@ func (g *HTMLGenerator) GenerateContactPage(success bool, errorMsg string, event FormData ContactFormData } + description := "Get in touch with " + g.siteName + if repoAnnouncement != nil && repoAnnouncement.DTag != "" { + description = "Contact " + g.siteName + " - Submit issues, feedback, or questions" + } + data := ContactPageData{ PageData: PageData{ Title: "Contact", - Description: "Contact " + g.siteName, + Description: description, CanonicalURL: g.siteURL + "/contact", OGImage: g.siteURL + g.defaultImage, OGType: "website", @@ -863,9 +906,14 @@ func (g *HTMLGenerator) GenerateFeedPage(feedItems []FeedItemInfo) (string, erro ctx := context.Background() profiles := g.fetchProfilesBatch(ctx, pubkeys) + description := "Recent notes from TheForest relay" + if len(feedItems) > 0 { + description = fmt.Sprintf("Browse %d recent notes from TheForest relay", len(feedItems)) + } + data := PageData{ Title: "TheForest Feed", - Description: "Recent notes from TheForest relay", + Description: description, CanonicalURL: canonicalURL, OGImage: g.siteURL + g.defaultImage, OGType: "website", diff --git a/static/css/main.css b/static/css/main.css index f896988..aed1707 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -498,21 +498,49 @@ a:focus { } .landing-page .hero { - text-align: center; margin-bottom: 3rem; padding: 2rem 0; } +.hero-content { + display: flex; + align-items: center; + gap: 3rem; + max-width: 1000px; + margin: 0 auto; +} + +.hero-text { + flex: 1; + text-align: left; +} + +.hero-image { + flex: 0 0 auto; + max-width: 300px; + width: 100%; +} + +.hero-portrait { + width: 100%; + height: auto; + display: block; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); +} + .landing-page .hero h1 { font-size: 2.5rem; margin-bottom: 1rem; + text-align: left; } .landing-page .hero .lead { font-size: 1.25rem; color: var(--text-secondary); - max-width: 600px; - margin: 0 auto; + max-width: none; + margin: 0; + text-align: left; } .landing-page .feed-section { diff --git a/static/css/responsive.css b/static/css/responsive.css index 66fd3d2..b007af1 100644 --- a/static/css/responsive.css +++ b/static/css/responsive.css @@ -19,6 +19,13 @@ /* Show mobile selector on mobile */ .mobile-selector { display: block; + width: 100%; + box-sizing: border-box; + } + + .mobile-selector .mobile-select { + width: 100%; + box-sizing: border-box; } /* Show mobile custom dropdown on mobile */ @@ -35,11 +42,37 @@ flex-direction: column; padding: 1rem; gap: 1rem; + width: 100%; + max-width: 100%; + box-sizing: border-box; + } + + .layout-container.wiki-layout { + flex-direction: column; } .main-content { width: 100%; min-width: 0; + max-width: 100%; + overflow-x: hidden; + } + + /* Ensure wiki pages don't overflow */ + .wiki-page, + .wiki-index-page { + width: 100%; + max-width: 100%; + overflow-x: hidden; + box-sizing: border-box; + } + + .page-content, + .wiki-index-content { + width: 100%; + max-width: 100%; + overflow-x: hidden; + box-sizing: border-box; } /* Ensure no overflow on mobile */ @@ -48,6 +81,29 @@ word-wrap: break-word; } + /* Prevent horizontal scroll on feed pages */ + .feed-page *, + .feed-page .feed-container * { + max-width: 100%; + box-sizing: border-box; + } + + /* Prevent horizontal scroll on wiki pages */ + .wiki-page .page-content *, + .wiki-index-page .wiki-index-content * { + max-width: 100%; + box-sizing: border-box; + } + + /* Ensure pre and code blocks are scrollable but don't break layout */ + .page-content pre, + .wiki-index-content pre { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + max-width: 100%; + box-sizing: border-box; + } + /* Navigation */ .mobile-menu-toggle { display: flex; @@ -133,14 +189,31 @@ margin-bottom: 2rem; } + .hero-content { + flex-direction: column; + gap: 2rem; + text-align: center; + } + + .hero-text { + text-align: center; + } + + .hero-image { + max-width: 250px; + margin: 0 auto; + } + .landing-page .hero h1 { font-size: 1.75rem; margin-bottom: 0.75rem; + text-align: center; } .landing-page .hero .lead { font-size: 1rem; - padding: 0 1rem; + padding: 0; + text-align: center; } .landing-page .features { @@ -423,6 +496,16 @@ } /* Wiki Pages */ + .wiki-layout { + flex-direction: column; + } + + .wiki-index-links .wiki-link { + white-space: normal; + word-wrap: break-word; + overflow-wrap: break-word; + } + .breadcrumbs { margin-bottom: 0.75rem; font-size: 0.875rem; @@ -587,61 +670,146 @@ .feed-page { padding: 1rem; max-width: 100%; + width: 100%; + box-sizing: border-box; + margin: 0; } .feed-about-blurb { padding: 1rem; margin-bottom: 1.5rem; + width: 100%; + box-sizing: border-box; } .feed-about-blurb h2 { font-size: 1.25rem; margin-bottom: 0.75rem; + word-wrap: break-word; + overflow-wrap: break-word; } .feed-about-blurb p { font-size: 0.9rem; margin-bottom: 0.75rem; + word-wrap: break-word; + overflow-wrap: break-word; } .feed-about-blurb ul { margin-left: 1.25rem; font-size: 0.9rem; + word-wrap: break-word; + overflow-wrap: break-word; } .feed-about-blurb code { font-size: 0.8rem; padding: 0.15rem 0.3rem; word-break: break-all; + overflow-wrap: anywhere; } .feed-container { padding: 1rem; + width: 100%; + max-width: 100%; + box-sizing: border-box; + overflow-x: hidden; + } + + .feed-container h3 { + flex-direction: column; + align-items: flex-start; + gap: 0.5rem; + } + + .feed-link-header { + margin-left: 0; + margin-top: 0.25rem; + word-break: break-all; + overflow-wrap: anywhere; } .feed-item { padding: 0.75rem 0; + width: 100%; + max-width: 100%; + box-sizing: border-box; + overflow-x: hidden; + } + + .feed-item:hover { + margin: 0; + padding: 0.75rem 0; } .feed-header { flex-direction: column; align-items: flex-start; gap: 0.25rem; + width: 100%; } .feed-content { font-size: 0.85rem; word-wrap: break-word; overflow-wrap: break-word; + width: 100%; + max-width: 100%; + box-sizing: border-box; + } + + .feed-content * { + max-width: 100%; + box-sizing: border-box; + } + + /* Ensure images in feed content are responsive */ + .feed-content img { + max-width: 100%; + height: auto; + display: block; + } + + /* Ensure code blocks in feed content are scrollable */ + .feed-content pre, + .feed-content code { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + max-width: 100%; + word-break: break-all; + overflow-wrap: anywhere; + } + + .feed-items { + width: 100%; + max-width: 100%; + box-sizing: border-box; + } + + .feed-empty { + word-wrap: break-word; + overflow-wrap: break-word; } .feed-time { font-size: 0.8rem; + white-space: normal; + word-wrap: break-word; } .feed-link { word-break: break-all; overflow-wrap: anywhere; + max-width: 100%; + display: block; + } + + .feed-author { + max-width: 100%; + word-break: break-all; + overflow-wrap: anywhere; } /* Article Content */ @@ -780,6 +948,14 @@ height: auto; } + /* Ensure images in wiki content are responsive */ + .page-content img, + .wiki-index-content img { + max-width: 100%; + height: auto; + display: block; + } + /* Prevent horizontal scroll */ body { overflow-x: hidden; @@ -869,6 +1045,22 @@ padding: 1.5rem; } + .hero-content { + gap: 2rem; + } + + .hero-image { + max-width: 250px; + } + + .landing-page .hero h1 { + font-size: 2rem; + } + + .landing-page .hero .lead { + font-size: 1.1rem; + } + .feature-grid { grid-template-columns: repeat(2, 1fr); gap: 1.5rem; diff --git a/templates/base.html b/templates/base.html index d336d8f..609920a 100644 --- a/templates/base.html +++ b/templates/base.html @@ -11,6 +11,7 @@ + {{if .Description}}{{end}} diff --git a/templates/landing.html b/templates/landing.html index be3a4f7..f51ccc8 100644 --- a/templates/landing.html +++ b/templates/landing.html @@ -1,8 +1,15 @@ {{define "content"}}
-

Welcome to {{.SiteName}}

-

Your gateway to decentralized knowledge and community-driven content.

+
+
+

Welcome to {{.SiteName}}

+

Your gateway to decentralized knowledge and community-driven content.

+
+
+ {{.SiteName}} +
+