File size: 10,952 Bytes
8643122
 
 
 
 
 
25b3b03
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8643122
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25b3b03
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8643122
 
 
25b3b03
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8643122
 
 
25b3b03
 
 
 
 
 
 
 
 
 
8643122
25b3b03
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8643122
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
"""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>
    """