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
*.pot
# Django stuff:
# Ignore Node.js dependencies (they will be installed inside the container)
node_modules/
# Ignore database and log files
*.db
*.log
*.db-journal
# Sphinx documentation
docs/_build/

1
.gitignore vendored
View File

@@ -9,3 +9,4 @@ reddit_stock_analyzer.egg-info/
images/
public/
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
EXPOSE 5000
@@ -10,6 +24,7 @@ RUN python3 -m pip install --upgrade pip
RUN python3 -m pip install --no-cache-dir -r requirements.txt
COPY . .
COPY --from=builder /usr/src/build/static/css/style.css ./static/css/style.css
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 content %}
<div class="image-container" style="text-align: left; max-width: 800px;">
<h1 style="text-align: center;">About RSTAT</h1>
<h2 style="color: #cbd5e0; border-bottom: 1px solid #4a5568; padding-bottom: 0.5rem; margin-top: 2rem;">What is this?
</h2>
<p style="color: #a0aec0; line-height: 1.8;">
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>
<!-- This outer div now handles the centering -->
<div class="flex flex-col items-center">
<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">
<div class="text-center mb-10">
<h1 class="text-3xl sm:text-4xl font-extrabold tracking-tight text-white">About RSTAT</h1>
</div>
<div class="brand-subtitle">
<a href="https://www.reddit.com/r/rstat/" target="_blank">
visit us for more.
</a>
</div>
</footer>
<!-- The 'prose' class will now work correctly inside this standard block flow -->
<article class="prose prose-slate prose-invert max-w-none">
<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>
</footer>
</div>
</div>
{% endblock %}

View File

@@ -7,540 +7,100 @@
<title>{% block title %}RSTAT Dashboard{% endblock %}</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<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>
/* This sets the custom font as the default for the page */
body {
margin: 0;
padding: 2rem;
font-family: 'Inter', sans-serif;
background: #1a1a1a;
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
}
.navbar {
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 {
[class*="text-"]>a {
color: inherit;
text-decoration: none;
display: inline-block;
transition: transform 0.1s ease-in-out;
transition: color 0.2s ease-in-out;
}
td.ticker a:hover {
transform: scale(1.05);
}
td.financials a {
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 {
[class*="text-"]>a:hover {
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>
</head>
<body>
{% 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 --- -->
<div class="dropdown">
<div class="dropdown-button {% if subreddit_name %}active{% endif %}">
Subreddits ▼
</div>
<div class="dropdown-menu">
{% for sub in all_subreddits %}
<a href="/subreddit/{{ sub }}">{{ sub }}</a>
{% endfor %}
</div>
</div>
<!-- --- END OF NEW HTML STRUCTURE --- -->
<div class="view-switcher">
<a href="?view=daily" {% if view_type=='daily' %}class="active" {% endif %}>Daily</a>
<a href="?view=weekly" {% if view_type=='weekly' %}class="active" {% endif %}>Weekly</a>
<a href="/about" title="About this Project" style="margin-left: 1rem;">
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"
style="vertical-align: middle;">
<circle cx="12" cy="12" r="10"></circle>
<line x1="12" y1="16" x2="12" y2="12"></line>
<line x1="12" y1="8" x2="12.01" y2="8"></line>
</svg>
</a>
</div>
</nav>
{% endif %}
<main>
{% block content %}{% endblock %}
</main>
<body class="bg-slate-900 text-slate-200 min-h-screen">
{% if not is_image_mode %}
<footer class="page-footer">
<img src="{{ url_for('static', filename='dogecoin_logo.png') }}" alt="Dogecoin Logo" class="doge-logo">
<span>Support this service with Dogecoin: <code>DRTLo2BsBijY4MrLmNNHzmjZ5tVvpTebFE</code></span>
</footer>
{% endif %}
<header class="p-4 sm:p-6 w-full">
<nav
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 class="flex items-center gap-4">
<!-- 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 %}
<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 %}
</div>
</div>
</div>
<div class="flex items-center gap-2 sm:ml-auto">
<a href="?view=daily"
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"
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>
<a href="/about" title="About this Project"
class="p-2 rounded-md text-slate-400 hover:bg-slate-700 hover:text-white transition-colors">
<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>
<line x1="12" y1="16" x2="12" y2="12"></line>
<line x1="12" y1="8" x2="12.01" y2="8"></line>
</svg>
</a>
</div>
</nav>
{% endif %}
<main class="w-full p-4 sm:p-6">
{% block content %}{% endblock %}
</main>
{% if not is_image_mode %}
<footer class="mt-auto p-6 text-center">
<div class="flex items-center justify-center gap-2">
<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>
{% endif %}
</body>
</html>

View File

@@ -3,91 +3,125 @@
{% block title %}{{ title }}{% endblock %}
{% block content %}
<div class="image-container">
<header>
<div class="title-block">
<h1>Reddit Ticker Mentions</h1>
<h2>{{ subtitle }}</h2>
<div class="flex flex-col items-center">
<div
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">
<header class="flex flex-col sm:flex-row justify-between sm:items-start mb-8">
<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 class="text-left sm:text-right mt-2 sm:mt-0 flex-shrink-0">
<div class="text-md font-semibold text-slate-400 whitespace-nowrap">{{ date_string }}</div>
{% if not is_image_mode %}
<a href="{{ base_url }}?view={{ view_type }}&image=true" class="inline-block mt-2 sm:float-right"
title="View as Shareable Image">
<svg class="text-slate-400 hover:text-white transition-colors" xmlns="http://www.w3.org/2000/svg"
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>
</svg>
</a>
{% endif %}
</div>
</header>
{% set ticker_colors = {
1: 'text-violet-400', 2: 'text-lime-400', 3: 'text-cyan-400',
4: 'text-yellow-400', 5: 'text-red-400', 6: 'text-orange-400',
7: 'text-emerald-400', 8: 'text-blue-400', 9: 'text-gray-300',
10: 'text-pink-400'
} %}
<!-- Ticker List -->
<div class="flex flex-col">
<!-- 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 %}
<!-- THIS IS THE UPDATED LINE -->
<div
class="p-4 flex flex-col sm:flex-row sm:items-center sm:gap-4 hover:bg-slate-800/50 transition-colors duration-150">
<!-- 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 %}
{{ ticker.symbol }}
{% else %}
<a href="/deep-dive/{{ ticker.symbol }}">{{ ticker.symbol }}</a>
{% endif %}
</span>
</div>
</div>
<!-- Financial Data Points -->
<div class="w-full grid grid-cols-2 sm:grid-cols-4 gap-4 text-right">
<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>
<div class="header-action">
<div class="date">{{ date_string }}</div>
<!-- Only show the icon if we are NOT already in image mode -->
{% if not is_image_mode %}
<a href="{{ base_url }}?view={{ view_type }}&image=true" class="icon-link" title="View as Shareable Image">
<svg xmlns="http://www.w3.org/2000/svg" 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>
</svg>
</a>
{% endif %}
</div>
</header>
<table>
<thead>
<tr>
<th class="rank">Rank</th>
<th class="ticker">Ticker</th>
<th class="mentions">Mentions</th>
<th class="sentiment">Sentiment</th>
<th class="financials">Mkt Cap</th>
<th class="financials">Close Price</th>
</tr>
</thead>
<tbody>
{% for ticker in tickers %}
<tr>
<td data-label="Rank" class="rank">{{ loop.index }}</td>
<td data-label="Ticker" class="ticker">
<strong>
{% if is_image_mode %}
{{ ticker.symbol }}
{% else %}
<a href="/deep-dive/{{ ticker.symbol }}">{{ ticker.symbol }}</a>
{% endif %}
</strong>
</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 class="brand-subtitle">
<a href="https://www.reddit.com/r/rstat/" target="_blank">
visit us for more.
</a>
</div>
</footer>
<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>
</footer>
</div>
</div>
{% endblock %}

View File

@@ -3,30 +3,52 @@
{% block title %}Deep Dive: {{ symbol }}{% endblock %}
{% block content %}
<!-- We wrap the content in the .image-container class to get the same beautiful styling -->
<div class="image-container">
<h1>Deep Dive Analysis for: <strong>{{ symbol }}</strong></h1>
<p style="text-align: left; color: #a0aec0;">Showing posts that mention {{ symbol }}, sorted by most recent.</p>
<!-- This outer div handles the centering -->
<div class="flex flex-col items-center">
<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">
{% for post in posts %}
<div class="post-card">
<h3><a href="{{ post.post_url }}" target="_blank">{{ post.title }}</a></h3>
<div class="post-meta">
<span>r/{{ post.subreddit_name }}</span> |
<span>{{ post.comment_count }} comments analyzed</span> |
<span>Avg. Sentiment:
{% if post.avg_comment_sentiment > 0.1 %}
<span class="sentiment-bullish">{{ "%.2f"|format(post.avg_comment_sentiment) }}</span>
{% elif post.avg_comment_sentiment < -0.1 %} <span class="sentiment-bearish">{{
"%.2f"|format(post.avg_comment_sentiment) }}</span>
{% else %}
<span class="sentiment-neutral">{{ "%.2f"|format(post.avg_comment_sentiment) }}</span>
{% endif %}
</span>
</div>
<!-- --- 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 %}
<!-- 'not-prose' is used on the container so we can control styling precisely -->
<div class="bg-slate-800/50 ring-1 ring-slate-700/50 rounded-lg p-4 text-left not-prose">
<h3 class="text-lg font-bold text-slate-200 mb-2">
<!-- This link WILL be styled by the parent 'prose' class -->
<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:
{% if post.avg_comment_sentiment > 0.1 %}
<span class="font-bold text-green-400">{{ "%.2f"|format(post.avg_comment_sentiment) }}
(Bullish)</span>
{% elif post.avg_comment_sentiment < -0.1 %} <span class="font-bold text-red-400">{{
"%.2f"|format(post.avg_comment_sentiment) }} (Bearish)</span>
{% else %}
<span class="font-bold text-slate-500">{{ "%.2f"|format(post.avg_comment_sentiment) }}
(Neutral)</span>
{% endif %}
</span>
</div>
</div>
{% else %}
<div class="text-center text-slate-500 p-8 not-prose">No analyzed posts found for this ticker.</div>
{% endfor %}
</div>
</article>
</div>
{% else %}
<p>No analyzed posts found for this ticker. Run the 'rstat' scraper to gather data.</p>
{% endfor %}
</div>
{% endblock %}