| |
| from typing import Optional, Dict, Any |
| import base64, mimetypes |
| from html import escape |
| from pathlib import Path |
| import streamlit as st |
|
|
| |
| BASE_DIR = Path(__file__).parent |
| ASSETS = BASE_DIR / "assets" |
|
|
| |
| st.set_page_config(page_title="Smart Thinking — AI Suite", layout="wide") |
|
|
| |
| 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, |
| "tagline_italic": True, |
| "tagline_color": "#0B1220", |
| "logo_bottom": 14, |
| "block_bottom": 22, |
| }, |
| "grid": { |
| "columns": 4, |
| "gap": 40, |
| "card_width": 300, |
| }, |
| "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, |
|
|
| |
| "title_color": "#0B1220", |
| "title_size": 26, |
| "title_weight": 900, |
| "title_mt": 0, |
| "title_mb": 0, |
| "blurb_color": "#566275", |
| "blurb_size": 16, |
| "blurb_mt": 0, |
| "blurb_mb": 40, |
|
|
| |
| "logo_max_w": 350, |
| "logo_area_h": 300, |
| "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, |
| }, |
| } |
|
|
| |
| 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": "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": "\u00A0Porosity", |
| "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", |
| }, |
| }, |
| ] |
|
|
| |
| 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)}" />' |
|
|
| |
| 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_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, |
| ) |
|
|
| |
| 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") |
| 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) |
|
|
| |
| 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, |
| ) |
|
|