simple-web-app

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit aaafbad6381ecd892a85003397a8d10df77d104a
parent 0f7fa4aba0acc426160684ba14f6cabd92ac18c2
Author: Silas Brack <silasbrack@gmail.com>
Date:   Sun,  7 Jun 2026 20:29:23 +0200

feat: clean up design and remove duplicate sort links

- Remove hot/new/top from feed (already in header nav)
- Replace HN orange with a cleaner blue accent
- System font stack, better spacing, subtle borders
- Sticky header, pill-style nav links and tags
- Dark mode support
- Focus states on inputs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Diffstat:
Mstatic/style.css | 784+++++++++++++++++++++++++++++++------------------------------------------------
Mstatic/style.css.gz | 0
Mtemplates/application.html | 1-
Mtemplates/feed.html | 27++++++++++-----------------
4 files changed, 316 insertions(+), 496 deletions(-)

diff --git a/static/style.css b/static/style.css @@ -1,67 +1,57 @@ -/* HN/Lobsters Clone Styles */ - :root { - --color-bg: #f6f6ef; - --color-header: #ff6600; - --color-header-text: #000; - --color-link: #000; - --color-link-visited: #828282; - --color-meta: #828282; - --color-score: #ff6600; - --color-voted: #ff6600; - --color-border: #ddd; - --color-text: #000; - --color-error: #ff0000; - --font-main: Verdana, Geneva, sans-serif; - --font-size: 10pt; - --max-width: 85%; + --color-bg: #fafafa; + --color-surface: #fff; + --color-accent: #4a6fa5; + --color-accent-hover: #3a5a8a; + --color-text: #1a1a1a; + --color-text-secondary: #6b7280; + --color-link: #1a1a1a; + --color-link-visited: #6b7280; + --color-voted: #e85d04; + --color-border: #e5e7eb; + --color-error: #dc2626; + --color-success: #16a34a; + --font-main: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + --font-mono: "SF Mono", Menlo, monospace; + --max-width: 780px; + --radius: 6px; } @media (prefers-color-scheme: dark) { :root { - --color-bg: #1a1a1a; - --color-header: #ff6600; - --color-header-text: #fff; - --color-link: #c9c9c9; - --color-link-visited: #828282; - --color-meta: #828282; - --color-border: #333; - --color-text: #c9c9c9; + --color-bg: #111; + --color-surface: #1a1a1a; + --color-accent: #6b93d6; + --color-accent-hover: #8baee0; + --color-text: #e5e5e5; + --color-text-secondary: #9ca3af; + --color-link: #e5e5e5; + --color-link-visited: #9ca3af; + --color-border: #2a2a2a; } } -* { - box-sizing: border-box; - margin: 0; - padding: 0; -} +* { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: var(--font-main); - font-size: var(--font-size); - background-color: var(--color-bg); + font-size: 14px; + background: var(--color-bg); color: var(--color-text); - line-height: 1.4; -} - -a { - color: var(--color-link); - text-decoration: none; -} - -a:visited { - color: var(--color-link-visited); + line-height: 1.5; } -a:hover { - text-decoration: underline; -} +a { color: var(--color-link); text-decoration: none; } +a:visited { color: var(--color-link-visited); } +a:hover { color: var(--color-accent); } /* Header */ .site-header { - background-color: var(--color-header); - padding: 2px; - margin-bottom: 10px; + background: var(--color-surface); + border-bottom: 1px solid var(--color-border); + position: sticky; + top: 0; + z-index: 10; } .nav-main { @@ -69,385 +59,332 @@ a:hover { margin: 0 auto; display: flex; align-items: center; - gap: 10px; - padding: 2px 0; + gap: 16px; + padding: 10px 16px; } .logo { - font-weight: bold; - color: var(--color-header-text); - padding: 0 4px; - border: 1px solid var(--color-header-text); - margin-right: 5px; + font-weight: 700; + font-size: 16px; + color: var(--color-accent); + letter-spacing: -0.5px; } +.logo:hover { text-decoration: none; color: var(--color-accent-hover); } +.logo:visited { color: var(--color-accent); } -.logo:hover { - text-decoration: none; -} - -.nav-links { - display: flex; - gap: 8px; -} +.nav-links { display: flex; gap: 4px; } .nav-link { - color: var(--color-header-text); -} - -.nav-separator { - color: var(--color-header-text); + color: var(--color-text-secondary); + padding: 4px 10px; + border-radius: var(--radius); + font-size: 13px; + transition: background 0.15s, color 0.15s; +} +.nav-link:visited { color: var(--color-text-secondary); } +.nav-link:hover { + background: var(--color-border); + color: var(--color-text); + text-decoration: none; } -.nav-right { - margin-left: auto; -} +.nav-right { margin-left: auto; } /* Container */ .container { max-width: var(--max-width); margin: 0 auto; - padding: 0 5px; + padding: 16px; } /* Feed */ -.feed-nav { - margin-bottom: 10px; - padding: 5px 0; - border-bottom: 1px solid var(--color-border); -} - -.sort-links { - margin-right: 15px; -} - -.sort-link { - margin-right: 10px; -} - -.sort-link.active { - font-weight: bold; -} - -.tag-links { - display: inline; +.feed-tags { + display: flex; + gap: 6px; + margin-bottom: 12px; + flex-wrap: wrap; } .tag-link { - margin-right: 8px; - font-size: 9pt; + font-size: 12px; + color: var(--color-text-secondary); + background: var(--color-surface); + border: 1px solid var(--color-border); + padding: 2px 10px; + border-radius: 99px; } +.tag-link:hover { border-color: var(--color-accent); color: var(--color-accent); text-decoration: none; } /* Story List */ -.story-list { - list-style: none; - padding: 0; - margin: 0; -} +.story-list { list-style: none; } .story-item { display: flex; - padding: 5px 0; + padding: 10px 0; align-items: flex-start; + border-bottom: 1px solid var(--color-border); } +.story-item:last-child { border-bottom: none; } .story-vote { - width: 20px; + width: 28px; text-align: center; flex-shrink: 0; + padding-top: 2px; } .vote-btn { background: none; border: none; - color: var(--color-meta); + color: var(--color-border); cursor: pointer; - font-size: 10pt; - padding: 0; + font-size: 14px; + padding: 2px; line-height: 1; + transition: color 0.15s; } +.vote-btn:hover { color: var(--color-voted); } +.vote-btn.voted { color: var(--color-voted); } -.vote-btn:hover { - color: var(--color-voted); -} - -.vote-btn.voted { - color: var(--color-voted); -} +.vote-placeholder { color: var(--color-border); } -.vote-placeholder { - color: var(--color-meta); - opacity: 0.3; -} +.comment-vote { font-size: 12px; } -.comment-vote { - font-size: 9pt; -} +.story-content { flex: 1; min-width: 0; } -.story-content { - flex: 1; - min-width: 0; -} - -.story-title { - margin-bottom: 2px; -} +.story-title { margin-bottom: 3px; } .story-rank { - color: var(--color-meta); - margin-right: 5px; + color: var(--color-text-secondary); + font-size: 12px; + margin-right: 6px; } -.story-link { - font-size: 10pt; -} +.story-link { font-size: 14px; font-weight: 500; } .story-domain { - font-size: 8pt; - color: var(--color-meta); - margin-left: 5px; -} - -.story-domain a { - color: var(--color-meta); + font-size: 12px; + color: var(--color-text-secondary); + margin-left: 6px; } +.story-domain a { color: var(--color-text-secondary); } .story-meta { - font-size: 8pt; - color: var(--color-meta); -} - -.story-meta a { - color: var(--color-meta); -} - -.story-score { - color: var(--color-meta); -} - -.story-author { - color: var(--color-meta); -} - -.story-time { - color: var(--color-meta); -} - -.story-comments { - color: var(--color-meta); + font-size: 12px; + color: var(--color-text-secondary); } +.story-meta a { color: var(--color-text-secondary); } +.story-meta a:hover { color: var(--color-accent); } .story-tag { - color: var(--color-meta); - background: rgba(0,0,0,0.05); - padding: 1px 4px; - border-radius: 2px; - font-size: 8pt; -} - -.more-link { - padding: 10px 0 20px 25px; + color: var(--color-text-secondary); + background: var(--color-surface); + border: 1px solid var(--color-border); + padding: 0 6px; + border-radius: 99px; + font-size: 11px; } +.more-link { padding: 16px 0; text-align: center; } .more-link a { - color: var(--color-meta); + color: var(--color-accent); + font-size: 13px; } /* Story Page */ -.story-page { - padding: 10px 0; -} +.story-page { padding: 8px 0; } -.story-full { - margin-bottom: 20px; -} +.story-full { margin-bottom: 24px; } .story-header { display: flex; align-items: flex-start; - margin-bottom: 10px; + margin-bottom: 8px; } .story-title-full { - font-size: 13pt; - font-weight: normal; - margin-bottom: 5px; -} - -.story-title-full a { - color: var(--color-link); + font-size: 18px; + font-weight: 600; + margin-bottom: 6px; + line-height: 1.3; } +.story-title-full a { color: var(--color-link); } .story-text { - margin: 15px 0; - padding: 10px; - background: rgba(0,0,0,0.02); - border-radius: 3px; - line-height: 1.5; + margin: 16px 0; + padding: 12px 16px; + background: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: var(--radius); + line-height: 1.6; white-space: pre-wrap; } /* Comment Form */ .comment-form { - margin: 20px 0; - padding: 10px; - background: rgba(0,0,0,0.02); - border-radius: 3px; + margin: 24px 0; } .comment-form textarea { width: 100%; font-family: var(--font-main); - font-size: 10pt; - padding: 8px; + font-size: 14px; + padding: 10px 12px; border: 1px solid var(--color-border); - border-radius: 3px; - margin-bottom: 10px; + border-radius: var(--radius); + margin-bottom: 8px; resize: vertical; + background: var(--color-surface); + color: var(--color-text); +} +.comment-form textarea:focus { + outline: none; + border-color: var(--color-accent); } -.comment-form button { - background: var(--color-header); +.comment-form button, +.form-actions button, +.search-form button { + background: var(--color-accent); color: #fff; border: none; - padding: 6px 12px; - border-radius: 3px; + padding: 8px 16px; + border-radius: var(--radius); cursor: pointer; - font-size: 10pt; + font-size: 13px; + font-weight: 500; + transition: background 0.15s; } - -.comment-form button:hover { - opacity: 0.9; +.comment-form button:hover, +.form-actions button:hover, +.search-form button:hover { + background: var(--color-accent-hover); } .login-prompt { - color: var(--color-meta); - margin: 10px 0; + color: var(--color-text-secondary); + margin: 12px 0; + font-size: 13px; } /* Comments */ -.comments { - margin-top: 20px; -} +.comments { margin-top: 24px; } .comment { - margin: 10px 0; - padding: 5px 0; + margin: 0; + padding: 10px 0; + border-top: 1px solid var(--color-border); } .comment-header { - font-size: 8pt; - color: var(--color-meta); - margin-bottom: 5px; -} - -.comment-author { - color: var(--color-meta); - font-weight: bold; + font-size: 12px; + color: var(--color-text-secondary); + margin-bottom: 4px; } -.comment-score { - margin: 0 5px; -} - -.comment-time { - margin-left: 5px; -} +.comment-author { font-weight: 600; } +.comment-score { margin: 0 4px; } +.comment-time { margin-left: 4px; } .comment-text { - font-size: 9pt; - line-height: 1.4; - margin: 5px 0; + font-size: 13px; + line-height: 1.5; + margin: 4px 0; white-space: pre-wrap; } -.comment-actions { - font-size: 8pt; - margin-top: 5px; -} - -.comment-reply { - color: var(--color-meta); - text-decoration: underline; -} +.comment-actions { font-size: 12px; margin-top: 4px; } +.comment-reply { color: var(--color-text-secondary); } +.comment-reply:hover { color: var(--color-accent); } -.comment-children { - border-left: 1px solid var(--color-border); - margin-left: 0; - padding-left: 10px; +/* Forms */ +.submit-page, .reply-page, .search-page, .auth-page { + max-width: 520px; + padding: 24px 0; } -/* Submit Page */ -.submit-page { - max-width: 600px; - padding: 20px 0; +.auth-page { + margin: 0 auto; + padding: 48px 16px; } -.submit-page h2 { - font-size: 13pt; +.submit-page h2, .reply-page h2, .search-page h2, .auth-page h2 { + font-size: 18px; + font-weight: 600; margin-bottom: 20px; } -.submit-form { - background: rgba(0,0,0,0.02); - padding: 15px; - border-radius: 3px; -} +.auth-page h2 { text-align: center; } -.form-group { - margin-bottom: 15px; +.submit-form, .auth-form { + background: var(--color-surface); + border: 1px solid var(--color-border); + padding: 20px; + border-radius: var(--radius); } +.form-group { margin-bottom: 14px; } + .form-group label { display: block; - font-size: 9pt; - color: var(--color-meta); - margin-bottom: 5px; + font-size: 13px; + font-weight: 500; + color: var(--color-text-secondary); + margin-bottom: 4px; } .form-group input, .form-group textarea { width: 100%; font-family: var(--font-main); - font-size: 10pt; - padding: 8px; + font-size: 14px; + padding: 8px 12px; border: 1px solid var(--color-border); - border-radius: 3px; + border-radius: var(--radius); background: var(--color-bg); color: var(--color-text); } - -.form-group textarea { - resize: vertical; +.form-group input:focus, +.form-group textarea:focus { + outline: none; + border-color: var(--color-accent); } +.form-group textarea { resize: vertical; } + .form-separator { text-align: center; - color: var(--color-meta); - margin: 15px 0; + color: var(--color-text-secondary); + margin: 14px 0; + font-size: 12px; } -.form-actions { - margin-top: 15px; +.form-actions { margin-top: 16px; } + +.form-actions a { + margin-left: 12px; + color: var(--color-text-secondary); + font-size: 13px; } -.form-actions button { - background: var(--color-header); - color: #fff; - border: none; - padding: 8px 16px; - border-radius: 3px; - cursor: pointer; - font-size: 10pt; +.auth-form .form-group small { + display: block; + font-size: 11px; + color: var(--color-text-secondary); + margin-top: 3px; } -.form-actions button:hover { - opacity: 0.9; +.auth-link { + text-align: center; + margin-top: 14px; + font-size: 13px; + color: var(--color-text-secondary); } .submit-info { - margin-top: 15px; - font-size: 9pt; - color: var(--color-meta); + margin-top: 14px; + font-size: 12px; + color: var(--color-text-secondary); } .tag-checkboxes { @@ -460,106 +397,83 @@ a:hover { display: inline-flex; align-items: center; gap: 4px; - font-size: 9pt; + font-size: 13px; color: var(--color-text); cursor: pointer; } -.tag-checkbox input[type="checkbox"] { - width: auto; -} +.tag-checkbox input[type="checkbox"] { width: auto; } /* Reply Page */ -.reply-page { - max-width: 600px; - padding: 20px 0; -} - -.reply-page h2 { - font-size: 13pt; - margin-bottom: 15px; -} - .parent-comment { - background: rgba(0,0,0,0.02); - padding: 10px; - border-radius: 3px; - margin-bottom: 15px; + background: var(--color-surface); + border: 1px solid var(--color-border); + padding: 12px 16px; + border-radius: var(--radius); + margin-bottom: 16px; } .parent-header { - font-size: 9pt; - color: var(--color-meta); - margin-bottom: 5px; + font-size: 12px; + color: var(--color-text-secondary); + margin-bottom: 6px; } .parent-text { - font-size: 9pt; - color: var(--color-text); - border-left: 2px solid var(--color-border); - padding-left: 10px; - margin: 10px 0; + font-size: 13px; + border-left: 3px solid var(--color-border); + padding-left: 12px; + margin: 8px 0; } .reply-form textarea { width: 100%; font-family: var(--font-main); - font-size: 10pt; - padding: 8px; + font-size: 14px; + padding: 10px 12px; border: 1px solid var(--color-border); - border-radius: 3px; - margin-bottom: 10px; + border-radius: var(--radius); + margin-bottom: 8px; resize: vertical; + background: var(--color-surface); + color: var(--color-text); } .cancel-link { - margin-left: 10px; - color: var(--color-meta); + margin-left: 12px; + color: var(--color-text-secondary); + font-size: 13px; } /* User Page */ -.user-page { - padding: 20px 0; -} +.user-page { padding: 24px 0; } -.user-header { - margin-bottom: 20px; -} - -.user-header h2 { - font-size: 13pt; - margin-bottom: 5px; -} +.user-header { margin-bottom: 20px; } +.user-header h2 { font-size: 18px; margin-bottom: 4px; } -.user-stats { - font-size: 9pt; - color: var(--color-meta); -} - -.user-karma { - margin-right: 10px; -} +.user-stats { font-size: 13px; color: var(--color-text-secondary); } +.user-karma { margin-right: 12px; } -.user-about { - margin-top: 10px; - font-size: 9pt; - line-height: 1.4; -} +.user-about { margin-top: 8px; font-size: 13px; line-height: 1.5; } .user-tabs { - margin-bottom: 15px; - padding-bottom: 10px; + margin-bottom: 16px; + padding-bottom: 8px; border-bottom: 1px solid var(--color-border); + display: flex; + gap: 4px; } .user-tab { - margin-right: 15px; - color: var(--color-meta); + color: var(--color-text-secondary); + padding: 4px 10px; + border-radius: var(--radius); + font-size: 13px; } - .user-tab.active { - color: var(--color-link); - font-weight: bold; + background: var(--color-border); + color: var(--color-text); + font-weight: 500; } .user-comment { @@ -568,178 +482,92 @@ a:hover { } .user-comment-meta { - font-size: 8pt; - color: var(--color-meta); - margin-bottom: 5px; + font-size: 12px; + color: var(--color-text-secondary); + margin-bottom: 4px; } -.user-comment-text { - font-size: 9pt; - line-height: 1.4; -} +.user-comment-text { font-size: 13px; line-height: 1.5; } /* Tag Page */ -.tag-page h2 { - font-size: 13pt; - margin-bottom: 15px; -} - -/* Search Page */ -.search-page { - max-width: 600px; - padding: 20px 0; -} - -.search-page h2 { - font-size: 13pt; - margin-bottom: 15px; -} +.tag-page h2 { font-size: 18px; margin-bottom: 16px; } +/* Search */ .search-form { display: flex; - gap: 10px; + gap: 8px; margin-bottom: 20px; } .search-form input { flex: 1; font-family: var(--font-main); - font-size: 10pt; - padding: 8px; + font-size: 14px; + padding: 8px 12px; border: 1px solid var(--color-border); - border-radius: 3px; - background: var(--color-bg); + border-radius: var(--radius); + background: var(--color-surface); color: var(--color-text); } +.search-form input:focus { outline: none; border-color: var(--color-accent); } -.search-form button { - background: var(--color-header); - color: #fff; - border: none; - padding: 8px 16px; - border-radius: 3px; - cursor: pointer; - font-size: 10pt; -} - -.search-results-list { - list-style: none; -} +.search-results-list { list-style: none; } .search-result { padding: 10px 0; border-bottom: 1px solid var(--color-border); } -.search-result-title { - font-size: 10pt; - display: block; - margin-bottom: 5px; -} - -.search-result-meta { - font-size: 8pt; - color: var(--color-meta); - margin-bottom: 5px; -} - -.search-result-meta span { - margin-right: 10px; -} - -.search-result-text { - font-size: 9pt; - color: var(--color-meta); -} - -/* Auth Pages */ -.auth-page { - max-width: 400px; - margin: 0 auto; - padding: 40px 20px; -} - -.auth-page h2 { - font-size: 13pt; - margin-bottom: 20px; - text-align: center; -} - -.auth-form { - background: rgba(0,0,0,0.02); - padding: 20px; - border-radius: 3px; -} - -.auth-form .form-group small { - display: block; - font-size: 8pt; - color: var(--color-meta); - margin-top: 3px; -} - -.auth-link { - text-align: center; - margin-top: 15px; - font-size: 9pt; - color: var(--color-meta); -} - -/* Settings Page */ -.settings-page { - max-width: 600px; - padding: 20px 0; -} +.search-result-title { font-size: 14px; font-weight: 500; display: block; margin-bottom: 4px; } +.search-result-meta { font-size: 12px; color: var(--color-text-secondary); margin-bottom: 4px; } +.search-result-meta span { margin-right: 10px; } +.search-result-text { font-size: 13px; color: var(--color-text-secondary); } -/* Error Messages */ +/* Messages */ .error-message { - background: rgba(255, 0, 0, 0.1); - border: 1px solid var(--color-error); + background: rgba(220, 38, 38, 0.08); + border: 1px solid rgba(220, 38, 38, 0.3); color: var(--color-error); - padding: 10px; - border-radius: 3px; - margin-bottom: 15px; - font-size: 9pt; + padding: 10px 14px; + border-radius: var(--radius); + margin-bottom: 16px; + font-size: 13px; +} + +.success-message { + background: rgba(22, 163, 74, 0.08); + border: 1px solid rgba(22, 163, 74, 0.3); + color: var(--color-success); + padding: 10px 14px; + border-radius: var(--radius); + margin-bottom: 16px; + font-size: 13px; } .empty-message { - color: var(--color-meta); + color: var(--color-text-secondary); font-style: italic; - padding: 20px 0; + padding: 24px 0; + text-align: center; } +/* Settings */ +.settings-page { max-width: 520px; padding: 24px 0; } + /* Footer */ .site-footer { max-width: var(--max-width); - margin: 20px auto; - padding: 10px 0; + margin: 24px auto; + padding: 12px 16px; border-top: 1px solid var(--color-border); text-align: center; - font-size: 9pt; -} - -.site-footer a { - color: var(--color-meta); + font-size: 12px; } +.site-footer a { color: var(--color-text-secondary); } /* Responsive */ -@media (max-width: 768px) { - :root { - --max-width: 100%; - } - - .nav-main { - flex-wrap: wrap; - padding: 5px; - } - - .nav-links { - order: 3; - width: 100%; - margin-top: 5px; - } - - .story-item { - padding: 8px 5px; - } +@media (max-width: 640px) { + :root { --max-width: 100%; } + .nav-main { padding: 8px 12px; gap: 8px; } + .container { padding: 12px; } } diff --git a/static/style.css.gz b/static/style.css.gz Binary files differ. diff --git a/templates/application.html b/templates/application.html @@ -16,7 +16,6 @@ <a href="/" class="nav-link" preload="mousedown">hot</a> <a href="/new" class="nav-link" preload="mousedown">new</a> <a href="/top" class="nav-link" preload="mousedown">top</a> - <span class="nav-separator">|</span> <a href="/submit" class="nav-link" preload="mousedown">submit</a> </div> <div class="nav-right"> diff --git a/templates/feed.html b/templates/feed.html @@ -1,22 +1,15 @@ <div class="feed"> - <div class="feed-nav"> - <span class="sort-links"> - <a href="/" class="sort-link{% if sort == "hot" %} active{% endif %}">hot</a> - <a href="/new" class="sort-link{% if sort == "new" %} active{% endif %}">new</a> - <a href="/top" class="sort-link{% if sort == "top" %} active{% endif %}">top</a> - </span> - {% if !tags.is_empty() %} - <span class="tag-links"> - {% for tag in tags %} - {% match tag.name %} - {% when Some with (name) %} - <a href="/tag/{{ name }}" class="tag-link">{{ name }}</a> - {% when None %} - {% endmatch %} - {% endfor %} - </span> - {% endif %} + {% if !tags.is_empty() %} + <div class="feed-tags"> + {% for tag in tags %} + {% match tag.name %} + {% when Some with (name) %} + <a href="/tag/{{ name }}" class="tag-link">{{ name }}</a> + {% when None %} + {% endmatch %} + {% endfor %} </div> + {% endif %} <ol class="story-list" start="{{ page * 30 + 1 }}"> {% for story in stories %}