Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import streamlit.components.v1 as components | |
| import os, json, re | |
| from reportlab.lib.pagesizes import letter | |
| from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle | |
| from reportlab.lib.units import inch | |
| from reportlab.platypus import ( | |
| SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, | |
| HRFlowable, KeepTogether, PageBreak, | |
| ) | |
| from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT | |
| from reportlab.lib import colors | |
| from io import BytesIO | |
| from datetime import datetime | |
| from config import * | |
| # ββ Shorthand color references | |
| NAVY = BRAND["colors"]["navy"] | |
| TEAL = BRAND["colors"]["teal"] | |
| SKY = BRAND["colors"]["sky"] | |
| ORANGE = BRAND["colors"]["orange"] | |
| AMBER = BRAND["colors"]["amber"] | |
| WHITE = BRAND["colors"]["white"] | |
| # ============================================================================= | |
| # RECOMMENDATION ENGINE | |
| # ============================================================================= | |
| def score_peptides(selected_goals): | |
| """Score every peptide against the patient's selected goals. | |
| Returns a list of (peptide_key, score, matched_categories) sorted by score | |
| descending, then by suggested-vial cost ascending (value-first tiebreaker). | |
| """ | |
| # Build category weights from selected goals | |
| cat_scores = {} | |
| for goal in selected_goals: | |
| for cat_name, weight in GOAL_MAPPINGS.get(goal, []): | |
| cat_scores[cat_name] = cat_scores.get(cat_name, 0) + weight | |
| if not cat_scores: | |
| return [] | |
| results = [] | |
| for pep_key, pep in PEPTIDES.items(): | |
| score = 0 | |
| matched = [] | |
| for cat in pep["categories"]: | |
| if cat in cat_scores: | |
| score += cat_scores[cat] | |
| matched.append(cat) | |
| if score > 0: | |
| results.append((pep_key, score, matched)) | |
| # Sort: highest score first, then cheapest suggested-vial price as tiebreaker | |
| results.sort( | |
| key=lambda x: ( | |
| -x[1], | |
| PEPTIDES[x[0]]["pricing"].get(PEPTIDES[x[0]]["suggested_vials"], 9999), | |
| ) | |
| ) | |
| return results | |
| def get_top_categories(selected_goals, limit=3): | |
| """Return the top N matched categories from selected goals.""" | |
| cat_scores = {} | |
| for goal in selected_goals: | |
| for cat_name, weight in GOAL_MAPPINGS.get(goal, []): | |
| cat_scores[cat_name] = cat_scores.get(cat_name, 0) + weight | |
| sorted_cats = sorted(cat_scores.items(), key=lambda x: -x[1]) | |
| return [c[0] for c in sorted_cats[:limit]] | |
| def group_by_tier(scored_peptides): | |
| """Group scored peptides into branded blends, other blends, and singles. | |
| Each tier is sorted high-to-low by score, then cheapest suggested price.""" | |
| branded = [] | |
| blends = [] | |
| singles = [] | |
| for pep_key, score, matched in scored_peptides: | |
| pep = PEPTIDES[pep_key] | |
| if pep.get("brand_name"): | |
| branded.append((pep_key, score, matched)) | |
| elif pep.get("is_blend"): | |
| blends.append((pep_key, score, matched)) | |
| else: | |
| singles.append((pep_key, score, matched)) | |
| # Ensure each tier is explicitly sorted: highest score first, most expensive suggested price first as tiebreaker | |
| _sort_key = lambda x: (-x[1], -PEPTIDES[x[0]]["pricing"].get(PEPTIDES[x[0]]["suggested_vials"], 0)) | |
| branded.sort(key=_sort_key) | |
| blends.sort(key=_sort_key) | |
| singles.sort(key=_sort_key) | |
| return branded, blends, singles | |
| # ============================================================================= | |
| # PDF GENERATOR | |
| # ============================================================================= | |
| def generate_pdf(patient_info, selected_goals, top_categories, recommendations): | |
| """Generate a branded PDF of the peptide recommendation.""" | |
| buffer = BytesIO() | |
| doc = SimpleDocTemplate( | |
| buffer, pagesize=letter, | |
| topMargin=0.55 * inch, bottomMargin=0.6 * inch, | |
| leftMargin=0.65 * inch, rightMargin=0.65 * inch, | |
| ) | |
| story = [] | |
| pw = letter[0] - 1.3 * inch | |
| def ps(name, **kwargs): | |
| return ParagraphStyle(name, **kwargs) | |
| C_NAVY = colors.HexColor(NAVY) | |
| C_TEAL = colors.HexColor(TEAL) | |
| C_ORANGE = colors.HexColor(ORANGE) | |
| C_AMBER = colors.HexColor(AMBER) | |
| C_SKY = colors.HexColor(SKY) | |
| C_LIGHT = colors.HexColor("#eef4f8") | |
| C_CARD = colors.HexColor("#f0f6fa") | |
| C_BORDER = colors.HexColor("#b8d0de") | |
| brand_s = ps("Br", fontName="Helvetica-Bold", fontSize=9, textColor=C_ORANGE, spaceAfter=0, alignment=TA_CENTER) | |
| title_s = ps("T", fontName="Helvetica-Bold", fontSize=20, leading=26, textColor=colors.white, spaceAfter=0, alignment=TA_CENTER) | |
| sub_s = ps("Su", fontName="Helvetica", fontSize=10, textColor=C_SKY, spaceAfter=0, alignment=TA_CENTER) | |
| meta_s = ps("Me", fontName="Helvetica", fontSize=9, leading=14, textColor=C_NAVY, spaceAfter=3) | |
| h2_s = ps("H2", fontName="Helvetica-Bold", fontSize=11, textColor=colors.white, spaceBefore=0, spaceAfter=0) | |
| h3_s = ps("H3", fontName="Helvetica-Bold", fontSize=10, textColor=C_NAVY, spaceBefore=4, spaceAfter=3) | |
| body_s = ps("B", fontName="Helvetica", fontSize=9.5, leading=15, textColor=C_NAVY, spaceAfter=5) | |
| bullet_s = ps("Bu", fontName="Helvetica", fontSize=9.5, leading=15, textColor=C_NAVY, leftIndent=14, spaceAfter=3) | |
| disc_s = ps("Di", fontName="Helvetica-Oblique", fontSize=8, leading=12, textColor=colors.HexColor("#4a6070"), spaceAfter=4) | |
| price_s = ps("Pr", fontName="Helvetica-Bold", fontSize=10, textColor=C_ORANGE, spaceAfter=2) | |
| pep_hdr_s = ps("Ph", fontName="Helvetica-Bold", fontSize=10, textColor=colors.white, spaceBefore=0, spaceAfter=0) | |
| ft_s = ps("Ft", fontName="Helvetica", fontSize=8, leading=12, textColor=C_NAVY, spaceAfter=0, alignment=TA_CENTER) | |
| def banner(para, bg, line_below=None, pad_v=7, pad_h=12): | |
| t = Table([[para]], colWidths=[pw]) | |
| cmds = [ | |
| ("BACKGROUND", (0, 0), (-1, -1), bg), | |
| ("LEFTPADDING", (0, 0), (-1, -1), pad_h), | |
| ("RIGHTPADDING", (0, 0), (-1, -1), pad_h), | |
| ("TOPPADDING", (0, 0), (-1, -1), pad_v), | |
| ("BOTTOMPADDING", (0, 0), (-1, -1), pad_v), | |
| ] | |
| if line_below: | |
| cmds.append(("LINEBELOW", (0, 0), (-1, -1), 3, line_below)) | |
| t.setStyle(TableStyle(cmds)) | |
| return t | |
| def section_hdr(text): | |
| return KeepTogether([ | |
| Spacer(1, 8), | |
| banner(Paragraph(text.upper(), h2_s), C_NAVY, line_below=C_ORANGE, pad_v=8), | |
| Spacer(1, 6), | |
| ]) | |
| # ββ COVER ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| hdr_tbl = Table([ | |
| [Paragraph("IGNITE PERFORMANCE & HEALTH", brand_s)], | |
| [Paragraph("Peptide Protocol Recommendation", title_s)], | |
| [Paragraph("Physician-Supervised Peptide Therapy", sub_s)], | |
| ], colWidths=[pw]) | |
| hdr_tbl.setStyle(TableStyle([ | |
| ("BACKGROUND", (0, 0), (-1, -1), C_NAVY), | |
| ("LEFTPADDING", (0, 0), (-1, -1), 20), | |
| ("RIGHTPADDING", (0, 0), (-1, -1), 20), | |
| ("TOPPADDING", (0, 0), (0, 0), 14), | |
| ("BOTTOMPADDING", (0, 0), (0, 0), 4), | |
| ("TOPPADDING", (0, 1), (0, 1), 4), | |
| ("BOTTOMPADDING", (0, 1), (0, 1), 4), | |
| ("TOPPADDING", (0, 2), (0, 2), 2), | |
| ("BOTTOMPADDING", (0, 2), (0, 2), 14), | |
| ("ALIGN", (0, 0), (-1, -1), "CENTER"), | |
| ])) | |
| story.append(hdr_tbl) | |
| story.append(HRFlowable(width="100%", thickness=5, color=C_ORANGE, spaceAfter=12)) | |
| # ββ Patient info ββ | |
| name = patient_info.get("name", "") | |
| date_str = datetime.now().strftime("%B %d, %Y") | |
| pi_tbl = Table([[ | |
| Paragraph(f"<b>Prepared for:</b> {name}", meta_s), | |
| Paragraph(f"<b>Date:</b> {date_str}", meta_s), | |
| ]], colWidths=[pw * 0.6, pw * 0.4]) | |
| pi_tbl.setStyle(TableStyle([ | |
| ("BACKGROUND", (0, 0), (-1, -1), C_LIGHT), | |
| ("BOX", (0, 0), (-1, -1), 0.75, C_BORDER), | |
| ("LINEBELOW", (0, 0), (-1, -1), 2, C_ORANGE), | |
| ("LEFTPADDING", (0, 0), (-1, -1), 10), | |
| ("RIGHTPADDING", (0, 0), (-1, -1), 10), | |
| ("TOPPADDING", (0, 0), (-1, -1), 9), | |
| ("BOTTOMPADDING", (0, 0), (-1, -1), 9), | |
| ("VALIGN", (0, 0), (-1, -1), "MIDDLE"), | |
| ])) | |
| story.append(pi_tbl) | |
| story.append(Spacer(1, 14)) | |
| # ββ Goals & Categories ββ | |
| story.append(section_hdr("Your Goals")) | |
| goals_text = " Β· ".join(selected_goals) | |
| story.append(Paragraph(f"<b>Selected goals:</b> {goals_text}", body_s)) | |
| cats_text = " Β· ".join(top_categories) | |
| story.append(Paragraph(f"<b>Top therapeutic areas:</b> {cats_text}", body_s)) | |
| story.append(Spacer(1, 8)) | |
| # ββ Recommendations ββ | |
| story.append(section_hdr("Recommended Peptides")) | |
| branded, blends, singles = group_by_tier(recommendations) | |
| def render_pep_block(pep_key, score, matched): | |
| pep = PEPTIDES[pep_key] | |
| display_name = pep.get("brand_name") or pep_key | |
| sv = pep["suggested_vials"] | |
| sv_price = pep["pricing"].get(sv, 0) | |
| per_week = sv_price / max((sv * 4), 1) # rough per-week estimate | |
| items = [ | |
| Spacer(1, 4), | |
| banner(Paragraph(f" {display_name}", pep_hdr_s), C_TEAL, line_below=C_NAVY, pad_v=5), | |
| ] | |
| if pep.get("brand_name") and pep_key != pep["brand_name"]: | |
| items.append(Paragraph(f"<i>{pep_key}</i>", disc_s)) | |
| items.append(Paragraph(pep["description"], body_s)) | |
| # ββ Layered MOA ββ | |
| moa_scott = pep.get("moa_scott", "") | |
| moa_clinical = pep.get("moa_clinical", "") | |
| if moa_scott: | |
| items.append(Paragraph(f'<font color="{ORANGE}"><b>How it works:</b></font> {moa_scott}', body_s)) | |
| if moa_clinical: | |
| items.append(Paragraph(f"<i>{moa_clinical}</i>", disc_s)) | |
| items.append(Paragraph( | |
| f"<b>Dosage:</b> {pep['dosage']} | <b>Cycle:</b> {pep['cycle']} | " | |
| f"<b>Suggested vials:</b> {sv}", bullet_s)) | |
| items.append(Paragraph( | |
| f"<b>Suggested cycle cost:</b> ${sv_price:,.2f} ({sv} vials) | " | |
| f"<b>~${per_week:,.2f}/week</b>", price_s)) | |
| items.append(Paragraph( | |
| f"Categories: {', '.join(matched)}", disc_s)) | |
| items.append(Spacer(1, 6)) | |
| return KeepTogether(items) | |
| if branded: | |
| story.append(Paragraph("<b>Ignite Branded Blends</b> β curated multi-peptide protocols", h3_s)) | |
| for pep_key, score, matched in branded[:6]: | |
| story.append(render_pep_block(pep_key, score, matched)) | |
| if blends: | |
| story.append(Paragraph("<b>Combination Blends</b>", h3_s)) | |
| for pep_key, score, matched in blends[:4]: | |
| story.append(render_pep_block(pep_key, score, matched)) | |
| if singles: | |
| story.append(Paragraph("<b>Individual Peptides</b>", h3_s)) | |
| for pep_key, score, matched in singles[:4]: | |
| story.append(render_pep_block(pep_key, score, matched)) | |
| # ββ Disclaimer ββ | |
| story.append(Spacer(1, 12)) | |
| story.append(HRFlowable(width="100%", thickness=1, color=C_BORDER, spaceAfter=8)) | |
| story.append(Paragraph(BRAND["disclaimer"], disc_s)) | |
| story.append(Spacer(1, 6)) | |
| # ββ Footer ββ | |
| story.append(HRFlowable(width="100%", thickness=3, color=C_ORANGE, spaceAfter=0)) | |
| ft_brand_s = ps("Fb", fontName="Helvetica-Bold", fontSize=10, textColor=C_ORANGE, spaceAfter=1, alignment=TA_CENTER) | |
| ft_info_s = ps("Fi", fontName="Helvetica", fontSize=8, leading=12, textColor=colors.white, spaceAfter=1, alignment=TA_CENTER) | |
| ft_web_s = ps("Fw", fontName="Helvetica-Bold", fontSize=8, textColor=C_SKY, spaceAfter=0, alignment=TA_CENTER) | |
| ft_tbl = Table([ | |
| [Paragraph("IGNITE PERFORMANCE & HEALTH", ft_brand_s)], | |
| [Paragraph("14830 Clayton Rd, Chesterfield, MO 63017 | (314) 887-0858 | info@ignitepah.com", ft_info_s)], | |
| [Paragraph("www.ignitepah.com", ft_web_s)], | |
| ], colWidths=[pw]) | |
| ft_tbl.setStyle(TableStyle([ | |
| ("BACKGROUND", (0, 0), (-1, -1), C_NAVY), | |
| ("TOPPADDING", (0, 0), (0, 0), 8), | |
| ("BOTTOMPADDING", (0, -1), (0, -1), 8), | |
| ("TOPPADDING", (0, 1), (-1, -1), 1), | |
| ("BOTTOMPADDING", (0, 1), (-1, -1), 1), | |
| ("ALIGN", (0, 0), (-1, -1), "CENTER"), | |
| ])) | |
| story.append(ft_tbl) | |
| doc.build(story) | |
| buffer.seek(0) | |
| return buffer | |
| # ============================================================================= | |
| # FOOTER RENDERER | |
| # ============================================================================= | |
| def render_footer(): | |
| st.markdown( | |
| '<div class="site-footer">' | |
| f'{"<img class=footer-logo src=" + chr(34) + LOGO_B64 + chr(34) + " alt=Ignite />" if LOGO_B64 else ""}' | |
| '<p class="brand">Ignite Performance & Health</p>' | |
| '<p class="tagline">Physician-Supervised Peptide Therapy</p>' | |
| '<div style="width:60px;height:2px;background:#f58300;margin:14px auto;border-radius:2px;"></div>' | |
| '<p class="contact-label">Visit Us</p>' | |
| '<p class="contact-row">14830 Clayton Rd, Chesterfield, MO 63017</p>' | |
| '<p class="contact-label">Get In Touch</p>' | |
| '<p class="contact-row">' | |
| '<a href="tel:3148870858">(314) 887-0858</a>' | |
| '<span class="sep">|</span>' | |
| '<a href="mailto:info@ignitepah.com">info@ignitepah.com</a>' | |
| '</p>' | |
| '<p class="contact-row"><a href="https://ignitepah.com" target="_blank">www.ignitepah.com</a></p>' | |
| '<p class="disclaimer">' | |
| f'{BRAND["disclaimer"]}' | |
| '</p>' | |
| '</div>', | |
| unsafe_allow_html=True, | |
| ) | |
| # ============================================================================= | |
| # PAGE CONFIG & CSS | |
| # ============================================================================= | |
| st.set_page_config( | |
| page_title="Ignite Performance & Health β Peptide Protocol Selector", | |
| layout="wide", | |
| initial_sidebar_state="collapsed", | |
| ) | |
| st.markdown( | |
| '<link rel="preconnect" href="https://fonts.googleapis.com">' | |
| '<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>' | |
| '<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">', | |
| unsafe_allow_html=True, | |
| ) | |
| st.markdown(""" | |
| <style> | |
| .stApp { background-color: #f0f6fa; } | |
| html, body, [class*="css"] { font-family: 'Barlow', 'Segoe UI', sans-serif; } | |
| .stMarkdown p, .stMarkdown span, | |
| div[data-testid="stMarkdownContainer"] p, | |
| div[data-testid="stMarkdownContainer"] span, | |
| div[data-testid="stMarkdownContainer"] li, | |
| .stRadio label, .stRadio span, | |
| .stCheckbox label, .stCheckbox span, | |
| .stNumberInput label, .stTextInput label, .stTextArea label, | |
| .stCaption, .stCaption p, .stForm p, .stForm span, .stForm label { color: #023047 !important; } | |
| div[data-testid="stMarkdownContainer"] .site-footer p, | |
| div[data-testid="stMarkdownContainer"] .site-footer span, | |
| div[data-testid="stMarkdownContainer"] .site-footer a, | |
| .stMarkdown .site-footer p, .stMarkdown .site-footer a { color: #ffffff !important; } | |
| div[data-testid="stMarkdownContainer"] .site-footer .brand, | |
| .stMarkdown .site-footer .brand { color: #f58300 !important; } | |
| div[data-testid="stMarkdownContainer"] .site-footer .tagline, | |
| .stMarkdown .site-footer .tagline { color: #8ecae6 !important; } | |
| div[data-testid="stMarkdownContainer"] .site-footer .disclaimer, | |
| .stMarkdown .site-footer .disclaimer { color: rgba(255,255,255,0.7) !important; } | |
| div[data-testid="stMarkdownContainer"] .cta-box p, | |
| div[data-testid="stMarkdownContainer"] .cta-box span, | |
| div[data-testid="stMarkdownContainer"] .cta-box a, | |
| .stMarkdown .cta-box p, .stMarkdown .cta-box span, .stMarkdown .cta-box a { color: #ffffff !important; } | |
| div[data-testid="stMarkdownContainer"] .cta-box h3, | |
| .stMarkdown .cta-box h3 { color: #ffc533 !important; } | |
| div[data-testid="stMarkdownContainer"] .cta-box strong, | |
| .stMarkdown .cta-box strong { color: #ffc533 !important; } | |
| div[data-testid="stMarkdownContainer"] .section-band, | |
| .stMarkdown .section-band { color: #ffc533 !important; } | |
| div[data-testid="stMarkdownContainer"] .hero .hero-title, | |
| .stMarkdown .hero .hero-title { color: #f58300 !important; } | |
| div[data-testid="stMarkdownContainer"] .hero .hero-sub, | |
| .stMarkdown .hero .hero-sub { color: #8ecae6 !important; } | |
| div[data-testid="stMarkdownContainer"] .hero .hero-tagline, | |
| .stMarkdown .hero .hero-tagline { color: rgba(255,255,255,0.85) !important; } | |
| div[data-testid="stMarkdownContainer"] .cat-pill, | |
| .stMarkdown .cat-pill { color: #ffffff !important; } | |
| div[data-testid="stMarkdownContainer"] .pep-badge p, | |
| div[data-testid="stMarkdownContainer"] .pep-badge span, | |
| div[data-testid="stMarkdownContainer"] .pep-badge-branded p, | |
| div[data-testid="stMarkdownContainer"] .pep-badge-branded span, | |
| .stMarkdown .pep-badge, .stMarkdown .pep-badge-branded { color: #ffffff !important; } | |
| div[data-testid="stMarkdownContainer"] .pep-moa-scott, | |
| .stMarkdown .pep-moa-scott { color: #023047 !important; } | |
| div[data-testid="stMarkdownContainer"] .pep-moa-label, | |
| .stMarkdown .pep-moa-label { color: #f58300 !important; } | |
| div[data-testid="stMarkdownContainer"] .pep-moa-clinical, | |
| .stMarkdown .pep-moa-clinical { color: #6b8a9e !important; } | |
| .stTextInput input, .stNumberInput input, .stTextArea textarea { | |
| color: #023047 !important; background-color: #ffffff !important; | |
| border: 1px solid #ccdde8 !important; border-radius: 6px !important; | |
| } | |
| .stTextInput input:focus, .stNumberInput input:focus, .stTextArea textarea:focus { | |
| border-color: #219ebc !important; box-shadow: 0 0 0 2px rgba(33,158,188,0.2) !important; | |
| } | |
| /* ββ Hero ββ */ | |
| .hero { | |
| background: linear-gradient(135deg, #023047 60%, #219ebc 100%); | |
| border-radius: 12px; padding: 2.2rem 2.5rem 1.8rem; margin-bottom: 1.5rem; | |
| text-align: center; | |
| } | |
| .hero-logo { display: block; margin: 0 auto 18px; height: 130px; width: auto; } | |
| .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; } | |
| .hero-sub { color: #8ecae6 !important; font-size: 1.1rem; font-weight: 600; margin: 8px 0 0; } | |
| .hero-tagline { color: rgba(255,255,255,0.85) !important; font-size: 0.85rem; margin: 8px 0 0; letter-spacing: 0.5px; } | |
| /* ββ Section bands ββ */ | |
| .section-band { | |
| background: linear-gradient(90deg, #023047 0%, #012233 100%); | |
| color: #ffc533 !important; font-family: 'Barlow Condensed', sans-serif; | |
| font-size: 1.1rem; font-weight: 800; letter-spacing: 1.2px; | |
| padding: 12px 16px; margin: 20px 0 14px; | |
| border-radius: 4px; text-transform: uppercase; | |
| } | |
| /* ββ Form & buttons ββ */ | |
| .stForm { background: white; padding: 20px; border-radius: 8px; } | |
| .stFormSubmitButton > button, | |
| .stFormSubmitButton > button:active, | |
| .stFormSubmitButton > button:focus { | |
| background: linear-gradient(135deg, #f58300 0%, #ffc533 100%) !important; | |
| color: #023047 !important; font-size: 1rem; font-weight: 700; | |
| letter-spacing: 0.5px; padding: 12px 24px; border-radius: 6px !important; | |
| border: none !important; box-shadow: 0 4px 12px rgba(245,131,0,0.25) !important; | |
| } | |
| .stFormSubmitButton > button p, | |
| .stFormSubmitButton > button span { color: #023047 !important; font-weight: 700 !important; } | |
| .stFormSubmitButton > button:hover { | |
| background: linear-gradient(135deg, #ffc533 0%, #f58300 100%) !important; | |
| } | |
| .stDownloadButton > button, | |
| .stDownloadButton > button:active, | |
| .stDownloadButton > button:focus { | |
| background: linear-gradient(135deg, #f58300 0%, #ffc533 100%) !important; | |
| color: #023047 !important; font-size: 0.9rem; font-weight: 700; | |
| padding: 10px 20px; border-radius: 6px !important; | |
| border: none !important; box-shadow: 0 2px 8px rgba(245,131,0,0.25) !important; | |
| width: 100%; | |
| } | |
| .stDownloadButton > button p, | |
| .stDownloadButton > button span { color: #023047 !important; font-weight: 700 !important; } | |
| .stDownloadButton > button:hover { | |
| background: linear-gradient(135deg, #ffc533 0%, #f58300 100%) !important; | |
| } | |
| .stButton > button, | |
| .stButton > button:active, | |
| .stButton > button:focus { | |
| background: linear-gradient(135deg, #f58300 0%, #ffc533 100%) !important; | |
| color: #023047 !important; font-size: 1rem; font-weight: 700; | |
| padding: 12px 24px; border-radius: 6px !important; | |
| border: none !important; box-shadow: 0 4px 12px rgba(245,131,0,0.25) !important; | |
| } | |
| .stButton > button p, | |
| .stButton > button span { color: #023047 !important; font-weight: 700 !important; } | |
| .stButton > button:hover { | |
| background: linear-gradient(135deg, #ffc533 0%, #f58300 100%) !important; | |
| } | |
| /* ββ Peptide cards ββ */ | |
| .pep-card { | |
| background: white; border: 1px solid #b8d0de; border-radius: 8px; | |
| padding: 16px; margin-bottom: 16px; | |
| } | |
| .pep-card-branded { | |
| background: white; border: 2px solid #219ebc; border-radius: 8px; | |
| padding: 16px; margin-bottom: 16px; | |
| } | |
| .pep-badge { | |
| display: inline-block; background: #219ebc; color: white; | |
| font-size: 0.7rem; font-weight: 700; padding: 4px 8px; | |
| border-radius: 3px; margin-bottom: 8px; letter-spacing: 0.5px; | |
| } | |
| .pep-badge-branded { | |
| display: inline-block; background: #f58300; color: white; | |
| font-size: 0.7rem; font-weight: 700; padding: 4px 8px; | |
| border-radius: 3px; margin-bottom: 8px; letter-spacing: 0.5px; | |
| } | |
| .pep-name { | |
| color: #023047 !important; font-family: 'Barlow Condensed', sans-serif; | |
| font-size: 1.3rem; font-weight: 800; margin-bottom: 2px; | |
| } | |
| .pep-components { | |
| color: #219ebc !important; font-size: 0.85rem; font-style: italic; margin-bottom: 8px; | |
| } | |
| .pep-desc { | |
| color: #4a6070 !important; font-size: 0.9rem; margin-bottom: 14px; line-height: 1.5; | |
| } | |
| .pep-label { | |
| color: #f58300 !important; font-weight: 700; font-size: 0.75rem; | |
| text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px; | |
| } | |
| .pep-value { | |
| color: #023047 !important; font-size: 0.9rem; margin-bottom: 10px; | |
| } | |
| .pep-moa-scott { | |
| color: #023047 !important; font-size: 0.9rem; line-height: 1.55; margin-bottom: 6px; | |
| padding: 10px 12px; background: #f8fbfd; border-left: 3px solid #f58300; border-radius: 0 4px 4px 0; | |
| } | |
| .pep-moa-label { color: #f58300 !important; font-weight: 700; } | |
| .pep-moa-clinical { | |
| color: #6b8a9e !important; font-size: 0.8rem; font-style: italic; line-height: 1.5; | |
| margin-bottom: 12px; padding: 0 12px; | |
| } | |
| .pep-cats { | |
| display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 12px; | |
| } | |
| .cat-pill { | |
| background: #219ebc; color: #ffffff !important; | |
| font-size: 0.72rem; font-weight: 700; padding: 4px 10px; | |
| border-radius: 12px; white-space: nowrap; | |
| } | |
| /* ββ Pricing grid ββ */ | |
| .pricing-grid { | |
| display: grid; gap: 8px; margin-top: 10px; | |
| } | |
| .price-cell { | |
| background: #f0f6fa; border: 1px solid #b8d0de; border-radius: 6px; | |
| padding: 10px 8px; text-align: center; | |
| } | |
| .price-cell-suggested { | |
| background: linear-gradient(135deg, #fff8ec 0%, #fff0d6 100%); border: 2.5px solid #f58300; border-radius: 6px; | |
| padding: 12px 8px; text-align: center; box-shadow: 0 2px 8px rgba(245,131,0,0.2); | |
| position: relative; | |
| } | |
| .price-vials { color: #023047 !important; font-weight: 700; font-size: 0.8rem; margin-bottom: 2px; } | |
| .price-per-week { color: #f58300 !important; font-family: 'Barlow Condensed', sans-serif; font-size: 1.55rem; font-weight: 800; } | |
| .price-per-week-label { color: #f58300 !important; font-size: 0.65rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.3px; } | |
| .price-amount { color: #4a6070 !important; font-size: 0.72rem; font-weight: 600; margin-top: 2px; } | |
| .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; } | |
| /* ββ Styled HR ββ */ | |
| .styled-hr { margin: 2rem 0; border: 0; height: 2px; background: linear-gradient(90deg, transparent, #f58300, transparent); } | |
| /* ββ CTA box ββ */ | |
| .cta-box { | |
| background: linear-gradient(135deg, #023047 0%, #034a6e 100%); | |
| border: 2px solid #f58300; border-radius: 8px; padding: 24px; | |
| margin: 24px 0; color: #ffffff !important; | |
| } | |
| .cta-box, .cta-box * { color: #ffffff !important; } | |
| .cta-box h3 { color: #ffc533 !important; font-family: 'Barlow Condensed', sans-serif; font-size: 1.4rem; font-weight: 800; margin-bottom: 12px; } | |
| .cta-box p { color: #ffffff !important; line-height: 1.6; margin-bottom: 10px; } | |
| .cta-box strong { color: #ffc533 !important; } | |
| .cta-link { | |
| display: inline-block; background: #f58300; color: #ffffff !important; | |
| font-weight: 700; padding: 12px 20px; border-radius: 6px; | |
| text-decoration: none; margin-top: 14px; letter-spacing: 0.5px; | |
| } | |
| .cta-link:hover { background: #ffc533; color: #023047 !important; } | |
| /* ββ Site footer ββ */ | |
| .site-footer { | |
| background: #023047; color: #ffffff !important; padding: 32px 24px; text-align: center; | |
| margin-top: 40px; border-top: 4px solid #f58300; | |
| } | |
| .site-footer, .site-footer * { color: #ffffff !important; } | |
| .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; } | |
| .site-footer .tagline { color: #8ecae6 !important; font-size: 0.85rem; margin-bottom: 14px; } | |
| .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; } | |
| .site-footer .contact-row { color: white !important; font-size: 0.95rem; margin-bottom: 4px; line-height: 1.6; } | |
| .site-footer .contact-row a { color: white !important; text-decoration: none; } | |
| .site-footer .contact-row a:hover { color: #ffc533 !important; } | |
| .site-footer .sep { color: #8ecae6 !important; margin: 0 10px; } | |
| .site-footer .disclaimer { color: rgba(255,255,255,0.7) !important; font-size: 0.8rem; margin-top: 16px; line-height: 1.6; } | |
| /* ββ Mobile ββ */ | |
| @media (max-width: 768px) { | |
| .hero { padding: 1.5rem 1.2rem 1.2rem; } | |
| .hero-logo { height: 90px; } | |
| .hero-title { font-size: 1.6rem !important; } | |
| .pep-name { font-size: 1.1rem; } | |
| .pricing-grid { grid-template-columns: repeat(3, 1fr) !important; } | |
| .price-per-week { font-size: 1.2rem; } | |
| } | |
| @media (max-width: 480px) { | |
| .hero-title { font-size: 1.3rem !important; } | |
| .pricing-grid { grid-template-columns: repeat(2, 1fr) !important; } | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # ============================================================================= | |
| # SESSION STATE | |
| # ============================================================================= | |
| if "pep_results" not in st.session_state: | |
| st.session_state["pep_results"] = None | |
| st.session_state["pep_goals"] = [] | |
| st.session_state["pep_top_cats"] = [] | |
| st.session_state["pep_patient_name"] = "" | |
| st.session_state["pep_patient_email"] = "" | |
| st.session_state["pep_patient_phone"] = "" | |
| # ============================================================================= | |
| # RESULTS PAGE | |
| # ============================================================================= | |
| if st.session_state["pep_results"]: | |
| pname = st.session_state.get("pep_patient_name", "") | |
| pemail = st.session_state.get("pep_patient_email", "") | |
| pphone = st.session_state.get("pep_patient_phone", "") | |
| selected_goals = st.session_state.get("pep_goals", []) | |
| top_cats = st.session_state.get("pep_top_cats", []) | |
| results = st.session_state["pep_results"] | |
| # ββ Hero ββ | |
| st.markdown( | |
| '<div class="hero">' | |
| f'{"<img class=hero-logo src=" + chr(34) + LOGO_B64 + chr(34) + " alt=Ignite />" if LOGO_B64 else ""}' | |
| '<div class="hero-title">Peptide Protocol Selector</div>' | |
| '<div class="hero-sub">Your Personalized Peptide Recommendations</div>' | |
| f'<div class="hero-tagline">Prepared for {pname}</div>' | |
| '</div>', | |
| unsafe_allow_html=True, | |
| ) | |
| # ββ Goals summary ββ | |
| st.markdown('<div class="section-band">Your Health Goals</div>', unsafe_allow_html=True) | |
| goals_html = " Β· ".join(selected_goals) | |
| st.markdown(f'<p style="color:#023047;font-size:0.95rem;margin-bottom:8px;">{goals_html}</p>', unsafe_allow_html=True) | |
| cats_html = " ".join(f'<span class="cat-pill">{c}</span>' for c in top_cats) | |
| st.markdown(f'<div style="display:flex;gap:6px;flex-wrap:wrap;margin-bottom:20px;">{cats_html}</div>', unsafe_allow_html=True) | |
| # ββ Group and display ββ | |
| branded, blends, singles = group_by_tier(results) | |
| def render_card(pep_key, score, matched, is_branded=False): | |
| pep = PEPTIDES[pep_key] | |
| display_name = pep.get("brand_name") or pep_key | |
| card_class = "pep-card-branded" if is_branded else "pep-card" | |
| badge_class = "pep-badge-branded" if is_branded else "pep-badge" | |
| # Only show badge on singles and non-branded blends | |
| show_badge = not is_branded | |
| badge_text = "BLEND" if pep["is_blend"] else "SINGLE" | |
| components_str = " + ".join(pep["components"]) if pep["is_blend"] else "" | |
| cat_pills = " ".join(f'<span class="cat-pill">{c}</span>' for c in matched) | |
| sv = pep["suggested_vials"] | |
| price_cells = "" | |
| for vials in range(1, 7): | |
| price = pep["pricing"][vials] | |
| is_suggested = vials == sv | |
| cell_class = "price-cell-suggested" if is_suggested else "price-cell" | |
| suggested_label = '<div class="price-suggested-label">β Recommended</div>' if is_suggested else "" | |
| per_week = price / max(vials * 4, 1) | |
| price_cells += ( | |
| f'<div class="{cell_class}">' | |
| f'<div class="price-vials">{vials} Vial{"s" if vials > 1 else ""}</div>' | |
| f'<div class="price-per-week">${per_week:,.2f}</div>' | |
| f'<div class="price-per-week-label">per week</div>' | |
| f'<div class="price-amount">${price:,.2f} total</div>' | |
| f'{suggested_label}' | |
| f'</div>' | |
| ) | |
| badge_html = f'<div class="{badge_class}">{badge_text}</div>' if show_badge else "" | |
| st.markdown( | |
| f'<div class="{card_class}">' | |
| f'{badge_html}' | |
| f'<div class="pep-name">{display_name}</div>' | |
| f'{"<div class=pep-components>" + components_str + "</div>" if components_str else ""}' | |
| f'<div class="pep-desc">{pep["description"]}</div>' | |
| f'<div class="pep-moa-scott"><span class="pep-moa-label">How it works:</span> {pep.get("moa_scott", "")}</div>' | |
| f'<div class="pep-moa-clinical">{pep.get("moa_clinical", "")}</div>' | |
| f'<div class="pep-cats">{cat_pills}</div>' | |
| f'<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:12px;">' | |
| f'<div>' | |
| f'<div class="pep-label">Dosage</div>' | |
| f'<div class="pep-value">{pep["dosage"]}</div>' | |
| f'</div>' | |
| f'<div>' | |
| f'<div class="pep-label">Cycle</div>' | |
| f'<div class="pep-value">{pep["cycle"]}</div>' | |
| f'</div>' | |
| f'</div>' | |
| f'<div class="pep-label">Vial Amount: {pep["vial_amount"]} | Source: {pep["source"]}</div>' | |
| f'<div class="pep-label" style="margin-top:10px;">Pricing</div>' | |
| f'<div class="pricing-grid" style="grid-template-columns:repeat(6,1fr)">{price_cells}</div>' | |
| f'</div>', | |
| unsafe_allow_html=True, | |
| ) | |
| if branded: | |
| st.markdown('<div class="section-band">Ignite Branded Blends β Recommended</div>', unsafe_allow_html=True) | |
| st.markdown( | |
| '<p style="color:#4a6070;font-size:0.9rem;margin-bottom:16px;">' | |
| 'Curated multi-peptide protocols from our proprietary blend line. These combine complementary peptides ' | |
| 'into a single vial for convenience and synergistic benefit.</p>', | |
| unsafe_allow_html=True, | |
| ) | |
| for pep_key, score, matched in branded[:6]: | |
| render_card(pep_key, score, matched, is_branded=True) | |
| if blends: | |
| st.markdown('<div class="section-band">Combination Blends</div>', unsafe_allow_html=True) | |
| for pep_key, score, matched in blends[:4]: | |
| render_card(pep_key, score, matched, is_branded=False) | |
| if singles: | |
| st.markdown('<div class="section-band">Individual Peptides</div>', unsafe_allow_html=True) | |
| st.markdown( | |
| '<p style="color:#4a6070;font-size:0.9rem;margin-bottom:16px;">' | |
| 'Single-peptide options for targeted protocols or for patients who prefer to build their stack one component at a time.</p>', | |
| unsafe_allow_html=True, | |
| ) | |
| for pep_key, score, matched in singles[:4]: | |
| render_card(pep_key, score, matched, is_branded=False) | |
| # ββ CTA ββ | |
| st.markdown( | |
| f'<div class="cta-box">' | |
| f'<h3>Ready to Get Started, {pname}?</h3>' | |
| f'<p>These recommendations are based on your stated goals. All peptide protocols require ' | |
| f'physician evaluation before we begin. Our team will walk through each option during your consultation, ' | |
| f'answer your questions, and build a protocol that fits your situation.</p>' | |
| f'<p><strong>What happens next:</strong> our team reaches out, we schedule your consultation, ' | |
| f'and we go from there.</p>' | |
| f'<p><strong>Contact:</strong> {pemail} | {pphone}</p>' | |
| f'<a class="cta-link" href="{BRAND["scheduling_url"]}" target="_blank">Schedule Your Consultation</a>' | |
| f'</div>', | |
| unsafe_allow_html=True, | |
| ) | |
| # ββ PDF Download ββ | |
| st.markdown('<hr class="styled-hr">', unsafe_allow_html=True) | |
| st.markdown("#### Download Your Recommendation") | |
| if "cached_pep_pdf" not in st.session_state: | |
| st.session_state["cached_pep_pdf"] = generate_pdf( | |
| {"name": pname, "email": pemail, "phone": pphone}, | |
| selected_goals, | |
| top_cats, | |
| results, | |
| ) | |
| st.download_button( | |
| "Download PDF", | |
| data=st.session_state["cached_pep_pdf"], | |
| file_name="ignite_peptide_protocol.pdf", | |
| mime="application/pdf", | |
| use_container_width=True, | |
| ) | |
| # ββ Start over ββ | |
| st.markdown('<hr class="styled-hr">', unsafe_allow_html=True) | |
| if st.button("Start New Selection", use_container_width=True): | |
| for key in ["pep_results", "pep_goals", "pep_top_cats", | |
| "pep_patient_name", "pep_patient_email", "pep_patient_phone", | |
| "cached_pep_pdf"]: | |
| if key in st.session_state: | |
| del st.session_state[key] | |
| st.session_state["pep_results"] = None | |
| st.session_state["pep_goals"] = [] | |
| st.session_state["pep_top_cats"] = [] | |
| st.session_state["pep_patient_name"] = "" | |
| st.session_state["pep_patient_email"] = "" | |
| st.session_state["pep_patient_phone"] = "" | |
| # Scroll to top before rerun | |
| components.html("<script>window.parent.document.querySelector('section.main').scrollTo(0,0);</script>", height=0) | |
| st.rerun() | |
| render_footer() | |
| st.stop() | |
| # ============================================================================= | |
| # INTAKE FORM | |
| # ============================================================================= | |
| st.markdown( | |
| '<div class="hero">' | |
| f'{"<img class=hero-logo src=" + chr(34) + LOGO_B64 + chr(34) + " alt=Ignite />" if LOGO_B64 else ""}' | |
| '<div class="hero-title">Peptide Protocol Selector</div>' | |
| '<div class="hero-sub">Find the Right Peptides for Your Goals</div>' | |
| '<div class="hero-tagline">Physician-Supervised Β· Evidence-Informed Β· Personalized</div>' | |
| '</div>', | |
| unsafe_allow_html=True, | |
| ) | |
| st.markdown( | |
| "Select your health goals below and we will match you to the peptide protocols that fit best. " | |
| "Every recommendation requires physician review before we proceed. " | |
| "This is not a prescription tool β it is a starting point for your consultation.", | |
| ) | |
| with st.form("peptide_intake"): | |
| st.markdown('<div class="section-band">Your Information</div>', unsafe_allow_html=True) | |
| c1, c2 = st.columns(2) | |
| with c1: | |
| first_name = st.text_input("First Name *") | |
| email = st.text_input("Email Address *") | |
| with c2: | |
| last_name = st.text_input("Last Name *") | |
| phone = st.text_input("Cell Phone *") | |
| st.markdown('<div class="section-band">What Are You Looking to Address?</div>', unsafe_allow_html=True) | |
| st.caption("Select all goals that apply. We will match peptides to your priorities.") | |
| # Build flat goal list grouped by category | |
| goal_checks = {} | |
| cat_keys = list(CATEGORIES.keys()) | |
| mid = (len(cat_keys) + 1) // 2 | |
| c1, c2 = st.columns(2) | |
| with c1: | |
| for cat_name in cat_keys[:mid]: | |
| cat = CATEGORIES[cat_name] | |
| st.markdown( | |
| f'<p style="color:#219ebc;font-weight:700;font-size:0.85rem;margin:12px 0 4px;text-transform:uppercase;">' | |
| f'{cat_name}</p>', | |
| unsafe_allow_html=True, | |
| ) | |
| for goal in cat["goals"]: | |
| goal_checks[goal] = st.checkbox(goal, key=f"g_{goal}") | |
| with c2: | |
| for cat_name in cat_keys[mid:]: | |
| cat = CATEGORIES[cat_name] | |
| st.markdown( | |
| f'<p style="color:#219ebc;font-weight:700;font-size:0.85rem;margin:12px 0 4px;text-transform:uppercase;">' | |
| f'{cat_name}</p>', | |
| unsafe_allow_html=True, | |
| ) | |
| for goal in cat["goals"]: | |
| goal_checks[goal] = st.checkbox(goal, key=f"g_{goal}") | |
| st.markdown('<div class="section-band">Additional Context</div>', unsafe_allow_html=True) | |
| additional = st.text_area( | |
| "Anything else we should know? (injuries, current medications, previous peptide experience, etc.)", | |
| placeholder="Optional β helps our physician team customize your protocol.", | |
| ) | |
| # ββ Consent ββ | |
| st.markdown('<div class="section-band">Consent & Privacy</div>', unsafe_allow_html=True) | |
| st.markdown( | |
| '<p style="color:#023047;font-size:0.88rem;line-height:1.6;margin-bottom:12px;">' | |
| '<strong style="color:#f58300;">Important:</strong> This platform is ' | |
| '<strong>not HIPAA-compliant</strong>. Do not share sensitive medical records, ' | |
| 'Social Security numbers, or information you would not disclose on a non-secure platform.</p>', | |
| unsafe_allow_html=True, | |
| ) | |
| consent = st.checkbox( | |
| "I understand this is an informational tool, not a prescription. " | |
| "All peptide protocols require physician evaluation and approval.", | |
| key="consent", | |
| ) | |
| submitted = st.form_submit_button("Find My Peptides", use_container_width=True) | |
| # ββ Processing ββ | |
| if submitted: | |
| selected_goals = [g for g, checked in goal_checks.items() if checked] | |
| errors = [] | |
| if not first_name: errors.append("First Name") | |
| if not last_name: errors.append("Last Name") | |
| if not email: errors.append("Email Address") | |
| if not phone: errors.append("Cell Phone") | |
| if not selected_goals: errors.append("At least one health goal") | |
| if not consent: errors.append("Consent acknowledgment") | |
| if errors: | |
| st.error(f"Please complete: **{', '.join(errors)}**") | |
| st.stop() | |
| results = score_peptides(selected_goals) | |
| top_cats = get_top_categories(selected_goals) | |
| if not results: | |
| st.warning("No peptides matched your selected goals. Please try different selections.") | |
| st.stop() | |
| st.session_state["pep_results"] = results | |
| st.session_state["pep_goals"] = selected_goals | |
| st.session_state["pep_top_cats"] = top_cats | |
| st.session_state["pep_patient_name"] = f"{first_name} {last_name}" | |
| st.session_state["pep_patient_email"] = email | |
| st.session_state["pep_patient_phone"] = phone | |
| st.rerun() | |
| render_footer() | |