|
|
import base64, mimetypes |
|
|
from html import escape |
|
|
from pathlib import Path |
|
|
import streamlit as st |
|
|
|
|
|
|
|
|
BASE_DIR = Path(__file__).parent |
|
|
ASSETS = BASE_DIR / "assets" |
|
|
|
|
|
|
|
|
HERO_LOGO_SIZE = 160 |
|
|
TOP_SPACER = 100 |
|
|
CARD_WIDTH = 300 |
|
|
CARD_THUMB_HEIGHT = 120 |
|
|
GRID_GAP = 80 |
|
|
BUTTON_TEXT = "Click to Run APP" |
|
|
OPEN_IN_NEW_TAB = False |
|
|
|
|
|
|
|
|
COMPANY_NAME = "Smart Thinking" |
|
|
TAGLINE = "We Deliver Smart AI-Based Solutions For O&G Industry" |
|
|
|
|
|
APP1 = { |
|
|
"title": "ST_GeoMech", |
|
|
"url": "https://smart-thinking-ai-suite-geomech.hf.space", |
|
|
"thumb": ASSETS / "app1.png", |
|
|
"blurb": "Real-Time UCS Prediction", |
|
|
"bg": "#F7FBFF", |
|
|
"border":"#E8F2FF", |
|
|
} |
|
|
APP2 = { |
|
|
"title": "ST_TOC", |
|
|
"url": "https://smart-thinking-gr.hf.space/", |
|
|
"thumb": ASSETS / "app2.png", |
|
|
"blurb": "Real-Time GR Log Prediction", |
|
|
"bg": "#F6FFF7", |
|
|
"border":"#E4F9E6", |
|
|
} |
|
|
|
|
|
APP3 = { |
|
|
"title": "ST_Perm", |
|
|
"url": "https://example.com/", |
|
|
"thumb": ASSETS / "app3.png", |
|
|
"blurb": "Describe your third app here", |
|
|
"bg": "#F9F6FF", |
|
|
"border":"#EDE6FF", |
|
|
} |
|
|
|
|
|
APP4 = { |
|
|
"title": "ST_Porosity", |
|
|
"url": "https://example.com/", |
|
|
"thumb": ASSETS / "app3.png", |
|
|
"blurb": "Describe your third app here", |
|
|
"bg": "#F9F6FF", |
|
|
"border":"#EDE6FF", |
|
|
} |
|
|
|
|
|
LOGO_PATH = ASSETS / "logo.png" |
|
|
|
|
|
|
|
|
page_icon = str(LOGO_PATH) if LOGO_PATH.exists() else "🧭" |
|
|
st.set_page_config(page_title=f"{COMPANY_NAME} — Apps", page_icon=page_icon, layout="wide") |
|
|
|
|
|
|
|
|
st.markdown( |
|
|
f""" |
|
|
<style> |
|
|
:root {{ |
|
|
--top-spacer: {TOP_SPACER}px; |
|
|
--card-width: {CARD_WIDTH}px; |
|
|
--grid-gap: {GRID_GAP}px; |
|
|
--tagline-gap: 0px; /* distance between H1 and tagline */ |
|
|
--grid-top-space: 20px; /* space above the cards */ |
|
|
}} |
|
|
|
|
|
html, body, [data-testid="stAppViewContainer"] {{ height: 100%; }} |
|
|
[data-testid="stAppViewContainer"] > .main {{ padding-top: 0 !important; padding-bottom: 0 !important; }} |
|
|
.block-container {{ |
|
|
max-width: 1120px; |
|
|
min-height: 100vh; |
|
|
display: flex; flex-direction: column; |
|
|
justify-content: center; |
|
|
gap: 10px; |
|
|
padding: var(--top-spacer) 0 8px !important; |
|
|
}} |
|
|
|
|
|
/* HERO */ |
|
|
.hero {{ text-align:center; margin: 0 0 8px; }} |
|
|
.hero img {{ |
|
|
width: {HERO_LOGO_SIZE}px; max-width: 95vw; height: auto; |
|
|
display:block; margin: 0 auto 8px; |
|
|
filter: drop-shadow(0 2px 8px rgba(0,0,0,.12)); |
|
|
}} |
|
|
/* Make tagline closer to the company name */ |
|
|
.hero h1 {{ font-size: 2.3rem; margin: .2rem 0 0; }} |
|
|
.hero p {{ |
|
|
color: #5a5f6a; |
|
|
margin: var(--tagline-gap) auto 0; |
|
|
max-width: 720px; |
|
|
font-style: italic; |
|
|
}} |
|
|
|
|
|
/* GRID: precise card width + extra space above cards */ |
|
|
.grid {{ |
|
|
display: flex; |
|
|
gap: var(--grid-gap); |
|
|
justify-content: center; |
|
|
align-items: stretch; |
|
|
flex-wrap: nowrap; /* keep three across on desktop */ |
|
|
margin-top: var(--grid-top-space); |
|
|
}} |
|
|
/* When viewport too narrow for 3-up, allow wrapping */ |
|
|
@media (max-width: calc(3 * var(--card-width) + 2 * var(--grid-gap) + 64px)) {{ |
|
|
.grid {{ flex-wrap: wrap; }} |
|
|
}} |
|
|
|
|
|
.card {{ |
|
|
width: var(--card-width); |
|
|
background: var(--card-bg, #fff); |
|
|
border: 1px solid var(--card-border, rgba(0,0,0,.06)); |
|
|
border-radius:16px; padding:14px; |
|
|
box-shadow:0 4px 18px rgba(0,0,0,.05); |
|
|
transition:transform .12s ease, box-shadow .12s ease; |
|
|
text-align:center; display:flex; flex-direction:column; gap:10px; |
|
|
}} |
|
|
.card:hover {{ transform: translateY(-2px); box-shadow: 0 10px 28px rgba(0,0,0,.08); }} |
|
|
|
|
|
.thumb {{ |
|
|
width: 100%; |
|
|
height: {CARD_THUMB_HEIGHT}px; |
|
|
border-radius:14px; |
|
|
border:1px solid rgba(0,0,0,.06); |
|
|
object-fit: contain; /* show full image */ |
|
|
background:#fff; |
|
|
}} |
|
|
|
|
|
.card h3 {{ margin:6px 0 2px; }} |
|
|
.card p {{ color:#5a5f6a; min-height:30px; margin:0 8px 2px; }} |
|
|
|
|
|
/* Light button with dark text */ |
|
|
.btn {{ |
|
|
display:inline-block; padding:10px 14px; border-radius:12px; |
|
|
border:1px solid #e5e7eb; text-decoration:none; |
|
|
background:#f3f4f6; color:#111827; font-weight:500; |
|
|
}} |
|
|
.btn:hover {{ background:#e5e7eb; }} |
|
|
|
|
|
.footer {{ text-align:center; color:#6b7280; font-size:1.0em; margin-top: 6px; }} |
|
|
.footer hr {{ margin: 6px 0; }} |
|
|
</style> |
|
|
""", |
|
|
unsafe_allow_html=True, |
|
|
) |
|
|
|
|
|
|
|
|
def data_uri(path: Path) -> str | None: |
|
|
if 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) -> str: |
|
|
uri = data_uri(path) |
|
|
if uri: |
|
|
return f'<img class="{cls}" src="{uri}" alt="{escape(alt)}" />' |
|
|
return f'<div class="{cls}" style="display:flex;align-items:center;justify-content:center;color:#50545c;background:linear-gradient(180deg,#f6f7fb,#eceff4);">{escape(alt)}</div>' |
|
|
|
|
|
|
|
|
st.markdown( |
|
|
'<div class="hero">' |
|
|
+ (img_tag(LOGO_PATH, "logo", "") if LOGO_PATH.exists() else "") |
|
|
+ f"<h1>{escape(COMPANY_NAME)}</h1>" |
|
|
+ f"<p>{escape(TAGLINE)}</p>" |
|
|
+ "</div>", |
|
|
unsafe_allow_html=True, |
|
|
) |
|
|
|
|
|
|
|
|
def app_card(app: dict) -> str: |
|
|
target = "_blank" if OPEN_IN_NEW_TAB else "_self" |
|
|
style = f"--card-bg:{app.get('bg','#fff')}; --card-border:{app.get('border','rgba(0,0,0,.06)')};" |
|
|
return ( |
|
|
f"<div class='card' style='{style}'>" |
|
|
+ img_tag(app["thumb"], app["title"], "thumb") |
|
|
+ f"<h3>{escape(app['title'])}</h3>" |
|
|
+ f"<p>{escape(app['blurb'])}</p>" |
|
|
+ f"<a class='btn' href='{escape(app['url'])}' target='{target}'>{escape(BUTTON_TEXT)}</a>" |
|
|
+ "</div>" |
|
|
) |
|
|
|
|
|
|
|
|
st.markdown("<div class='grid'>" + app_card(APP1) + app_card(APP2) + app_card(APP3) + "</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, |
|
|
) |
|
|
|