Major frontend overhaul. Added tailwindcss.

This commit is contained in:
2025-07-30 23:51:33 +02:00
parent ff0f528d36
commit 3beadf57a3
12 changed files with 3096 additions and 694 deletions

View File

@@ -61,8 +61,13 @@ coverage.xml
*.mo *.mo
*.pot *.pot
# Django stuff: # Ignore Node.js dependencies (they will be installed inside the container)
node_modules/
# Ignore database and log files
*.db
*.log *.log
*.db-journal
# Sphinx documentation # Sphinx documentation
docs/_build/ docs/_build/

1
.gitignore vendored
View File

@@ -9,3 +9,4 @@ reddit_stock_analyzer.egg-info/
images/ images/
public/ public/
config/certbot/ config/certbot/
node_modules/

View File

@@ -1,3 +1,17 @@
FROM node:24-bookworm-slim AS builder
WORKDIR /usr/src/build
COPY package.json package-lock.json ./
RUN npm install
COPY tailwind.config.js ./
COPY templates/ ./templates/
COPY static/css/input.css ./static/css/input.css
RUN npx tailwindcss -i ./static/css/input.css -o ./static/css/style.css --minify
FROM python:3.13.5-slim FROM python:3.13.5-slim
EXPOSE 5000 EXPOSE 5000
@@ -10,6 +24,7 @@ RUN python3 -m pip install --upgrade pip
RUN python3 -m pip install --no-cache-dir -r requirements.txt RUN python3 -m pip install --no-cache-dir -r requirements.txt
COPY . . COPY . .
COPY --from=builder /usr/src/build/static/css/style.css ./static/css/style.css
RUN python3 -m pip install -e . RUN python3 -m pip install -e .

1294
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

25
package.json Normal file
View File

@@ -0,0 +1,25 @@
{
"name": "reddit_stock_analyzer",
"version": "1.0.0",
"description": "A powerful, installable command-line tool and web dashboard to scan Reddit for stock ticker mentions, perform sentiment analysis, generate insightful reports, and create shareable summary images.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "ssh://git@git.pkhamre.com:43721/pkhamre/reddit_stock_analyzer.git"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs",
"devDependencies": {
"@tailwindcss/cli": "^4.1.11",
"@tailwindcss/typography": "^0.5.16",
"tailwindcss": "^4.1.11"
},
"dependencies": {
"@tailwindplus/elements": "^1.0.3"
}
}

2
static/css/input.css Normal file
View File

@@ -0,0 +1,2 @@
@import "tailwindcss";
@plugin "@tailwindcss/typography";

1441
static/css/style.css Normal file

File diff suppressed because it is too large Load Diff

27
tailwind.config.js Normal file
View File

@@ -0,0 +1,27 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./templates/**/*.html',
],
safelist: [
'text-violet-400',
'text-lime-400',
'text-cyan-400',
'text-yellow-400',
'text-red-400',
'text-orange-400',
'text-emerald-400',
'text-blue-400',
'text-gray-300',
'text-pink-400'
],
theme: {
extend: {
fontFamily: {
sans: ['Inter', 'sans-serif'],
},
},
},
plugins: [
],
}

View File

@@ -3,73 +3,49 @@
{% block title %}About RSTAT{% endblock %} {% block title %}About RSTAT{% endblock %}
{% block content %} {% block content %}
<div class="image-container" style="text-align: left; max-width: 800px;"> <!-- This outer div now handles the centering -->
<h1 style="text-align: center;">About RSTAT</h1> <div class="flex flex-col items-center">
<h2 style="color: #cbd5e0; border-bottom: 1px solid #4a5568; padding-bottom: 0.5rem; margin-top: 2rem;">What is this? <div class="w-full max-w-3xl bg-slate-800/50 ring-1 ring-slate-700 rounded-2xl p-6 sm:p-10 shadow-2xl">
</h2> <div class="text-center mb-10">
<p style="color: #a0aec0; line-height: 1.8;"> <h1 class="text-3xl sm:text-4xl font-extrabold tracking-tight text-white">About RSTAT</h1>
RSTAT (Reddit Stock Analysis Tool) is an automated data pipeline that scans popular financial communities on Reddit
to identify and analyze trending stock tickers. It provides a daily and weekly snapshot of the most discussed
stocks, their social sentiment, and key financial data.
</p>
<h2 style="color: #cbd5e0; border-bottom: 1px solid #4a5568; padding-bottom: 0.5rem; margin-top: 2rem;">How does it
work?</h2>
<p style="color: #a0aec0; line-height: 1.8;">
The system is composed of several automated scripts:
<ul>
<li style="margin-bottom: 0.5rem;">A <strong>scraper</strong> runs on an hourly schedule to read new posts and
comments from a predefined list of subreddits.</li>
<li style="margin-bottom: 0.5rem;">A <strong>sentiment analyzer</strong> scores each mention as Bullish, Bearish, or
Neutral using a natural language processing model.</li>
<li style="margin-bottom: 0.5rem;">A <strong>data fetcher</strong> enriches the ticker data with the latest closing
price and market capitalization from Yahoo Finance.</li>
<li style="margin-bottom: 0.5rem;">All data is stored in a local <strong>SQLite database</strong>.</li>
<li style="margin-bottom: 0.5rem;">This <strong>web dashboard</strong> reads from the database to provide a clean,
interactive visualization of the results.</li>
</ul>
</p>
<h2 style="color: #cbd5e0; border-bottom: 1px solid #4a5568; padding-bottom: 0.5rem; margin-top: 2rem;">What qualifies
as a "mention"?</h2>
<p style="color: #a0aec0; line-height: 1.8;">
The counting logic is context-aware. If a stock ticker is found in a post's <strong>title</strong>, the system
assumes the entire comment section is about that ticker and credits it with a mention for every comment. If no
ticker is in the title, it only counts <strong>direct mentions</strong> within comments. This provides a more
accurate picture of a stock's overall discussion volume.
</p>
<h2 style="color: #cbd5e0; border-bottom: 1px solid #4a5568; padding-bottom: 0.5rem; margin-top: 2rem;">Supporting the
Project</h2>
<p style="color: #a0aec0; line-height: 1.8;">
RSTAT is a free and <b>soon-to-be</b> open-source project developed as a passion for data and financial markets. To ensure the
dashboard remains fast, reliable, and publicly accessible, it is hosted on a small virtual server with running costs
of approximately $6 per month and the domain about €30 per year.
</p>
<p style="color: #a0aec0; line-height: 1.8;">
If you find this tool useful and would like to help cover these costs, donations are gratefully accepted. In the
spirit of Reddit's market communities, the preferred method is Dogecoin (DOGE). You can send any amount to the
following address:
</p>
<pre style="background-color: #1a202c; padding: 1rem; border-radius: 8px; font-size: 1rem; text-align: center; word-wrap: break-word;">
<code style="color: #e2e8f0;">DRTLo2BsBijY4MrLmNNHzmjZ5tVvpTebFE</code></pre>
<p style="color: #a0aec0; text-align: center;">
Thank you for your support!
</p>
<footer style="margin-top: 3rem; text-align: center;">
<div class="brand-name">
<a href="https://www.reddit.com/r/rstat/" target="_blank">
r/rstat
</a>
</div> </div>
<div class="brand-subtitle">
<a href="https://www.reddit.com/r/rstat/" target="_blank"> <!-- The 'prose' class will now work correctly inside this standard block flow -->
visit us for more. <article class="prose prose-slate prose-invert max-w-none">
</a> <h2>What is this?</h2>
<p>RSTAT (Reddit Stock Analysis Tool) is an automated data pipeline that scans popular financial communities on
Reddit to identify and analyze trending stock tickers. It provides a daily and weekly snapshot of the most
discussed stocks, their social sentiment, and key financial data.</p>
<h2>How does it work?</h2>
<ul>
<li>A <strong>scraper</strong> runs on a schedule to read new posts and comments from a predefined list of
subreddits.</li>
<li>A <strong>sentiment analyzer</strong> scores each mention as Bullish, Bearish, or Neutral using a natural
language processing model.</li>
<li>A <strong>data fetcher</strong> enriches the ticker data with the latest closing price and market
capitalization from Yahoo Finance.</li>
<li>All data is stored in a local <strong>SQLite database</strong>.</li>
<li>This <strong>web dashboard</strong> reads from the database to provide a clean, interactive visualization of
the results.</li>
</ul>
<h2>Supporting the Project</h2>
<p>RSTAT is a free and open-source project. To ensure the dashboard remains fast and reliable, it is hosted on a
small virtual server with running costs of approximately $6 per month. If you find this tool useful, donations
are gratefully accepted via Dogecoin (DOGE).</p>
<div class="not-prose bg-slate-900/50 ring-1 ring-slate-700 rounded-lg p-3 text-center">
<code class="text-sm text-slate-200 break-all">DRTLo2BsBijY4MrLmNNHzmjZ5tVvpTebFE</code>
</div>
</article>
<footer class="mt-12 text-center">
<div class="text-xl font-extrabold tracking-tight text-white">r/rstat</div>
<div class="text-sm text-slate-400">
<a href="https://www.reddit.com/r/rstat/" target="_blank" class="hover:text-white transition-colors">visit us
for more.</a>
</div> </div>
</footer> </footer>
</div>
</div> </div>
{% endblock %} {% endblock %}

View File

@@ -7,522 +7,78 @@
<title>{% block title %}RSTAT Dashboard{% endblock %}</title> <title>{% block title %}RSTAT Dashboard{% endblock %}</title>
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<style> <style>
/* This sets the custom font as the default for the page */
body { body {
margin: 0;
padding: 2rem;
font-family: 'Inter', sans-serif; font-family: 'Inter', sans-serif;
background: #1a1a1a;
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
} }
.navbar { [class*="text-"]>a {
width: 100%;
max-width: 1200px;
background-color: rgba(45, 55, 72, 0.5);
padding: 1rem 2rem;
border-radius: 12px;
margin-bottom: 2rem;
display: flex;
flex-wrap: wrap;
gap: 1rem;
align-items: center;
}
.navbar a {
color: #cbd5e0;
text-decoration: none;
font-weight: 600;
padding: 0.5rem 1rem;
border-radius: 6px;
transition: background-color 0.2s, color 0.2s;
}
.navbar a.active,
.navbar a:hover {
background-color: #4a5568;
color: #ffffff;
}
.view-switcher {
margin-left: auto;
display: flex;
gap: 0.5rem;
}
.dropdown {
position: relative;
/* Establishes a positioning context for the menu */
display: inline-block;
}
.dropdown {
position: relative;
display: inline-block;
/* Remove the padding that was causing the misalignment */
/* padding-bottom: 0.5rem; */
}
.dropdown-button {
color: #cbd5e0;
font-weight: 600;
padding: 0.5rem 1rem;
border-radius: 6px;
cursor: pointer;
transition: background-color 0.2s, color 0.2s;
display: block;
/* Ensures it behaves predictably with padding */
}
.dropdown-button.active,
.dropdown:hover .dropdown-button {
background-color: #4a5568;
color: #ffffff;
}
.dropdown-menu {
visibility: hidden;
opacity: 0;
position: absolute;
background-color: #2d3748;
min-width: 200px;
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.4);
z-index: 1;
border-radius: 8px;
padding: 0.5rem 0;
/* Use 'top: 100%' to position it right below the container, plus a small gap */
top: calc(100% + 0.25rem);
left: 0;
transition: opacity 0.2s ease-in-out, visibility 0.2s ease-in-out;
}
.dropdown-menu a {
color: #e2e8f0;
padding: 0.75rem 1.5rem;
text-decoration: none;
display: block;
text-align: left;
}
.dropdown-menu a:hover {
background-color: #4a5568;
}
.dropdown:hover .dropdown-menu {
visibility: visible;
opacity: 1;
}
.image-container {
width: 750px;
background: linear-gradient(145deg, #2d3748, #1a202c);
color: #ffffff;
border-radius: 16px;
padding: 2.5rem;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
text-align: center;
}
header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 2rem;
}
.header-action {
display: flex;
align-items: center;
gap: 1rem;
}
.header-action .icon-link svg {
color: #a0aec0;
transition: color 0.2s;
}
.header-action .icon-link:hover svg {
color: #ffffff;
}
.title-block {
text-align: left;
}
.title-block h1 {
font-size: 2.5rem;
font-weight: 800;
margin: 0;
line-height: 1;
}
.title-block h2 {
font-size: 1.25rem;
font-weight: 600;
margin: 0.5rem 0 0;
color: #a0aec0;
}
.date {
font-size: 1.1rem;
font-weight: 600;
color: #a0aec0;
letter-spacing: 0.02em;
}
table {
width: 100%;
border-collapse: collapse;
text-align: left;
}
th,
td {
padding: 1rem 0.5rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
th {
font-weight: 700;
text-transform: uppercase;
font-size: 0.75rem;
color: #718096;
letter-spacing: 0.05em;
}
th.mentions,
th.sentiment {
text-align: center;
}
th.financials {
text-align: right;
}
td {
font-size: 1.1rem;
font-weight: 600;
}
tr:last-child td {
border-bottom: none;
}
td.rank {
font-weight: 700;
color: #cbd5e0;
width: 5%;
}
td.ticker {
width: 15%;
}
td.financials {
text-align: right;
width: 20%;
}
td.mentions {
text-align: center;
width: 15%;
}
td.sentiment {
text-align: center;
width: 20%;
}
.sentiment-bullish {
color: #48bb78;
font-weight: 700;
}
.sentiment-bearish {
color: #f56565;
font-weight: 700;
}
.sentiment-neutral {
color: #a0aec0;
font-weight: 600;
}
footer {
margin-top: 2.5rem;
}
.brand-name {
font-size: 1.75rem;
font-weight: 800;
letter-spacing: -1px;
}
.brand-subtitle {
font-size: 1rem;
color: #a0aec0;
}
td.ticker a {
color: inherit; color: inherit;
text-decoration: none; text-decoration: none;
display: inline-block; transition: color 0.2s ease-in-out;
transition: transform 0.1s ease-in-out;
} }
td.ticker a:hover {
transform: scale(1.05);
}
td.financials a { [class*="text-"]>a:hover {
color: inherit;
text-decoration: none;
transition: color 0.2s;
}
td.financials a:hover {
color: #93c5fd;
}
tr:nth-child(1) td.ticker {
color: #d8b4fe;
}
tr:nth-child(6) td.ticker {
color: #fca5a5;
}
tr:nth-child(2) td.ticker {
color: #a3e635;
}
tr:nth-child(7) td.ticker {
color: #fdba74;
}
tr:nth-child(3) td.ticker {
color: #67e8f9;
}
tr:nth-child(8) td.ticker {
color: #6ee7b7;
}
tr:nth-child(4) td.ticker {
color: #fde047;
}
tr:nth-child(9) td.ticker {
color: #93c5fd;
}
tr:nth-child(5) td.ticker {
color: #fcd34d;
}
tr:nth-child(10) td.ticker {
color: #d1d5db;
}
.post-card a {
color: #93c5fd;
text-decoration: none;
transition: color 0.2s, text-decoration 0.2s;
}
.post-card a:hover {
color: #ffffff; color: #ffffff;
text-decoration: underline;
}
footer a {
color: inherit;
/* Inherit the color from .brand-subtitle */
text-decoration: none;
transition: color 0.2s;
}
footer a:hover {
color: #ffffff;
/* Make it brighter on hover */
}
@media (max-width: 768px) {
body {
padding: 0.5rem;
}
.navbar {
flex-direction: column;
align-items: stretch;
padding: 1rem;
}
.view-switcher {
margin-left: 0;
justify-content: center;
}
.dropdown-menu {
width: 100%;
}
.image-container {
width: 100%;
padding: 1.5rem 1rem;
}
header {
flex-direction: column;
gap: 0.5rem;
}
.header-action {
width: 100%;
justify-content: space-between;
}
table,
thead,
tbody,
th,
td,
tr {
display: block;
}
thead {
display: none;
}
tr {
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 8px;
margin-bottom: 1rem;
padding: 0.5rem;
}
td {
border: none;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
position: relative;
padding-left: 50%;
text-align: right;
display: flex;
/* Use flex for better alignment */
align-items: center;
justify-content: flex-end;
}
td:last-child {
border-bottom: none;
}
td::before {
content: attr(data-label);
position: absolute;
left: 1rem;
width: 45%;
padding-right: 1rem;
font-weight: 700;
text-align: left;
color: #718096;
}
td.ticker,
td.rank {
text-align: right;
}
}
.image-container footer {
margin-top: 2.5rem;
}
.image-container .brand-name {
font-size: 1.75rem;
font-weight: 800;
letter-spacing: -1px;
}
.image-container .brand-subtitle {
font-size: 1rem;
color: #a0aec0;
}
.image-container footer a {
color: inherit;
text-decoration: none;
transition: color 0.2s;
}
.image-container footer a:hover {
color: #ffffff;
}
.page-footer {
margin-top: 2rem;
padding: 1rem;
width: 100%;
max-width: 750px;
box-sizing: border-box;
background-color: rgba(45, 55, 72, 0.5);
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
gap: 0.75rem;
color: #cbd5e0;
font-size: 0.9rem;
font-weight: 500;
}
.page-footer code {
background-color: #1a202c;
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-weight: 600;
color: #e2e8f0;
}
.page-footer .doge-logo {
width: 22px; /* Set a consistent size for the image */
height: 22px;
vertical-align: middle; /* Align it nicely with the text */
} }
</style> </style>
</head> </head>
<body> <body class="bg-slate-900 text-slate-200 min-h-screen">
{% if not is_image_mode %}
<nav class="navbar">
<a href="/" {% if not subreddit_name %}class="active" {% endif %}>Overall</a>
<!-- --- THIS IS THE NEW HTML STRUCTURE FOR THE DROPDOWN --- --> {% if not is_image_mode %}
<div class="dropdown"> <header class="p-4 sm:p-6 w-full">
<div class="dropdown-button {% if subreddit_name %}active{% endif %}"> <nav
Subreddits ▼ class="w-full max-w-7xl mx-auto bg-slate-800/50 ring-1 ring-slate-700 rounded-xl p-4 flex flex-col sm:flex-row items-center gap-4">
</div> <div class="flex items-center gap-4">
<div class="dropdown-menu"> <!-- Home Link -->
<a href="/"
class="font-bold {% if not subreddit_name %}text-white{% else %}text-slate-400 hover:text-white{% endif %} transition-colors">Home</a>
<!-- Alpine.js Dropdown Component -->
<div x-data="{ isOpen: false }" class="relative">
<!-- The Button that toggles the 'isOpen' state -->
<button @click="isOpen = !isOpen"
class="font-bold flex items-center gap-1 cursor-pointer {% if subreddit_name %}text-white{% else %}text-slate-400 hover:text-white{% endif %} transition-colors">
<span>Subreddits</span>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"
class="transition-transform duration-200" :class="{'rotate-180': isOpen}">
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</button>
<!-- The Dropdown Menu, controlled by Alpine.js -->
<div x-show="isOpen" @click.outside="isOpen = false"
x-transition:enter="transition ease-out duration-100"
x-transition:enter-start="opacity-0 scale-95" x-transition:enter-end="opacity-100 scale-100"
x-transition:leave="transition ease-in duration-75"
x-transition:leave-start="opacity-100 scale-100" x-transition:leave-end="opacity-0 scale-95"
class="absolute left-0 mt-2 bg-slate-800 ring-1 ring-slate-700 shadow-lg rounded-lg py-1 w-48 z-10"
style="display: none;">
{% for sub in all_subreddits %} {% for sub in all_subreddits %}
<a href="/subreddit/{{ sub }}">{{ sub }}</a> <a href="/subreddit/{{ sub }}"
class="block px-4 py-2 text-sm text-slate-300 hover:bg-slate-700 hover:text-white">{{ sub
}}</a>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
<!-- --- END OF NEW HTML STRUCTURE --- --> </div>
<div class="view-switcher"> <div class="flex items-center gap-2 sm:ml-auto">
<a href="?view=daily" {% if view_type=='daily' %}class="active" {% endif %}>Daily</a> <a href="?view=daily"
<a href="?view=weekly" {% if view_type=='weekly' %}class="active" {% endif %}>Weekly</a> class="px-3 py-1 rounded-md text-sm font-semibold {% if view_type == 'daily' %}bg-sky-500 text-white{% else %}bg-slate-700/50 text-slate-300 hover:bg-slate-700 hover:text-white{% endif %} transition-all">Daily</a>
<a href="?view=weekly"
<a href="/about" title="About this Project" style="margin-left: 1rem;"> class="px-3 py-1 rounded-md text-sm font-semibold {% if view_type == 'weekly' %}bg-sky-500 text-white{% else %}bg-slate-700/50 text-slate-300 hover:bg-slate-700 hover:text-white{% endif %} transition-all">Weekly</a>
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" <a href="/about" title="About this Project"
stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="p-2 rounded-md text-slate-400 hover:bg-slate-700 hover:text-white transition-colors">
style="vertical-align: middle;"> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle> <circle cx="12" cy="12" r="10"></circle>
<line x1="12" y1="16" x2="12" y2="12"></line> <line x1="12" y1="16" x2="12" y2="12"></line>
<line x1="12" y1="8" x2="12.01" y2="8"></line> <line x1="12" y1="8" x2="12.01" y2="8"></line>
@@ -531,14 +87,18 @@
</div> </div>
</nav> </nav>
{% endif %} {% endif %}
<main>
<main class="w-full p-4 sm:p-6">
{% block content %}{% endblock %} {% block content %}{% endblock %}
</main> </main>
{% if not is_image_mode %} {% if not is_image_mode %}
<footer class="page-footer"> <footer class="mt-auto p-6 text-center">
<img src="{{ url_for('static', filename='dogecoin_logo.png') }}" alt="Dogecoin Logo" class="doge-logo"> <div class="flex items-center justify-center gap-2">
<span>Support this service with Dogecoin: <code>DRTLo2BsBijY4MrLmNNHzmjZ5tVvpTebFE</code></span> <img src="{{ url_for('static', filename='dogecoin_logo.png') }}" alt="Doge" class="w-5 h-5">
<span class="text-sm text-slate-500">Support this service with Dogecoin: <code
class="text-xs bg-slate-800 p-1 rounded">DRTLo2BsBijY4MrLmNNHzmjZ5tVvpTebFE</code></span>
</div>
</footer> </footer>
{% endif %} {% endif %}
</body> </body>

View File

@@ -3,91 +3,125 @@
{% block title %}{{ title }}{% endblock %} {% block title %}{{ title }}{% endblock %}
{% block content %} {% block content %}
<div class="image-container"> <div class="flex flex-col items-center">
<header> <div
<div class="title-block"> class="w-full max-w-3xl bg-gradient-to-br from-slate-800 to-slate-900 ring-1 ring-slate-700 rounded-2xl p-6 sm:p-8 shadow-2xl">
<h1>Reddit Ticker Mentions</h1> <header class="flex flex-col sm:flex-row justify-between sm:items-start mb-8">
<h2>{{ subtitle }}</h2> <div class="text-left">
<h1 class="text-2xl sm:text-4xl font-extrabold tracking-tight text-white">Reddit Ticker Mentions</h1>
<h2 class="text-lg sm:text-xl font-semibold mt-1 text-slate-400">{{ subtitle }}</h2>
</div> </div>
<div class="text-left sm:text-right mt-2 sm:mt-0 flex-shrink-0">
<div class="header-action"> <div class="text-md font-semibold text-slate-400 whitespace-nowrap">{{ date_string }}</div>
<div class="date">{{ date_string }}</div>
<!-- Only show the icon if we are NOT already in image mode -->
{% if not is_image_mode %} {% if not is_image_mode %}
<a href="{{ base_url }}?view={{ view_type }}&image=true" class="icon-link" title="View as Shareable Image"> <a href="{{ base_url }}?view={{ view_type }}&image=true" class="inline-block mt-2 sm:float-right"
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" title="View as Shareable Image">
stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"> <svg class="text-slate-400 hover:text-white transition-colors" xmlns="http://www.w3.org/2000/svg"
<path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"></path> width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"
stroke-linecap="round" stroke-linejoin="round">
<path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z">
</path>
<circle cx="12" cy="13" r="4"></circle> <circle cx="12" cy="13" r="4"></circle>
</svg> </svg>
</a> </a>
{% endif %} {% endif %}
</div> </div>
</header> </header>
<table>
<thead> {% set ticker_colors = {
<tr> 1: 'text-violet-400', 2: 'text-lime-400', 3: 'text-cyan-400',
<th class="rank">Rank</th> 4: 'text-yellow-400', 5: 'text-red-400', 6: 'text-orange-400',
<th class="ticker">Ticker</th> 7: 'text-emerald-400', 8: 'text-blue-400', 9: 'text-gray-300',
<th class="mentions">Mentions</th> 10: 'text-pink-400'
<th class="sentiment">Sentiment</th> } %}
<th class="financials">Mkt Cap</th>
<th class="financials">Close Price</th> <!-- Ticker List -->
</tr> <div class="flex flex-col">
</thead>
<tbody> <!-- 1. The Desktop Header Row (hidden on mobile) -->
<div
class="hidden sm:flex items-center text-xs font-bold text-slate-500 uppercase tracking-wider px-4 py-3 border-b border-slate-700">
<div class="w-1/4 flex items-center gap-4 text-left">
<span class="w-6 text-center">#</span>
<span>Ticker</span>
</div>
<div class="w-3/4 grid grid-cols-4 gap-4 text-right">
<div class="text-center">Mentions</div>
<div class="text-center">Sentiment</div>
<div>Mkt Cap</div>
<div>Close Price</div>
</div>
</div>
<!-- 2. Ticker Rows -->
<div class="divide-y divide-slate-800">
{% for ticker in tickers %} {% for ticker in tickers %}
<tr> <!-- THIS IS THE UPDATED LINE -->
<td data-label="Rank" class="rank">{{ loop.index }}</td> <div
<td data-label="Ticker" class="ticker"> class="p-4 flex flex-col sm:flex-row sm:items-center sm:gap-4 hover:bg-slate-800/50 transition-colors duration-150">
<strong> <!-- Rank & Ticker Symbol -->
<div class="flex items-center gap-4 w-full sm:w-1/4 text-left mb-4 sm:mb-0">
<span class="text-lg font-bold text-slate-500 w-6 text-center">{{ loop.index }}</span>
<div class="text-xl font-bold">
<span class="{{ ticker_colors.get(loop.index, 'text-slate-200') }}">
{% if is_image_mode %} {% if is_image_mode %}
{{ ticker.symbol }} {{ ticker.symbol }}
{% else %} {% else %}
<a href="/deep-dive/{{ ticker.symbol }}">{{ ticker.symbol }}</a> <a href="/deep-dive/{{ ticker.symbol }}">{{ ticker.symbol }}</a>
{% endif %} {% endif %}
</strong> </span>
</td>
<td data-label="Mentions" class="mentions">{{ ticker.total_mentions }}</td>
<td data-label="Sentiment" class="sentiment">
{% if ticker.bullish_mentions > ticker.bearish_mentions %}
<span class="sentiment-bullish">Bullish</span>
{% elif ticker.bearish_mentions > ticker.bearish_mentions %}
<span class="sentiment-bearish">Bearish</span>
{% else %}
<span class="sentiment-neutral">Neutral</span>
{% endif %}
</td>
<td data-label="Mkt Cap" class="financials">{{ ticker.market_cap | format_mc }}</td>
<td data-label="Close Price" class="financials">
{% if ticker.closing_price %}
<a href="https://finance.yahoo.com/quote/{{ ticker.symbol }}" target="_blank" title="View on Yahoo Finance">
${{ "%.2f"|format(ticker.closing_price) }}
</a>
{% else %}
N/A
{% endif %}
</td>
</tr>
{% else %}
<tr>
<td colspan="6" style="text-align: center; padding: 2rem;">No ticker data found for this period.</td>
</tr>
{% endfor %}
</tbody>
</table>
<footer style="margin-top: 3rem; text-align: center;">
<div class="brand-name">
<a href="https://www.reddit.com/r/rstat/" target="_blank">
r/rstat
</a>
</div> </div>
<div class="brand-subtitle"> </div>
<a href="https://www.reddit.com/r/rstat/" target="_blank"> <!-- Financial Data Points -->
visit us for more. <div class="w-full grid grid-cols-2 sm:grid-cols-4 gap-4 text-right">
</a> <div class="text-center sm:text-center">
<div class="sm:hidden text-xs font-bold text-slate-500 uppercase tracking-wider mb-1">
Mentions</div>
<div class="text-lg font-semibold text-white">{{ ticker.total_mentions }}</div>
</div>
<div class="text-center sm:text-center">
<div class="sm:hidden text-xs font-bold text-slate-500 uppercase tracking-wider mb-1">
Sentiment</div>
<div class="text-lg font-semibold">
{% if ticker.bullish_mentions > ticker.bearish_mentions %}<span
class="text-green-400">Bullish</span>
{% elif ticker.bearish_mentions > ticker.bullish_mentions %}<span
class="text-red-400">Bearish</span>
{% else %}<span class="text-slate-400">Neutral</span>{% endif %}
</div>
</div>
<div>
<div class="sm:hidden text-xs font-bold text-slate-500 uppercase tracking-wider mb-1">Mkt
Cap</div>
<div class="text-lg font-semibold text-white">{{ ticker.market_cap | format_mc }}</div>
</div>
<div>
<div class="sm:hidden text-xs font-bold text-slate-500 uppercase tracking-wider mb-1">Close
Price</div>
<div class="text-lg font-semibold text-white">
{% if ticker.closing_price %}<a
href="https://finance.yahoo.com/quote/{{ ticker.symbol }}" target="_blank"
class="hover:text-blue-400 transition-colors">${{
"%.2f"|format(ticker.closing_price) }}</a>
{% else %}N/A{% endif %}
</div>
</div>
</div>
</div>
{% else %}
<div class="text-center text-slate-500 p-8">No ticker data found for this period.</div>
{% endfor %}
</div>
</div>
<footer class="mt-8 text-center">
<div class="text-xl font-extrabold tracking-tight text-white">r/rstat</div>
<div class="text-sm text-slate-400">
<a href="https://www.reddit.com/r/rstat/" target="_blank"
class="hover:text-white transition-colors">visit us for more.</a>
</div> </div>
</footer> </footer>
</div>
</div> </div>
{% endblock %} {% endblock %}

View File

@@ -3,30 +3,52 @@
{% block title %}Deep Dive: {{ symbol }}{% endblock %} {% block title %}Deep Dive: {{ symbol }}{% endblock %}
{% block content %} {% block content %}
<!-- We wrap the content in the .image-container class to get the same beautiful styling --> <!-- This outer div handles the centering -->
<div class="image-container"> <div class="flex flex-col items-center">
<h1>Deep Dive Analysis for: <strong>{{ symbol }}</strong></h1> <div class="w-full max-w-3xl bg-slate-800/50 ring-1 ring-slate-700 rounded-2xl p-6 sm:p-10 shadow-2xl">
<p style="text-align: left; color: #a0aec0;">Showing posts that mention {{ symbol }}, sorted by most recent.</p>
<!-- --- THIS IS THE KEY CHANGE --- -->
<!-- We wrap all the content in an <article> tag with the 'prose' classes -->
<article class="prose prose-slate prose-invert max-w-none">
<header class="text-center mb-8">
<!-- The h1 and p tags will now be beautifully styled by 'prose' -->
<h1>Deep Dive Analysis: <span class="text-sky-400">{{ symbol }}</span></h1>
<p>Showing posts that mention {{ symbol }}, sorted by most recent.</p>
</header>
<div class="space-y-4 not-prose">
{% for post in posts %} {% for post in posts %}
<div class="post-card"> <!-- 'not-prose' is used on the container so we can control styling precisely -->
<h3><a href="{{ post.post_url }}" target="_blank">{{ post.title }}</a></h3> <div class="bg-slate-800/50 ring-1 ring-slate-700/50 rounded-lg p-4 text-left not-prose">
<div class="post-meta"> <h3 class="text-lg font-bold text-slate-200 mb-2">
<span>r/{{ post.subreddit_name }}</span> | <!-- This link WILL be styled by the parent 'prose' class -->
<span>{{ post.comment_count }} comments analyzed</span> | <a href="{{ post.post_url }}" target="_blank">{{ post.title }}</a>
</h3>
<div class="text-sm text-slate-400 flex flex-col sm:flex-row sm:items-center gap-x-4 gap-y-1">
<span class="font-semibold">r/{{ post.subreddit_name }}</span>
<span class="hidden sm:inline">|</span>
<span>{{ post.comment_count }} comments analyzed</span>
<span class="hidden sm:inline">|</span>
<span>Avg. Sentiment: <span>Avg. Sentiment:
{% if post.avg_comment_sentiment > 0.1 %} {% if post.avg_comment_sentiment > 0.1 %}
<span class="sentiment-bullish">{{ "%.2f"|format(post.avg_comment_sentiment) }}</span> <span class="font-bold text-green-400">{{ "%.2f"|format(post.avg_comment_sentiment) }}
{% elif post.avg_comment_sentiment < -0.1 %} <span class="sentiment-bearish">{{ (Bullish)</span>
"%.2f"|format(post.avg_comment_sentiment) }}</span> {% elif post.avg_comment_sentiment < -0.1 %} <span class="font-bold text-red-400">{{
"%.2f"|format(post.avg_comment_sentiment) }} (Bearish)</span>
{% else %} {% else %}
<span class="sentiment-neutral">{{ "%.2f"|format(post.avg_comment_sentiment) }}</span> <span class="font-bold text-slate-500">{{ "%.2f"|format(post.avg_comment_sentiment) }}
(Neutral)</span>
{% endif %} {% endif %}
</span> </span>
</div> </div>
</div> </div>
{% else %} {% else %}
<p>No analyzed posts found for this ticker. Run the 'rstat' scraper to gather data.</p> <div class="text-center text-slate-500 p-8 not-prose">No analyzed posts found for this ticker.</div>
{% endfor %} {% endfor %}
</div>
</article>
</div>
</div> </div>
{% endblock %} {% endblock %}