# -*- coding: utf-8 -*- from typing import Optional, Dict, Any import base64 import 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="ST_GeoMech SUITE", layout="wide") # ========= CARDS PER ROW (single control) ========= # Change this to any integer >= 1 (e.g., 3, 4, 5...) CARDS_PER_ROW: int = 3 # ========= GLOBAL THEME (one place) ========= THEME: Dict[str, Any] = { "strip": { "gap": 10, "below_gap": 30, "pill_pad_v": 8, "pill_pad_h": 14, "pill_font": 16, "tagline_font": 15, "bg1": "#064E3B", "bg2": "#065F46", "text": "#FFFFFF", "tagline_color": "#000000", }, "page": { "top_padding": 50, "container_width": 1120, "bg_radial": True, }, "hero": { "width": 400, "margin_bottom": 30, "logo": ASSETS / "AI_Suite_GeoMech_logo.png", }, "grid": { "gap": 70, # Card width is now fluid; keep for max-width if you like "card_width": 380, }, "card": { "radius": 22, "border_width": 2, "pad_v": 24, "pad_h": 20, "border": "#0B1220", "border_hover": "#243447", "bg_top": "#FFFFFF", "bg_bot": "#FBFCFE", "title_color": "#0B1220", "blurb_color": "#566275", }, "icon": { "diam": 118, "img": 106, "circle_bg": "#F1F5F9", "circle_border": "rgba(12,18,32,0.10)", }, "button": { "pad_v": 12, "pad_h": 20, "radius": 14, "bg1": "#0B1220", "bg2": "#162338", "text": "#FFFFFF", "border": "rgba(11,18,32,.55)", }, } # ========= CARDS (content + per-card overrides) ========= CARDS = [ { "title": " ST_GeoMEch_UCS", "blurb": "Real-time Uniconfined Compressive Strength Prediction.", "url": "https://smart-thinking-ucs.hf.space/", "icon": ASSETS / "UCS_logo.png", "style": {"bg_top": "#EAF7F1", "bg_bot": "#F6FBF8", "border": "#0F3D3E"}, }, { "title": " ST_GeoMech_Ym", "blurb": "Real-time Static Young's Modulus Prediction.", "url": "https://smart-thinking-ym.hf.space", "icon": ASSETS / "Ym_logo.png", "style": {"bg_top": "#EAF7FD", "bg_bot": "#F5FBFF", "border": "#0E4A6E"}, }, { "title": " ST_GeoMech_SMW", "blurb": "Real-Time Safe Mud Window Prediction", "url": "https://smart-thinking-smw.hf.space", "icon": ASSETS / "SMW_logo.png", "style": {"bg_top": "#EEF0FF", "bg_bot": "#F7F8FF", "border": "#3E4EB8"}, }, ] # ========= 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'' # ========= CSS (driven from THEME) ========= strip = THEME["strip"] page = THEME["page"] hero = THEME["hero"] grid = THEME["grid"] card = THEME["card"] icon = THEME["icon"] 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""" """, unsafe_allow_html=True, ) # ========= Strip (top-left) ========= st.markdown( """
ST_GeoMech SUITE Predicting Rock Mechanical Behaviour While Drilling
""", unsafe_allow_html=True, ) # ========= Hero ========= hero_html = img_tag(THEME["hero"]["logo"], "ST_GeoMech SUITE") st.markdown(f"
{hero_html}
", unsafe_allow_html=True) 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']}") 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") if "btn_bg1" in style: s.append(f"--c-btn-bg1:{style['btn_bg1']}") if "btn_bg2" in style: s.append(f"--c-btn-bg2:{style['btn_bg2']}") if "btn_text" in style: s.append(f"--c-btn-text:{style['btn_text']}") if "btn_border" in style: s.append(f"--c-btn-border:{style['btn_border']}") if "btn_fs" in style: s.append(f"--c-btn-fs:{int(style['btn_fs'])}px") if "btn_pad_v" in style: s.append(f"--c-btn-pv:{int(style['btn_pad_v'])}px") if "btn_pad_h" in style: s.append(f"--c-btn-ph:{int(style['btn_pad_h'])}px") if "icon_diam" in style: s.append(f"--c-icon-d:{int(style['icon_diam'])}px") if "icon_img" in style: s.append(f"--c-icon-img:{int(style['icon_img'])}px") if "icon_bg" in style: s.append(f"--c-icon-bg:{style['icon_bg']}") if "icon_border" in style: s.append(f"--c-icon-border:{style['icon_border']}") return "; ".join(s) def app_card(card_cfg: Dict[str, Any]) -> str: style = card_cfg.get("style", {}) vars_inline = build_card_vars(style) icon_path = card_cfg.get("icon") icon_html = img_tag(icon_path, "icon") if isinstance(icon_path, Path) and icon_path.exists() else "" target = "_self" return ( f"
" + f"
{icon_html}
" + f"

{escape(card_cfg['title'])}

" + f"

{escape(card_cfg['blurb'])}

" + f"Run App" + "
" ) st.markdown("
" + "".join(app_card(c) for c in CARDS) + "
", unsafe_allow_html=True) # ========= Footer ========= st.markdown( """
""", unsafe_allow_html=True, )