From a457bea95358825e55dbc7f48d57183004121109 Mon Sep 17 00:00:00 2001 From: Tyler Date: Wed, 13 May 2026 22:39:50 -0700 Subject: Apply Prism design system — brass/ink palette, EB Garamond + IBM Plex typography MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements the design kit: champagne brass accent (#C2AA7A), deep midnight backgrounds, EB Garamond italic headings, IBM Plex Sans/Mono body and numbers, terminal-density KPI cards, restyled sidebar brand mark, flat pill tabs, and a global Plotly theme so all charts inherit the dark palette automatically. Co-Authored-By: Claude Sonnet 4.6 --- app.py | 668 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 571 insertions(+), 97 deletions(-) (limited to 'app.py') diff --git a/app.py b/app.py index d86475b..d5e082a 100644 --- a/app.py +++ b/app.py @@ -11,50 +11,414 @@ st.set_page_config( initial_sidebar_state="expanded", ) -# ── Global CSS ──────────────────────────────────────────────────────────────── +# ── Design system CSS ───────────────────────────────────────────────────────── st.markdown(""" """, unsafe_allow_html=True) +import plotly.graph_objects as go +import plotly.io as pio + +# ── Plotly theme ────────────────────────────────────────────────────────────── +_prism_layout = go.Layout( + paper_bgcolor="#0B0E13", + plot_bgcolor="#0B0E13", + font=dict(family="IBM Plex Mono, SF Mono, Menlo, monospace", color="#C7C0AE", size=11), + title=dict( + font=dict(family="EB Garamond, Georgia, serif", color="#F2ECDC", size=18), + x=0, + ), + colorway=["#C2AA7A", "#4F8C5E", "#4A78B5", "#B5494B", "#C49545", "#8B7FBF"], + xaxis=dict( + gridcolor="#232934", + linecolor="#232934", + tickfont=dict(family="IBM Plex Mono, monospace", color="#5E5849", size=10), + title=dict(font=dict(color="#8E8676", size=11)), + showgrid=True, + zeroline=False, + ), + yaxis=dict( + gridcolor="#232934", + linecolor="#232934", + tickfont=dict(family="IBM Plex Mono, monospace", color="#5E5849", size=10), + title=dict(font=dict(color="#8E8676", size=11)), + showgrid=True, + zeroline=False, + ), + legend=dict( + bgcolor="rgba(17,21,28,0.85)", + bordercolor="#232934", + borderwidth=1, + font=dict(family="IBM Plex Sans, sans-serif", color="#C7C0AE", size=11), + ), + margin=dict(l=48, r=16, t=32, b=40), + hoverlabel=dict( + bgcolor="#181D26", + bordercolor="#2E3645", + font=dict(family="IBM Plex Mono, monospace", color="#F2ECDC", size=11), + ), +) +_prism_template = go.layout.Template(layout=_prism_layout) +pio.templates["prism"] = _prism_template +pio.templates.default = "prism" + from components.market_bar import render_market_bar from components.top_movers import render_top_movers from components.overview import render_overview @@ -71,21 +435,49 @@ if "ticker" not in st.session_state: st.session_state["ticker"] = None -# ── Sidebar ────────────────────────────────────────────────────────────────── +# ── Sidebar ─────────────────────────────────────────────────────────────────── with st.sidebar: - col_logo, col_title = st.columns([1, 2]) - with col_logo: - st.image("assets/logo.png", width=60) - with col_title: - st.markdown("### Prism") - st.caption("Financial Analysis Dashboard") - st.divider() + # Brand mark + st.markdown(""" +
+
P
+
+ Prism + Research Terminal +
+
+ """, unsafe_allow_html=True) with st.form("ticker_search_form", clear_on_submit=False): query = st.text_input( - "Search company or ticker", - placeholder="e.g. Apple, AAPL, MSFT…", + "Ticker or company", + placeholder="AAPL, Apple, MSFT…", key="search_query", ).strip() @@ -108,50 +500,117 @@ with st.sidebar: if submitted and selected_symbol: st.session_state["ticker"] = selected_symbol - if st.session_state["ticker"]: - st.caption(f"Currently viewing: **{st.session_state['ticker']}**") - ticker = st.session_state["ticker"] - # Quick company info in sidebar - st.divider() + # Company snapshot if ticker: + st.divider() info = get_company_info(ticker) - if ticker and info: - st.caption(info.get("longName", ticker)) - price = get_latest_price(ticker) - prev_close = info.get("previousClose") or info.get("regularMarketPreviousClose") - if price is not None: - if prev_close and prev_close > 0: - chg = price - prev_close - chg_pct = chg / prev_close * 100 - sign = "+" if chg >= 0 else "" - color = "#2ecc71" if chg >= 0 else "#e74c3c" - st.markdown( - f"${price:,.2f}" - f" {sign}{chg:+.2f} ({sign}{chg_pct:.2f}%)", - unsafe_allow_html=True, - ) - else: - st.markdown( - f"${price:,.2f}", - unsafe_allow_html=True, - ) - _EXCHANGE_NAMES = { - "NYQ": "NYSE", "NMS": "NASDAQ", "NGM": "NASDAQ", - "NCM": "NASDAQ", "ASE": "AMEX", "PCX": "NYSE Arca", - "BTS": "BATS", "TSX": "TSX", "LSE": "LSE", - } - raw_exchange = info.get("exchange", "") - exchange = _EXCHANGE_NAMES.get(raw_exchange, raw_exchange) or "—" - st.caption(f"Exchange: {exchange}") - st.caption(f"Currency: {info.get('currency', 'USD')}") - st.caption(f"Sector: {info.get('sector', '—')}") - employees = info.get("fullTimeEmployees") - st.caption(f"Employees: {employees:,}" if isinstance(employees, int) else "Employees: —") - website = info.get("website") - if website: - st.markdown(f"[🌐 Website]({website})") + if info: + co_name = info.get("longName", ticker) + price = get_latest_price(ticker) + prev_close = info.get("previousClose") or info.get("regularMarketPreviousClose") + + # Ticker + name + st.markdown(f""" +
+
{ticker}
+
{co_name}
+
+ """, unsafe_allow_html=True) + + # Price + change + if price is not None: + if prev_close and prev_close > 0: + chg = price - prev_close + chg_pct = chg / prev_close * 100 + sign = "+" if chg >= 0 else "" + px_color = "#4F8C5E" if chg >= 0 else "#B5494B" + st.markdown(f""" +
+ ${price:,.2f} + {sign}{chg_pct:.2f}% +
+ """, unsafe_allow_html=True) + else: + st.markdown(f""" +
+ ${price:,.2f} +
+ """, unsafe_allow_html=True) + + # Company meta + _EXCHANGE_NAMES = { + "NYQ": "NYSE", "NMS": "NASDAQ", "NGM": "NASDAQ", + "NCM": "NASDAQ", "ASE": "AMEX", "PCX": "NYSE Arca", + "BTS": "BATS", "TSX": "TSX", "LSE": "LSE", + } + raw_exchange = info.get("exchange", "") + exchange = _EXCHANGE_NAMES.get(raw_exchange, raw_exchange) or "—" + sector = info.get("sector", "—") + currency = info.get("currency", "USD") + employees = info.get("fullTimeEmployees") + emp_str = f"{employees:,}" if isinstance(employees, int) else "—" + + rows = [ + ("Exchange", exchange), + ("Sector", sector), + ("Currency", currency), + ("Employees", emp_str), + ] + rows_html = "".join(f""" +
+ {k} + {v} +
+ """ for k, v in rows) + + st.markdown(f""" +
{rows_html}
+ """, unsafe_allow_html=True) + + website = info.get("website", "") + if website: + st.markdown(f""" +
+ Website ↗ +
+ """, unsafe_allow_html=True) + + elif ticker: + st.caption(f"Viewing: **{ticker}**") # ── Market Bar ──────────────────────────────────────────────────────────────── @@ -171,17 +630,32 @@ st.divider() # ── Main Content ────────────────────────────────────────────────────────────── if not ticker: - st.info("Search for a company or ticker in the sidebar to get started.") + st.markdown(""" +
+
Search for a ticker to begin.
+
Enter a company name or symbol in the sidebar.
+
+ """, unsafe_allow_html=True) st.stop() tab_overview, tab_financials, tab_valuation, tab_options, tab_insiders, tab_filings, tab_news = st.tabs([ - "📈 Overview", - "📊 Financials", - "💰 Valuation", - "🎯 Options", - "👤 Insiders", - "📁 Filings", - "📰 News", + "Overview", + "Financials", + "Valuation", + "Options", + "Insiders", + "Filings", + "News", ]) with tab_overview: -- cgit v1.3-2-g0d8e