
Hacker News clone
Clone of Hacker News website
Prompt
Create a webpage copying hackernews, it should handle routing that mimics clicking a HN thread with many comments in it. It should handle this internally rather than actually redirecting to another URL.
A system prompt was added to support web rendering
# Hacker News Clone with Client-Side Routing I'll create a Hacker News clone that handles routing internally without page reloads. The solution will include a front page with stories and a detailed view with comments when a story is clicked. ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Hacker News Clone</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; } body { background-color: #f6f6ef; color: #000; line-height: 1.4; padding: 8px; } .container { max-width: 800px; margin: 0 auto; background-color: #fff; border-radius: 4px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } header { background-color: #ff6600; padding: 3px 10px; display: flex; align-items: center; } .logo { font-weight: bold; font-size: 14px; border: 1px solid #fff; padding: 2px 5px; margin-right: 10px; } nav ul { display: flex; list-style: none; } nav li { margin-right: 15px; font-size: 14px; } nav a { color: #000; text-decoration: none; } nav a:hover { text-decoration: underline; } .content { padding: 10px; } .story-list { counter-reset: story-counter; } .story { display: flex; padding: 10px 0; border-bottom: 1px solid #eee; } .story:last-child { border-bottom: none; } .story-index { color: #828282; min-width: 30px; text-align: right; padding-right: 5px; } .story-content { flex: 1; } .story-title { font-size: 15px; margin-bottom: 3px; } .story-title a { color: #000; text-decoration: none; } .story-title a:hover { text-decoration: underline; } .story-domain { color: #828282; font-size: 12px; } .story-meta { color: #828282; font-size: 11px; } .story-meta a { color: #828282; text-decoration: none; } .story-meta a:hover { text-decoration: underline; } .comments-count::before { content: " | "; } .story-detail { margin-bottom: 20px; } .story-detail .title { font-size: 18px; margin-bottom: 5px; } .story-detail .meta { color: #828282; font-size: 12px; margin-bottom: 15px; } .comments { margin-top: 20px; } .comment { margin-bottom: 20px; padding-left: 20px; position: relative; } .comment::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 2px; background-color: #e0e0e0; } .comment-header { font-size: 12px; color: #828282; margin-bottom: 5px; } .comment-text { font-size: 14px; margin-bottom: 5px; } .comment-text p { margin-bottom: 10px; } .comment-replies { margin-top: 10px; padding-left: 20px; } .back-link { display: inline-block; margin-bottom: 15px; color: #ff6600; text-decoration: none; font-size: 14px; } .back-link:hover { text-decoration: underline; } footer { text-align: center; padding: 20px; color: #828282; font-size: 12px; border-top: 1px solid #eee; } footer a { color: #828282; text-decoration: none; } footer a:hover { text-decoration: underline; } </style> </head> <body> <div class="container"> <header> <div class="logo">Y</div> <nav> <ul> <li><a href="#" class="nav-link" data-view="home">Hacker News</a></li> <li><a href="#">new</a></li> <li><a href="#">past</a></li> <li><a href="#">comments</a></li> <li><a href="#">ask</a></li> <li><a href="#">show</a></li> <li><a href="#">jobs</a></li> <li><a href="#">submit</a></li> </ul> </nav> </header> <div class="content" id="content"> <!-- Content will be rendered here by JavaScript --> </div> <footer> <p> <a href="#">Guidelines</a> | <a href="#">FAQ</a> | <a href="#">API</a> | <a href="#">Security</a> | <a href="#">Lists</a> | <a href="#">Bookmarklet</a> | <a href="#">DMCA</a> | <a href="#">Apply to YC</a> | <a href="#">Contact</a> </p> </footer> </div> <script> // Mock data for stories and comments const stories = [ { id: 1, title: "Vue.js 3.0 Released", url: "https://vuejs.org", points: 256, user: "evanyou", time: "3 hours ago", comments: 42, text: "" }, { id: 2, title: "Show HN: I built a new JavaScript framework", url: "", points: 128, user: "jsdev", time: "5 hours ago", comments: 78, text: "After 6 months of development, I'm excited to share my new framework that solves all the problems of existing ones. It's lightweight, fast, and has a simple API." }, { id: 3, title: "The Future of Web Development", url: "https://example.com/future-web", points: 89, user: "techguru", time: "7 hours ago", comments: 24, text: "" }, { id: 4, title: "Ask HN: What programming languages are you learning in 2023?", url: "", points: 187, user: "curiousdev", time: "9 hours ago", comments: 156, text: "I'm looking to expand my skills this year and would love to know what languages others are learning. Any recommendations for backend development?" } ]; // Mock comments data const comments = { 1: [ { id: 101, user: "jsfan", time: "2 hours ago", text: "This is amazing! The new composition API is exactly what I needed for my projects.", replies: [ { id: 102, user: "vueuser", time: "1 hour ago", text: "I agree, the reactivity system is much improved." } ] }, { id: 103, user: "reactdev", time: "1 hour ago", text: "Interesting release. How does this compare to React 18?", replies: [ { id: 104, user: "frameworkfan", time: "45 minutes ago", text: "I've used both and Vue 3 feels more intuitive to me, especially with TypeScript support." }, { id: 105, user: "uidev", time: "30 minutes ago", text: "The performance benchmarks look promising. I'll definitely give it a try." } ] } ], 2: [ { id: 201, user: "curiouscoder", time: "4 hours ago", text: "This looks promising! How does it handle state management?", replies: [] }, { id: 202, user: "jsdev", time: "3 hours ago", text: "I've included a built-in state management solution that's inspired by Redux but much simpler to use.", replies: [ { id: 203, user: "frontenddev", time: "2 hours ago", text: "That's great! I've always found Redux to be too verbose for smaller projects." } ] } ], 4: [ { id: 401, user: "backenddev", time: "8 hours ago", text: "I'm learning Go for backend development. It's been great for building high-performance services.", replies: [ { id: 402, user: "golangfan", time: "7 hours ago", text: "Second this. Go's concurrency model is fantastic." } ] }, { id: 403, user: "fullstack", time: "6 hours ago", text: "Rust has been on my list for a while. The memory safety features are appealing.", replies: [] }, { id: 404, user: "pythonista", time: "5 hours ago", text: "Sticking with Python for backend. Django and FastAPI cover all my needs.", replies: [ { id: 405, user: "webdev", time: "4 hours ago", text: "Python is great, but I've been enjoying TypeScript with Node.js recently." } ] } ] }; // Router functionality const router = { currentView: 'home', currentStoryId: null, init: function() { window.addEventListener('hashchange', this.handleHashChange.bind(this)); this.handleHashChange(); }, handleHashChange: function() { const hash = window.location.hash.substring(1); if (hash.startsWith('story/')) { const storyId = parseInt(hash.split('/')[1]); if (!isNaN(storyId)) { this.currentView = 'story'; this.currentStoryId = storyId; this.renderStoryView(storyId); return; } } this.currentView = 'home'; this.currentStoryId = null; this.renderHomeView(); }, renderHomeView: function() { let html = ` <div class="story-list"> `; stories.forEach((story, index) => { html += ` <div class="story"> <div class="story-index">${index + 1}.</div> <div class="story-content"> <div class="story-title"> <a href="#story/${story.id}" class="story-link" data-id="${story.id}">${story.title}</a> ${story.url ? `<span class="story-domain">(${new URL(story.url).hostname})</span>` : ''} </div> <div class="story-meta"> ${story.points} points by <a href="#">${story.user}</a> <span>${story.time}</span> <a href="#story/${story.id}" class="comments-count">${story.comments} comments</a> </div> </div> </div> `; }); html += `</div>`; document.getElementById('content').innerHTML = html; }, renderStoryView: function(storyId) { const story = stories.find(s => s.id === storyId); if (!story) { this.renderHomeView(); return; } let html = ` <a href="#" class="back-link">← Back to homepage</a> <div class="story-detail"> <h1 class="title"> <a href="${story.url || '#'}">${story.title}</a> </h1> <div class="meta"> ${story.points} points by <a href="#">${story.user}</a> <span>${story.time}</span> </div> ${story.text ? `<div class="story-text">${story.text}</div>` : ''} </div> `; if (comments[storyId] && comments[storyId].length > 0) { html += `<div class="comments">`; html += `<h2>${story.comments} Comments</h2>`; const renderComments = (commentList, level = 0) => { let commentHtml = ''; commentList.forEach(comment => { commentHtml += ` <div class="comment" style="margin-left: ${level * 20}px"> <div class="comment-header"> <a href="#">${comment.user}</a> ${comment.time} </div> <div class="comment-text"> <p>${comment.text}</p> </div> `; if (comment.replies && comment.replies.length > 0) { commentHtml += `<div class="comment-replies">`; commentHtml += renderComments(comment.replies, level + 1); commentHtml += `</div>`; } commentHtml += `</div>`; }); return commentHtml; }; html += renderComments(comments[storyId]); html += `</div>`; } else { html += `<div class="comments"><p>No comments yet.</p></div>`; } document.getElementById('content').innerHTML = html; // Add event listener to back link document.querySelector('.back-link').addEventListener('click', (e) => { e.preventDefault(); window.location.hash = ''; }); } }; // Initialize the router when the page loads document.addEventListener('DOMContentLoaded', () => { router.init(); // Add event listener to nav link document.querySelector('.nav-link').addEventListener('click', (e) => { e.preventDefault(); window.location.hash = ''; }); }); </script> </body> </html> ``` ## Features Implemented 1. **Hacker News-like UI**: - Orange header with navigation - Numbered story list on homepage - Story details page with comments - Minimalist design similar to HN 2. **Client-Side Routing**: - Uses hash-based routing (#story/1) - No page reloads when navigating - Back button support 3. **Comment Threads**: - Nested comment replies - Visual hierarchy with indentation - User and timestamp for each comment 4. **Mock Data**: - Sample stories with different content types - Multiple comment threads with replies - Realistic user names and content 5. **Responsive Design**: - Works well on different screen sizes - Clean typography and spacing To use the application, simply click on any story title or comments link to view the story details and comments. Use the "Back to homepage" link to return to the main list.
Drag to resize
Of course. Here is a single-file webpage that mimics the functionality you described. ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Hacker News Clone</title> <style> body { font-family: Verdana, Geneva, sans-serif; font-size: 10pt; color: #828282; background-color: #f6f6ef; margin: 0; padding: 8px; } #container { max-width: 85%; margin: 0 auto; background-color: #f6f6ef; } header { background-color: #ff6600; padding: 4px; } nav { display: flex; align-items: center; gap: 5px; } nav a { color: #000000; text-decoration: none; } nav b { color: #ffffff; } .logo { border: 1px white solid; margin-right: 5px; vertical-align: middle; } .logo-link { display: flex; align-items: center; margin-right: 10px; } .logo-link:hover { cursor: pointer; } .logo-link strong { font-size: 10pt; } main { padding-top: 10px; } /* Stories List View */ .story-item { margin-bottom: 5px; line-height: 1.4; } .story-rank { color: #828282; margin-right: 5px; text-align: right; display: inline-block; width: 25px; } .story-title { font-size: 10pt; color: #000000; text-decoration: none; } .story-domain { font-size: 8pt; color: #828282; margin-left: 4px; } .story-meta { font-size: 8pt; color: #828282; padding-left: 30px; /* to align with title */ } .story-meta a { color: #828282; text-decoration: none; } .story-meta a:hover { text-decoration: underline; cursor: pointer; } /* Thread View */ #thread-view { padding: 10px; } .thread-header { margin-bottom: 15px; } .thread-title { font-size: 10pt; color: #000000; text-decoration: none; } .thread-domain { font-size: 8pt; color: #828282; margin-left: 4px; } .thread-meta { font-size: 8pt; color: #828282; margin-top: 5px; } .comment-form { margin-top: 10px; } .comment-form textarea { width: 400px; height: 80px; display: block; margin-bottom: 5px; font-family: inherit; } .comments-container { margin-top: 10px; } .comment { margin-top: 10px; font-size: 9pt; } .comment-meta { font-size: 8pt; color: #828282; margin-bottom: 4px; } .comment-meta a { color: #828282; text-decoration: none; } .comment-meta a:hover { text-decoration: underline; } .comment-content { line-height: 1.3; overflow-wrap: break-word; /* Prevents long text from overflowing */ } .comment-content p { margin-top: 0; color: #000000; } .comment-children { margin-left: 20px; /* Indentation for replies */ border-left: 1px solid #e7e7e7; padding-left: 10px; } /* General */ a:visited { color: #828282; } strong { color: #000000; } button { font-family: inherit; } </style> </head> <body> <div id="container"> <header> <nav> <a class="logo-link"> <img src="data:image/gif;base64,R0lGODlhEwATAPMAAGJyZv//AAC/v5qaDAC/v2HAVAi/v0F/v3/9/z19+/9/AEp/ADl/AAD//wAAACwAAAAAEwATAAAFL8AFJ51x+5ot5yGgnuSee5AFgBxQJASohwLzqLz96cKqABw3wABQ4BgcAQzCgADwplhMJg0FADs=" alt="Y Combinator Logo" class="logo"> <strong>Hacker News</strong> </a> <a href="#">new</a> | <a href="#">past</a> | <a href="#">comments</a> | <a href="#">ask</a> | <a href="#">show</a> | <a href="#">jobs</a> | <a href="#">submit</a> </nav> </header> <main id="main-content"> <div id="stories-view"></div> <div id="thread-view" style="display: none;"></div> </main> </div> <script> const FAKE_HN_DATA = [ { id: 1, title: "I'm a failed entrepreneur", url: 'nbtay.com', points: 874, user: 'nbtaylor', time: '12 hours ago', comments_count: 457, comments: [ { id: 101, user: 'pg', time: '11 hours ago', content: "<p>This is a surprisingly common feeling. The definition of 'failure' is often too harsh. You learned things that will be invaluable for your next venture.</p>", replies: [ { id: 103, user: 'tptacek', time: '10 hours ago', content: "<p>I'd add that many 'successful' entrepreneurs have a string of 'failures' behind them that aren't widely publicized. Survivorship bias is real.</p>", replies: [] }, { id: 104, user: 'nbtaylor', time: '9 hours ago', content: "<p>Thanks, that's encouraging to hear from you both.</p>", replies: [] } ]}, { id: 102, user: 'sama', time: '11 hours ago', content: "<p>What was the single biggest lesson you learned that you would share with a first-time founder?</p>", replies: [ { id: 105, user: 'nbtaylor', time: '9 hours ago', content: "<p>Talk to users earlier. Way, way earlier than you think you need to. I spent 6 months building what I thought they wanted. I was wrong.</p>", replies: [ { id: 106, user: 'patio11', time: '8 hours ago', content: "<p>This. A thousand times, this.</p>", replies: [] } ]} ]} ] }, { id: 2, title: 'Single-file web-apps', url: 'github.com/sveltejs', points: 421, user: 'rich_harris', time: '2 days ago', comments_count: 123, comments: [ { id: 201, user: 'react_dev', time: '2 days ago', content: "<p>Seems interesting, but how does this scale for a large application? The initial load time for the single file could be huge.</p>", replies: [ { id: 202, user: 'rich_harris', time: '2 days ago', content: "<p>That's a valid concern. The idea is less about massive enterprise apps and more about a huge class of applications that are currently over-engineered. Think internal tools, demos, and personal projects. The simplicity gained is often worth the trade-off.</p>", replies: [] } ]} ] }, { id: 3, title: 'Show HN: I built a tool to visualize git history', url: 'githistory.io', points: 256, user: 'git_viz', time: '4 hours ago', comments_count: 58, comments: [ { id: 301, user: 'linus_t', time: '3 hours ago', content: '<p>Cool visualization. The UI is clean. Have you thought about integrating it with popular IDEs?</p>', replies: []} ] }, { id: 4, title: 'Why we are leaving the cloud', url: '37signals.com', points: 1024, user: 'dhh', time: '18 hours ago', comments_count: 987, comments: [] } ]; document.addEventListener('DOMContentLoaded', () => { const storiesView = document.getElementById('stories-view'); const threadView = document.getElementById('thread-view'); const mainContent = document.getElementById('main-content'); const logoLink = document.querySelector('.logo-link'); function getDomain(url) { try { // A simplified version, doesn't handle all edge cases but good for this clone return url ? new URL('http://' + url).hostname.replace('www.', '') : ''; } catch (e) { return url || ''; } } function showStoriesView() { threadView.style.display = 'none'; storiesView.style.display = 'block'; } function showThreadView() { storiesView.style.display = 'none'; threadView.style.display = 'block'; window.scrollTo(0, 0); // Scroll to top when showing a new thread } function renderStories() { let html = ''; FAKE_HN_DATA.forEach((story, index) => { const domain = getDomain(story.url); html += ` <div class="story-item"> <div> <span class="story-rank">${index + 1}.</span> <a href="#" class="story-title" onclick="return false;">${story.title}</a> ${domain ? `<span class="story-domain">(${domain})</span>` : ''} </div> <div class="story-meta"> ${story.points} points by ${story.user} ${story.time} | <a class="comments-link" data-story-id="${story.id}"> ${story.comments_count > 0 ? story.comments_count + ' comments' : 'discuss'} </a> </div> </div> `; }); storiesView.innerHTML = html; } function renderCommentsRecursive(comments) { if (!comments || comments.length === 0) { return ''; } let html = '<div class="comment-children">'; comments.forEach(comment => { html += ` <div class="comment"> <div class="comment-meta"> <a href="#">${comment.user}</a> ${comment.time} </div> <div class="comment-content">${comment.content}</div> ${renderCommentsRecursive(comment.replies)} </div> `; }); html += '</div>'; return html; } function renderThread(storyId) { const story = FAKE_HN_DATA.find(s => s.id == storyId); if (!story) { threadView.innerHTML = '<p>Story not found.</p>'; return; } const domain = getDomain(story.url); const commentsHtml = renderCommentsRecursive(story.comments); let html = ` <div class="thread-header"> <a href="#" class="thread-title" onclick="return false;">${story.title}</a> ${domain ? `<span class="thread-domain">(${domain})</span>` : ''} <div class="thread-meta"> ${story.points} points by ${story.user} ${story.time} | <a class="comments-link" data-story-id="${story.id}"> ${story.comments_count > 0 ? story.comments_count + ' comments' : 'discuss'} </a> </div> </div> <div class="comment-form"> <textarea placeholder="Leave a comment..."></textarea> <button>add comment</button> </div> <div class="comments-container"> ${commentsHtml ? commentsHtml : '<p>No comments yet.</p>'} </div> `; threadView.innerHTML = html; } // --- Event Listeners & Initialization --- mainContent.addEventListener('click', (e) => { const commentsLink = e.target.closest('.comments-link'); if (commentsLink) { e.preventDefault(); const storyId = commentsLink.dataset.storyId; renderThread(storyId); showThreadView(); } }); logoLink.addEventListener('click', (e) => { e.preventDefault(); showStoriesView(); }); renderStories(); }); </script> </body> </html> ```
Drag to resize
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Hacker News Clone</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style> body { background: #f6f6ef; color: #222; font-family: Verdana, Geneva, sans-serif; margin: 0; padding: 0; } #app { max-width: 800px; margin: 0 auto; background: #fff; border: 1px solid #e5e5e5; min-height: 100vh; padding: 0 0 40px 0; } .top-bar { background: #ff6600; color: #fff; padding: 0.4em 1em; display: flex; align-items: center; } .top-bar .logo { font-weight: bold; font-size: 1.3em; background: #fff; color: #ff6600; padding: 0.07em 0.5em; border-radius: 5px; margin-right: 1em; } .top-bar .nav a { color: #fff; text-decoration: none; margin-right: 0.8em; font-size: 1em; } .top-bar .nav a:hover { text-decoration: underline; } .story-list { padding: 1em; } .story-item { padding: 0.6em 0; border-bottom: 1px solid #eee; display: flex; align-items: flex-start; } .story-num { color: #aaa; width: 2em; font-size: 1.2em; text-align: right; margin-right: 0.7em; flex-shrink: 0; } .story-main { flex-grow: 1; } .story-title { font-size: 1.08em; font-weight: bold; color: #222; cursor: pointer; text-decoration: none; } .story-title:visited { color: #666; } .story-meta { color: #777; font-size: 0.94em; margin-top: 2px; } .story-comments { color: #666; text-decoration: underline; cursor: pointer; margin-left: 1em; font-size: 0.94em; } .story-comments:hover { color: #ff6600; } .back-link { display: inline-block; margin: 1.2em 0 1em 0; cursor: pointer; color: #888; text-decoration: underline; font-size: 0.98em; } .back-link:hover { color: #ff6600; } .thread { padding: 0 1.1em; } .thread-title { font-size: 1.15em; font-weight: bold; color: #222; margin-bottom: 0.1em; } .thread-meta { color: #777; font-size: 0.94em; margin-bottom: 1.5em; } .comments { margin-left: 0.2em; padding: 0 0 8px 0; list-style: none; } .comment { margin-bottom: 1.25em; padding-left: 0.95em; border-left: 2px dotted #eee; } .comment-user { font-weight: bold; color: #8c4d00; font-size: 0.96em; } .comment-text { font-size: 0.98em; margin: 3px 0 1px 0; } .comment-time { color: #888; font-size: 0.88em; margin-top: 2px; } /* nested comments */ .comment .comments { margin-top: 4px; margin-left: 2em; border-left: 0px; padding-left: 0.3em; } @media (max-width: 680px) { #app { max-width: 100%; border: none; } .top-bar { flex-direction: column; font-size: 1em; } } </style> </head> <body> <div id="app"></div> <script> // ============ Demo Data ============== const STORIES = [ { id: 1, title: "Show HN: AI app makes lossless pizza compression possible", url: "https://aipizza.com", author: "misspizza", minutesAgo: 37, score: 142, commentsCount: 52, comments: [ { id: 101, user: "webdev42", minutesAgo: 35, text: "Is pineapple on pizza supported?", children: [ { id: 102, user: "pineapplesquid", minutesAgo: 34, text: "Only with 'experimental' flag set to true.", children: [ { id: 103, user: "misspizza", minutesAgo: 33, text: "We're working with fruit codecs, stay tuned!", children: [] } ] } ] }, { id: 104, user: "zorkmid", minutesAgo: 34, text: "Can you explain how you achieve lossless compression?", children: [ { id: 105, user: "cheeselover", minutesAgo: 33, text: "It's a reimplementation of the famous Mozzarella Encoding Format (MEF).", children: [] }, { id: 106, user: "misspizza", minutesAgo: 31, text: "We map ingredients to neural manifolds. Details are in the README.", children: [ { id: 107, user: "olivemachine", minutesAgo: 28, text: "How long before you support anchovies?", children: [] } ] } ] }, { id: 108, user: "mobiusloops", minutesAgo: 30, text: "Will it defeat JPEG-pizza?", children: [] }, { id: 109, user: "focaccia", minutesAgo: 27, text: "Does it just remove the crust to save space?", children: [] }, // ... more comments for realism { id: 110, user: "headless", minutesAgo: 24, text: "Tried it out, great results on deep-dish formats!", children: [ { id: 111, user: "misspizza", minutesAgo: 22, text: "Deep-dish is a challenge, but our model handles it well.", children: [] } ] } ] }, { id: 2, title: "Launch HN: GraphQL turns REST into past tense", url: "https://graphql-future.com", author: "devops_bro", minutesAgo: 43, score: 93, commentsCount: 29, comments: [ { id: 200, user: "restfulDev", minutesAgo: 41, text: "REST in peace", children: [] }, { id: 201, user: "apollo11", minutesAgo: 39, text: "How do you handle versioning?", children: [ { id: 202, user: "devops_bro", minutesAgo: 38, text: "Queries describe the schema version, so you don't need versioned endpoints.", children: [] } ] } ] }, { id: 3, title: "Ask HN: How do you stay motivated working remotely?", url: "https://news.ycombinator.com/item?id=3003", author: "aliceremote", minutesAgo: 55, score: 88, commentsCount: 37, comments: [ { id: 301, user: "remoteguy", minutesAgo: 53, text: "I set up virtual coworking calls every morning.", children: [] }, { id: 302, user: "writerbee", minutesAgo: 50, text: "Pomodoro timer and a lot of coffee.", children: [] } ] } ]; // ================ 'Routing' State ========== let route = { page: "home", storyId: null }; // Utility to find story by ID function findStory(id) { return STORIES.find(story => story.id === id); } // ================ Rendering Functions ============ function renderApp() { let html = ''; html += `<div class="top-bar"> <span class="logo">H</span> <nav class="nav"> <a href="#" onclick="navigateHome(event)">Hacker News</a> <a href="#" style="color: #fac16a;">new</a> <a href="#">comments</a> <a href="#">show</a> <a href="#">ask</a> <a href="#">jobs</a> </nav> </div>`; if (route.page === "home") { html += renderHome(); } else if (route.page === "thread" && route.storyId) { html += renderThread(route.storyId); } document.getElementById('app').innerHTML = html; } function renderHome() { return `<div class="story-list"> ${STORIES.map((story, i) => ` <div class="story-item"> <div class="story-num">${i+1}.</div> <div class="story-main"> <a class="story-title" href="${story.url}" target="_blank">${story.title}</a> <div class="story-meta"> ${story.score} points by ${story.author} ${story.minutesAgo} minutes ago <span class="story-comments" onclick="navigateThread(event, ${story.id})"> ${story.commentsCount} comments </span> </div> </div> </div> `).join('')} </div>`; } function renderThread(storyId) { const story = findStory(storyId); if (!story) return `<div class='thread'>Thread not found</div>`; return ` <div class="thread"> <span class="back-link" onclick="navigateHome(event)">← back to list</span> <div class="thread-title">${story.title}</div> <div class="thread-meta"> by ${story.author} · ${story.score} points · ${story.minutesAgo} minutes ago </div> <a href="${story.url}" style="font-size:0.97em;" target="_blank">(${story.url.replace(/^https?:\/\//, "")})</a> <h4 style="font-size:1em; margin:2em 0 0.7em 0; color:#999;font-weight:normal">${story.commentsCount} comments</h4> <ul class="comments">${renderComments(story.comments)}</ul> </div> `; } // Recursively render nested comments function renderComments(comments) { if (!comments || comments.length === 0) return ''; return comments.map(c => ` <li class="comment"> <span class='comment-user'>${c.user}</span> <span class='comment-time'>${c.minutesAgo} minutes ago</span> <div class='comment-text'>${c.text}</div> <ul class="comments">${renderComments(c.children)}</ul> </li> `).join(''); } // ================ Routing/Navigation ============ function navigateHome(e) { if (e) e.preventDefault(); route = { page: "home", storyId: null }; renderApp(); } function navigateThread(e, storyId) { e.preventDefault(); route = { page: "thread", storyId }; renderApp(); } // Attach global navigation funcs (for inline HTML onclick) window.navigateHome = navigateHome; window.navigateThread = navigateThread; // ================ Initial Render ================ renderApp(); </script> </body> </html> ```
Drag to resize
Here's a complete HTML implementation of a Hacker News clone with client-side routing that mimics thread navigation without page reloads: ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>HN Clone</title> <style> body { font-family: Verdana, Geneva, sans-serif; font-size: 10pt; color: #828282; margin: 0; padding: 0; background-color: #f6f6ef; } .container { width: 85%; max-width: 1000px; margin: 0 auto; padding: 10px 0; } header { background-color: #ff6600; padding: 2px; } .header-content { display: flex; align-items: center; } .logo { border: 1px solid white; color: white; font-weight: bold; padding: 0 5px; margin-right: 10px; } nav a { color: black; text-decoration: none; margin-right: 10px; } nav a:hover { text-decoration: underline; } .story { padding: 5px 0; } .story-title { color: #000; text-decoration: none; font-size: 11pt; } .story-title:hover { text-decoration: underline; } .story-info { font-size: 8pt; color: #828282; } .story-info a { color: #828282; text-decoration: none; } .story-info a:hover { text-decoration: underline; } .comments-container { margin-top: 20px; } .comment { margin-bottom: 10px; padding-left: 15px; border-left: 1px solid #e0e0e0; } .comment-header { font-size: 8pt; color: #828282; } .comment-text { margin-top: 5px; color: #000; font-size: 9pt; line-height: 1.4; } .comment-replies { margin-left: 20px; margin-top: 10px; } .back-link { display: block; margin-bottom: 15px; } </style> </head> <body> <header> <div class="container header-content"> <div class="logo">Y</div> <nav> <a href="#" class="home-link">Hacker News</a> <a href="#">new</a> <a href="#">past</a> <a href="#">comments</a> <a href="#">ask</a> <a href="#">show</a> <a href="#">jobs</a> <a href="#">submit</a> </nav> <div style="margin-left: auto;"> <a href="#">login</a> </div> </div> </header> <div class="container" id="content"> <!-- Content will be dynamically rendered here --> </div> <script> // Sample data const stories = [ { id: 1, title: "Rust 1.70.0 Released", url: "https://blog.rust-lang.org/2023/06/01/Rust-1.70.0.html", score: 256, by: "steveklabnik", time: Date.now() - 3600000 * 5, descendants: 42 }, { id: 2, title: "The future of TypeScript is JavaScript", url: "https://dev.to/this-is-learning/the-future-of-typescript-is-javascript-2ado", score: 189, by: "tldrews", time: Date.now() - 3600000 * 8, descendants: 98 }, { id: 3, title: "Show HN: I built a tool that helps you learn vim", url: "https://www.vimified.com", score: 143, by: "vimguy", time: Date.now() - 3600000 * 12, descendants: 37 } ]; const comments = { 1: [ { id: 101, by: "rustfan", time: Date.now() - 3600000 * 2, text: "This release has some great performance improvements. The compile times are noticeably better.", kids: [ { id: 1011, by: "newuser", time: Date.now() - 3600000 * 1, text: "I haven't noticed much difference in compile times. What project size are you working with?", kids: [] } ] }, { id: 102, by: "systems_expert", time: Date.now() - 3600000 * 3, text: "The new error messages are much clearer. This will help newcomers a lot.", kids: [] } ], 2: [ { id: 201, by: "jsdev", time: Date.now() - 3600000 * 4, text: "TypeScript has been a game changer for large JavaScript projects. I can't imagine going back.", kids: [ { id: 2011, by: "vanillajs", time: Date.now() - 3600000 * 3, text: "I still prefer plain JS with good tests. The type system adds too much overhead.", kids: [ { id: 20111, by: "tsadvocate", time: Date.now() - 3600000 * 2, text: "The overhead pays for itself in larger projects. Fewer runtime errors and better tooling.", kids: [] } ] } ] } ] }; // Router function router() { const path = window.location.hash.slice(1); const contentEl = document.getElementById('content'); if (path.startsWith('item/')) { const id = parseInt(path.split('/')[1]); renderStoryWithComments(id); } else { renderHome(); } } // Render home page with stories function renderHome() { const contentEl = document.getElementById('content'); let html = ''; stories.forEach((story, index) => { const timeAgo = formatTimeAgo(story.time); html += ` <div class="story"> <span class="story-index">${index + 1}.</span> <a href="#item/${story.id}" class="story-title">${story.title}</a> <span class="story-info"> (${new URL(story.url).hostname}) | ${story.score} points by <a href="#">${story.by}</a> ${timeAgo} | <a href="#item/${story.id}">${story.descendants} comments</a> </span> </div> `; }); contentEl.innerHTML = html; } // Render story with comments function renderStoryWithComments(id) { const story = stories.find(s => s.id === id); const storyComments = comments[id] || []; const contentEl = document.getElementById('content'); let html = ` <div class="story"> <a href="#" class="back-link">← back to stories</a> <a href="${story.url}" class="story-title">${story.title}</a> <div class="story-info"> ${story.score} points by <a href="#">${story.by}</a> ${formatTimeAgo(story.time)} | <a href="#">${story.descendants} comments</a> </div> </div> <div class="comments-container"> ${renderComments(storyComments)} </div> `; contentEl.innerHTML = html; // Add back link handler document.querySelector('.back-link').addEventListener('click', (e) => { e.preventDefault(); window.location.hash = ''; }); } // Recursively render comments function renderComments(commentsList) { if (!commentsList || commentsList.length === 0) return ''; return commentsList.map(comment => ` <div class="comment" id="comment-${comment.id}"> <div class="comment-header"> <a href="#">${comment.by}</a> ${formatTimeAgo(comment.time)} </div> <div class="comment-text"> ${comment.text} </div> ${comment.kids && comment.kids.length > 0 ? `<div class="comment-replies">${renderComments(comment.kids)}</div>` : ''} </div> `).join(''); } // Helper function to format time function formatTimeAgo(timestamp) { const seconds = Math.floor((Date.now() - timestamp) / 1000); if (seconds < 60) return `${seconds} seconds ago`; if (seconds < 3600) return `${Math.floor(seconds / 60)} minutes ago`; if (seconds < 86400) return `${Math.floor(seconds / 3600)} hours ago`; return `${Math.floor(seconds / 86400)} days ago`; } // Initialize router window.addEventListener('load', router); window.addEventListener('hashchange', router); // Handle home link document.querySelector('.home-link').addEventListener('click', (e) => { e.preventDefault(); window.location.hash = ''; }); </script> </body> </html> ``` This implementation includes: 1. A Hacker News-like UI with the orange header and story listings 2. Client-side routing using hash URLs (#item/1 for example) 3. Sample data for stories and nested comments 4. Three main views: - Home page with story listings - Story detail page with comments - Nested comment threads 5. Back button functionality to return to the home view 6. Time formatting for "X hours ago" display 7. All styling included inline The routing works by listening to hash changes and rendering the appropriate content without page reloads. The comment threads are rendered recursively to handle nested replies. You can test it by clicking on any of the story links (which will navigate to #item/1, etc.) and then use the back link to return to the home view.
Drag to resize
Below is a complete HTML file that creates a Hacker News-like webpage with internal routing to mimic clicking on a thread and displaying comments. The routing is handled using the `HashChange` event and JavaScript, without redirecting to another URL. ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Hacker News Clone</title> <style> body { font-family: Verdana, Geneva, sans-serif; line-height: 1.4; margin: 0; padding: 0; background-color: #f6f6ef; } header { background-color: #ff6600; padding: 10px; color: white; text-align: center; } .container { max-width: 800px; margin: 20px auto; padding: 0 10px; } .story { margin-bottom: 10px; padding: 10px; border-bottom: 1px solid #ccc; } .story a { color: #000; text-decoration: none; } .story a:hover { color: #ff6600; } .comment { margin-left: 20px; margin-bottom: 10px; padding: 10px; border-left: 2px solid #ccc; } .comment .author { color: #828282; font-size: 0.9em; } </style> </head> <body> <header> <h1>Hacker News Clone</h1> </header> <div class="container"> <div id="content"> <!-- Stories will be loaded here --> </div> </div> <script> const stories = [ { id: 1, title: "Example Story 1", url: "#", comments: [ { id: 11, author: "user1", text: "This is a comment on story 1." }, { id: 12, author: "user2", text: "Another comment here." }, ], }, { id: 2, title: "Example Story 2", url: "#", comments: [ { id: 21, author: "user3", text: "Comment for story 2." }, ], }, ]; function loadStories() { const content = document.getElementById('content'); content.innerHTML = ` <h2>Top Stories</h2> ${stories.map(story => ` <div class="story"> <a href="#story/${story.id}" onclick="loadStory(${story.id}); return false;"> ${story.title} </a> </div> `).join('')} `; } function loadStory(storyId) { const story = stories.find(s => s.id === storyId); const content = document.getElementById('content'); content.innerHTML = ` <h2>${story.title}</h2> <p><a href="#" onclick="loadStories(); return false;">Back to Stories</a></p> <h3>Comments</h3> ${story.comments.map(comment => ` <div class="comment"> <span class="author">${comment.author}:</span> ${comment.text} </div> `).join('')} `; } window.addEventListener('hashchange', function() { const hash = window.location.hash; if (hash.startsWith('#story/')) { const storyId = parseInt(hash.split('/')[1], 10); loadStory(storyId); } else { loadStories(); } }); // Initial load if (window.location.hash) { window.dispatchEvent(new HashChangeEvent('hashchange')); } else { loadStories(); } </script> </body> </html> ``` ### How It Works: 1. **Stories and Comments**: The `stories` array contains mock data for stories and their associated comments. 2. **Routing**: The `#` in the URL (hash) is used to determine what content to display. Clicking a story updates the hash without reloading the page. 3. **HashChange Event**: The `hashchange` event listener checks the current hash and loads either the list of stories or a specific story with comments. 4. **Internal Navigation**: The `onclick` handlers prevent default link behavior and update the content dynamically. To use this, simply open the HTML file in a browser. Clicking on a story will update the content to show comments without navigating away from the page.
Drag to resize
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>HN Clone</title> <style> body { font-family: Verdana, Arial, sans-serif; font-size: 14px; background: #f6f6ef; color: #000; margin: 0; padding: 0; } #app { width: 80%; max-width: 800px; margin: 0 auto; padding: 20px; } a { color: #0000ff; text-decoration: none; } a:hover { text-decoration: underline; } .item { padding: 5px 0; border-bottom: 1px solid #eee; } .item-title { font-size: 16px; margin-right: 5px; } .meta { color: #828282; font-size: 12px; } .back { display: inline-block; margin-bottom: 20px; font-size: 12px; } .comment { margin-top: 10px; border-top: 1px solid #eee; padding-top: 5px; } .comment .by { font-size: 12px; color: #828282; } </style> </head> <body> <div id="app"></div> <script> // Mock data const data = { posts: [ { id: 1, title: "Launch HN: Example Product", url: "https://example.com", comments: [ { id: 101, author: "alice", text: "Great product!", replies: [ { id: 102, author: "bob", text: "I agree with Alice.", replies: [ { id: 103, author: "carol", text: "What features does it have?", replies: [] } ] } ] }, { id: 104, author: "dave", text: "Needs more docs.", replies: [] } ] }, { id: 2, title: "Ask HN: How to learn Go?", url: "", comments: [ { id: 201, author: "eve", text: "The official tour is great.", replies: [] }, { id: 202, author: "frank", text: "I liked golangbot.com", replies: [] } ] }, { id: 3, title: "Show HN: Minimal SPA Router", url: "https://github.com/example/spa-router", comments: [ { id: 301, author: "grace", text: "Very useful, thanks!", replies: [ { id: 302, author: "heidi", text: "Can you add middleware support?", replies: [] } ] }, { id: 303, author: "ivan", text: "How about hash vs history API?", replies: [] }, { id: 304, author: "judy", text: "Looks cool!", replies: [] } ] } ] }; const app = document.getElementById('app'); function renderList() { document.title = "HN Clone"; app.innerHTML = ''; data.posts.forEach((post, idx) => { const div = document.createElement('div'); div.className = 'item'; div.innerHTML = ` <span class="rank">${idx + 1}.</span> <a class="item-title" href="${post.url || '#'}" target="_blank">${post.title}</a> <span class="meta">[${post.comments.length} comments]</span> <a class="meta" href="#item/${post.id}"> | discuss</a> `; app.appendChild(div); }); } function renderComments(comments, container, level = 0) { comments.forEach(c => { const div = document.createElement('div'); div.className = 'comment'; div.style.marginLeft = (level * 20) + 'px'; div.innerHTML = ` <div class="by">by ${c.author}</div> <div class="text">${c.text}</div> `; container.appendChild(div); if (c.replies && c.replies.length) { renderComments(c.replies, container, level + 1); } }); } function renderItem(id) { const post = data.posts.find(p => p.id === id); if (!post) { app.innerHTML = '<p>Item not found</p>'; return; } document.title = post.title + " – HN Clone"; app.innerHTML = ` <a href="#" class="back" id="back">« Back to list</a> <h2><a href="${post.url || '#'}" target="_blank">${post.title}</a></h2> <div><strong>${post.comments.length}</strong> comments</div> <div id="comments"></div> `; document.getElementById('back').onclick = e => { e.preventDefault(); location.hash = ''; }; const commentsContainer = document.getElementById('comments'); renderComments(post.comments, commentsContainer); } function router() { const hash = location.hash.slice(1); if (hash.startsWith('item/')) { const id = parseInt(hash.split('/')[1], 10); renderItem(id); } else { renderList(); } } window.addEventListener('hashchange', router); window.addEventListener('load', router); </script> </body> </html> ```
Drag to resize
### Hacker News Clone Webpage Below is an example implementation using HTML, CSS, and JavaScript. For simplicity, we'll be using a static data structure to mimic the Hacker News data. ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Hacker News Clone</title> <style> body { font-family: Arial, sans-serif; } .thread-list { list-style: none; padding: 0; margin: 0; } .thread-list li { padding: 10px; border-bottom: 1px solid #ccc; } .thread-list li a { text-decoration: none; color: #000; } .thread { padding: 20px; } .comment { margin-left: 20px; padding: 10px; border-left: 1px solid #ccc; } </style> </head> <body> <div id="app"> <ul class="thread-list" id="thread-list"> <!-- Thread list will be rendered here --> </ul> <div id="thread" style="display: none;"> <!-- Thread details will be rendered here --> </div> </div> <script> // Sample data const threads = [ { id: 1, title: 'Thread 1', comments: [ { id: 1, text: 'Comment 1', replies: [ { id: 2, text: 'Reply 1' } ] }, { id: 3, text: 'Comment 2' } ] }, { id: 2, title: 'Thread 2', comments: [ { id: 4, text: 'Comment 3' } ] } ]; // Function to render thread list function renderThreadList() { const threadList = document.getElementById('thread-list'); threadList.innerHTML = ''; threads.forEach((thread) => { const li = document.createElement('li'); const a = document.createElement('a'); a.href = '#'; a.textContent = thread.title; a.onclick = () => { renderThread(thread); window.history.pushState({ threadId: thread.id }, '', `?id=${thread.id}`); }; li.appendChild(a); threadList.appendChild(li); }); } // Function to render thread details function renderThread(thread) { const threadDiv = document.getElementById('thread'); threadDiv.innerHTML = ''; const h2 = document.createElement('h2'); h2.textContent = thread.title; threadDiv.appendChild(h2); renderComments(thread.comments, threadDiv); threadDiv.style.display = 'block'; document.getElementById('thread-list').style.display = 'none'; } // Function to render comments function renderComments(comments, parent) { comments.forEach((comment) => { const div = document.createElement('div'); div.className = 'comment'; const p = document.createElement('p'); p.textContent = comment.text; div.appendChild(p); if (comment.replies) { renderComments(comment.replies, div); } parent.appendChild(div); }); } // Function to handle back button window.onpopstate = (event) => { if (event.state && event.state.threadId) { const thread = threads.find((t) => t.id === event.state.threadId); if (thread) { renderThread(thread); } } else { document.getElementById('thread').style.display = 'none'; document.getElementById('thread-list').style.display = 'block'; } }; // Initialize the app renderThreadList(); // Check if there's a thread id in the URL const urlParams = new URLSearchParams(window.location.search); const threadId = urlParams.get('id'); if (threadId) { const thread = threads.find((t) => t.id === parseInt(threadId)); if (thread) { renderThread(thread); window.history.replaceState({ threadId: thread.id }, '', `?id=${thread.id}`); } } </script> </body> </html> ``` ### Usage 1. Save the above code in an HTML file. 2. Open the HTML file in a web browser. 3. Click on a thread to view its details. 4. Use the back button to return to the thread list. This implementation uses the `window.history.pushState` and `window.onpopstate` APIs to handle client-side routing, allowing the user to navigate between threads without reloading the page. The URL is updated accordingly to reflect the current thread being viewed.
Drag to resize
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Hacker News Clone</title> <style> body { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; background-color: #111; color: #fff; margin: 0; padding: 0; } header { background-color: #f60; padding: 10px 20px; text-align: center; } main { padding: 20px; max-width: 900px; margin: 0 auto; } .article-list, .article-detail { display: none; } .article-list.active { display: block; } .article-detail.active { display: block; } .article { border-bottom: 1px solid #444; padding: 10px 0; } .article a { color: #7f9; text-decoration: none; } .article a:hover { text-decoration: underline; } .comments { margin-top: 20px; padding-left: 20px; } .comment { margin-top: 10px; background-color: #161616; padding: 10px; border-radius: 4px; } </style> </head> <body> <header> <h1>Hacker News Clone</h1> </header> <main> <div class="article-list" id="articleList"></div> <div class="article-detail" id="articleDetail"></div> </main> <script> const DATA = [ { id: 1, title: "First article about AI", comments: [ { id: 11, text: "This is a great article!" }, { id: 12, text: "I disagree with the methodology." } ] }, { id: 2, title: "Quantum computing breakthrough", comments: [ { id: 21, text: "Wow, this is the future!" }, { id: 22, text: "Only in theory, not yet practical." } ] } ]; function getHashParam(name) { const hash = window.location.hash; const params = hash.split('/').filter(p => p); return name === 'id' ? (params[0] ? parseInt(params[0]) : null) : null; } function getURLParam(name) { const path = window.location.pathname; const segments = path.split('/').filter(Boolean); if (name === 'id' && segments.length >= 2 && segments[0] === 'article') { return parseInt(segments[1]); } return null; } function renderList() { const list = document.getElementById('articleList'); list.innerHTML = DATA.map(article => ` <div class="article">${article.id}. <a href="/article/${article.id}">${article.title}</a></div> `).join(''); } function renderDetail(id) { const article = DATA.find(a => a.id === id); const detail = document.getElementById('articleDetail'); let commentsHTML = ''; if (article.comments.length) { commentsHTML = '<div class="comments">'; article.comments.forEach(comment => { commentsHTML += `<div class="comment">Comment ${comment.id}: ${comment.text}</div>`; }); commentsHTML += '</div>'; } else { commentsHTML = '<div class="comments">No comments.</div>'; } detail.innerHTML = ` <h2>${article.title}</h2> ${commentsHTML} `; } function init() { const articleList = document.getElementById('articleList'); const articleDetail = document.getElementById('articleDetail'); const homePath = window.location.pathname === '/' || window.location.pathname === '' || window.location.hash.startsWith('#/'); const currentId = getURLParam('id') || getHashParam('id'); if (currentId) { articleList.classList.remove('active'); articleDetail.classList.add('active'); renderDetail(currentId); } else { articleList.classList.add('active'); articleDetail.classList.remove('active'); renderList(); } window.addEventListener('popstate', () => { const currentId = getURLParam('id') || getHashParam('id'); if (currentId) { articleList.classList.remove('active'); articleDetail.classList.add('active'); renderDetail(currentId); } else { articleList.classList.add('active'); articleDetail.classList.remove('active'); renderList(); } }); // Single page navigation articleList.addEventListener('click', (e) => { if (e.target.tagName === 'A') { e.preventDefault(); const url = e.target.getAttribute('href'); const id = getURLParam('id') || getHashParam('id'); window.history.pushState('', '', url); articleList.classList.remove('active'); articleDetail.classList.add('active'); renderDetail(id); } }); // Fallback hash navigation if needed window.addEventListener('hashchange', () => { const id = getHashParam('id'); if (id) { window.history.replaceState('', '', `/article/${id}`); articleList.classList.remove('active'); articleDetail.classList.add('active'); renderDetail(id); } else { articleList.classList.add('active'); articleDetail.classList.remove('active'); renderList(); } }); // Handle browser back/forward with non-hashed URLs window.addEventListener('popstate', () => { const id = getURLParam('id') || getHashParam('id'); if (id) { articleList.classList.remove('active'); articleDetail.classList.add('active'); renderDetail(id); } else { articleList.classList.add('active'); articleDetail.classList.remove('active'); renderList(); } }); } // Initial rendering document.addEventListener('DOMContentLoaded', init); </script> </body> </html> ```
Drag to resize
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Hacker News Clone</title> <style> body { font-family: Arial, sans-serif; margin: 0; padding: 0; background-color: #f6f6ef; color: #000; } header { background-color: #ff6600; color: white; padding: 10px; text-align: center; font-size: 20px; } .story-list { padding: 10px; } .story { margin-bottom: 20px; cursor: pointer; border-bottom: 1px solid #ccc; } .thread-view { padding: 10px; display: none; /* Hidden by default */ } .back-button { margin-bottom: 10px; cursor: pointer; color: blue; text-decoration: underline; } </style> </head> <body> <header>Hacker News Clone</header> <div id="story-list" class="story-list"> <h2>Top Stories</h2> <div class="story" onclick="showThread(1)">1. Example Story 1 (100 points) | 200 comments</div> <div class="story" onclick="showThread(2)">2. Example Story 2 (80 points) | 150 comments</div> <div class="story" onclick="showThread(3)">3. Example Story 3 (60 points) | 100 comments</div> </div> <div id="thread-view" class="thread-view"> <div class="back-button" onclick="showStoryList()">Back to Stories</div> <h2 id="thread-title"></h2> <p id="thread-content"></p> <h3>Comments:</h3> <ul id="comments-list"></ul> </div> <script> function showThread(id) { // Hide story list and show thread view document.getElementById('story-list').style.display = 'none'; document.getElementById('thread-view').style.display = 'block'; // Simulate thread data based on ID let title = ''; let content = ''; let comments = []; if (id === 1) { title = 'Example Story 1'; content = 'This is the full content of Example Story 1. It was posted by user123.'; comments = [ 'Great post! Thanks for sharing.', 'I disagree with this point...', 'Very informative. More details?' ]; } else if (id === 2) { title = 'Example Story 2'; content = 'Details about Example Story 2. Submitted by user456.'; comments = [ 'Interesting read!', 'Has anyone tried this?', 'This could be improved by...' ]; } else if (id === 3) { title = 'Example Story 3'; content = 'In-depth analysis of Example Story 3.'; comments = [ 'Solid analysis.', 'What are the implications?', 'I have a follow-up question.' ]; } // Update the DOM document.getElementById('thread-title').textContent = title; document.getElementById('thread-content').textContent = content; const commentsList = document.getElementById('comments-list'); commentsList.innerHTML = ''; // Clear existing comments comments.forEach(comment => { const li = document.createElement('li'); li.textContent = comment; commentsList.appendChild(li); }); } function showStoryList() { // Hide thread view and show story list document.getElementById('thread-view').style.display = 'none'; document.getElementById('story-list').style.display = 'block'; } </script> </body> </html> ```
Drag to resize