Spaces:
Sleeping
Sleeping
Scott Harshman commited on
Delete app.py
Browse files
app.py
DELETED
|
@@ -1,821 +0,0 @@
|
|
| 1 |
-
import streamlit as st
|
| 2 |
-
import streamlit.components.v1 as components
|
| 3 |
-
import os, json, re
|
| 4 |
-
from reportlab.lib.pagesizes import letter
|
| 5 |
-
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
| 6 |
-
from reportlab.lib.units import inch
|
| 7 |
-
from reportlab.platypus import (
|
| 8 |
-
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
|
| 9 |
-
HRFlowable, KeepTogether, PageBreak,
|
| 10 |
-
)
|
| 11 |
-
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
|
| 12 |
-
from reportlab.lib import colors
|
| 13 |
-
from io import BytesIO
|
| 14 |
-
from datetime import datetime
|
| 15 |
-
from config import *
|
| 16 |
-
|
| 17 |
-
# ── Shorthand color references
|
| 18 |
-
NAVY = BRAND["colors"]["navy"]
|
| 19 |
-
TEAL = BRAND["colors"]["teal"]
|
| 20 |
-
SKY = BRAND["colors"]["sky"]
|
| 21 |
-
ORANGE = BRAND["colors"]["orange"]
|
| 22 |
-
AMBER = BRAND["colors"]["amber"]
|
| 23 |
-
WHITE = BRAND["colors"]["white"]
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
# =============================================================================
|
| 27 |
-
# RECOMMENDATION ENGINE
|
| 28 |
-
# =============================================================================
|
| 29 |
-
def score_peptides(selected_goals):
|
| 30 |
-
"""Score every peptide against the patient's selected goals.
|
| 31 |
-
|
| 32 |
-
Returns a list of (peptide_key, score, matched_categories) sorted by score
|
| 33 |
-
descending, then by suggested-vial cost ascending (value-first tiebreaker).
|
| 34 |
-
"""
|
| 35 |
-
# Build category weights from selected goals
|
| 36 |
-
cat_scores = {}
|
| 37 |
-
for goal in selected_goals:
|
| 38 |
-
for cat_name, weight in GOAL_MAPPINGS.get(goal, []):
|
| 39 |
-
cat_scores[cat_name] = cat_scores.get(cat_name, 0) + weight
|
| 40 |
-
|
| 41 |
-
if not cat_scores:
|
| 42 |
-
return []
|
| 43 |
-
|
| 44 |
-
results = []
|
| 45 |
-
for pep_key, pep in PEPTIDES.items():
|
| 46 |
-
score = 0
|
| 47 |
-
matched = []
|
| 48 |
-
for cat in pep["categories"]:
|
| 49 |
-
if cat in cat_scores:
|
| 50 |
-
score += cat_scores[cat]
|
| 51 |
-
matched.append(cat)
|
| 52 |
-
if score > 0:
|
| 53 |
-
results.append((pep_key, score, matched))
|
| 54 |
-
|
| 55 |
-
# Sort: highest score first, then cheapest suggested-vial price as tiebreaker
|
| 56 |
-
results.sort(
|
| 57 |
-
key=lambda x: (
|
| 58 |
-
-x[1],
|
| 59 |
-
PEPTIDES[x[0]]["pricing"].get(PEPTIDES[x[0]]["suggested_vials"], 9999),
|
| 60 |
-
)
|
| 61 |
-
)
|
| 62 |
-
return results
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
def get_top_categories(selected_goals, limit=3):
|
| 66 |
-
"""Return the top N matched categories from selected goals."""
|
| 67 |
-
cat_scores = {}
|
| 68 |
-
for goal in selected_goals:
|
| 69 |
-
for cat_name, weight in GOAL_MAPPINGS.get(goal, []):
|
| 70 |
-
cat_scores[cat_name] = cat_scores.get(cat_name, 0) + weight
|
| 71 |
-
sorted_cats = sorted(cat_scores.items(), key=lambda x: -x[1])
|
| 72 |
-
return [c[0] for c in sorted_cats[:limit]]
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
def group_by_tier(scored_peptides):
|
| 76 |
-
"""Group scored peptides into branded blends, other blends, and singles."""
|
| 77 |
-
branded = []
|
| 78 |
-
blends = []
|
| 79 |
-
singles = []
|
| 80 |
-
for pep_key, score, matched in scored_peptides:
|
| 81 |
-
pep = PEPTIDES[pep_key]
|
| 82 |
-
if pep.get("brand_name"):
|
| 83 |
-
branded.append((pep_key, score, matched))
|
| 84 |
-
elif pep.get("is_blend"):
|
| 85 |
-
blends.append((pep_key, score, matched))
|
| 86 |
-
else:
|
| 87 |
-
singles.append((pep_key, score, matched))
|
| 88 |
-
return branded, blends, singles
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
# =============================================================================
|
| 92 |
-
# PDF GENERATOR
|
| 93 |
-
# =============================================================================
|
| 94 |
-
def generate_pdf(patient_info, selected_goals, top_categories, recommendations):
|
| 95 |
-
"""Generate a branded PDF of the peptide recommendation."""
|
| 96 |
-
buffer = BytesIO()
|
| 97 |
-
doc = SimpleDocTemplate(
|
| 98 |
-
buffer, pagesize=letter,
|
| 99 |
-
topMargin=0.55 * inch, bottomMargin=0.6 * inch,
|
| 100 |
-
leftMargin=0.65 * inch, rightMargin=0.65 * inch,
|
| 101 |
-
)
|
| 102 |
-
story = []
|
| 103 |
-
pw = letter[0] - 1.3 * inch
|
| 104 |
-
|
| 105 |
-
def ps(name, **kwargs):
|
| 106 |
-
return ParagraphStyle(name, **kwargs)
|
| 107 |
-
|
| 108 |
-
C_NAVY = colors.HexColor(NAVY)
|
| 109 |
-
C_TEAL = colors.HexColor(TEAL)
|
| 110 |
-
C_ORANGE = colors.HexColor(ORANGE)
|
| 111 |
-
C_AMBER = colors.HexColor(AMBER)
|
| 112 |
-
C_SKY = colors.HexColor(SKY)
|
| 113 |
-
C_LIGHT = colors.HexColor("#eef4f8")
|
| 114 |
-
C_CARD = colors.HexColor("#f0f6fa")
|
| 115 |
-
C_BORDER = colors.HexColor("#b8d0de")
|
| 116 |
-
|
| 117 |
-
brand_s = ps("Br", fontName="Helvetica-Bold", fontSize=9, textColor=C_ORANGE, spaceAfter=0, alignment=TA_CENTER)
|
| 118 |
-
title_s = ps("T", fontName="Helvetica-Bold", fontSize=20, leading=26, textColor=colors.white, spaceAfter=0, alignment=TA_CENTER)
|
| 119 |
-
sub_s = ps("Su", fontName="Helvetica", fontSize=10, textColor=C_SKY, spaceAfter=0, alignment=TA_CENTER)
|
| 120 |
-
meta_s = ps("Me", fontName="Helvetica", fontSize=9, leading=14, textColor=C_NAVY, spaceAfter=3)
|
| 121 |
-
h2_s = ps("H2", fontName="Helvetica-Bold", fontSize=11, textColor=colors.white, spaceBefore=0, spaceAfter=0)
|
| 122 |
-
h3_s = ps("H3", fontName="Helvetica-Bold", fontSize=10, textColor=C_NAVY, spaceBefore=4, spaceAfter=3)
|
| 123 |
-
body_s = ps("B", fontName="Helvetica", fontSize=9.5, leading=15, textColor=C_NAVY, spaceAfter=5)
|
| 124 |
-
bullet_s = ps("Bu", fontName="Helvetica", fontSize=9.5, leading=15, textColor=C_NAVY, leftIndent=14, spaceAfter=3)
|
| 125 |
-
disc_s = ps("Di", fontName="Helvetica-Oblique", fontSize=8, leading=12, textColor=colors.HexColor("#4a6070"), spaceAfter=4)
|
| 126 |
-
price_s = ps("Pr", fontName="Helvetica-Bold", fontSize=10, textColor=C_ORANGE, spaceAfter=2)
|
| 127 |
-
pep_hdr_s = ps("Ph", fontName="Helvetica-Bold", fontSize=10, textColor=colors.white, spaceBefore=0, spaceAfter=0)
|
| 128 |
-
ft_s = ps("Ft", fontName="Helvetica", fontSize=8, leading=12, textColor=C_NAVY, spaceAfter=0, alignment=TA_CENTER)
|
| 129 |
-
|
| 130 |
-
def banner(para, bg, line_below=None, pad_v=7, pad_h=12):
|
| 131 |
-
t = Table([[para]], colWidths=[pw])
|
| 132 |
-
cmds = [
|
| 133 |
-
("BACKGROUND", (0, 0), (-1, -1), bg),
|
| 134 |
-
("LEFTPADDING", (0, 0), (-1, -1), pad_h),
|
| 135 |
-
("RIGHTPADDING", (0, 0), (-1, -1), pad_h),
|
| 136 |
-
("TOPPADDING", (0, 0), (-1, -1), pad_v),
|
| 137 |
-
("BOTTOMPADDING", (0, 0), (-1, -1), pad_v),
|
| 138 |
-
]
|
| 139 |
-
if line_below:
|
| 140 |
-
cmds.append(("LINEBELOW", (0, 0), (-1, -1), 3, line_below))
|
| 141 |
-
t.setStyle(TableStyle(cmds))
|
| 142 |
-
return t
|
| 143 |
-
|
| 144 |
-
def section_hdr(text):
|
| 145 |
-
return KeepTogether([
|
| 146 |
-
Spacer(1, 8),
|
| 147 |
-
banner(Paragraph(text.upper(), h2_s), C_NAVY, line_below=C_ORANGE, pad_v=8),
|
| 148 |
-
Spacer(1, 6),
|
| 149 |
-
])
|
| 150 |
-
|
| 151 |
-
# ── COVER ────────────────────────────────────────────────────────────────
|
| 152 |
-
hdr_tbl = Table([
|
| 153 |
-
[Paragraph("IGNITE PERFORMANCE & HEALTH", brand_s)],
|
| 154 |
-
[Paragraph("Peptide Protocol Recommendation", title_s)],
|
| 155 |
-
[Paragraph("Physician-Supervised Peptide Therapy", sub_s)],
|
| 156 |
-
], colWidths=[pw])
|
| 157 |
-
hdr_tbl.setStyle(TableStyle([
|
| 158 |
-
("BACKGROUND", (0, 0), (-1, -1), C_NAVY),
|
| 159 |
-
("LEFTPADDING", (0, 0), (-1, -1), 20),
|
| 160 |
-
("RIGHTPADDING", (0, 0), (-1, -1), 20),
|
| 161 |
-
("TOPPADDING", (0, 0), (0, 0), 14),
|
| 162 |
-
("BOTTOMPADDING", (0, 0), (0, 0), 4),
|
| 163 |
-
("TOPPADDING", (0, 1), (0, 1), 4),
|
| 164 |
-
("BOTTOMPADDING", (0, 1), (0, 1), 4),
|
| 165 |
-
("TOPPADDING", (0, 2), (0, 2), 2),
|
| 166 |
-
("BOTTOMPADDING", (0, 2), (0, 2), 14),
|
| 167 |
-
("ALIGN", (0, 0), (-1, -1), "CENTER"),
|
| 168 |
-
]))
|
| 169 |
-
story.append(hdr_tbl)
|
| 170 |
-
story.append(HRFlowable(width="100%", thickness=5, color=C_ORANGE, spaceAfter=12))
|
| 171 |
-
|
| 172 |
-
# ── Patient info ──
|
| 173 |
-
name = patient_info.get("name", "")
|
| 174 |
-
date_str = datetime.now().strftime("%B %d, %Y")
|
| 175 |
-
pi_tbl = Table([[
|
| 176 |
-
Paragraph(f"<b>Prepared for:</b> {name}", meta_s),
|
| 177 |
-
Paragraph(f"<b>Date:</b> {date_str}", meta_s),
|
| 178 |
-
]], colWidths=[pw * 0.6, pw * 0.4])
|
| 179 |
-
pi_tbl.setStyle(TableStyle([
|
| 180 |
-
("BACKGROUND", (0, 0), (-1, -1), C_LIGHT),
|
| 181 |
-
("BOX", (0, 0), (-1, -1), 0.75, C_BORDER),
|
| 182 |
-
("LINEBELOW", (0, 0), (-1, -1), 2, C_ORANGE),
|
| 183 |
-
("LEFTPADDING", (0, 0), (-1, -1), 10),
|
| 184 |
-
("RIGHTPADDING", (0, 0), (-1, -1), 10),
|
| 185 |
-
("TOPPADDING", (0, 0), (-1, -1), 9),
|
| 186 |
-
("BOTTOMPADDING", (0, 0), (-1, -1), 9),
|
| 187 |
-
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
|
| 188 |
-
]))
|
| 189 |
-
story.append(pi_tbl)
|
| 190 |
-
story.append(Spacer(1, 14))
|
| 191 |
-
|
| 192 |
-
# ── Goals & Categories ──
|
| 193 |
-
story.append(section_hdr("Your Goals"))
|
| 194 |
-
goals_text = " · ".join(selected_goals)
|
| 195 |
-
story.append(Paragraph(f"<b>Selected goals:</b> {goals_text}", body_s))
|
| 196 |
-
cats_text = " · ".join(top_categories)
|
| 197 |
-
story.append(Paragraph(f"<b>Top therapeutic areas:</b> {cats_text}", body_s))
|
| 198 |
-
story.append(Spacer(1, 8))
|
| 199 |
-
|
| 200 |
-
# ── Recommendations ──
|
| 201 |
-
story.append(section_hdr("Recommended Peptides"))
|
| 202 |
-
|
| 203 |
-
branded, blends, singles = group_by_tier(recommendations)
|
| 204 |
-
|
| 205 |
-
def render_pep_block(pep_key, score, matched):
|
| 206 |
-
pep = PEPTIDES[pep_key]
|
| 207 |
-
display_name = pep.get("brand_name") or pep_key
|
| 208 |
-
sv = pep["suggested_vials"]
|
| 209 |
-
sv_price = pep["pricing"].get(sv, 0)
|
| 210 |
-
per_week = sv_price / max((sv * 4), 1) # rough per-week estimate
|
| 211 |
-
|
| 212 |
-
items = [
|
| 213 |
-
Spacer(1, 4),
|
| 214 |
-
banner(Paragraph(f" {display_name}", pep_hdr_s), C_TEAL, line_below=C_NAVY, pad_v=5),
|
| 215 |
-
]
|
| 216 |
-
if pep.get("brand_name") and pep_key != pep["brand_name"]:
|
| 217 |
-
items.append(Paragraph(f"<i>{pep_key}</i>", disc_s))
|
| 218 |
-
items.append(Paragraph(pep["description"], body_s))
|
| 219 |
-
items.append(Paragraph(
|
| 220 |
-
f"<b>Dosage:</b> {pep['dosage']} | <b>Cycle:</b> {pep['cycle']} | "
|
| 221 |
-
f"<b>Suggested vials:</b> {sv}", bullet_s))
|
| 222 |
-
items.append(Paragraph(
|
| 223 |
-
f"<b>Suggested cycle cost:</b> ${sv_price:,.2f} ({sv} vials) | "
|
| 224 |
-
f"<b>~${per_week:,.2f}/week</b>", price_s))
|
| 225 |
-
items.append(Paragraph(
|
| 226 |
-
f"Categories: {', '.join(matched)}", disc_s))
|
| 227 |
-
items.append(Spacer(1, 6))
|
| 228 |
-
return KeepTogether(items)
|
| 229 |
-
|
| 230 |
-
if branded:
|
| 231 |
-
story.append(Paragraph("<b>Ignite Branded Blends</b> — curated multi-peptide protocols", h3_s))
|
| 232 |
-
for pep_key, score, matched in branded[:6]:
|
| 233 |
-
story.append(render_pep_block(pep_key, score, matched))
|
| 234 |
-
|
| 235 |
-
if blends:
|
| 236 |
-
story.append(Paragraph("<b>Combination Blends</b>", h3_s))
|
| 237 |
-
for pep_key, score, matched in blends[:4]:
|
| 238 |
-
story.append(render_pep_block(pep_key, score, matched))
|
| 239 |
-
|
| 240 |
-
if singles:
|
| 241 |
-
story.append(Paragraph("<b>Individual Peptides</b>", h3_s))
|
| 242 |
-
for pep_key, score, matched in singles[:4]:
|
| 243 |
-
story.append(render_pep_block(pep_key, score, matched))
|
| 244 |
-
|
| 245 |
-
# ── Disclaimer ──
|
| 246 |
-
story.append(Spacer(1, 12))
|
| 247 |
-
story.append(HRFlowable(width="100%", thickness=1, color=C_BORDER, spaceAfter=8))
|
| 248 |
-
story.append(Paragraph(BRAND["disclaimer"], disc_s))
|
| 249 |
-
story.append(Spacer(1, 6))
|
| 250 |
-
|
| 251 |
-
# ── Footer ──
|
| 252 |
-
story.append(HRFlowable(width="100%", thickness=3, color=C_ORANGE, spaceAfter=0))
|
| 253 |
-
ft_brand_s = ps("Fb", fontName="Helvetica-Bold", fontSize=10, textColor=C_ORANGE, spaceAfter=1, alignment=TA_CENTER)
|
| 254 |
-
ft_info_s = ps("Fi", fontName="Helvetica", fontSize=8, leading=12, textColor=colors.white, spaceAfter=1, alignment=TA_CENTER)
|
| 255 |
-
ft_web_s = ps("Fw", fontName="Helvetica-Bold", fontSize=8, textColor=C_SKY, spaceAfter=0, alignment=TA_CENTER)
|
| 256 |
-
|
| 257 |
-
ft_tbl = Table([
|
| 258 |
-
[Paragraph("IGNITE PERFORMANCE & HEALTH", ft_brand_s)],
|
| 259 |
-
[Paragraph("14830 Clayton Rd, Chesterfield, MO 63017 | (314) 887-0858 | info@ignitepah.com", ft_info_s)],
|
| 260 |
-
[Paragraph("www.ignitepah.com", ft_web_s)],
|
| 261 |
-
], colWidths=[pw])
|
| 262 |
-
ft_tbl.setStyle(TableStyle([
|
| 263 |
-
("BACKGROUND", (0, 0), (-1, -1), C_NAVY),
|
| 264 |
-
("TOPPADDING", (0, 0), (0, 0), 8),
|
| 265 |
-
("BOTTOMPADDING", (0, -1), (0, -1), 8),
|
| 266 |
-
("TOPPADDING", (0, 1), (-1, -1), 1),
|
| 267 |
-
("BOTTOMPADDING", (0, 1), (-1, -1), 1),
|
| 268 |
-
("ALIGN", (0, 0), (-1, -1), "CENTER"),
|
| 269 |
-
]))
|
| 270 |
-
story.append(ft_tbl)
|
| 271 |
-
|
| 272 |
-
doc.build(story)
|
| 273 |
-
buffer.seek(0)
|
| 274 |
-
return buffer
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
# =============================================================================
|
| 278 |
-
# FOOTER RENDERER
|
| 279 |
-
# =============================================================================
|
| 280 |
-
def render_footer():
|
| 281 |
-
st.markdown(
|
| 282 |
-
'<div class="site-footer">'
|
| 283 |
-
f'{"<img class=footer-logo src=" + chr(34) + LOGO_B64 + chr(34) + " alt=Ignite />" if LOGO_B64 else ""}'
|
| 284 |
-
'<p class="brand">Ignite Performance & Health</p>'
|
| 285 |
-
'<p class="tagline">Physician-Supervised Peptide Therapy</p>'
|
| 286 |
-
'<div style="width:60px;height:2px;background:#f58300;margin:14px auto;border-radius:2px;"></div>'
|
| 287 |
-
'<p class="contact-label">Visit Us</p>'
|
| 288 |
-
'<p class="contact-row">14830 Clayton Rd, Chesterfield, MO 63017</p>'
|
| 289 |
-
'<p class="contact-label">Get In Touch</p>'
|
| 290 |
-
'<p class="contact-row">'
|
| 291 |
-
'<a href="tel:3148870858">(314) 887-0858</a>'
|
| 292 |
-
'<span class="sep">|</span>'
|
| 293 |
-
'<a href="mailto:info@ignitepah.com">info@ignitepah.com</a>'
|
| 294 |
-
'</p>'
|
| 295 |
-
'<p class="contact-row"><a href="https://ignitepah.com" target="_blank">www.ignitepah.com</a></p>'
|
| 296 |
-
'<p class="disclaimer">'
|
| 297 |
-
f'{BRAND["disclaimer"]}'
|
| 298 |
-
'</p>'
|
| 299 |
-
'</div>',
|
| 300 |
-
unsafe_allow_html=True,
|
| 301 |
-
)
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
# =============================================================================
|
| 305 |
-
# PAGE CONFIG & CSS
|
| 306 |
-
# =============================================================================
|
| 307 |
-
st.set_page_config(
|
| 308 |
-
page_title="Ignite Performance & Health — Peptide Protocol Selector",
|
| 309 |
-
layout="wide",
|
| 310 |
-
initial_sidebar_state="collapsed",
|
| 311 |
-
)
|
| 312 |
-
|
| 313 |
-
st.markdown(
|
| 314 |
-
'<link rel="preconnect" href="https://fonts.googleapis.com">'
|
| 315 |
-
'<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>'
|
| 316 |
-
'<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Barlow:ital,wght@0,400;0,600;0,700;0,800;1,700&family=Barlow+Condensed:wght@700;800&display=swap">',
|
| 317 |
-
unsafe_allow_html=True,
|
| 318 |
-
)
|
| 319 |
-
|
| 320 |
-
st.markdown("""
|
| 321 |
-
<style>
|
| 322 |
-
.stApp { background-color: #f0f6fa; }
|
| 323 |
-
html, body, [class*="css"] { font-family: 'Barlow', 'Segoe UI', sans-serif; }
|
| 324 |
-
.stMarkdown p, .stMarkdown span,
|
| 325 |
-
div[data-testid="stMarkdownContainer"] p,
|
| 326 |
-
div[data-testid="stMarkdownContainer"] span,
|
| 327 |
-
div[data-testid="stMarkdownContainer"] li,
|
| 328 |
-
.stRadio label, .stRadio span,
|
| 329 |
-
.stCheckbox label, .stCheckbox span,
|
| 330 |
-
.stNumberInput label, .stTextInput label, .stTextArea label,
|
| 331 |
-
.stCaption, .stCaption p, .stForm p, .stForm span, .stForm label { color: #023047 !important; }
|
| 332 |
-
div[data-testid="stMarkdownContainer"] .site-footer p,
|
| 333 |
-
div[data-testid="stMarkdownContainer"] .site-footer span,
|
| 334 |
-
div[data-testid="stMarkdownContainer"] .site-footer a,
|
| 335 |
-
.stMarkdown .site-footer p, .stMarkdown .site-footer a { color: #ffffff !important; }
|
| 336 |
-
div[data-testid="stMarkdownContainer"] .site-footer .brand,
|
| 337 |
-
.stMarkdown .site-footer .brand { color: #f58300 !important; }
|
| 338 |
-
div[data-testid="stMarkdownContainer"] .site-footer .tagline,
|
| 339 |
-
.stMarkdown .site-footer .tagline { color: #8ecae6 !important; }
|
| 340 |
-
div[data-testid="stMarkdownContainer"] .site-footer .disclaimer,
|
| 341 |
-
.stMarkdown .site-footer .disclaimer { color: rgba(255,255,255,0.7) !important; }
|
| 342 |
-
.stTextInput input, .stNumberInput input, .stTextArea textarea {
|
| 343 |
-
color: #023047 !important; background-color: #ffffff !important;
|
| 344 |
-
border: 1px solid #ccdde8 !important; border-radius: 6px !important;
|
| 345 |
-
}
|
| 346 |
-
.stTextInput input:focus, .stNumberInput input:focus, .stTextArea textarea:focus {
|
| 347 |
-
border-color: #219ebc !important; box-shadow: 0 0 0 2px rgba(33,158,188,0.2) !important;
|
| 348 |
-
}
|
| 349 |
-
|
| 350 |
-
/* ── Hero ── */
|
| 351 |
-
.hero {
|
| 352 |
-
background: linear-gradient(135deg, #023047 60%, #219ebc 100%);
|
| 353 |
-
border-radius: 12px; padding: 2.2rem 2.5rem 1.8rem; margin-bottom: 1.5rem;
|
| 354 |
-
text-align: center;
|
| 355 |
-
}
|
| 356 |
-
.hero-logo { display: block; margin: 0 auto 18px; height: 130px; width: auto; }
|
| 357 |
-
.hero-title { color: #f58300 !important; font-family: 'Barlow Condensed', sans-serif; font-size: 2.2rem; font-weight: 800; margin: 0 0 4px; letter-spacing: 1px; text-transform: uppercase; }
|
| 358 |
-
.hero-sub { color: #8ecae6 !important; font-size: 1.1rem; font-weight: 600; margin: 8px 0 0; }
|
| 359 |
-
.hero-tagline { color: rgba(255,255,255,0.85) !important; font-size: 0.85rem; margin: 8px 0 0; letter-spacing: 0.5px; }
|
| 360 |
-
|
| 361 |
-
/* ── Section bands ── */
|
| 362 |
-
.section-band {
|
| 363 |
-
background: linear-gradient(90deg, #023047 0%, #012233 100%);
|
| 364 |
-
color: #ffc533 !important; font-family: 'Barlow Condensed', sans-serif;
|
| 365 |
-
font-size: 1.1rem; font-weight: 800; letter-spacing: 1.2px;
|
| 366 |
-
padding: 12px 16px; margin: 20px 0 14px;
|
| 367 |
-
border-radius: 4px; text-transform: uppercase;
|
| 368 |
-
}
|
| 369 |
-
|
| 370 |
-
/* ── Form & buttons ── */
|
| 371 |
-
.stForm { background: white; padding: 20px; border-radius: 8px; }
|
| 372 |
-
.stFormSubmitButton > button,
|
| 373 |
-
.stFormSubmitButton > button:active,
|
| 374 |
-
.stFormSubmitButton > button:focus {
|
| 375 |
-
background: linear-gradient(135deg, #f58300 0%, #ffc533 100%) !important;
|
| 376 |
-
color: #023047 !important; font-size: 1rem; font-weight: 700;
|
| 377 |
-
letter-spacing: 0.5px; padding: 12px 24px; border-radius: 6px !important;
|
| 378 |
-
border: none !important; box-shadow: 0 4px 12px rgba(245,131,0,0.25) !important;
|
| 379 |
-
}
|
| 380 |
-
.stFormSubmitButton > button p,
|
| 381 |
-
.stFormSubmitButton > button span { color: #023047 !important; font-weight: 700 !important; }
|
| 382 |
-
.stFormSubmitButton > button:hover {
|
| 383 |
-
background: linear-gradient(135deg, #ffc533 0%, #f58300 100%) !important;
|
| 384 |
-
}
|
| 385 |
-
.stDownloadButton > button,
|
| 386 |
-
.stDownloadButton > button:active,
|
| 387 |
-
.stDownloadButton > button:focus {
|
| 388 |
-
background: linear-gradient(135deg, #f58300 0%, #ffc533 100%) !important;
|
| 389 |
-
color: #023047 !important; font-size: 0.9rem; font-weight: 700;
|
| 390 |
-
padding: 10px 20px; border-radius: 6px !important;
|
| 391 |
-
border: none !important; box-shadow: 0 2px 8px rgba(245,131,0,0.25) !important;
|
| 392 |
-
width: 100%;
|
| 393 |
-
}
|
| 394 |
-
.stDownloadButton > button p,
|
| 395 |
-
.stDownloadButton > button span { color: #023047 !important; font-weight: 700 !important; }
|
| 396 |
-
.stDownloadButton > button:hover {
|
| 397 |
-
background: linear-gradient(135deg, #ffc533 0%, #f58300 100%) !important;
|
| 398 |
-
}
|
| 399 |
-
.stButton > button,
|
| 400 |
-
.stButton > button:active,
|
| 401 |
-
.stButton > button:focus {
|
| 402 |
-
background: linear-gradient(135deg, #f58300 0%, #ffc533 100%) !important;
|
| 403 |
-
color: #023047 !important; font-size: 1rem; font-weight: 700;
|
| 404 |
-
padding: 12px 24px; border-radius: 6px !important;
|
| 405 |
-
border: none !important; box-shadow: 0 4px 12px rgba(245,131,0,0.25) !important;
|
| 406 |
-
}
|
| 407 |
-
.stButton > button p,
|
| 408 |
-
.stButton > button span { color: #023047 !important; font-weight: 700 !important; }
|
| 409 |
-
.stButton > button:hover {
|
| 410 |
-
background: linear-gradient(135deg, #ffc533 0%, #f58300 100%) !important;
|
| 411 |
-
}
|
| 412 |
-
|
| 413 |
-
/* ── Peptide cards ── */
|
| 414 |
-
.pep-card {
|
| 415 |
-
background: white; border: 1px solid #b8d0de; border-radius: 8px;
|
| 416 |
-
padding: 16px; margin-bottom: 16px;
|
| 417 |
-
}
|
| 418 |
-
.pep-card-branded {
|
| 419 |
-
background: white; border: 2px solid #219ebc; border-radius: 8px;
|
| 420 |
-
padding: 16px; margin-bottom: 16px;
|
| 421 |
-
}
|
| 422 |
-
.pep-badge {
|
| 423 |
-
display: inline-block; background: #219ebc; color: white;
|
| 424 |
-
font-size: 0.7rem; font-weight: 700; padding: 4px 8px;
|
| 425 |
-
border-radius: 3px; margin-bottom: 8px; letter-spacing: 0.5px;
|
| 426 |
-
}
|
| 427 |
-
.pep-badge-branded {
|
| 428 |
-
display: inline-block; background: #f58300; color: white;
|
| 429 |
-
font-size: 0.7rem; font-weight: 700; padding: 4px 8px;
|
| 430 |
-
border-radius: 3px; margin-bottom: 8px; letter-spacing: 0.5px;
|
| 431 |
-
}
|
| 432 |
-
.pep-name {
|
| 433 |
-
color: #023047 !important; font-family: 'Barlow Condensed', sans-serif;
|
| 434 |
-
font-size: 1.3rem; font-weight: 800; margin-bottom: 2px;
|
| 435 |
-
}
|
| 436 |
-
.pep-components {
|
| 437 |
-
color: #219ebc !important; font-size: 0.85rem; font-style: italic; margin-bottom: 8px;
|
| 438 |
-
}
|
| 439 |
-
.pep-desc {
|
| 440 |
-
color: #4a6070 !important; font-size: 0.9rem; margin-bottom: 14px; line-height: 1.5;
|
| 441 |
-
}
|
| 442 |
-
.pep-label {
|
| 443 |
-
color: #f58300 !important; font-weight: 700; font-size: 0.75rem;
|
| 444 |
-
text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px;
|
| 445 |
-
}
|
| 446 |
-
.pep-value {
|
| 447 |
-
color: #023047 !important; font-size: 0.9rem; margin-bottom: 10px;
|
| 448 |
-
}
|
| 449 |
-
.pep-cats {
|
| 450 |
-
display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 12px;
|
| 451 |
-
}
|
| 452 |
-
.cat-pill {
|
| 453 |
-
background: #219ebc; color: #ffffff !important;
|
| 454 |
-
font-size: 0.72rem; font-weight: 700; padding: 4px 10px;
|
| 455 |
-
border-radius: 12px; white-space: nowrap;
|
| 456 |
-
}
|
| 457 |
-
|
| 458 |
-
/* ── Pricing grid ── */
|
| 459 |
-
.pricing-grid {
|
| 460 |
-
display: grid; gap: 8px; margin-top: 10px;
|
| 461 |
-
}
|
| 462 |
-
.price-cell {
|
| 463 |
-
background: #f0f6fa; border: 1px solid #b8d0de; border-radius: 6px;
|
| 464 |
-
padding: 10px 8px; text-align: center;
|
| 465 |
-
}
|
| 466 |
-
.price-cell-suggested {
|
| 467 |
-
background: linear-gradient(135deg, #fff8ec 0%, #fff0d6 100%); border: 2.5px solid #f58300; border-radius: 6px;
|
| 468 |
-
padding: 12px 8px; text-align: center; box-shadow: 0 2px 8px rgba(245,131,0,0.2);
|
| 469 |
-
position: relative;
|
| 470 |
-
}
|
| 471 |
-
.price-vials { color: #023047 !important; font-weight: 700; font-size: 0.8rem; margin-bottom: 2px; }
|
| 472 |
-
.price-amount { color: #f58300 !important; font-family: 'Barlow Condensed', sans-serif; font-size: 1.55rem; font-weight: 800; }
|
| 473 |
-
.price-per-week { color: #023047 !important; font-size: 0.72rem; font-weight: 600; margin-top: 2px; }
|
| 474 |
-
.price-suggested-label { background: #f58300; color: #ffffff !important; font-size: 0.65rem; font-weight: 700; text-transform: uppercase; padding: 2px 6px; border-radius: 3px; display: inline-block; margin-top: 4px; }
|
| 475 |
-
|
| 476 |
-
/* ── Styled HR ── */
|
| 477 |
-
.styled-hr { margin: 2rem 0; border: 0; height: 2px; background: linear-gradient(90deg, transparent, #f58300, transparent); }
|
| 478 |
-
|
| 479 |
-
/* ── CTA box ── */
|
| 480 |
-
.cta-box {
|
| 481 |
-
background: linear-gradient(135deg, #023047 0%, #034a6e 100%);
|
| 482 |
-
border: 2px solid #f58300; border-radius: 8px; padding: 24px;
|
| 483 |
-
margin: 24px 0; color: #ffffff !important;
|
| 484 |
-
}
|
| 485 |
-
.cta-box, .cta-box * { color: #ffffff !important; }
|
| 486 |
-
.cta-box h3 { color: #ffc533 !important; font-family: 'Barlow Condensed', sans-serif; font-size: 1.4rem; font-weight: 800; margin-bottom: 12px; }
|
| 487 |
-
.cta-box p { color: #ffffff !important; line-height: 1.6; margin-bottom: 10px; }
|
| 488 |
-
.cta-box strong { color: #ffc533 !important; }
|
| 489 |
-
.cta-link {
|
| 490 |
-
display: inline-block; background: #f58300; color: #ffffff !important;
|
| 491 |
-
font-weight: 700; padding: 12px 20px; border-radius: 6px;
|
| 492 |
-
text-decoration: none; margin-top: 14px; letter-spacing: 0.5px;
|
| 493 |
-
}
|
| 494 |
-
.cta-link:hover { background: #ffc533; color: #023047 !important; }
|
| 495 |
-
|
| 496 |
-
/* ── Site footer ── */
|
| 497 |
-
.site-footer {
|
| 498 |
-
background: #023047; color: #ffffff !important; padding: 32px 24px; text-align: center;
|
| 499 |
-
margin-top: 40px; border-top: 4px solid #f58300;
|
| 500 |
-
}
|
| 501 |
-
.site-footer, .site-footer * { color: #ffffff !important; }
|
| 502 |
-
.site-footer .brand { color: #f58300 !important; font-family: 'Barlow Condensed', sans-serif; font-size: 1.3rem; font-weight: 800; margin: 8px 0 4px; letter-spacing: 1px; }
|
| 503 |
-
.site-footer .tagline { color: #8ecae6 !important; font-size: 0.85rem; margin-bottom: 14px; }
|
| 504 |
-
.site-footer .contact-label { color: #ffc533 !important; font-weight: 700; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.5px; margin-top: 12px; margin-bottom: 4px; }
|
| 505 |
-
.site-footer .contact-row { color: white !important; font-size: 0.95rem; margin-bottom: 4px; line-height: 1.6; }
|
| 506 |
-
.site-footer .contact-row a { color: white !important; text-decoration: none; }
|
| 507 |
-
.site-footer .contact-row a:hover { color: #ffc533 !important; }
|
| 508 |
-
.site-footer .sep { color: #8ecae6 !important; margin: 0 10px; }
|
| 509 |
-
.site-footer .disclaimer { color: rgba(255,255,255,0.7) !important; font-size: 0.8rem; margin-top: 16px; line-height: 1.6; }
|
| 510 |
-
|
| 511 |
-
/* ── Mobile ── */
|
| 512 |
-
@media (max-width: 768px) {
|
| 513 |
-
.hero { padding: 1.5rem 1.2rem 1.2rem; }
|
| 514 |
-
.hero-logo { height: 90px; }
|
| 515 |
-
.hero-title { font-size: 1.6rem !important; }
|
| 516 |
-
.pep-name { font-size: 1.1rem; }
|
| 517 |
-
.pricing-grid { grid-template-columns: repeat(3, 1fr) !important; }
|
| 518 |
-
.price-amount { font-size: 1.2rem; }
|
| 519 |
-
}
|
| 520 |
-
@media (max-width: 480px) {
|
| 521 |
-
.hero-title { font-size: 1.3rem !important; }
|
| 522 |
-
.pricing-grid { grid-template-columns: repeat(2, 1fr) !important; }
|
| 523 |
-
}
|
| 524 |
-
</style>
|
| 525 |
-
""", unsafe_allow_html=True)
|
| 526 |
-
|
| 527 |
-
|
| 528 |
-
# =============================================================================
|
| 529 |
-
# SESSION STATE
|
| 530 |
-
# =============================================================================
|
| 531 |
-
if "pep_results" not in st.session_state:
|
| 532 |
-
st.session_state["pep_results"] = None
|
| 533 |
-
st.session_state["pep_goals"] = []
|
| 534 |
-
st.session_state["pep_top_cats"] = []
|
| 535 |
-
st.session_state["pep_patient_name"] = ""
|
| 536 |
-
st.session_state["pep_patient_email"] = ""
|
| 537 |
-
st.session_state["pep_patient_phone"] = ""
|
| 538 |
-
|
| 539 |
-
|
| 540 |
-
# =============================================================================
|
| 541 |
-
# RESULTS PAGE
|
| 542 |
-
# =============================================================================
|
| 543 |
-
if st.session_state["pep_results"]:
|
| 544 |
-
pname = st.session_state.get("pep_patient_name", "")
|
| 545 |
-
pemail = st.session_state.get("pep_patient_email", "")
|
| 546 |
-
pphone = st.session_state.get("pep_patient_phone", "")
|
| 547 |
-
selected_goals = st.session_state.get("pep_goals", [])
|
| 548 |
-
top_cats = st.session_state.get("pep_top_cats", [])
|
| 549 |
-
results = st.session_state["pep_results"]
|
| 550 |
-
|
| 551 |
-
# ── Hero ──
|
| 552 |
-
st.markdown(
|
| 553 |
-
'<div class="hero">'
|
| 554 |
-
f'{"<img class=hero-logo src=" + chr(34) + LOGO_B64 + chr(34) + " alt=Ignite />" if LOGO_B64 else ""}'
|
| 555 |
-
'<div class="hero-title">Peptide Protocol Selector</div>'
|
| 556 |
-
'<div class="hero-sub">Your Personalized Peptide Recommendations</div>'
|
| 557 |
-
f'<div class="hero-tagline">Prepared for {pname}</div>'
|
| 558 |
-
'</div>',
|
| 559 |
-
unsafe_allow_html=True,
|
| 560 |
-
)
|
| 561 |
-
|
| 562 |
-
# ── Goals summary ──
|
| 563 |
-
st.markdown('<div class="section-band">Your Health Goals</div>', unsafe_allow_html=True)
|
| 564 |
-
goals_html = " · ".join(selected_goals)
|
| 565 |
-
st.markdown(f'<p style="color:#023047;font-size:0.95rem;margin-bottom:8px;">{goals_html}</p>', unsafe_allow_html=True)
|
| 566 |
-
cats_html = " ".join(f'<span class="cat-pill">{c}</span>' for c in top_cats)
|
| 567 |
-
st.markdown(f'<div style="display:flex;gap:6px;flex-wrap:wrap;margin-bottom:20px;">{cats_html}</div>', unsafe_allow_html=True)
|
| 568 |
-
|
| 569 |
-
# ── Group and display ──
|
| 570 |
-
branded, blends, singles = group_by_tier(results)
|
| 571 |
-
|
| 572 |
-
def render_card(pep_key, score, matched, is_branded=False):
|
| 573 |
-
pep = PEPTIDES[pep_key]
|
| 574 |
-
display_name = pep.get("brand_name") or pep_key
|
| 575 |
-
card_class = "pep-card-branded" if is_branded else "pep-card"
|
| 576 |
-
badge_class = "pep-badge-branded" if is_branded else "pep-badge"
|
| 577 |
-
# Only show badge on singles and non-branded blends
|
| 578 |
-
show_badge = not is_branded
|
| 579 |
-
badge_text = "BLEND" if pep["is_blend"] else "SINGLE"
|
| 580 |
-
|
| 581 |
-
components_str = " + ".join(pep["components"]) if pep["is_blend"] else ""
|
| 582 |
-
cat_pills = " ".join(f'<span class="cat-pill">{c}</span>' for c in matched)
|
| 583 |
-
|
| 584 |
-
sv = pep["suggested_vials"]
|
| 585 |
-
price_cells = ""
|
| 586 |
-
for vials in range(1, 7):
|
| 587 |
-
price = pep["pricing"][vials]
|
| 588 |
-
is_suggested = vials == sv
|
| 589 |
-
cell_class = "price-cell-suggested" if is_suggested else "price-cell"
|
| 590 |
-
suggested_label = '<div class="price-suggested-label">✓ Recommended</div>' if is_suggested else ""
|
| 591 |
-
per_week = price / max(vials * 4, 1)
|
| 592 |
-
price_cells += (
|
| 593 |
-
f'<div class="{cell_class}">'
|
| 594 |
-
f'<div class="price-vials">{vials} Vial{"s" if vials > 1 else ""}</div>'
|
| 595 |
-
f'<div class="price-amount">${price:,.2f}</div>'
|
| 596 |
-
f'<div class="price-per-week">${per_week:,.2f}/wk</div>'
|
| 597 |
-
f'{suggested_label}'
|
| 598 |
-
f'</div>'
|
| 599 |
-
)
|
| 600 |
-
|
| 601 |
-
badge_html = f'<div class="{badge_class}">{badge_text}</div>' if show_badge else ""
|
| 602 |
-
st.markdown(
|
| 603 |
-
f'<div class="{card_class}">'
|
| 604 |
-
f'{badge_html}'
|
| 605 |
-
f'<div class="pep-name">{display_name}</div>'
|
| 606 |
-
f'{"<div class=pep-components>" + components_str + "</div>" if components_str else ""}'
|
| 607 |
-
f'<div class="pep-desc">{pep["description"]}</div>'
|
| 608 |
-
f'<div class="pep-cats">{cat_pills}</div>'
|
| 609 |
-
f'<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:12px;">'
|
| 610 |
-
f'<div>'
|
| 611 |
-
f'<div class="pep-label">Dosage</div>'
|
| 612 |
-
f'<div class="pep-value">{pep["dosage"]}</div>'
|
| 613 |
-
f'</div>'
|
| 614 |
-
f'<div>'
|
| 615 |
-
f'<div class="pep-label">Cycle</div>'
|
| 616 |
-
f'<div class="pep-value">{pep["cycle"]}</div>'
|
| 617 |
-
f'</div>'
|
| 618 |
-
f'</div>'
|
| 619 |
-
f'<div class="pep-label">Vial Amount: {pep["vial_amount"]} | Source: {pep["source"]}</div>'
|
| 620 |
-
f'<div class="pep-label" style="margin-top:10px;">Pricing</div>'
|
| 621 |
-
f'<div class="pricing-grid" style="grid-template-columns:repeat(6,1fr)">{price_cells}</div>'
|
| 622 |
-
f'</div>',
|
| 623 |
-
unsafe_allow_html=True,
|
| 624 |
-
)
|
| 625 |
-
|
| 626 |
-
if branded:
|
| 627 |
-
st.markdown('<div class="section-band">Ignite Branded Blends — Recommended</div>', unsafe_allow_html=True)
|
| 628 |
-
st.markdown(
|
| 629 |
-
'<p style="color:#4a6070;font-size:0.9rem;margin-bottom:16px;">'
|
| 630 |
-
'Curated multi-peptide protocols from our proprietary blend line. These combine complementary peptides '
|
| 631 |
-
'into a single vial for convenience and synergistic benefit.</p>',
|
| 632 |
-
unsafe_allow_html=True,
|
| 633 |
-
)
|
| 634 |
-
for pep_key, score, matched in branded[:6]:
|
| 635 |
-
render_card(pep_key, score, matched, is_branded=True)
|
| 636 |
-
|
| 637 |
-
if blends:
|
| 638 |
-
st.markdown('<div class="section-band">Combination Blends</div>', unsafe_allow_html=True)
|
| 639 |
-
for pep_key, score, matched in blends[:4]:
|
| 640 |
-
render_card(pep_key, score, matched, is_branded=False)
|
| 641 |
-
|
| 642 |
-
if singles:
|
| 643 |
-
st.markdown('<div class="section-band">Individual Peptides</div>', unsafe_allow_html=True)
|
| 644 |
-
st.markdown(
|
| 645 |
-
'<p style="color:#4a6070;font-size:0.9rem;margin-bottom:16px;">'
|
| 646 |
-
'Single-peptide options for targeted protocols or for patients who prefer to build their stack one component at a time.</p>',
|
| 647 |
-
unsafe_allow_html=True,
|
| 648 |
-
)
|
| 649 |
-
for pep_key, score, matched in singles[:4]:
|
| 650 |
-
render_card(pep_key, score, matched, is_branded=False)
|
| 651 |
-
|
| 652 |
-
# ── CTA ──
|
| 653 |
-
st.markdown(
|
| 654 |
-
f'<div class="cta-box">'
|
| 655 |
-
f'<h3>Ready to Get Started, {pname}?</h3>'
|
| 656 |
-
f'<p>These recommendations are based on your stated goals. All peptide protocols require '
|
| 657 |
-
f'physician evaluation before we begin. Our team will walk through each option during your consultation, '
|
| 658 |
-
f'answer your questions, and build a protocol that fits your situation.</p>'
|
| 659 |
-
f'<p><strong>What happens next:</strong> our team reaches out, we schedule your consultation, '
|
| 660 |
-
f'and we go from there.</p>'
|
| 661 |
-
f'<p><strong>Contact:</strong> {pemail} | {pphone}</p>'
|
| 662 |
-
f'<a class="cta-link" href="{BRAND["scheduling_url"]}" target="_blank">Schedule Your Consultation</a>'
|
| 663 |
-
f'</div>',
|
| 664 |
-
unsafe_allow_html=True,
|
| 665 |
-
)
|
| 666 |
-
|
| 667 |
-
# ── PDF Download ──
|
| 668 |
-
st.markdown('<hr class="styled-hr">', unsafe_allow_html=True)
|
| 669 |
-
st.markdown("#### Download Your Recommendation")
|
| 670 |
-
|
| 671 |
-
if "cached_pep_pdf" not in st.session_state:
|
| 672 |
-
st.session_state["cached_pep_pdf"] = generate_pdf(
|
| 673 |
-
{"name": pname, "email": pemail, "phone": pphone},
|
| 674 |
-
selected_goals,
|
| 675 |
-
top_cats,
|
| 676 |
-
results,
|
| 677 |
-
)
|
| 678 |
-
|
| 679 |
-
st.download_button(
|
| 680 |
-
"Download PDF",
|
| 681 |
-
data=st.session_state["cached_pep_pdf"],
|
| 682 |
-
file_name="ignite_peptide_protocol.pdf",
|
| 683 |
-
mime="application/pdf",
|
| 684 |
-
use_container_width=True,
|
| 685 |
-
)
|
| 686 |
-
|
| 687 |
-
# ── Start over ──
|
| 688 |
-
st.markdown('<hr class="styled-hr">', unsafe_allow_html=True)
|
| 689 |
-
if st.button("Start New Selection", use_container_width=True):
|
| 690 |
-
for key in ["pep_results", "pep_goals", "pep_top_cats",
|
| 691 |
-
"pep_patient_name", "pep_patient_email", "pep_patient_phone",
|
| 692 |
-
"cached_pep_pdf"]:
|
| 693 |
-
if key in st.session_state:
|
| 694 |
-
del st.session_state[key]
|
| 695 |
-
st.session_state["pep_results"] = None
|
| 696 |
-
st.session_state["pep_goals"] = []
|
| 697 |
-
st.session_state["pep_top_cats"] = []
|
| 698 |
-
st.session_state["pep_patient_name"] = ""
|
| 699 |
-
st.session_state["pep_patient_email"] = ""
|
| 700 |
-
st.session_state["pep_patient_phone"] = ""
|
| 701 |
-
st.rerun()
|
| 702 |
-
|
| 703 |
-
render_footer()
|
| 704 |
-
st.stop()
|
| 705 |
-
|
| 706 |
-
|
| 707 |
-
# =============================================================================
|
| 708 |
-
# INTAKE FORM
|
| 709 |
-
# =============================================================================
|
| 710 |
-
st.markdown(
|
| 711 |
-
'<div class="hero">'
|
| 712 |
-
f'{"<img class=hero-logo src=" + chr(34) + LOGO_B64 + chr(34) + " alt=Ignite />" if LOGO_B64 else ""}'
|
| 713 |
-
'<div class="hero-title">Peptide Protocol Selector</div>'
|
| 714 |
-
'<div class="hero-sub">Find the Right Peptides for Your Goals</div>'
|
| 715 |
-
'<div class="hero-tagline">Physician-Supervised · Evidence-Informed · Personalized</div>'
|
| 716 |
-
'</div>',
|
| 717 |
-
unsafe_allow_html=True,
|
| 718 |
-
)
|
| 719 |
-
|
| 720 |
-
st.markdown(
|
| 721 |
-
"Select your health goals below and we will match you to the peptide protocols that fit best. "
|
| 722 |
-
"Every recommendation requires physician review before we proceed. "
|
| 723 |
-
"This is not a prescription tool — it is a starting point for your consultation.",
|
| 724 |
-
)
|
| 725 |
-
|
| 726 |
-
with st.form("peptide_intake"):
|
| 727 |
-
|
| 728 |
-
st.markdown('<div class="section-band">Your Information</div>', unsafe_allow_html=True)
|
| 729 |
-
c1, c2 = st.columns(2)
|
| 730 |
-
with c1:
|
| 731 |
-
first_name = st.text_input("First Name *")
|
| 732 |
-
email = st.text_input("Email Address *")
|
| 733 |
-
with c2:
|
| 734 |
-
last_name = st.text_input("Last Name *")
|
| 735 |
-
phone = st.text_input("Cell Phone *")
|
| 736 |
-
|
| 737 |
-
st.markdown('<div class="section-band">What Are You Looking to Address?</div>', unsafe_allow_html=True)
|
| 738 |
-
st.caption("Select all goals that apply. We will match peptides to your priorities.")
|
| 739 |
-
|
| 740 |
-
# Build flat goal list grouped by category
|
| 741 |
-
goal_checks = {}
|
| 742 |
-
cat_keys = list(CATEGORIES.keys())
|
| 743 |
-
mid = (len(cat_keys) + 1) // 2
|
| 744 |
-
|
| 745 |
-
c1, c2 = st.columns(2)
|
| 746 |
-
with c1:
|
| 747 |
-
for cat_name in cat_keys[:mid]:
|
| 748 |
-
cat = CATEGORIES[cat_name]
|
| 749 |
-
st.markdown(
|
| 750 |
-
f'<p style="color:#219ebc;font-weight:700;font-size:0.85rem;margin:12px 0 4px;text-transform:uppercase;">'
|
| 751 |
-
f'{cat_name}</p>',
|
| 752 |
-
unsafe_allow_html=True,
|
| 753 |
-
)
|
| 754 |
-
for goal in cat["goals"]:
|
| 755 |
-
goal_checks[goal] = st.checkbox(goal, key=f"g_{goal}")
|
| 756 |
-
with c2:
|
| 757 |
-
for cat_name in cat_keys[mid:]:
|
| 758 |
-
cat = CATEGORIES[cat_name]
|
| 759 |
-
st.markdown(
|
| 760 |
-
f'<p style="color:#219ebc;font-weight:700;font-size:0.85rem;margin:12px 0 4px;text-transform:uppercase;">'
|
| 761 |
-
f'{cat_name}</p>',
|
| 762 |
-
unsafe_allow_html=True,
|
| 763 |
-
)
|
| 764 |
-
for goal in cat["goals"]:
|
| 765 |
-
goal_checks[goal] = st.checkbox(goal, key=f"g_{goal}")
|
| 766 |
-
|
| 767 |
-
st.markdown('<div class="section-band">Additional Context</div>', unsafe_allow_html=True)
|
| 768 |
-
additional = st.text_area(
|
| 769 |
-
"Anything else we should know? (injuries, current medications, previous peptide experience, etc.)",
|
| 770 |
-
placeholder="Optional — helps our physician team customize your protocol.",
|
| 771 |
-
)
|
| 772 |
-
|
| 773 |
-
# ── Consent ──
|
| 774 |
-
st.markdown('<div class="section-band">Consent & Privacy</div>', unsafe_allow_html=True)
|
| 775 |
-
st.markdown(
|
| 776 |
-
'<p style="color:#023047;font-size:0.88rem;line-height:1.6;margin-bottom:12px;">'
|
| 777 |
-
'<strong style="color:#f58300;">Important:</strong> This platform is '
|
| 778 |
-
'<strong>not HIPAA-compliant</strong>. Do not share sensitive medical records, '
|
| 779 |
-
'Social Security numbers, or information you would not disclose on a non-secure platform.</p>',
|
| 780 |
-
unsafe_allow_html=True,
|
| 781 |
-
)
|
| 782 |
-
consent = st.checkbox(
|
| 783 |
-
"I understand this is an informational tool, not a prescription. "
|
| 784 |
-
"All peptide protocols require physician evaluation and approval.",
|
| 785 |
-
key="consent",
|
| 786 |
-
)
|
| 787 |
-
|
| 788 |
-
submitted = st.form_submit_button("Find My Peptides", use_container_width=True)
|
| 789 |
-
|
| 790 |
-
# ── Processing ──
|
| 791 |
-
if submitted:
|
| 792 |
-
selected_goals = [g for g, checked in goal_checks.items() if checked]
|
| 793 |
-
|
| 794 |
-
errors = []
|
| 795 |
-
if not first_name: errors.append("First Name")
|
| 796 |
-
if not last_name: errors.append("Last Name")
|
| 797 |
-
if not email: errors.append("Email Address")
|
| 798 |
-
if not phone: errors.append("Cell Phone")
|
| 799 |
-
if not selected_goals: errors.append("At least one health goal")
|
| 800 |
-
if not consent: errors.append("Consent acknowledgment")
|
| 801 |
-
|
| 802 |
-
if errors:
|
| 803 |
-
st.error(f"Please complete: **{', '.join(errors)}**")
|
| 804 |
-
st.stop()
|
| 805 |
-
|
| 806 |
-
results = score_peptides(selected_goals)
|
| 807 |
-
top_cats = get_top_categories(selected_goals)
|
| 808 |
-
|
| 809 |
-
if not results:
|
| 810 |
-
st.warning("No peptides matched your selected goals. Please try different selections.")
|
| 811 |
-
st.stop()
|
| 812 |
-
|
| 813 |
-
st.session_state["pep_results"] = results
|
| 814 |
-
st.session_state["pep_goals"] = selected_goals
|
| 815 |
-
st.session_state["pep_top_cats"] = top_cats
|
| 816 |
-
st.session_state["pep_patient_name"] = f"{first_name} {last_name}"
|
| 817 |
-
st.session_state["pep_patient_email"] = email
|
| 818 |
-
st.session_state["pep_patient_phone"] = phone
|
| 819 |
-
st.rerun()
|
| 820 |
-
|
| 821 |
-
render_footer()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|