| |
| 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="ST_LOG SUITE — Apps", page_icon="🧭", layout="wide") |
|
|
| |
| SUITE_NAME = "ST_LOG SUITE" |
| SUITE_TAGLINE = "Generating AI-Based Well Logging Profiles While Drilling" |
|
|
| |
| NAVY_900 = "#0B1220" |
| NAVY_700 = "#1F2937" |
| SLATE_600 = "#344054" |
| PAPER_TOP = "#FFFFFF" |
| PAPER_BOT = "#FBFCFE" |
| GOLD = "#EAB308" |
|
|
| |
| TOP_PADDING_PX = 50 |
| STRIP_GAP_PX = 10 |
| STRIP_PILL_PAD_V_PX = 8 |
| STRIP_PILL_PAD_H_PX = 14 |
| STRIP_PILL_FONT_PX = 16 |
| TAGLINE_FONT_PX = 15 |
| STRIP_BELOW_GAP_PX = 30 |
|
|
| HERO_LOGO_WIDTH_PX = 400 |
| HERO_MARGIN_BOTTOM_PX= 30 |
|
|
| GRID_GAP_PX = 50 |
| CARD_WIDTH_PX = 340 |
| CARD_RADIUS_PX = 22 |
| CARD_BORDER_PX = 2 |
| CARD_PAD_V_PX = 24 |
| CARD_PAD_H_PX = 20 |
|
|
| ICON_DIAM_PX = 118 |
| ICON_IMG_PX = 106 |
|
|
| TITLE_FONT_PX = 25 |
| BLURB_FONT_PX = 16 |
| BUTTON_FONT_PX = 16 |
| BUTTON_PAD_V_PX = 12 |
| BUTTON_PAD_H_PX = 20 |
| BUTTON_RADIUS_PX = 14 |
|
|
| |
| SHOW_CARD_CHIP = False |
| USE_TINTED_CARD_BG = True |
|
|
| |
| HERO_LOGO = ASSETS / "AI_Suite_Log_logo.png" |
| ICON_GR = ASSETS / "GR_logo.png" |
| ICON_TS = ASSETS / "Ts_logo.png" |
| ICON_TC = ASSETS / "Tc_logo.png" |
|
|
| APPS = [ |
| { |
| "title": "ST_Log_GR", |
| "url": "https://smart-thinking-gr.hf.space/", |
| "blurb": "Real-time gamma-ray log prediction.", |
| "icon": ICON_GR, |
| "tint": "linear-gradient(180deg, rgba(14,116,144,0.05) 0%, rgba(14,116,144,0.02) 100%)", |
| }, |
| { |
| "title": "ST_Log_Sonic (Ts)", |
| "url": "https://smart-thinking-sonic-ts.hf.space", |
| "blurb": "Predict shear slowness (DtS) in real time.", |
| "icon": ICON_TS, |
| "tint": "linear-gradient(180deg, rgba(2,132,199,0.05) 0%, rgba(2,132,199,0.02) 100%)", |
| }, |
| { |
| "title": "ST_Log_Sonic (Tc)", |
| "url": "https://smart-thinking-sonic-tc.hf.space", |
| "blurb": "Predict compressional slowness (DtC) in real time.", |
| "icon": ICON_TC, |
| "tint": "linear-gradient(180deg, rgba(99,102,241,0.05) 0%, rgba(99,102,241,0.02) 100%)", |
| }, |
| ] |
|
|
| |
| def data_uri(path: Path) -> str | None: |
| 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)}" />' |
|
|
| |
| st.markdown(f""" |
| <style> |
| :root {{ |
| /* sizes */ |
| --top-pad: {TOP_PADDING_PX}px; |
| --strip-gap: {STRIP_GAP_PX}px; |
| --strip-pill-pv: {STRIP_PILL_PAD_V_PX}px; |
| --strip-pill-ph: {STRIP_PILL_PAD_H_PX}px; |
| --strip-pill-fs: {STRIP_PILL_FONT_PX}px; |
| --tagline-fs: {TAGLINE_FONT_PX}px; |
| --strip-row-mb: {STRIP_BELOW_GAP_PX}px; |
| |
| --hero-w: {HERO_LOGO_WIDTH_PX}px; |
| --hero-mb: {HERO_MARGIN_BOTTOM_PX}px; |
| |
| --grid-gap: {GRID_GAP_PX}px; |
| --card-w: {CARD_WIDTH_PX}px; |
| --card-r: {CARD_RADIUS_PX}px; |
| --card-bw: {CARD_BORDER_PX}px; |
| --card-pv: {CARD_PAD_V_PX}px; |
| --card-ph: {CARD_PAD_H_PX}px; |
| |
| --icon-d: {ICON_DIAM_PX}px; |
| --icon-img: {ICON_IMG_PX}px; |
| |
| --title-fs: {TITLE_FONT_PX}px; |
| --blurb-fs: {BLURB_FONT_PX}px; |
| --btn-fs: {BUTTON_FONT_PX}px; |
| --btn-pv: {BUTTON_PAD_V_PX}px; |
| --btn-ph: {BUTTON_PAD_H_PX}px; |
| --btn-r: {BUTTON_RADIUS_PX}px; |
| |
| /* colors */ |
| --navy1: {NAVY_900}; |
| --navy2: {NAVY_700}; |
| --gold: {GOLD}; |
| --slate6:{SLATE_600}; |
| |
| /* outlines / 3D */ |
| --card-stroke: {NAVY_900}; |
| --card-stroke-hover: #1E293B; |
| }} |
| |
| 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; |
| gap: 14px; |
| padding: var(--top-pad) 0 28px !important; |
| 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%); |
| }} |
| |
| /* ===== TOP-LEFT STRIP ROW ===== */ |
| .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; /* was 0 0 8px 0 */ |
| }} |
| |
| .suite-pill {{ |
| background: linear-gradient(90deg, var(--navy1) 0%, var(--navy2) 100%); |
| color: var(--gold); |
| 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(--slate6); font-weight: 600; opacity: .95; |
| font-size: var(--tagline-fs); |
| white-space: nowrap; |
| }} |
| |
| /* HERO LOGO (below the strip) */ |
| .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 — dark outline + premium 3D look */ |
| .card {{ |
| position: relative; |
| width: var(--card-w); |
| border-radius: var(--card-r); |
| padding: var(--card-pv) var(--card-ph); |
| background: var(--card-bg, linear-gradient(180deg, {PAPER_TOP} 0%, {PAPER_BOT} 100%)); |
| border: var(--card-bw) solid var(--card-stroke); |
| box-shadow: |
| 0 14px 32px rgba(2,20,35,.12), |
| 0 1px 0 rgba(255,255,255,0.90) inset, |
| 0 -1px 10px rgba(255,255,255,0.40) 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(--card-stroke-hover); |
| box-shadow: |
| 0 22px 56px rgba(2,20,35,.18), |
| 0 1px 0 rgba(255,255,255,0.92) inset, |
| 0 -1px 12px rgba(255,255,255,0.45) inset; |
| filter: saturate(1.03); |
| }} |
| |
| .suite-chip {{ |
| position: absolute; top: 12px; right: 12px; |
| background: linear-gradient(90deg, var(--navy1) 0%, var(--navy2) 100%); |
| color: var(--gold); padding: 6px 10px; border-radius: 999px; |
| font-weight: 700; font-size: 13px; letter-spacing: .2px; |
| box-shadow: 0 6px 14px rgba(2,12,30,.18); |
| }} |
| |
| .icon-wrap {{ |
| width: var(--icon-d); height: var(--icon-d); border-radius: 9999px; |
| background: #F1F5F9; |
| display:grid; place-items:center; |
| border: 1px solid rgba(12,18,32,0.10); |
| box-shadow: inset 0 1px 0 rgba(255,255,255,.95), 0 10px 22px rgba(2,20,35,.07); |
| }} |
| .icon-wrap img {{ |
| width: var(--icon-img); height: var(--icon-img); border-radius:9999px; display:block; |
| }} |
| |
| .card h3 {{ |
| margin: 0; font-size: var(--title-fs); font-weight: 900; color:#0b1220; |
| letter-spacing: .15px; |
| }} |
| .card p {{ |
| color:#566275; min-height: 40px; margin: 0 10px; font-size: var(--blurb-fs); |
| }} |
| |
| .btn {{ |
| display:inline-block; padding: var(--btn-pv) var(--btn-ph); border-radius: var(--btn-r); |
| border: 1px solid rgba(11,18,32,.45); |
| background: linear-gradient(180deg, var(--navy1) 0%, var(--navy2) 100%); |
| font-weight: 800; letter-spacing:.3px; font-size: var(--btn-fs); |
| margin: 6px auto 0; |
| box-shadow: 0 12px 28px rgba(11,18,32,.28), inset 0 1px 0 rgba(255,255,255,.10); |
| }} |
| a.btn, a.btn:link, a.btn:visited, a.btn:hover, a.btn:active, a.btn:focus {{ |
| color: #ffffff !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); |
| }} |
| .btn:focus {{ |
| outline: none; |
| box-shadow: 0 0 0 4px rgba(30,41,59,.28), 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) |
|
|
| |
| st.markdown( |
| f""" |
| <div class="suite-row"> |
| <span class="suite-pill">{escape(SUITE_NAME)}</span> |
| <span class="suite-tagline">{escape(SUITE_TAGLINE)}</span> |
| </div> |
| """, |
| unsafe_allow_html=True, |
| ) |
|
|
| |
| hero_html = img_tag(HERO_LOGO, "ST_LOG SUITE") |
| st.markdown(f"<div class='hero'>{hero_html}</div>", unsafe_allow_html=True) |
|
|
| |
| def app_card(app: dict) -> str: |
| tint = app.get("tint") if USE_TINTED_CARD_BG else f"linear-gradient(180deg, {PAPER_TOP} 0%, {PAPER_BOT} 100%)" |
| icon_html = img_tag(app.get("icon"), "icon", |
| style=f"width:{ICON_IMG_PX}px;height:{ICON_IMG_PX}px;border-radius:9999px;") \ |
| if app.get("icon") and app["icon"].exists() else "" |
| target = "_self" |
| chip = f"<div class='suite-chip'>{escape(SUITE_NAME)}</div>" if SHOW_CARD_CHIP else "" |
| style_vars = f"--card-bg:{tint};" |
| return ( |
| f"<div class='card' style='{style_vars}'>" |
| + chip |
| + f"<div class='icon-wrap'>{icon_html}</div>" |
| + f"<h3>{escape(app['title'])}</h3>" |
| + f"<p>{escape(app['blurb'])}</p>" |
| + f"<a class='btn' href='{escape(app['url'])}' target='{target}' rel='noopener'>Run App</a>" |
| + "</div>" |
| ) |
|
|
| cards_html = "".join(app_card(a) for a in APPS) |
| st.markdown(f"<div class='grid'>{cards_html}</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, |
| ) |
|
|