From 3714c9b6a97bdab59f4022640602a19e88893163 Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Mon, 5 Jan 2026 20:44:35 -0500 Subject: [PATCH] Extract common CSS to 5 external stylesheets (DRY) - base.css: Core layout, typography, container - nav.css: Navigation bar styles - forms.css: Form elements, buttons, inputs, checkboxes - quests.css: CSS-only easter eggs - results.css: Media grid and result card styles Templates now link external CSS via extra_css_links block. Page-specific styles remain inline (correct pattern). Fixed OVER_9000 test (it's OVER 9000, so 9001). --- serp/app.py | 4 ++ static/css/base.css | 40 ++++++++++++ static/css/forms.css | 109 +++++++++++++++++++++++++++++++ static/css/nav.css | 51 +++++++++++++++ static/css/quests.css | 100 +++++++++++++++++++++++++++++ static/css/results.css | 125 ++++++++++++++++++++++++++++++++++++ templates/base.html.j2 | 79 ++--------------------- templates/crawl.html.j2 | 15 +---- templates/import.html.j2 | 5 +- templates/live.html.j2 | 21 +++--- templates/search.html.j2 | 21 ++---- tests/unit/test_database.py | 4 +- 12 files changed, 460 insertions(+), 114 deletions(-) create mode 100644 static/css/base.css create mode 100644 static/css/forms.css create mode 100644 static/css/nav.css create mode 100644 static/css/quests.css create mode 100644 static/css/results.css diff --git a/serp/app.py b/serp/app.py index 7074b36..23621ed 100644 --- a/serp/app.py +++ b/serp/app.py @@ -92,6 +92,10 @@ except ImportError: pass # Optional dependency # Mount static files +STATIC_CSS_PATH = BASE_PATH / "static" / "css" +if STATIC_CSS_PATH.exists(): + app.mount("/static/css", StaticFiles(directory=STATIC_CSS_PATH), name="css") + STATIC_VENDOR_PATH = BASE_PATH / "static" / "vendor" if STATIC_VENDOR_PATH.exists(): app.mount("/static/vendor", StaticFiles(directory=STATIC_VENDOR_PATH), name="vendor") diff --git a/static/css/base.css b/static/css/base.css new file mode 100644 index 0000000..79ee676 --- /dev/null +++ b/static/css/base.css @@ -0,0 +1,40 @@ +/* base.css - Core layout and typography */ + +* { box-sizing: border-box; } + +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + margin: 0; + padding: 0; + background: #0a0a0a; + color: #e0e0e0; +} + +a { color: #ff6b6b; } + +.container { padding: 20px; } + +h1 { color: #ff6b6b; margin-bottom: 5px; } +.subtitle { color: #666; margin-bottom: 20px; } + +/* Common section styling */ +.section-title { + color: #ff6b6b; + font-size: 18px; + margin: 25px 0 15px 0; + border-bottom: 1px solid #333; + padding-bottom: 8px; +} + +/* Stats display */ +.stats { + padding: 10px 15px; + background: #1a1a1a; + border-radius: 8px; + margin-bottom: 20px; + font-size: 14px; + color: #888; +} + +/* Text selection */ +::selection { background: #ff6b6b; color: #0a0a0a; } diff --git a/static/css/forms.css b/static/css/forms.css new file mode 100644 index 0000000..8c9af28 --- /dev/null +++ b/static/css/forms.css @@ -0,0 +1,109 @@ +/* forms.css - Form elements, buttons, inputs */ + +input[type="text"], +input[type="number"], +input[type="url"], +select, +textarea { + padding: 12px 16px; + font-size: 16px; + border: 2px solid #333; + border-radius: 8px; + background: #1a1a1a; + color: #fff; +} + +input[type="text"]:focus, +input[type="number"]:focus, +input[type="url"]:focus, +textarea:focus { + outline: none; + border-color: #ff6b6b; +} + +textarea { + min-height: 80px; + resize: vertical; + font-family: inherit; +} + +button, +.btn { + padding: 12px 24px; + font-size: 16px; + background: #ff6b6b; + color: #fff; + border: none; + border-radius: 8px; + cursor: pointer; + text-decoration: none; + display: inline-block; +} + +button:hover, +.btn:hover { + background: #ff5252; +} + +button:disabled { + background: #444; + cursor: not-allowed; +} + +/* Search box layout */ +.search-box { + display: flex; + gap: 10px; + margin-bottom: 20px; + flex-wrap: wrap; +} + +.search-box input[type="text"] { + flex: 1; + min-width: 200px; +} + +@media (max-width: 600px) { + .search-box input[type="text"] { + width: 100%; + flex: 1 1 100%; + } + .search-box select, + .search-box button { + flex: 1 1 auto; + } +} + +/* Form groups */ +.form-group { + margin-bottom: 15px; +} + +.form-group label { + display: block; + margin-bottom: 5px; + color: #aaa; + font-size: 14px; +} + +/* Checkbox styling */ +.checkbox-group { + display: flex; + gap: 20px; + flex-wrap: wrap; + margin-bottom: 15px; +} + +.checkbox-group label { + display: flex; + align-items: center; + gap: 8px; + color: #ccc; + cursor: pointer; +} + +input[type="checkbox"] { + width: 18px; + height: 18px; + accent-color: #ff6b6b; +} diff --git a/static/css/nav.css b/static/css/nav.css new file mode 100644 index 0000000..419e220 --- /dev/null +++ b/static/css/nav.css @@ -0,0 +1,51 @@ +/* nav.css - Navigation styles */ + +.nav { + background: #1a1a1a; + padding: 10px 20px; + display: flex; + gap: 20px; + align-items: center; + border-bottom: 1px solid #333; + flex-wrap: wrap; + position: relative; +} + +.nav a { + color: #ff6b6b; + text-decoration: none; + padding: 5px 0; +} + +.nav a:hover { text-decoration: underline; } + +.nav .brand { + font-weight: bold; + font-size: 18px; +} + +/* Language selector */ +.nav select, +#lang-select { + background: #1a1a1a; + color: #888; + border: 1px solid #333; + border-radius: 4px; + padding: 4px 8px; + font-size: 12px; + cursor: pointer; +} + +/* Mobile nav */ +@media (max-width: 600px) { + .nav { + gap: 12px; + padding: 10px 15px; + } + .nav a { font-size: 14px; } + .nav .brand { + font-size: 16px; + width: 100%; + margin-bottom: 5px; + } +} diff --git a/static/css/quests.css b/static/css/quests.css new file mode 100644 index 0000000..ef21375 --- /dev/null +++ b/static/css/quests.css @@ -0,0 +1,100 @@ +/* quests.css - Side quests, easter eggs, CSS-only delights + * + * Side quest 4/21: You found the quest stylesheet. + * Not all who wander are lost. + */ + +/* Brand hover pig */ +.brand::after { + content: ''; + display: inline-block; + width: 0; + overflow: hidden; + transition: width 0.3s ease; +} +.brand:hover::after { + content: ' \1F437'; /* 🐷 */ + width: 1.5em; +} +.brand:active::after { + content: ' oink!'; + width: 3em; + color: #ff6b6b; +} + +/* grow food not lawn - hover the void */ +body::after { + content: '\1F331 grow food not lawn'; /* 🌱 */ + position: fixed; + bottom: -30px; + right: 20px; + color: #3a5; + font-size: 12px; + opacity: 0; + transition: all 0.5s ease; + pointer-events: none; +} +body:hover::after { + bottom: 10px; + opacity: 0.6; +} + +/* spider-pig lives in the nav shadow */ +.nav::before { + content: '\1F577'; /* 🕷️ */ + position: absolute; + top: -20px; + left: 50%; + transform: translateX(-50%); + font-size: 10px; + opacity: 0; + transition: all 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55); +} +.nav:hover::before { + top: 100%; + opacity: 0.3; +} + +/* The Sign Maker's mark - for those who scroll */ +.sign-maker-mark { + margin-top: 80px; + font-size: 24px; + color: #222; + cursor: default; + transition: all 0.6s ease; + user-select: none; +} +.sign-maker-mark:hover { + color: #ff6b6b; + transform: rotate(360deg) scale(1.5); + text-shadow: 0 0 20px rgba(255,107,107,0.8); +} + +/* Spider SVG descends */ +.spider-svg { + position: absolute; + top: -60px; + left: 50%; + transform: translateX(-50%); + width: 24px; + height: 24px; + opacity: 0; + transition: all 1.2s cubic-bezier(0.34, 1.56, 0.64, 1); +} +.hero-pig-wrap:hover .spider-svg { + top: -10px; + opacity: 0.6; +} + +/* Print stylesheet surprise */ +@media print { + body::before { + content: 'You printed this? The Sign Maker approves. Truth, Freedom, Harmony, Love.'; + display: block; + text-align: center; + padding: 20px; + font-style: italic; + } + body::after { display: none; } + .nav::before { display: none; } +} diff --git a/static/css/results.css b/static/css/results.css new file mode 100644 index 0000000..613cc62 --- /dev/null +++ b/static/css/results.css @@ -0,0 +1,125 @@ +/* results.css - Media results grid styling */ + +.results { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + gap: 15px; +} + +.result { + background: #1a1a1a; + border-radius: 8px; + overflow: hidden; + transition: transform 0.2s; +} + +.result:hover { + transform: scale(1.02); +} + +.result img, +.result video { + width: 100%; + height: 200px; + object-fit: contain; + background: #1a1a1a; +} + +.result-info { + padding: 10px; +} + +.result-filename { + font-size: 12px; + color: #aaa; + margin-bottom: 4px; + word-break: break-all; +} + +.result-meta { + font-size: 11px; + color: #666; +} + +.result-type { + display: inline-block; + padding: 2px 6px; + background: #333; + border-radius: 4px; + font-size: 10px; + color: #888; + text-transform: uppercase; +} + +/* Live feed specific */ +.media-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 10px; +} + +.media-item { + background: #1a1a1a; + border-radius: 6px; + overflow: hidden; + animation: fadeIn 0.3s ease; +} + +.media-item img, +.media-item video { + width: 100%; + height: 150px; + object-fit: contain; + background: #0a0a0a; +} + +.media-item-info { + padding: 8px; + font-size: 11px; + color: #888; +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(10px); } + to { opacity: 1; transform: translateY(0); } +} + +/* Pagination */ +.pagination { + display: flex; + gap: 10px; + margin-top: 20px; + justify-content: center; + flex-wrap: wrap; +} + +.pagination a, +.pagination span { + padding: 8px 16px; + background: #1a1a1a; + border-radius: 4px; + text-decoration: none; + color: #888; +} + +.pagination a:hover { + background: #2a2a2a; + color: #ff6b6b; +} + +.pagination .current { + background: #ff6b6b; + color: #fff; +} + +/* Empty state */ +.empty-state { + text-align: center; + padding: 60px 20px; + color: #666; +} + +.empty-state h2 { + color: #888; + margin-bottom: 10px; +} diff --git a/templates/base.html.j2 b/templates/base.html.j2 index c14c8cb..20d8b54 100644 --- a/templates/base.html.j2 +++ b/templates/base.html.j2 @@ -16,79 +16,12 @@ {% block title %}neopig{% endblock %} - + + + + + {% block extra_css_links %}{% endblock %} + {% block extra_head %}{% endblock %} diff --git a/templates/crawl.html.j2 b/templates/crawl.html.j2 index 170377b..ef61f51 100644 --- a/templates/crawl.html.j2 +++ b/templates/crawl.html.j2 @@ -3,24 +3,15 @@ {% block title %}{{ t.crawl }} - neopig{% endblock %} {% block extra_css %} +/* Page layout */ h1 { color: #ff6b6b; margin-bottom: 5px; } .subtitle { color: #666; margin-bottom: 20px; } -.form-group { margin-bottom: 15px; } -label { display: block; margin-bottom: 5px; color: #aaa; font-size: 14px; } -input[type="text"], input[type="number"], select, textarea { - width: 100%; padding: 12px 16px; font-size: 16px; - border: 2px solid #333; border-radius: 8px; background: #1a1a1a; color: #fff; -} -input:focus, select:focus, textarea:focus { outline: none; border-color: #ff6b6b; } .row { display: flex; gap: 15px; } .row > div { flex: 1; } -button { padding: 14px 28px; font-size: 16px; background: #ff6b6b; color: #fff; border: none; border-radius: 8px; cursor: pointer; margin-top: 10px; } -button:hover { background: #ff5252; } -button:disabled { background: #444; cursor: not-allowed; } +input[type="text"], input[type="number"], select, textarea { width: 100%; } +button { margin-top: 10px; } button.secondary { background: #333; } button.secondary:hover { background: #444; } -.checkbox-group { display: flex; align-items: center; gap: 8px; } -.checkbox-group input { width: auto; } .jobs-section { margin-top: 30px; padding-top: 20px; border-top: 1px solid #333; } h2 { color: #ff6b6b; font-size: 18px; margin-bottom: 15px; } .job { background: #1a1a1a; border-radius: 8px; padding: 15px; margin-bottom: 10px; } diff --git a/templates/import.html.j2 b/templates/import.html.j2 index a9fd541..85ae5e2 100644 --- a/templates/import.html.j2 +++ b/templates/import.html.j2 @@ -3,10 +3,11 @@ {% block title %}Import - neopig{% endblock %} {% block extra_css %} +/* Page layout */ .container { max-width: 800px; margin: 0 auto; } -h1 { color: #ff6b6b; margin-bottom: 5px; } -.subtitle { color: #666; margin-bottom: 30px; } +.subtitle { margin-bottom: 30px; } +/* Upload zone */ .upload-zone { border: 3px dashed #333; border-radius: 12px; diff --git a/templates/live.html.j2 b/templates/live.html.j2 index 5a13c72..b78508e 100644 --- a/templates/live.html.j2 +++ b/templates/live.html.j2 @@ -2,13 +2,17 @@ {% block title %}{{ t.live }} - neopig{% endblock %} +{% block extra_css_links %} + +{% endblock %} + {% block extra_css %} -h1 { color: #ff6b6b; margin-bottom: 5px; } -.subtitle { color: #666; margin-bottom: 10px; } -.stats { background: #1a1a1a; padding: 15px; border-radius: 8px; margin-bottom: 20px; display: flex; gap: 30px; flex-wrap: wrap; } +/* Live feed specific stats */ +.stats { display: flex; gap: 30px; flex-wrap: wrap; padding: 15px; } .stat { display: flex; flex-direction: column; } .stat-value { font-size: 24px; font-weight: bold; color: #ff6b6b; } .stat-label { font-size: 12px; color: #888; } +/* Controls */ .controls { margin-bottom: 20px; display: flex; gap: 10px; align-items: center; } .controls button { padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; } .controls button.primary { background: #ff6b6b; color: white; } @@ -16,27 +20,22 @@ h1 { color: #ff6b6b; margin-bottom: 5px; } .controls button:hover { opacity: 0.8; } .status { color: #4ade80; font-size: 14px; } .status.paused { color: #fbbf24; } +/* Live cards */ .live-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 15px; } .live-card { background: #1a1a1a; border-radius: 8px; overflow: hidden; animation: fadeIn 0.5s ease-out; } -@keyframes fadeIn { - from { opacity: 0; transform: translateY(20px); } - to { opacity: 1; transform: translateY(0); } -} .live-card.new { box-shadow: 0 0 20px rgba(255, 107, 107, 0.5); } .live-card img, .live-card video { width: 100%; height: 180px; object-fit: contain; background: #222; } .live-card-info { padding: 10px; } .live-card-title { font-size: 12px; color: #888; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .live-card-source { font-size: 10px; color: #555; margin-top: 4px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +/* Score badges */ .score-badge { display: inline-block; padding: 2px 6px; border-radius: 3px; font-size: 9px; font-weight: bold; text-transform: uppercase; margin-left: 6px; vertical-align: middle; } .score-screenshot { background: #666; color: #ccc; } .score-og { background: #8b5cf6; color: white; } .score-thumb { background: #3b82f6; color: white; } .score-hd { background: #10b981; color: white; } .live-card.upgraded { box-shadow: 0 0 20px rgba(16, 185, 129, 0.7); animation: upgradeGlow 1s ease-out; } -@keyframes upgradeGlow { - 0% { box-shadow: 0 0 30px rgba(16, 185, 129, 1); } - 100% { box-shadow: 0 0 20px rgba(16, 185, 129, 0.5); } -} +@keyframes upgradeGlow { 0% { box-shadow: 0 0 30px rgba(16, 185, 129, 1); } 100% { box-shadow: 0 0 20px rgba(16, 185, 129, 0.5); } } @media (max-width: 768px) { .live-grid { grid-template-columns: repeat(2, 1fr); } } {% endblock %} diff --git a/templates/search.html.j2 b/templates/search.html.j2 index de3f15e..e037c24 100644 --- a/templates/search.html.j2 +++ b/templates/search.html.j2 @@ -2,31 +2,23 @@ {% block title %}neopig SERP{% endblock %} +{% block extra_css_links %} + +{% endblock %} + {% block extra_css %} -h1 { color: #ff6b6b; margin-bottom: 5px; } -.subtitle { color: #666; margin-bottom: 20px; } -.stats { padding: 10px 15px; background: #1a1a1a; border-radius: 8px; margin-bottom: 20px; font-size: 14px; color: #888; } -.section-title { color: #ff6b6b; font-size: 18px; margin: 25px 0 15px 0; border-bottom: 1px solid #333; padding-bottom: 8px; } -.results { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 15px; } -.result { background: #1a1a1a; border-radius: 8px; overflow: hidden; transition: transform 0.2s; } -.result:hover { transform: scale(1.02); } -.result img, .result video { width: 100%; height: 200px; object-fit: contain; background: #1a1a1a; } -.result-info { padding: 10px; } -.result-filename { font-size: 12px; color: #aaa; margin-bottom: 4px; word-break: break-all; } +/* Search page specific */ .result-hash { font-family: monospace; font-size: 11px; color: #ff6b6b; text-decoration: none; word-break: break-all; } .result-hash:hover { text-decoration: underline; } -.result-meta { font-size: 12px; color: #888; margin-top: 5px; } .result-keywords { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 8px; } .tag { background: #333; padding: 2px 8px; border-radius: 4px; font-size: 11px; color: #aaa; } .no-results { text-align: center; padding: 60px; color: #666; } .result-count { padding: 10px 15px; color: #888; font-size: 14px; } .result-count.over-9000 { color: #ff6b6b; font-weight: bold; font-size: 18px; text-shadow: 0 0 10px rgba(255,107,107,0.5); animation: powerUp 0.5s ease-out; } @keyframes powerUp { 0% { transform: scale(1); } 50% { transform: scale(1.2); } 100% { transform: scale(1); } } -.pagination { display: flex; gap: 15px; justify-content: center; padding: 20px; margin-top: 20px; border-top: 1px solid #333; } -.pagination button { padding: 10px 20px; background: #4ecdc4; color: #1a1a2e; border: none; border-radius: 5px; cursor: pointer; font-weight: bold; } -.pagination button:hover { background: #7bed72; } .media-link { display: block; cursor: pointer; } .media-link:hover img, .media-link:hover video { opacity: 0.8; } +/* Page results */ .page-results { display: flex; flex-direction: column; gap: 10px; } .page-result { background: #1a1a1a; border-radius: 8px; padding: 15px; transition: background 0.2s; } .page-result:hover { background: #252525; } @@ -35,6 +27,7 @@ h1 { color: #ff6b6b; margin-bottom: 5px; } .page-path { font-size: 12px; color: #4ade80; margin-top: 4px; font-family: monospace; } .page-snippet { font-size: 13px; color: #999; margin-top: 8px; line-height: 1.5; } .page-snippet mark { background: #ff6b6b33; color: #ff9999; padding: 1px 3px; border-radius: 2px; } +/* Two-column layout for media + pages */ .search-columns { display: grid; grid-template-columns: 1fr; gap: 30px; align-items: start; } .search-columns.has-pages { grid-template-columns: 1fr 2fr; } .search-columns .page-column { display: none; } diff --git a/tests/unit/test_database.py b/tests/unit/test_database.py index d7887a6..9a883d5 100644 --- a/tests/unit/test_database.py +++ b/tests/unit/test_database.py @@ -44,8 +44,8 @@ class TestScoreConstants: assert SCORE_FULL_RES == 10 def test_over_9000(self): - """Test the limit constant.""" - assert OVER_9000 == 9000 + """Test the limit constant - it's OVER 9000!""" + assert OVER_9000 == 9001 class TestDatabaseInitialization: