Spaces:
Sleeping
Sleeping
| # -*- 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="ST_LOG SUITE", layout="wide") | |
| # ========= GLOBAL THEME (one place) ========= | |
| THEME: Dict[str, Any] = { | |
| "strip": { | |
| "gap": 10, # px between pill and tagline | |
| "below_gap": 30, # px between strip row and hero | |
| "pill_pad_v": 8, # px | |
| "pill_pad_h": 14, # px | |
| "pill_font": 16, # px | |
| "tagline_font": 15, # px | |
| # GOLD header palette (high contrast) | |
| "bg1": "#D4AF37", # gold | |
| "bg2": "#B88917", # deeper gold | |
| "text": "#0B1220", # dark text in pill for contrast | |
| "tagline_color": "#000000", | |
| }, | |
| "page": { | |
| "top_padding": 50, | |
| "container_width": 1120, | |
| "bg_radial": True, | |
| }, | |
| "hero": { | |
| "width": 400, | |
| "margin_bottom": 30, | |
| "logo": ASSETS / "AI_Suite_Log_logo.png", | |
| }, | |
| "grid": { | |
| "gap": 50, | |
| "card_width": 340, | |
| }, | |
| "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 ========= | |
| CARDS = [ | |
| { | |
| "title": " ST_Log_GR", | |
| "blurb": "Real-time gamma-ray log prediction.", | |
| "url": "https://smart-thinking-gr.hf.space/", | |
| "icon": ASSETS / "GR_logo.png", | |
| "style": { | |
| "bg_top": "#EAF7F1", | |
| "bg_bot": "#F6FBF8", | |
| "border": "#0F3D3E", | |
| }, | |
| }, | |
| { | |
| "title": " ST_Log_Sonic (Ts)", | |
| "blurb": "Predict shear slowness (DtS) in real time.", | |
| "url": "https://smart-thinking-sonic-ts.hf.space", | |
| "icon": ASSETS / "Ts_logo.png", | |
| "style": { | |
| "bg_top": "#EAF7FD", | |
| "bg_bot": "#F5FBFF", | |
| "border": "#0E4A6E", | |
| }, | |
| }, | |
| { | |
| "title": " ST_Log_Sonic (Tc)", | |
| "blurb": "Predict compressional slowness (DtC) in real time.", | |
| "url": "https://smart-thinking-sonic-tc.hf.space", | |
| "icon": ASSETS / "Tc_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'<img{cls_attr}{style_attr} src="{uri}" alt="{escape(alt)}" />' | |
| # ========= CSS ========= | |
| 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""" | |
| <style> | |
| :root {{ | |
| --top-pad: {page["top_padding"]}px; | |
| --strip-gap: {strip["gap"]}px; | |
| --strip-row-mb: {strip["below_gap"]}px; | |
| --strip-pill-pv: {strip["pill_pad_v"]}px; | |
| --strip-pill-ph: {strip["pill_pad_h"]}px; | |
| --strip-pill-fs: {strip["pill_font"]}px; | |
| --tagline-fs: {strip["tagline_font"]}px; | |
| --hero-w: {hero["width"]}px; | |
| --hero-mb: {hero["margin_bottom"]}px; | |
| --grid-gap: {grid["gap"]}px; | |
| --card-w: {grid["card_width"]}px; | |
| --card-r: {card["radius"]}px; | |
| --card-bw: {card["border_width"]}px; | |
| --card-pv: {card["pad_v"]}px; | |
| --card-ph: {card["pad_h"]}px; | |
| --icon-d: {icon["diam"]}px; | |
| --icon-img: {icon["img"]}px; | |
| --stripBg1: {strip["bg1"]}; | |
| --stripBg2: {strip["bg2"]}; | |
| --stripText: {strip["text"]}; | |
| --taglineColor: {strip["tagline_color"]}; | |
| --cardStroke: {card["border"]}; | |
| --cardStrokeHover: {card["border_hover"]}; | |
| --btn1: {button["bg1"]}; | |
| --btn2: {button["bg2"]}; | |
| --btnText: {button["text"]}; | |
| --btnBorder: {button["border"]}; | |
| }} | |
| html, body, [data-testid="stAppViewContainer"] {{ height: 100%; }} | |
| [data-testid="stAppViewContainer"] > .main {{ padding-top: 0 !important; padding-bottom: 0 !important; }} | |
| .block-container {{ | |
| max-width: {page["container_width"]}px; | |
| min-height: 100vh; | |
| display: flex; flex-direction: column; | |
| gap: 14px; | |
| padding: var(--top-pad) 0 28px !important; | |
| {bg_radial_css} | |
| }} | |
| /* ===== TOP-LEFT STRIP ===== */ | |
| .suite-row {{ | |
| display:flex; align-items:center; gap: var(--strip-gap); | |
| justify-content:flex-start; flex-wrap: wrap; | |
| margin: 0 0 var(--strip-row-mb) 0; | |
| }} | |
| .suite-pill {{ | |
| background: linear-gradient(90deg, var(--stripBg1) 0%, var(--stripBg2) 100%); | |
| color: var(--stripText); | |
| padding: var(--strip-pill-pv) var(--strip-pill-ph); | |
| border-radius: 999px; | |
| font-weight: 800; letter-spacing: .25px; | |
| font-size: var(--strip-pill-fs); | |
| box-shadow: 0 6px 14px rgba(2,12,30,.18); | |
| white-space: nowrap; | |
| }} | |
| .suite-tagline {{ | |
| color: var(--taglineColor); font-weight: 600; opacity: .95; | |
| font-size: var(--tagline-fs); | |
| white-space: nowrap; | |
| }} | |
| /* ===== HERO ===== */ | |
| .hero {{ text-align:center; margin: 0 0 var(--hero-mb); }} | |
| .hero img {{ | |
| width: var(--hero-w); max-width: 92vw; height: auto; | |
| display:block; margin: 0 auto var(--hero-mb); | |
| filter: drop-shadow(0 6px 16px rgba(0,0,0,.10)); | |
| }} | |
| /* ===== GRID ===== */ | |
| .grid {{ | |
| display: grid; | |
| grid-template-columns: repeat(3, var(--card-w)); | |
| gap: var(--grid-gap); | |
| justify-content: center; align-items: stretch; | |
| margin-top: 10px; | |
| }} | |
| @media (max-width: 1120px) {{ | |
| .grid {{ grid-template-columns: repeat(2, var(--card-w)); gap: calc(var(--grid-gap) - 12px); }} | |
| }} | |
| @media (max-width: 720px) {{ | |
| .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, {card["bg_top"]}) 0%, var(--c-bg-bot, {card["bg_bot"]}) 100%); | |
| border: var(--c-bw, var(--card-bw)) solid var(--c-border, var(--cardStroke)); | |
| box-shadow: | |
| 0 14px 32px rgba(2,20,35,.12), | |
| 0 1px 0 rgba(255,255,255,0.70) inset, | |
| 0 -1px 6px rgba(255,255,255,0.18) inset; | |
| transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease, filter .18s ease; | |
| text-align:center; display:flex; flex-direction:column; gap:16px; align-items: center; | |
| }} | |
| .card:hover {{ | |
| transform: translateY(-6px) scale(1.01); | |
| border-color: var(--cardStrokeHover); | |
| box-shadow: | |
| 0 22px 56px rgba(2,20,35,.18), | |
| 0 1px 0 rgba(255,255,255,.82) inset, | |
| 0 -1px 10px rgba(255,255,255,.28) inset; | |
| filter: saturate(1.03); | |
| }} | |
| /* ===== ICON / LOGO — self-contained, not trimmed ===== */ | |
| .icon-wrap {{ | |
| width: var(--c-icon-d, {icon["diam"]}px); height: var(--c-icon-d, {icon["diam"]}px); | |
| border-radius: 9999px; display:grid; place-items:center; | |
| background: var(--c-icon-bg, {icon["circle_bg"]}); | |
| border: 1px solid var(--c-icon-border, {icon["circle_border"]}); | |
| box-shadow: inset 0 1px 0 rgba(255,255,255,.95), 0 10px 22px rgba(2,20,35,.07); | |
| }} | |
| .icon-wrap img {{ | |
| width: calc(var(--c-icon-img, {icon["img"]}px) - 12px); | |
| height: calc(var(--c-icon-img, {icon["img"]}px) - 12px); | |
| padding: 6px; | |
| box-sizing: border-box; | |
| object-fit: contain; /* never crop */ | |
| background: #FFFFFF; /* clean canvas for mixed logos */ | |
| border: 2px solid var(--c-border, var(--cardStroke)); /* match card border */ | |
| border-radius: 14px; | |
| display:block; | |
| }} | |
| .card h3 {{ | |
| margin: 0; font-size: 25px; font-weight: 900; | |
| color: {card["title_color"]}; letter-spacing: .15px; | |
| }} | |
| .card p {{ | |
| color: {card["blurb_color"]}; min-height: 40px; margin: 0 10px; font-size: 16px; | |
| }} | |
| .btn {{ | |
| display:inline-block; padding: {button["pad_v"]}px {button["pad_h"]}px; | |
| border-radius: {button["radius"]}px; | |
| border: 1px solid {button["border"]}; | |
| background: linear-gradient(180deg, {button["bg1"]} 0%, {button["bg2"]} 100%); | |
| font-weight: 800; letter-spacing:.3px; font-size: 16px; | |
| margin: 6px auto 0; | |
| box-shadow: 0 12px 28px rgba(11,18,32,.28), inset 0 1px 0 rgba(255,255,255,.10); | |
| color: {button["text"]}; | |
| text-decoration: none; | |
| }} | |
| a.btn, a.btn:link, a.btn:visited, a.btn:hover, a.btn:active, a.btn:focus {{ | |
| color: {button["text"]} !important; text-decoration: none !important; | |
| }} | |
| .btn:hover {{ | |
| filter: brightness(1.03) saturate(1.04); | |
| transform: translateY(-1px); | |
| box-shadow: 0 16px 38px rgba(11,18,32,.36); | |
| }} | |
| .footer {{ text-align:center; color:#3f4a5a; font-size:0.96em; margin-top: 26px; }} | |
| .footer hr {{ margin: 12px 0; border-color: rgba(0,0,0,.08); }} | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # ========= Strip (top-left) ========= | |
| st.markdown( | |
| f""" | |
| <div class="suite-row"> | |
| <span class="suite-pill">{"ST_LOG SUITE"}</span> | |
| <span class="suite-tagline">{"Generating AI-Based Well Logging Profiles While Drilling"}</span> | |
| </div> | |
| """, | |
| unsafe_allow_html=True, | |
| ) | |
| # ========= Hero ========= | |
| hero_html = img_tag(THEME["hero"]["logo"], "ST_LOG SUITE") | |
| st.markdown(f"<div class='hero'>{hero_html}</div>", 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_html = img_tag(card_cfg.get("icon"), "icon") if card_cfg.get("icon") and card_cfg["icon"].exists() else "" | |
| target = "_self" | |
| return ( | |
| f"<div class='card' style='{vars_inline}'>" | |
| + f"<div class='icon-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['url'])}' target='{target}' rel='noopener'>Run App</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, | |
| ) | |