ST_AI_ShowCase / app.py
UCS2014's picture
Update app.py
45fe096 verified
# -*- coding: utf-8 -*-
from typing import Optional, Dict, Any
import base64, mimetypes
from html import escape
from pathlib import Path
import streamlit as st
# ========= PATHS =========
BASE_DIR = Path(__file__).parent
ASSETS = BASE_DIR / "assets"
# ========= META =========
st.set_page_config(page_title="Smart Thinking — AI Suite", layout="wide")
# ========= GLOBAL THEME (all knobs live here) =========
THEME: Dict[str, Any] = {
"page": {
"container_width": 1120,
"bg_radial": True,
"section_gap": 44,
"footer_gap": 36,
"top_padding": 40,
},
"hero": {
"logo": ASSETS / "logo.png",
"width": 340,
"tagline": "We Deliver AI-Based Solutions For O&G Industry",
"tagline_size": 22,
"tagline_weight": 500, # lighter than bold
"tagline_italic": True, # italic tagline
"tagline_color": "#0B1220",
"logo_bottom": 14,
"block_bottom": 22,
},
"grid": {
"columns": 4,
"gap": 40,
"card_width": 400,
},
"card": {
"radius": 22,
"border_width": 2,
"border": "#0B1220",
"border_hover": "#243447",
"bg_top": "#FFFFFF",
"bg_bot": "#FBFCFE",
"pad_v": 24,
"pad_h": 22,
"gap": 12, # base gap between stacked elements
# Title & paragraph (FULL CONTROL)
"title_color": "#0B1220",
"title_size": 26,
"title_weight": 900,
"title_mt": 0, # space above title
"title_mb": 0, # space below title
"blurb_color": "#566275",
"blurb_size": 16,
"blurb_mt": 0, # space above blurb
"blurb_mb": 40, # space below blurb
# Card logo controls (no frame/border)
"logo_max_w": 350,
"logo_area_h": 300, # fixed area so top spacing is identical across cards
"logo_top": 6,
"logo_bottom": 0,
},
"button": {
"pad_v": 12, "pad_h": 20, "radius": 14,
"bg1": "#0B1220", "bg2": "#162338",
"text": "#FFFFFF", "border": "rgba(11,18,32,.55)",
"font_size": 16, "font_weight": 800,
"btn_mt": 8, # space above the button
},
}
# ========= CARDS =========
CARDS = [
{
"title": "\u00A0Geomechanics",
"blurb": "Predict Rock Mechanical Behaviour Using AI",
"url": "https://smart-thinking-ai-suite-geomech.hf.space",
"icon": ASSETS / "GeoMech_logo.png",
"style": {
"bg_top": "#EEF7FF",
"bg_bot": "#F7FBFF",
"border": "#0E4A6E",
},
},
{
"title": "\u00A0TOC",
"blurb": "Generate Synthetic Logging Profiles Using AI",
"url": "https://smart-thinking-ai-suite-log.hf.space",
"icon": ASSETS / "log_logo.png",
"style": {
"bg_top": "#F8FFF4",
"bg_bot": "#FBFFF8",
"border": "#0F3D3E",
},
},
{
"title": "\u00A0Permeability",
"blurb": "Predict K Via AI",
"url": "https://smart-thinking-ai-suite-perm.hf.space",
"icon": ASSETS / "AI Permeability Prediction Logo.png",
"style": {
"bg_top": "#FFF7E6",
"bg_bot": "#FFFDF8",
"border": "#8B5E00",
},
},
{
"title": "\u00A0Porosity",
"blurb": "Predict Porosity Via AI",
"url": "https://smart-thinking-ai-suite-poro.hf.space",
"icon": ASSETS / "Porosity logo.png",
"style": {
"bg_top": "#F3E8FF",
"bg_bot": "#FAF7FF",
"border": "#4B0082",
},
},
]
# ========= Helpers =========
def data_uri(path: Path) -> Optional[str]:
if not path or not path.exists(): return None
mime, _ = mimetypes.guess_type(path.name)
if not mime: mime = "image/png"
b64 = base64.b64encode(path.read_bytes()).decode("utf-8")
return f"data:{mime};base64,{b64}"
def img_tag(path: Path, alt: str, cls: str = "", style: str = "") -> str:
uri = data_uri(path)
if not uri: return ""
cls_attr = f' class="{cls}"' if cls else ""
style_attr = f' style="{style}"' if style else ""
return f'<img{cls_attr}{style_attr} src="{uri}" alt="{escape(alt)}" />'
# ========= CSS =========
page = THEME["page"]; hero = THEME["hero"]
grid = THEME["grid"]; card = THEME["card"]; button = THEME["button"]
bg_radial_css = """
background:
radial-gradient(980px 460px at 50% -140px,
rgba(2,12,30,0.06) 0%,
rgba(2,12,30,0.04) 28%,
rgba(255,255,255,0.98) 60%,
rgba(255,255,255,1) 100%);
""" if page["bg_radial"] else "background: #fff;"
st.markdown(f"""
<style>
:root {{
--container-w: {page["container_width"]}px;
--top-pad: {page["top_padding"]}px;
--section-gap: {page["section_gap"]}px;
--footer-gap: {page["footer_gap"]}px;
--hero-w: {hero["width"]}px;
--hero-logo-mb: {hero["logo_bottom"]}px;
--hero-block-mb: {hero["block_bottom"]}px;
--hero-tag-fs: {hero["tagline_size"]}px;
--hero-tag-weight: {hero["tagline_weight"]};
--hero-tag-style: {"italic" if hero.get("tagline_italic", True) else "normal"};
--hero-tag-color: {hero["tagline_color"]};
--grid-gap: {grid["gap"]}px;
--grid-cols: {grid["columns"]};
--card-w: {grid["card_width"]}px;
--card-r: {card["radius"]}px;
--card-bw: {card["border_width"]}px;
--card-border: {card["border"]};
--card-border-hover: {card["border_hover"]};
--card-bg-top: {card["bg_top"]};
--card-bg-bot: {card["bg_bot"]};
--card-pv: {card["pad_v"]}px;
--card-ph: {card["pad_h"]}px;
--card-gap: {card["gap"]}px;
--card-title-color: {card["title_color"]};
--card-title-size: {card["title_size"]}px;
--card-title-weight: {card["title_weight"]};
--card-title-mt: {card["title_mt"]}px;
--card-title-mb: {card["title_mb"]}px;
--card-blurb-color: {card["blurb_color"]};
--card-blurb-size: {card["blurb_size"]}px;
--card-blurb-mt: {card["blurb_mt"]}px;
--card-blurb-bm: {card["blurb_mb"]}px;
--logo-max-w: {card["logo_max_w"]}px;
--logo-area-h: {card["logo_area_h"]}px; /* fixed logo area height */
--logo-top: {card["logo_top"]}px;
--logo-bottom: {card["logo_bottom"]}px;
--btn1: {button["bg1"]};
--btn2: {button["bg2"]};
--btnText: {button["text"]};
--btnBorder: {button["border"]};
--btnFS: {button["font_size"]}px;
--btnFW: {button["font_weight"]};
--btnPV: {button["pad_v"]}px;
--btnPH: {button["pad_h"]}px;
--btnRadius: {button["radius"]}px;
--btnMT: {button["btn_mt"]}px;
}}
html, body, [data-testid="stAppViewContainer"] {{ height: 100%; }}
[data-testid="stAppViewContainer"] > .main {{ padding-top: 0 !important; padding-bottom: 0 !important; }}
.block-container {{
max-width: var(--container-w);
min-height: 100vh;
display: flex; flex-direction: column;
gap: var(--section-gap);
padding: var(--top-pad) 0 var(--footer-gap) !important;
{bg_radial_css}
}}
/* ===== HERO ===== */
.hero {{ text-align:center; }}
.hero img {{
width: var(--hero-w); max-width: 92vw; height: auto;
display:block; margin: 0 auto var(--hero-logo-mb);
filter: drop-shadow(0 6px 16px rgba(0,0,0,.10));
}}
.hero .tagline {{
font-size: var(--hero-tag-fs);
font-weight: var(--hero-tag-weight);
font-style: var(--hero-tag-style);
color: var(--hero-tag-color);
letter-spacing:.2px;
margin-bottom: var(--hero-block-mb);
}}
/* ===== GRID ===== */
.grid {{
display: grid;
grid-template-columns: repeat(var(--grid-cols), var(--card-w));
gap: var(--grid-gap);
justify-content: center; align-items: stretch;
}}
@media (max-width: 1040px) {{
.grid {{ grid-template-columns: 1fr; }}
}}
/* ===== CARD ===== */
.card {{
position: relative;
width: var(--c-w, var(--card-w));
border-radius: var(--c-radius, var(--card-r));
padding: var(--c-pv, var(--card-pv)) var(--c-ph, var(--card-ph));
background: linear-gradient(180deg, var(--c-bg-top, var(--card-bg-top)) 0%, var(--c-bg-bot, var(--card-bg-bot)) 100%);
border: var(--c-bw, var(--card-bw)) solid var(--c-border, var(--card-border));
box-shadow: 0 14px 32px rgba(2,20,35,.10), 0 1px 0 rgba(255,255,255,0.75) inset;
transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease, filter .18s ease;
text-align:center; display:flex; flex-direction:column;
align-items:center; justify-content:flex-start;
gap: var(--card-gap);
}}
.card:hover {{
transform: translateY(-6px) scale(1.01);
border-color: var(--card-border-hover);
box-shadow: 0 22px 56px rgba(2,20,35,.18);
filter: saturate(1.03);
}}
/* FIXED LOGO AREA → equal top spacing on all cards */
.card .logo-wrap {{
width: 100%;
height: var(--logo-area-h);
padding-top: var(--logo-top);
padding-bottom: var(--logo-bottom);
display:flex; align-items:center; justify-content:center;
}}
.card .logo-img {{
max-width: var(--logo-max-w);
max-height: 100%;
width: auto; height: auto;
object-fit: contain;
display:block;
}}
.card h3 {{
margin: var(--card-title-mt) auto var(--card-title-mb) auto; /* centered + controllable */
font-size: var(--card-title-size);
font-weight: var(--card-title-weight);
color: var(--c-title-color, var(--card-title-color));
letter-spacing: .15px;
text-align:center;
width: 100%;
}}
.card p {{
color: var(--c-blurb-color, var(--card-blurb-color));
font-size: var(--card-blurb-size);
margin: var(--card-blurb-mt) auto var(--card-blurb-bm) auto; /* centered + controllable */
text-align:center;
width: 100%;
}}
.btn {{
display:inline-block;
margin-top: var(--btnMT);
padding: var(--btnPV) var(--btnPH);
border-radius: var(--btnRadius);
border: 1px solid var(--btnBorder);
background: linear-gradient(180deg, var(--btn1) 0%, var(--btn2) 100%);
font-weight: var(--btnFW);
font-size: var(--btnFS);
letter-spacing:.3px;
box-shadow: 0 12px 28px rgba(11,18,32,.28), inset 0 1px 0 rgba(255,255,255,.10);
color: var(--btnText);
text-decoration: none;
}}
a.btn, a.btn:link, a.btn:visited, a.btn:hover, a.btn:active, a.btn:focus {{
color: var(--btnText) !important; text-decoration: none !important;
}}
.btn:hover {{ filter: brightness(1.03) saturate(1.04); transform: translateY(-1px); }}
.footer {{ text-align:center; color:#3f4a5a; font-size:0.96em; margin-top: 8px; }}
.footer hr {{ margin: 12px 0; border-color: rgba(0,0,0,.08); }}
</style>
""", unsafe_allow_html=True)
# ========= HERO =========
hero_html = img_tag(hero["logo"], "Company Logo")
st.markdown(
f"""
<div class="hero">
{hero_html}
<div class="tagline">{escape(hero["tagline"])}</div>
</div>
""",
unsafe_allow_html=True,
)
# ========= Cards =========
def build_card_vars(style: Dict[str, Any]) -> str:
s = []
if "width" in style: s.append(f"--c-w:{int(style['width'])}px")
if "radius" in style: s.append(f"--c-radius:{int(style['radius'])}px")
if "pad_v" in style: s.append(f"--c-pv:{int(style['pad_v'])}px")
if "pad_h" in style: s.append(f"--c-ph:{int(style['pad_h'])}px")
if "bg_top" in style: s.append(f"--c-bg-top:{style['bg_top']}")
if "bg_bot" in style: s.append(f"--c-bg-bot:{style['bg_bot']}") # ← FIXED QUOTES
if "border" in style: s.append(f"--c-border:{style['border']}")
if "border_width" in style: s.append(f"--c-bw:{int(style['border_width'])}px")
if "title_color" in style: s.append(f"--c-title-color:{style['title_color']}")
if "title_fs" in style: s.append(f"--c-title-fs:{int(style['title_fs'])}px")
if "blurb_color" in style: s.append(f"--c-blurb-color:{style['blurb_color']}")
if "blurb_fs" in style: s.append(f"--c-blurb-fs:{int(style['blurb_fs'])}px")
return "; ".join(s)
def app_card(card_cfg: Dict[str, Any]) -> str:
style = card_cfg.get("style", {})
vars_inline = build_card_vars(style)
icon_html = img_tag(card_cfg.get("icon"), "logo", cls="logo-img") if card_cfg.get("icon") and card_cfg["icon"].exists() else ""
target = "_self"
return (
f"<div class='card' style='{vars_inline}'>"
+ f"<div class='logo-wrap'>{icon_html}</div>"
+ f"<h3>{escape(card_cfg['title'])}</h3>"
+ f"<p>{escape(card_cfg['blurb'])}</p>"
+ f"<a class='btn' href='{escape(card_cfg.get('url', '#'))}' target='{target}' rel='noopener'>Explore</a>"
+ "</div>"
)
st.markdown("<div class='grid'>" + "".join(app_card(c) for c in CARDS) + "</div>", unsafe_allow_html=True)
# ========= Footer =========
st.markdown(
"""
<hr>
<div class='footer'>
© 2025 Smart Thinking AI-Solutions Team. All rights reserved.<br>
Website: <a href="https://smartthinking.com.sa" target="_blank" rel="noopener noreferrer">smartthinking.com.sa</a>
</div>
""",
unsafe_allow_html=True,
)