siddhant-rajhans
TRIBE-style landing redesign with black/white theme toggle
25b3b03
"""Futuristic theme injection for Streamlit."""
import streamlit as st
from pathlib import Path
THEME_MODES = ("black", "white")
def get_theme_mode() -> str:
"""Read the active theme from session state. Defaults to ``"black"``."""
mode = st.session_state.get("ui_theme_mode", "black")
return mode if mode in THEME_MODES else "black"
def set_theme_mode(mode: str) -> None:
"""Set the active theme. Use ``"black"`` for pure-black background,
``"white"`` for pure-white background. Anything else is normalized."""
st.session_state["ui_theme_mode"] = mode if mode in THEME_MODES else "black"
def inject_theme(mode: str | None = None):
"""Inject custom CSS for the chosen theme and hide Streamlit chrome.
When ``mode`` is ``None`` we read the active mode from session state
via :func:`get_theme_mode`. The base ``assets/theme.css`` is always
included; theme-specific overrides are appended on top.
"""
if mode is None:
mode = get_theme_mode()
base_css_path = Path(__file__).parent / "assets" / "theme.css"
overlay_css_path = Path(__file__).parent / "assets" / f"theme_{mode}.css"
css = base_css_path.read_text() if base_css_path.exists() else ""
if overlay_css_path.exists():
css += "\n\n" + overlay_css_path.read_text()
st.markdown(f"<style>{css}</style>", unsafe_allow_html=True)
def hero_header(title, subtitle="", github_url="https://github.com/siddhant-rajhans/cortexlab"):
"""Render a futuristic hero header with gradient title."""
st.markdown(f"""
<div style="text-align: center; padding: 1.5rem 0 0.5rem 0;">
<h1 style="
font-size: 3rem;
font-weight: 800;
background: linear-gradient(135deg, #7C3AED 0%, #3B82F6 40%, #06B6D4 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 0.3rem;
letter-spacing: -0.04em;
">{title}</h1>
<p style="color: #94A3B8; font-size: 1.1rem; margin-bottom: 0.8rem;">{subtitle}</p>
<div style="display: flex; justify-content: center; gap: 1rem; flex-wrap: wrap;">
<a href="{github_url}" target="_blank" style="
display: inline-flex; align-items: center; gap: 0.4rem;
padding: 0.5rem 1.2rem;
background: rgba(124, 58, 237, 0.15);
border: 1px solid rgba(124, 58, 237, 0.3);
border-radius: 8px;
color: #C4B5FD;
text-decoration: none;
font-size: 0.85rem;
font-weight: 500;
transition: all 0.3s ease;
">
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/></svg>
GitHub
</a>
<a href="https://huggingface.co/SID2000/cortexlab" target="_blank" style="
display: inline-flex; align-items: center; gap: 0.4rem;
padding: 0.5rem 1.2rem;
background: rgba(59, 130, 246, 0.15);
border: 1px solid rgba(59, 130, 246, 0.3);
border-radius: 8px;
color: #93C5FD;
text-decoration: none;
font-size: 0.85rem;
font-weight: 500;
">HuggingFace</a>
<a href="https://huggingface.co/spaces/SID2000/cortexlab-dashboard" target="_blank" style="
display: inline-flex; align-items: center; gap: 0.4rem;
padding: 0.5rem 1.2rem;
background: rgba(6, 182, 212, 0.15);
border: 1px solid rgba(6, 182, 212, 0.3);
border-radius: 8px;
color: #67E8F9;
text-decoration: none;
font-size: 0.85rem;
font-weight: 500;
">Live Demo</a>
</div>
</div>
""", unsafe_allow_html=True)
def glow_card(title, value, subtitle="", color="#06B6D4"):
"""Render a glowing metric card.
Layout uses the ``cl-stat-card`` CSS class so background / border /
text colors come from theme variables and flip automatically when
the user switches between Black and White modes. Only the accent
color (the big number) stays per-card.
"""
st.markdown(
f"""
<div class="cl-stat-card" style="--card-accent: {color};">
<div class="cl-stat-label">{title}</div>
<div class="cl-stat-value">{value}</div>
<div class="cl-stat-sub">{subtitle}</div>
</div>
""",
unsafe_allow_html=True,
)
def section_header(title, description=""):
"""Render a styled section header with optional description.
Colors come from theme variables (``--text-primary`` for the title,
``--text-secondary`` for the description, ``--border-glass`` for
the underline) so the same markup reads correctly on dark and
light backgrounds.
"""
desc_html = (
f'<p class="cl-section-desc">{description}</p>' if description else ""
)
st.markdown(
f"""
<div class="cl-section">
<h2 class="cl-section-title">{title}</h2>
{desc_html}
</div>
""",
unsafe_allow_html=True,
)
_FEATURE_ICONS: dict[str, str] = {
# Heroicons-style monochrome strokes; small currentColor SVGs so the
# accent color flows from the parent `--card-accent` variable.
"target": (
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" '
'stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">'
'<circle cx="12" cy="12" r="9"/>'
'<circle cx="12" cy="12" r="5"/>'
'<circle cx="12" cy="12" r="1.5" fill="currentColor"/>'
'</svg>'
),
"bars": (
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" '
'stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">'
'<path d="M5 21V11"/><path d="M12 21V4"/><path d="M19 21V14"/>'
'<path d="M3 21h18"/></svg>'
),
"clock": (
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" '
'stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">'
'<circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2.5"/></svg>'
),
"graph": (
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" '
'stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">'
'<circle cx="6" cy="7" r="2.2"/><circle cx="18" cy="7" r="2.2"/>'
'<circle cx="6" cy="17" r="2.2"/><circle cx="18" cy="17" r="2.2"/>'
'<circle cx="12" cy="12" r="2.4"/>'
'<path d="M8 7h2.5M13.5 7H16M8 17h2.5M13.5 17H16M6 9.2v5.6M18 9.2v5.6"/></svg>'
),
"brain": (
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" '
'stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">'
'<path d="M9 5a3 3 0 0 0-3 3v0a2.5 2.5 0 0 0-2 4 2.5 2.5 0 0 0 1 4.5A3 3 0 0 0 9 19V5z"/>'
'<path d="M15 5a3 3 0 0 1 3 3v0a2.5 2.5 0 0 1 2 4 2.5 2.5 0 0 1-1 4.5A3 3 0 0 1 15 19V5z"/>'
'<path d="M12 5v14"/></svg>'
),
"broadcast": (
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" '
'stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">'
'<circle cx="12" cy="12" r="2.5" fill="currentColor"/>'
'<path d="M8.5 8.5a5 5 0 0 0 0 7"/><path d="M15.5 15.5a5 5 0 0 0 0-7"/>'
'<path d="M5.5 5.5a9 9 0 0 0 0 13"/><path d="M18.5 18.5a9 9 0 0 0 0-13"/></svg>'
),
"arrow": (
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" '
'stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">'
'<path d="M5 12h14"/><path d="M13 6l6 6-6 6"/></svg>'
),
}
def feature_icon(name: str) -> str:
"""Return inline SVG markup for a named card icon. Unknown names
fall back to a hollow circle so layout still works."""
return _FEATURE_ICONS.get(
name,
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" '
'stroke-width="1.6" aria-hidden="true"><circle cx="12" cy="12" r="9"/></svg>',
)
def feature_card(icon, title, description, color="#7C3AED"):
"""Render a feature card for the home page (no inline CTA).
Kept for backwards compatibility. New callers should prefer
:func:`feature_card_link` which produces a uniform-height card
with a built-in "Open ..." link, so a row of cards lines up.
``icon`` may be either a name registered in ``_FEATURE_ICONS``
(e.g. ``"target"``) or raw SVG / HTML markup.
"""
icon_html = _FEATURE_ICONS.get(icon, icon)
return f"""
<span class="cl-feature-card" style="--card-accent: {color};">
<span class="cl-feature-icon">{icon_html}</span>
<span class="cl-feature-title">{title}</span>
<span class="cl-feature-desc">{description}</span>
</span>
"""
def feature_card_link(icon, title, description, href: str, color="#7C3AED"):
"""Render a feature card as a single clickable anchor element.
Inner blocks are rendered as ``<span>`` elements with
``display: block`` in CSS — anchors only allow phrasing-content
children in HTML5, and Streamlit's HTML sanitizer hoists ``<div>``
children out of ``<a>``, splitting the card visually. Spans are
safe.
``icon`` may be either a name registered in ``_FEATURE_ICONS``
(``"target"``, ``"bars"``, ``"clock"``, ``"graph"``, ``"brain"``,
``"broadcast"``) or raw SVG markup.
``href`` is the multipage URL Streamlit exposes (e.g.
``./Brain_Alignment``).
"""
icon_html = _FEATURE_ICONS.get(icon, icon)
arrow = _FEATURE_ICONS["arrow"]
return f"""
<a class="cl-feature-card cl-feature-link" href="{href}"
style="--card-accent: {color};" target="_self">
<span class="cl-feature-icon">{icon_html}</span>
<span class="cl-feature-title">{title}</span>
<span class="cl-feature-desc">{description}</span>
<span class="cl-feature-cta">
<span class="cl-feature-cta-label">Open</span>
<span class="cl-feature-arrow">{arrow}</span>
</span>
</a>
"""