UCS2014 commited on
Commit
8caa672
·
verified ·
1 Parent(s): d9b4c22

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +84 -138
app.py CHANGED
@@ -15,16 +15,9 @@ st.set_page_config(page_title="ST_LOG SUITE", layout="wide")
15
  # ========= GLOBAL THEME (one place) =========
16
  THEME: Dict[str, Any] = {
17
  "strip": {
18
- "gap": 10, # px between pill and tagline
19
- "below_gap": 30, # px between strip row and hero
20
- "pill_pad_v": 8, # px
21
- "pill_pad_h": 14, # px
22
- "pill_font": 16, # px
23
- "tagline_font": 15, # px
24
- # GOLD header palette (high contrast)
25
- "bg1": "#D4AF37", # gold
26
- "bg2": "#B88917", # deeper gold
27
- "text": "#000052", # dark text in pill for contrast
28
  "tagline_color": "#000000",
29
  },
30
  "page": {
@@ -37,36 +30,32 @@ THEME: Dict[str, Any] = {
37
  "margin_bottom": 30,
38
  "logo": ASSETS / "AI_Suite_Log_logo.png",
39
  },
 
40
  "grid": {
41
- "gap": 50,
42
- "card_width": 340,
 
 
 
 
 
 
43
  },
44
  "card": {
45
- "radius": 22,
46
- "border_width": 2,
47
- "pad_v": 24,
48
- "pad_h": 20,
49
- "border": "#0B1220",
50
- "border_hover": "#243447",
51
- "bg_top": "#FFFFFF",
52
- "bg_bot": "#FBFCFE",
53
- "title_color": "#0B1220",
54
- "blurb_color": "#566275",
55
  },
56
  "icon": {
57
- "diam": 118,
58
- "img": 106,
59
  "circle_bg": "#F1F5F9",
60
  "circle_border": "rgba(12,18,32,0.10)",
61
  },
62
  "button": {
63
- "pad_v": 12,
64
- "pad_h": 20,
65
- "radius": 14,
66
- "bg1": "#0B1220",
67
- "bg2": "#162338",
68
- "text": "#FFFFFF",
69
- "border": "rgba(11,18,32,.55)",
70
  },
71
  }
72
 
@@ -77,44 +66,28 @@ CARDS = [
77
  "blurb": "Real-time gamma-ray log prediction.",
78
  "url": "https://smart-thinking-gr.hf.space/",
79
  "icon": ASSETS / "GR_logo.png",
80
- "style": {
81
- "bg_top": "#EAF7F1",
82
- "bg_bot": "#F6FBF8",
83
- "border": "#0F3D3E",
84
- },
85
  },
86
  {
87
  "title": " ST_Log_Sonic (Ts)",
88
  "blurb": "Predict shear slowness (DtS) in real time.",
89
  "url": "https://smart-thinking-sonic-ts.hf.space",
90
  "icon": ASSETS / "Ts_logo.png",
91
- "style": {
92
- "bg_top": "#EAF7FD",
93
- "bg_bot": "#F5FBFF",
94
- "border": "#0E4A6E",
95
- },
96
  },
97
  {
98
  "title": " ST_Log_Sonic (Tc)",
99
  "blurb": "Predict compressional slowness (DtC) in real time.",
100
  "url": "https://smart-thinking-sonic-tc.hf.space",
101
  "icon": ASSETS / "Tc_logo.png",
102
- "style": {
103
- "bg_top": "#EEF0FF",
104
- "bg_bot": "#F7F8FF",
105
- "border": "#3E4EB8",
106
- },
107
  },
108
  {
109
  "title": " ST_Log_RHOB",
110
  "blurb": "Predict formation bulk density in real time.",
111
  "url": "https://smart-thinking-rhob.hf.space",
112
  "icon": ASSETS / "RHOB_logo.png",
113
- "style": {
114
- "bg_top": "#EAF7FD",
115
- "bg_bot": "#F5FBFF",
116
- "border": "#0E4A6E",
117
- },
118
  },
119
  ]
120
 
@@ -123,15 +96,13 @@ def data_uri(path: Path) -> Optional[str]:
123
  if not path or not path.exists():
124
  return None
125
  mime, _ = mimetypes.guess_type(path.name)
126
- if not mime:
127
- mime = "image/png"
128
  b64 = base64.b64encode(path.read_bytes()).decode("utf-8")
129
  return f"data:{mime};base64,{b64}"
130
 
131
  def img_tag(path: Path, alt: str, cls: str = "", style: str = "") -> str:
132
  uri = data_uri(path)
133
- if not uri:
134
- return ""
135
  cls_attr = f' class="{cls}"' if cls else ""
136
  style_attr = f' style="{style}"' if style else ""
137
  return f'<img{cls_attr}{style_attr} src="{uri}" alt="{escape(alt)}" />'
@@ -164,23 +135,28 @@ st.markdown(f"""
164
  --hero-w: {hero["width"]}px;
165
  --hero-mb: {hero["margin_bottom"]}px;
166
 
167
- --grid-gap: {grid["gap"]}px;
168
  --card-w: {grid["card_width"]}px;
 
 
 
 
 
169
  --card-r: {card["radius"]}px;
170
  --card-bw: {card["border_width"]}px;
171
  --card-pv: {card["pad_v"]}px;
172
  --card-ph: {card["pad_h"]}px;
173
 
 
 
 
 
 
174
  --icon-d: {icon["diam"]}px;
175
  --icon-img: {icon["img"]}px;
176
 
177
- --stripBg1: {strip["bg1"]};
178
- --stripBg2: {strip["bg2"]};
179
- --stripText: {strip["text"]};
180
- --taglineColor: {strip["tagline_color"]};
181
-
182
- --cardStroke: {card["border"]};
183
- --cardStrokeHover: {card["border_hover"]};
184
 
185
  --btn1: {button["bg1"]};
186
  --btn2: {button["bg2"]};
@@ -207,8 +183,8 @@ st.markdown(f"""
207
  margin: 0 0 var(--strip-row-mb) 0;
208
  }}
209
  .suite-pill {{
210
- background: linear-gradient(90deg, var(--stripBg1) 0%, var(--stripBg2) 100%);
211
- color: var(--stripText);
212
  padding: var(--strip-pill-pv) var(--strip-pill-ph);
213
  border-radius: 999px;
214
  font-weight: 800; letter-spacing: .25px;
@@ -217,7 +193,7 @@ st.markdown(f"""
217
  white-space: nowrap;
218
  }}
219
  .suite-tagline {{
220
- color: var(--taglineColor); font-weight: 600; opacity: .95;
221
  font-size: var(--tagline-fs);
222
  white-space: nowrap;
223
  }}
@@ -230,73 +206,60 @@ st.markdown(f"""
230
  filter: drop-shadow(0 6px 16px rgba(0,0,0,.10));
231
  }}
232
 
233
- /* ===== GRID ===== */
234
  .grid {{
235
  display: grid;
236
- grid-template-columns: repeat(3, var(--card-w));
237
- gap: var(--grid-gap);
238
- justify-content: center; align-items: stretch;
 
 
239
  margin-top: 10px;
240
  }}
241
- @media (max-width: 1120px) {{
242
- .grid {{ grid-template-columns: repeat(2, var(--card-w)); gap: calc(var(--grid-gap) - 12px); }}
243
- }}
244
- @media (max-width: 720px) {{
245
- .grid {{ grid-template-columns: 1fr; }}
246
- }}
247
 
248
  /* ===== CARD ===== */
249
  .card {{
250
  position: relative;
251
- width: var(--c-w, var(--card-w));
252
- border-radius: var(--c-radius, var(--card-r));
253
- padding: var(--c-pv, var(--card-pv)) var(--c-ph, var(--card-ph));
254
- background: linear-gradient(180deg, var(--c-bg-top, {card["bg_top"]}) 0%, var(--c-bg-bot, {card["bg_bot"]}) 100%);
255
- border: var(--c-bw, var(--card-bw)) solid var(--c-border, var(--cardStroke));
256
- box-shadow:
257
- 0 14px 32px rgba(2,20,35,.12),
258
- 0 1px 0 rgba(255,255,255,0.70) inset,
259
- 0 -1px 6px rgba(255,255,255,0.18) inset;
260
  transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease, filter .18s ease;
261
  text-align:center; display:flex; flex-direction:column; gap:16px; align-items: center;
262
  }}
263
  .card:hover {{
264
  transform: translateY(-6px) scale(1.01);
265
  border-color: var(--cardStrokeHover);
266
- box-shadow:
267
- 0 22px 56px rgba(2,20,35,.18),
268
- 0 1px 0 rgba(255,255,255,.82) inset,
269
- 0 -1px 10px rgba(255,255,255,.28) inset;
270
  filter: saturate(1.03);
271
  }}
272
 
273
- /* ===== ICON / LOGO — self-contained, not trimmed ===== */
274
  .icon-wrap {{
275
- width: var(--c-icon-d, {icon["diam"]}px); height: var(--c-icon-d, {icon["diam"]}px);
276
  border-radius: 9999px; display:grid; place-items:center;
277
- background: var(--c-icon-bg, {icon["circle_bg"]});
278
- border: 1px solid var(--c-icon-border, {icon["circle_border"]});
279
  box-shadow: inset 0 1px 0 rgba(255,255,255,.95), 0 10px 22px rgba(2,20,35,.07);
280
  }}
281
  .icon-wrap img {{
282
- width: calc(var(--c-icon-img, {icon["img"]}px) - 12px);
283
- height: calc(var(--c-icon-img, {icon["img"]}px) - 12px);
284
- padding: 6px;
285
- box-sizing: border-box;
286
- object-fit: contain; /* never crop */
287
- background: #FFFFFF; /* clean canvas for mixed logos */
288
- border: 2px solid var(--c-border, var(--cardStroke)); /* match card border */
289
- border-radius: 14px;
290
- display:block;
291
  }}
292
 
293
- .card h3 {{
294
- margin: 0; font-size: 25px; font-weight: 900;
295
- color: {card["title_color"]}; letter-spacing: .15px;
296
- }}
297
- .card p {{
298
- color: {card["blurb_color"]}; min-height: 40px; margin: 0 10px; font-size: 16px;
299
- }}
300
 
301
  .btn {{
302
  display:inline-block; padding: {button["pad_v"]}px {button["pad_h"]}px;
@@ -306,17 +269,12 @@ st.markdown(f"""
306
  font-weight: 800; letter-spacing:.3px; font-size: 16px;
307
  margin: 6px auto 0;
308
  box-shadow: 0 12px 28px rgba(11,18,32,.28), inset 0 1px 0 rgba(255,255,255,.10);
309
- color: {button["text"]};
310
- text-decoration: none;
311
  }}
312
  a.btn, a.btn:link, a.btn:visited, a.btn:hover, a.btn:active, a.btn:focus {{
313
  color: {button["text"]} !important; text-decoration: none !important;
314
  }}
315
- .btn:hover {{
316
- filter: brightness(1.03) saturate(1.04);
317
- transform: translateY(-1px);
318
- box-shadow: 0 16px 38px rgba(11,18,32,.36);
319
- }}
320
 
321
  .footer {{ text-align:center; color:#3f4a5a; font-size:0.96em; margin-top: 26px; }}
322
  .footer hr {{ margin: 12px 0; border-color: rgba(0,0,0,.08); }}
@@ -338,31 +296,19 @@ st.markdown(
338
  hero_html = img_tag(THEME["hero"]["logo"], "ST_LOG SUITE")
339
  st.markdown(f"<div class='hero'>{hero_html}</div>", unsafe_allow_html=True)
340
 
 
341
  def build_card_vars(style: Dict[str, Any]) -> str:
342
  s = []
343
- if "width" in style: s.append(f"--c-w:{int(style['width'])}px")
344
  if "radius" in style: s.append(f"--c-radius:{int(style['radius'])}px")
345
- if "pad_v" in style: s.append(f"--c-pv:{int(style['pad_v'])}px")
346
- if "pad_h" in style: s.append(f"--c-ph:{int(style['pad_h'])}px")
347
- if "bg_top" in style: s.append(f"--c-bg-top:{style['bg_top']}")
348
- if "bg_bot" in style: s.append(f"--c-bg-bot:{style['bg_bot']}")
349
- if "border" in style: s.append(f"--c-border:{style['border']}")
350
- if "border_width" in style: s.append(f"--c-bw:{int(style['border_width'])}px")
351
- if "title_color" in style: s.append(f"--c-title-color:{style['title_color']}")
352
- if "title_fs" in style: s.append(f"--c-title-fs:{int(style['title_fs'])}px")
353
- if "blurb_color" in style: s.append(f"--c-blurb-color:{style['blurb_color']}")
354
- if "blurb_fs" in style: s.append(f"--c-blurb-fs:{int(style['blurb_fs'])}px")
355
- if "btn_bg1" in style: s.append(f"--c-btn-bg1:{style['btn_bg1']}")
356
- if "btn_bg2" in style: s.append(f"--c-btn-bg2:{style['btn_bg2']}")
357
- if "btn_text" in style: s.append(f"--c-btn-text:{style['btn_text']}")
358
- if "btn_border" in style: s.append(f"--c-btn-border:{style['btn_border']}")
359
- if "btn_fs" in style: s.append(f"--c-btn-fs:{int(style['btn_fs'])}px")
360
- if "btn_pad_v" in style: s.append(f"--c-btn-pv:{int(style['btn_pad_v'])}px")
361
- if "btn_pad_h" in style: s.append(f"--c-btn-ph:{int(style['btn_pad_h'])}px")
362
- if "icon_diam" in style: s.append(f"--c-icon-d:{int(style['icon_diam'])}px")
363
- if "icon_img" in style: s.append(f"--c-icon-img:{int(style['icon_img'])}px")
364
- if "icon_bg" in style: s.append(f"--c-icon-bg:{style['icon_bg']}")
365
- if "icon_border" in style: s.append(f"--c-icon-border:{style['icon_border']}")
366
  return "; ".join(s)
367
 
368
  def app_card(card_cfg: Dict[str, Any]) -> str:
 
15
  # ========= GLOBAL THEME (one place) =========
16
  THEME: Dict[str, Any] = {
17
  "strip": {
18
+ "gap": 10, "below_gap": 30, "pill_pad_v": 8, "pill_pad_h": 14,
19
+ "pill_font": 16, "tagline_font": 15,
20
+ "bg1": "#D4AF37", "bg2": "#B88917", "text": "#000052",
 
 
 
 
 
 
 
21
  "tagline_color": "#000000",
22
  },
23
  "page": {
 
30
  "margin_bottom": 30,
31
  "logo": ASSETS / "AI_Suite_Log_logo.png",
32
  },
33
+ # ---------- GRID CONTROLS (you asked for this) ----------
34
  "grid": {
35
+ "card_width": 340, # fixed card width to keep sizes consistent
36
+ "gap_x": 40, # horizontal gap between cards (px)
37
+ "gap_y": 40, # vertical gap between rows (px)
38
+ "cols_desktop": 4, # cards per row on desktop
39
+ "cols_tablet": 2, # cards per row for medium widths
40
+ "cols_mobile": 1, # cards per row on phones
41
+ # Optional: force left/center alignment of the whole grid row
42
+ "justify": "center", # 'start' | 'center' | 'end'
43
  },
44
  "card": {
45
+ "radius": 22, "border_width": 2, "pad_v": 24, "pad_h": 20,
46
+ "border": "#0B1220", "border_hover": "#243447",
47
+ "bg_top": "#FFFFFF", "bg_bot": "#FBFCFE",
48
+ "title_color": "#0B1220", "blurb_color": "#566275",
 
 
 
 
 
 
49
  },
50
  "icon": {
51
+ "diam": 118, "img": 106,
 
52
  "circle_bg": "#F1F5F9",
53
  "circle_border": "rgba(12,18,32,0.10)",
54
  },
55
  "button": {
56
+ "pad_v": 12, "pad_h": 20, "radius": 14,
57
+ "bg1": "#0B1220", "bg2": "#162338",
58
+ "text": "#FFFFFF", "border": "rgba(11,18,32,.55)",
 
 
 
 
59
  },
60
  }
61
 
 
66
  "blurb": "Real-time gamma-ray log prediction.",
67
  "url": "https://smart-thinking-gr.hf.space/",
68
  "icon": ASSETS / "GR_logo.png",
69
+ "style": {"bg_top": "#EAF7F1", "bg_bot": "#F6FBF8", "border": "#0F3D3E"},
 
 
 
 
70
  },
71
  {
72
  "title": " ST_Log_Sonic (Ts)",
73
  "blurb": "Predict shear slowness (DtS) in real time.",
74
  "url": "https://smart-thinking-sonic-ts.hf.space",
75
  "icon": ASSETS / "Ts_logo.png",
76
+ "style": {"bg_top": "#EAF7FD", "bg_bot": "#F5FBFF", "border": "#0E4A6E"},
 
 
 
 
77
  },
78
  {
79
  "title": " ST_Log_Sonic (Tc)",
80
  "blurb": "Predict compressional slowness (DtC) in real time.",
81
  "url": "https://smart-thinking-sonic-tc.hf.space",
82
  "icon": ASSETS / "Tc_logo.png",
83
+ "style": {"bg_top": "#EEF0FF", "bg_bot": "#F7F8FF", "border": "#3E4EB8"},
 
 
 
 
84
  },
85
  {
86
  "title": " ST_Log_RHOB",
87
  "blurb": "Predict formation bulk density in real time.",
88
  "url": "https://smart-thinking-rhob.hf.space",
89
  "icon": ASSETS / "RHOB_logo.png",
90
+ "style": {"bg_top": "#EAF7FD", "bg_bot": "#F5FBFF", "border": "#0E4A6E"},
 
 
 
 
91
  },
92
  ]
93
 
 
96
  if not path or not path.exists():
97
  return None
98
  mime, _ = mimetypes.guess_type(path.name)
99
+ if not mime: mime = "image/png"
 
100
  b64 = base64.b64encode(path.read_bytes()).decode("utf-8")
101
  return f"data:{mime};base64,{b64}"
102
 
103
  def img_tag(path: Path, alt: str, cls: str = "", style: str = "") -> str:
104
  uri = data_uri(path)
105
+ if not uri: return ""
 
106
  cls_attr = f' class="{cls}"' if cls else ""
107
  style_attr = f' style="{style}"' if style else ""
108
  return f'<img{cls_attr}{style_attr} src="{uri}" alt="{escape(alt)}" />'
 
135
  --hero-w: {hero["width"]}px;
136
  --hero-mb: {hero["margin_bottom"]}px;
137
 
138
+ /* GRID controls you can tweak */
139
  --card-w: {grid["card_width"]}px;
140
+ --grid-gap-x: {grid["gap_x"]}px;
141
+ --grid-gap-y: {grid["gap_y"]}px;
142
+ --grid-cols: {grid["cols_desktop"]};
143
+ --grid-justify: {grid["justify"]};
144
+
145
  --card-r: {card["radius"]}px;
146
  --card-bw: {card["border_width"]}px;
147
  --card-pv: {card["pad_v"]}px;
148
  --card-ph: {card["pad_h"]}px;
149
 
150
+ --cardBgTop: {card["bg_top"]};
151
+ --cardBgBot: {card["bg_bot"]};
152
+ --cardStroke: {card["border"]};
153
+ --cardStrokeHover: {card["border_hover"]};
154
+
155
  --icon-d: {icon["diam"]}px;
156
  --icon-img: {icon["img"]}px;
157
 
158
+ --titleColor: {card["title_color"]};
159
+ --blurbColor: {card["blurb_color"]};
 
 
 
 
 
160
 
161
  --btn1: {button["bg1"]};
162
  --btn2: {button["bg2"]};
 
183
  margin: 0 0 var(--strip-row-mb) 0;
184
  }}
185
  .suite-pill {{
186
+ background: linear-gradient(90deg, {strip["bg1"]} 0%, {strip["bg2"]} 100%);
187
+ color: {strip["text"]};
188
  padding: var(--strip-pill-pv) var(--strip-pill-ph);
189
  border-radius: 999px;
190
  font-weight: 800; letter-spacing: .25px;
 
193
  white-space: nowrap;
194
  }}
195
  .suite-tagline {{
196
+ color: {strip["tagline_color"]}; font-weight: 600; opacity: .95;
197
  font-size: var(--tagline-fs);
198
  white-space: nowrap;
199
  }}
 
206
  filter: drop-shadow(0 6px 16px rgba(0,0,0,.10));
207
  }}
208
 
209
+ /* ===== GRID (cards per row are controlled via --grid-cols) ===== */
210
  .grid {{
211
  display: grid;
212
+ grid-template-columns: repeat(var(--grid-cols), var(--card-w));
213
+ column-gap: var(--grid-gap-x);
214
+ row-gap: var(--grid-gap-y);
215
+ justify-content: var(--grid-justify);
216
+ align-items: stretch;
217
  margin-top: 10px;
218
  }}
219
+ /* Override # of columns on smaller screens */
220
+ @media (max-width: 1120px) {{ .grid {{ --grid-cols: {grid["cols_tablet"]}; }} }}
221
+ @media (max-width: 720px) {{ .grid {{ --grid-cols: {grid["cols_mobile"]}; }} }}
 
 
 
222
 
223
  /* ===== CARD ===== */
224
  .card {{
225
  position: relative;
226
+ width: var(--card-w);
227
+ border-radius: {card["radius"]}px;
228
+ padding: var(--card-pv) var(--card-ph);
229
+ background: linear-gradient(180deg, var(--cardBgTop) 0%, var(--cardBgBot) 100%);
230
+ border: var(--card-bw) solid var(--cardStroke);
231
+ box-shadow: 0 14px 32px rgba(2,20,35,.12),
232
+ 0 1px 0 rgba(255,255,255,0.70) inset,
233
+ 0 -1px 6px rgba(255,255,255,0.18) inset;
 
234
  transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease, filter .18s ease;
235
  text-align:center; display:flex; flex-direction:column; gap:16px; align-items: center;
236
  }}
237
  .card:hover {{
238
  transform: translateY(-6px) scale(1.01);
239
  border-color: var(--cardStrokeHover);
240
+ box-shadow: 0 22px 56px rgba(2,20,35,.18),
241
+ 0 1px 0 rgba(255,255,255,0.82) inset,
242
+ 0 -1px 10px rgba(255,255,255,0.28) inset;
 
243
  filter: saturate(1.03);
244
  }}
245
 
246
+ /* Icon (kept same styling) */
247
  .icon-wrap {{
248
+ width: var(--icon-d); height: var(--icon-d);
249
  border-radius: 9999px; display:grid; place-items:center;
250
+ background: {icon["circle_bg"]};
251
+ border: 1px solid {icon["circle_border"]};
252
  box-shadow: inset 0 1px 0 rgba(255,255,255,.95), 0 10px 22px rgba(2,20,35,.07);
253
  }}
254
  .icon-wrap img {{
255
+ width: calc(var(--icon-img) - 12px);
256
+ height: calc(var(--icon-img) - 12px);
257
+ padding: 6px; box-sizing: border-box; object-fit: contain;
258
+ background: #FFFFFF; border: 2px solid var(--cardStroke); border-radius: 14px; display:block;
 
 
 
 
 
259
  }}
260
 
261
+ .card h3 {{ margin: 0; font-size: 25px; font-weight: 900; color: var(--titleColor); letter-spacing: .15px; }}
262
+ .card p {{ color: var(--blurbColor); min-height: 40px; margin: 0 10px; font-size: 16px; }}
 
 
 
 
 
263
 
264
  .btn {{
265
  display:inline-block; padding: {button["pad_v"]}px {button["pad_h"]}px;
 
269
  font-weight: 800; letter-spacing:.3px; font-size: 16px;
270
  margin: 6px auto 0;
271
  box-shadow: 0 12px 28px rgba(11,18,32,.28), inset 0 1px 0 rgba(255,255,255,.10);
272
+ color: {button["text"]}; text-decoration: none;
 
273
  }}
274
  a.btn, a.btn:link, a.btn:visited, a.btn:hover, a.btn:active, a.btn:focus {{
275
  color: {button["text"]} !important; text-decoration: none !important;
276
  }}
277
+ .btn:hover {{ filter: brightness(1.03) saturate(1.04); transform: translateY(-1px); }}
 
 
 
 
278
 
279
  .footer {{ text-align:center; color:#3f4a5a; font-size:0.96em; margin-top: 26px; }}
280
  .footer hr {{ margin: 12px 0; border-color: rgba(0,0,0,.08); }}
 
296
  hero_html = img_tag(THEME["hero"]["logo"], "ST_LOG SUITE")
297
  st.markdown(f"<div class='hero'>{hero_html}</div>", unsafe_allow_html=True)
298
 
299
+ # ========= Cards =========
300
  def build_card_vars(style: Dict[str, Any]) -> str:
301
  s = []
302
+ if "width" in style: s.append(f"--card-w:{int(style['width'])}px")
303
  if "radius" in style: s.append(f"--c-radius:{int(style['radius'])}px")
304
+ if "pad_v" in style: s.append(f"--card-pv:{int(style['pad_v'])}px")
305
+ if "pad_h" in style: s.append(f"--card-ph:{int(style['pad_h'])}px")
306
+ if "bg_top" in style: s.append(f"--cardBgTop:{style['bg_top']}")
307
+ if "bg_bot" in style: s.append(f"--cardBgBot:{style['bg_bot']}")
308
+ if "border" in style: s.append(f"--cardStroke:{style['border']}")
309
+ if "border_width" in style: s.append(f"--card-bw:{int(style['border_width'])}px")
310
+ if "title_color" in style: s.append(f"--titleColor:{style['title_color']}")
311
+ if "blurb_color" in style: s.append(f"--blurbColor:{style['blurb_color']}")
 
 
 
 
 
 
 
 
 
 
 
 
 
312
  return "; ".join(s)
313
 
314
  def app_card(card_cfg: Dict[str, Any]) -> str: