From 59d3bd0c6fcab44b03be88073e36a57729921131 Mon Sep 17 00:00:00 2001 From: Boyu Gou Date: Wed, 13 May 2026 10:00:54 -0700 Subject: [PATCH 001/167] Add drugs.com mirror site (port 40015) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a complete Flask mirror of drugs.com — the most popular drug information portal — as a new benchmark site in the WebHarbor suite. Site covers 81 drugs across 20+ drug classes with real FDA label content (sourced from the OpenFDA API at seed time), 20 news articles, 22 drug interactions, and 67 user reviews. Four benchmark users are pre-seeded (alice.j@test.com / TestPass123!). Pages implemented: - Drug detail (/.html) with Uses/Warnings/Before taking/Dosage/ Side effects/Interactions/FAQ/Reviews sections and Drug Status sidebar - Drug A-Z (/drug_information.html, /drugs-a-to-z.html) - Drug Interaction Checker (/drug_interactions.html, /interaction-checker/) - Pill Identifier (/pill_identification.html, /pill-identifier.html) - Drug classes (/drug-classes, /drug-class/) - Conditions (/conditions, /condition/) - News (/news/, /news/, /news/article/) - Search, Login/Register, My Med List, Account 21 benchmark tasks in tasks.jsonl covering: drug lookup, interaction checking, pill identification, A-Z browsing, conditions, drug classes, FAQ, news, ratings, auth flow, and saved med list. Registers site in SITES arrays (websyn_start.sh, control_server.py) and raises EXPOSE range to 40015 in Dockerfile. Seed DB at instance_seed/drugs_com.db (managed via HuggingFace dataset). Co-Authored-By: Claude Sonnet 4.6 --- Dockerfile | 5 +- control_server.py | 2 +- sites/drugs_com/_health.py | 3 + sites/drugs_com/app.py | 1154 +++++++++++++++++ sites/drugs_com/requirements.txt | 6 + sites/drugs_com/static/css/.gitkeep | 0 sites/drugs_com/static/css/main.css | 328 +++++ sites/drugs_com/static/icons/.gitkeep | 0 sites/drugs_com/static/js/.gitkeep | 0 sites/drugs_com/tasks.jsonl | 21 + sites/drugs_com/templates/.gitkeep | 0 sites/drugs_com/templates/account.html | 48 + sites/drugs_com/templates/base.html | 95 ++ sites/drugs_com/templates/condition.html | 82 ++ sites/drugs_com/templates/drug_az.html | 65 + sites/drugs_com/templates/drug_class.html | 75 ++ sites/drugs_com/templates/drug_detail.html | 262 ++++ sites/drugs_com/templates/index.html | 100 ++ .../templates/interaction_checker.html | 150 +++ sites/drugs_com/templates/login.html | 26 + sites/drugs_com/templates/my_med_list.html | 31 + sites/drugs_com/templates/news.html | 71 + sites/drugs_com/templates/news_article.html | 68 + .../drugs_com/templates/pill_identifier.html | 105 ++ sites/drugs_com/templates/register.html | 30 + sites/drugs_com/templates/search.html | 54 + websyn_start.sh | 2 +- 27 files changed, 2779 insertions(+), 4 deletions(-) create mode 100644 sites/drugs_com/_health.py create mode 100644 sites/drugs_com/app.py create mode 100644 sites/drugs_com/requirements.txt create mode 100644 sites/drugs_com/static/css/.gitkeep create mode 100644 sites/drugs_com/static/css/main.css create mode 100644 sites/drugs_com/static/icons/.gitkeep create mode 100644 sites/drugs_com/static/js/.gitkeep create mode 100644 sites/drugs_com/tasks.jsonl create mode 100644 sites/drugs_com/templates/.gitkeep create mode 100644 sites/drugs_com/templates/account.html create mode 100644 sites/drugs_com/templates/base.html create mode 100644 sites/drugs_com/templates/condition.html create mode 100644 sites/drugs_com/templates/drug_az.html create mode 100644 sites/drugs_com/templates/drug_class.html create mode 100644 sites/drugs_com/templates/drug_detail.html create mode 100644 sites/drugs_com/templates/index.html create mode 100644 sites/drugs_com/templates/interaction_checker.html create mode 100644 sites/drugs_com/templates/login.html create mode 100644 sites/drugs_com/templates/my_med_list.html create mode 100644 sites/drugs_com/templates/news.html create mode 100644 sites/drugs_com/templates/news_article.html create mode 100644 sites/drugs_com/templates/pill_identifier.html create mode 100644 sites/drugs_com/templates/register.html create mode 100644 sites/drugs_com/templates/search.html diff --git a/Dockerfile b/Dockerfile index 991e5ab..d90e5a4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,7 +19,8 @@ RUN pip3 install --no-cache-dir \ SQLAlchemy==2.0.36 \ WTForms==3.2.1 \ email-validator==2.2.0 \ - Pillow==11.0.0 + Pillow==11.0.0 \ + requests==2.32.3 WORKDIR /opt/WebSyn @@ -33,6 +34,6 @@ COPY control_server.py /opt/control_server.py COPY site_runner.py /opt/site_runner.py RUN chmod +x /opt/websyn_start.sh -EXPOSE 8101 40000-40014 +EXPOSE 8101 40000-40015 CMD ["/opt/websyn_start.sh"] diff --git a/control_server.py b/control_server.py index c255253..d3604af 100644 --- a/control_server.py +++ b/control_server.py @@ -26,7 +26,7 @@ 'allrecipes', 'amazon', 'apple', 'arxiv', 'bbc_news', 'booking', 'github', 'google_flights', 'google_map', 'google_search', 'huggingface', 'wolfram_alpha', 'cambridge_dictionary', - 'coursera', 'espn', + 'coursera', 'espn', 'drugs_com', ] BASE_PORT = 40000 WEBSYN_DIR = '/opt/WebSyn' diff --git a/sites/drugs_com/_health.py b/sites/drugs_com/_health.py new file mode 100644 index 0000000..22a72a5 --- /dev/null +++ b/sites/drugs_com/_health.py @@ -0,0 +1,3 @@ +"""Per-site health probe (optional, called by control_server).""" +def health(): + return {"ok": True, "site": "drugs_com"} diff --git a/sites/drugs_com/app.py b/sites/drugs_com/app.py new file mode 100644 index 0000000..306a125 --- /dev/null +++ b/sites/drugs_com/app.py @@ -0,0 +1,1154 @@ +"""Drugs.com mirror — Flask app for the WebHarbor benchmark. + +Full mirror with drug catalog, A-Z index, drug detail pages, search, +interaction checker, pill identifier, conditions/classes, news, accounts, +reviews, and a "My Med List" save feature. +""" +import os +import json +import re +import string +from datetime import datetime, timedelta +from itertools import combinations + +from flask import ( + Flask, render_template, request, redirect, url_for, flash, jsonify, + abort, session, +) +from flask_sqlalchemy import SQLAlchemy +from flask_login import ( + LoginManager, UserMixin, login_user, logout_user, login_required, + current_user, +) +from flask_bcrypt import Bcrypt + +try: + import requests + HAS_REQUESTS = True +except ImportError: + HAS_REQUESTS = False + + +# --------------------------------------------------------------------------- +# App setup +# --------------------------------------------------------------------------- +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +os.makedirs(os.path.join(BASE_DIR, "instance"), exist_ok=True) + +app = Flask(__name__, instance_path=os.path.join(BASE_DIR, "instance")) +app.config["SECRET_KEY"] = "drugs_com-dev-secret-please-change" +app.config["SQLALCHEMY_DATABASE_URI"] = ( + "sqlite:///" + os.path.join(BASE_DIR, "instance", "drugs_com.db") +) +app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False + +db = SQLAlchemy(app) +bcrypt = Bcrypt(app) +login_manager = LoginManager(app) +login_manager.login_view = "login" + + +def slugify(text): + text = (text or "").lower().strip() + text = re.sub(r"[^a-z0-9]+", "-", text) + return text.strip("-") + + +# --------------------------------------------------------------------------- +# Models +# --------------------------------------------------------------------------- +class User(UserMixin, db.Model): + id = db.Column(db.Integer, primary_key=True) + username = db.Column(db.String(80), unique=True, nullable=False) + email = db.Column(db.String(120), unique=True, nullable=False) + password_hash = db.Column(db.String(255), nullable=False) + created_at = db.Column(db.DateTime, default=datetime.utcnow) + + reviews = db.relationship("DrugReview", backref="user", lazy=True) + saved_drugs = db.relationship("SavedDrug", backref="user", lazy=True) + + def set_password(self, raw): + self.password_hash = bcrypt.generate_password_hash(raw).decode("utf-8") + + def check_password(self, raw): + return bcrypt.check_password_hash(self.password_hash, raw) + + +class DrugClass(db.Model): + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(120), unique=True, nullable=False) + slug = db.Column(db.String(120), unique=True, nullable=False) + description = db.Column(db.Text) + drugs = db.relationship("Drug", backref="drug_class", lazy=True) + + +class Drug(db.Model): + id = db.Column(db.Integer, primary_key=True) + generic_name = db.Column(db.String(120), unique=True, nullable=False) + slug = db.Column(db.String(120), unique=True, nullable=False) + brand_names_json = db.Column(db.Text, default="[]") + drug_class_id = db.Column(db.Integer, db.ForeignKey("drug_class.id")) + availability = db.Column(db.String(40), default="Rx") + csa_schedule = db.Column(db.String(40), default="Not a controlled drug") + pregnancy_risk = db.Column(db.String(80), default="Consult your doctor") + pronunciation = db.Column(db.String(120)) + description = db.Column(db.Text) + uses = db.Column(db.Text) + warnings = db.Column(db.Text) + dosage = db.Column(db.Text) + side_effects = db.Column(db.Text) + interactions_text = db.Column(db.Text) + faq_json = db.Column(db.Text, default="[]") + avg_rating = db.Column(db.Float, default=0.0) + review_count = db.Column(db.Integer, default=0) + is_featured = db.Column(db.Boolean, default=False) + conditions_json = db.Column(db.Text, default="[]") + related_drugs_json = db.Column(db.Text, default="[]") + reviewer_name = db.Column(db.String(120), default="Drugs.com editorial team") + reviewer_credential = db.Column(db.String(120), default="PharmD") + last_updated = db.Column(db.DateTime, default=datetime.utcnow) + + @property + def brand_names(self): + try: + return json.loads(self.brand_names_json or "[]") + except Exception: + return [] + + @property + def faq(self): + try: + return json.loads(self.faq_json or "[]") + except Exception: + return [] + + @property + def conditions_list(self): + try: + return json.loads(self.conditions_json or "[]") + except Exception: + return [] + + @property + def related_drugs(self): + try: + return json.loads(self.related_drugs_json or "[]") + except Exception: + return [] + + +class DrugImage(db.Model): + id = db.Column(db.Integer, primary_key=True) + drug_id = db.Column(db.Integer, db.ForeignKey("drug.id"), nullable=False) + imprint = db.Column(db.String(80)) + shape = db.Column(db.String(40)) + color = db.Column(db.String(40)) + strength = db.Column(db.String(80)) + manufacturer = db.Column(db.String(120)) + drug = db.relationship("Drug", backref="images") + + +class DrugInteraction(db.Model): + id = db.Column(db.Integer, primary_key=True) + drug_a_id = db.Column(db.Integer, db.ForeignKey("drug.id"), nullable=False) + drug_b_id = db.Column(db.Integer, db.ForeignKey("drug.id"), nullable=False) + severity = db.Column(db.String(20), default="unknown") + description = db.Column(db.Text) + drug_a = db.relationship("Drug", foreign_keys=[drug_a_id]) + drug_b = db.relationship("Drug", foreign_keys=[drug_b_id]) + + +class DrugReview(db.Model): + id = db.Column(db.Integer, primary_key=True) + drug_id = db.Column(db.Integer, db.ForeignKey("drug.id"), nullable=False) + user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False) + rating = db.Column(db.Integer, nullable=False) + title = db.Column(db.String(200)) + body = db.Column(db.Text) + condition_treated = db.Column(db.String(120)) + helpful_count = db.Column(db.Integer, default=0) + created_at = db.Column(db.DateTime, default=datetime.utcnow) + drug = db.relationship("Drug", backref="reviews") + + +class Condition(db.Model): + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(120), unique=True, nullable=False) + slug = db.Column(db.String(120), unique=True, nullable=False) + description = db.Column(db.Text) + drug_count = db.Column(db.Integer, default=0) + + +class DrugCondition(db.Model): + id = db.Column(db.Integer, primary_key=True) + drug_id = db.Column(db.Integer, db.ForeignKey("drug.id"), nullable=False) + condition_id = db.Column(db.Integer, db.ForeignKey("condition.id"), nullable=False) + + +class NewsArticle(db.Model): + id = db.Column(db.Integer, primary_key=True) + title = db.Column(db.String(255), nullable=False) + category = db.Column(db.String(80), nullable=False) + body = db.Column(db.Text) + source = db.Column(db.String(120), default="Drugs.com") + published_at = db.Column(db.DateTime, default=datetime.utcnow) + is_featured = db.Column(db.Boolean, default=False) + + +class SavedDrug(db.Model): + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False) + drug_id = db.Column(db.Integer, db.ForeignKey("drug.id"), nullable=False) + notes = db.Column(db.Text, default="") + created_at = db.Column(db.DateTime, default=datetime.utcnow) + drug = db.relationship("Drug") + + +@login_manager.user_loader +def load_user(uid): + return User.query.get(int(uid)) + + +# --------------------------------------------------------------------------- +# Seed data +# --------------------------------------------------------------------------- +DRUG_CLASSES = [ + ("Nonsteroidal anti-inflammatory drugs", "NSAIDs reduce pain, inflammation, and fever by inhibiting cyclooxygenase enzymes."), + ("Analgesics", "Analgesics relieve pain. They include opioid and non-opioid options."), + ("Opioids", "Opioids are strong pain relievers that work on opioid receptors in the central nervous system."), + ("Anticonvulsants", "Anticonvulsants treat seizures and are also used for nerve pain and mood disorders."), + ("ACE inhibitors", "ACE inhibitors lower blood pressure by blocking the conversion of angiotensin I to angiotensin II."), + ("Beta blockers", "Beta blockers slow the heart and reduce blood pressure by blocking adrenaline."), + ("Calcium channel blockers", "Calcium channel blockers relax blood vessels and lower blood pressure."), + ("Statins", "Statins lower cholesterol by inhibiting HMG-CoA reductase."), + ("ARBs", "Angiotensin II receptor blockers lower blood pressure by blocking angiotensin II."), + ("Diuretics", "Diuretics help the body remove excess salt and water through urine."), + ("Anticoagulants", "Anticoagulants prevent dangerous blood clots."), + ("Antiplatelet drugs", "Antiplatelet drugs prevent platelets from sticking together."), + ("Biguanides", "Biguanides reduce glucose production in the liver. Metformin is the most common."), + ("GLP-1 receptor agonists", "GLP-1 agonists improve blood sugar control and are used for type 2 diabetes and obesity."), + ("SGLT2 inhibitors", "SGLT2 inhibitors lower blood sugar by causing the kidneys to remove glucose in urine."), + ("DPP-4 inhibitors", "DPP-4 inhibitors increase insulin release after meals."), + ("Insulins", "Insulin is a hormone replacement used to control blood glucose in diabetes."), + ("SSRIs", "Selective serotonin reuptake inhibitors treat depression and anxiety."), + ("SNRIs", "Serotonin and norepinephrine reuptake inhibitors treat depression, anxiety, and pain."), + ("Atypical antidepressants", "Atypical antidepressants work via novel mechanisms."), + ("Benzodiazepines", "Benzodiazepines treat anxiety, seizures, and insomnia."), + ("Atypical antipsychotics", "Atypical antipsychotics treat schizophrenia, bipolar disorder, and other conditions."), + ("Mood stabilizers", "Mood stabilizers manage bipolar disorder."), + ("Tricyclic antidepressants", "TCAs treat depression and certain pain conditions."), + ("Penicillins", "Penicillins are beta-lactam antibiotics."), + ("Macrolides", "Macrolide antibiotics treat respiratory and skin infections."), + ("Fluoroquinolones", "Fluoroquinolones treat a broad range of bacterial infections."), + ("Tetracyclines", "Tetracyclines treat bacterial infections, acne, and other conditions."), + ("Cephalosporins", "Cephalosporins are beta-lactam antibiotics related to penicillins."), + ("Lincosamides", "Lincosamide antibiotics like clindamycin treat anaerobic and gram-positive infections."), + ("Thyroid hormones", "Thyroid hormone replacements treat hypothyroidism."), + ("Bronchodilators", "Bronchodilators relax airway muscles, easing breathing."), + ("Leukotriene modifiers", "Leukotriene modifiers reduce asthma and allergy symptoms."), + ("Corticosteroids", "Corticosteroids reduce inflammation and suppress the immune system."), + ("Muscle relaxants", "Muscle relaxants reduce muscle spasms and pain."), + ("Antiemetics", "Antiemetics prevent and treat nausea and vomiting."), + ("PDE5 inhibitors", "PDE5 inhibitors treat erectile dysfunction and pulmonary hypertension."), + ("5-alpha-reductase inhibitors", "5-ARIs treat benign prostatic hyperplasia and male pattern hair loss."), + ("Alpha blockers", "Alpha blockers relax smooth muscle, helping with BPH and high blood pressure."), + ("DMARDs", "Disease-modifying antirheumatic drugs slow autoimmune disease progression."), + ("TNF inhibitors", "TNF inhibitors are biologics that block tumor necrosis factor."), + ("Triptans", "Triptans treat migraine and cluster headaches."), + ("Sleep aids", "Sleep aids help with short-term insomnia."), + ("Opioid antagonists", "Opioid antagonists block opioid receptors; used for overdose reversal."), + ("Stimulants", "Stimulants treat ADHD and narcolepsy."), + ("Xanthine oxidase inhibitors", "Xanthine oxidase inhibitors lower uric acid in gout."), + ("Retinoids", "Retinoids treat severe acne and other skin conditions."), + ("Antihistamines", "Antihistamines relieve allergy symptoms by blocking histamine."), + ("Proton pump inhibitors", "PPIs reduce stomach acid production."), + ("H2 blockers", "H2 blockers reduce stomach acid by blocking histamine H2 receptors."), + ("Decongestants", "Decongestants relieve nasal congestion."), + ("Cough suppressants", "Cough suppressants reduce coughing."), + ("Antimalarials", "Antimalarials treat malaria and certain autoimmune diseases."), + ("Antimetabolites", "Antimetabolites disrupt cell metabolism; used in cancer and autoimmune diseases."), + ("Monoclonal antibodies", "Monoclonal antibodies target specific molecules in disease."), + ("Supplements", "Dietary supplements provide nutrients or substances like melatonin."), +] + + +DRUGS_DATA = [ + # (generic_name, class_name, availability, csa, pronunciation, brand_names, conditions) + ("ibuprofen", "Nonsteroidal anti-inflammatory drugs", "Rx and/or OTC", "Not a controlled drug", "eye-bue-PROE-fen", ["Advil", "Motrin", "Nuprin"], ["pain", "fever", "arthritis", "migraine"]), + ("naproxen", "Nonsteroidal anti-inflammatory drugs", "Rx and/or OTC", "Not a controlled drug", "na-PROX-en", ["Aleve", "Naprosyn", "Anaprox"], ["pain", "arthritis", "gout"]), + ("aspirin", "Nonsteroidal anti-inflammatory drugs", "OTC", "Not a controlled drug", "AS-pir-in", ["Bayer", "Bufferin", "Ecotrin"], ["pain", "fever", "heart_disease"]), + ("meloxicam", "Nonsteroidal anti-inflammatory drugs", "Rx", "Not a controlled drug", "mel-OX-i-kam", ["Mobic", "Vivlodex"], ["arthritis", "pain"]), + ("celecoxib", "Nonsteroidal anti-inflammatory drugs", "Rx", "Not a controlled drug", "sel-e-KOX-ib", ["Celebrex"], ["arthritis", "pain"]), + ("acetaminophen", "Analgesics", "OTC", "Not a controlled drug", "a-seet-a-MIN-oh-fen", ["Tylenol", "Panadol"], ["pain", "fever"]), + ("tramadol", "Opioids", "Rx", "C-IV", "TRAM-a-dol", ["Ultram", "ConZip"], ["pain"]), + ("oxycodone", "Opioids", "Rx", "C-II", "ox-i-KOE-done", ["OxyContin", "Roxicodone", "Percocet"], ["pain"]), + ("hydrocodone", "Opioids", "Rx", "C-II", "hye-droe-KOE-done", ["Vicodin", "Norco", "Lortab"], ["pain"]), + ("gabapentin", "Anticonvulsants", "Rx", "Not a controlled drug", "GA-ba-PEN-tin", ["Neurontin", "Gralise"], ["epilepsy", "pain"]), + ("pregabalin", "Anticonvulsants", "Rx", "C-V", "pre-GAB-a-lin", ["Lyrica"], ["pain", "epilepsy"]), + ("lisinopril", "ACE inhibitors", "Rx", "Not a controlled drug", "lyse-IN-oh-pril", ["Zestril", "Prinivil"], ["hypertension", "heart_disease"]), + ("metoprolol", "Beta blockers", "Rx", "Not a controlled drug", "me-TOE-proe-lol", ["Lopressor", "Toprol XL"], ["hypertension", "heart_disease"]), + ("amlodipine", "Calcium channel blockers", "Rx", "Not a controlled drug", "am-LOE-di-peen", ["Norvasc"], ["hypertension"]), + ("atorvastatin", "Statins", "Rx", "Not a controlled drug", "a-TOR-va-sta-tin", ["Lipitor"], ["high_cholesterol", "heart_disease"]), + ("rosuvastatin", "Statins", "Rx", "Not a controlled drug", "roe-SOO-va-sta-tin", ["Crestor"], ["high_cholesterol"]), + ("losartan", "ARBs", "Rx", "Not a controlled drug", "loe-SAR-tan", ["Cozaar"], ["hypertension"]), + ("carvedilol", "Beta blockers", "Rx", "Not a controlled drug", "KAR-ve-dil-ol", ["Coreg"], ["heart_disease", "hypertension"]), + ("hydrochlorothiazide", "Diuretics", "Rx", "Not a controlled drug", "hye-droe-klor-oh-THYE-a-zide", ["Microzide"], ["hypertension"]), + ("furosemide", "Diuretics", "Rx", "Not a controlled drug", "fur-OH-se-mide", ["Lasix"], ["heart_disease", "hypertension"]), + ("warfarin", "Anticoagulants", "Rx", "Not a controlled drug", "WAR-far-in", ["Coumadin", "Jantoven"], ["heart_disease"]), + ("clopidogrel", "Antiplatelet drugs", "Rx", "Not a controlled drug", "kloh-PID-oh-grel", ["Plavix"], ["heart_disease"]), + ("spironolactone", "Diuretics", "Rx", "Not a controlled drug", "speer-on-oh-LAK-tone", ["Aldactone"], ["hypertension", "heart_disease"]), + ("metformin", "Biguanides", "Rx", "Not a controlled drug", "met-FOR-min", ["Glucophage", "Fortamet", "Glumetza"], ["diabetes", "obesity"]), + ("semaglutide", "GLP-1 receptor agonists", "Rx", "Not a controlled drug", "sem-a-GLOO-tide", ["Ozempic", "Wegovy", "Rybelsus"], ["diabetes", "obesity"]), + ("tirzepatide", "GLP-1 receptor agonists", "Rx", "Not a controlled drug", "tir-ZEP-a-tide", ["Mounjaro", "Zepbound"], ["diabetes", "obesity"]), + ("empagliflozin", "SGLT2 inhibitors", "Rx", "Not a controlled drug", "em-pa-gli-FLOE-zin", ["Jardiance"], ["diabetes", "heart_disease"]), + ("sitagliptin", "DPP-4 inhibitors", "Rx", "Not a controlled drug", "sit-a-GLIP-tin", ["Januvia"], ["diabetes"]), + ("insulin glargine", "Insulins", "Rx", "Not a controlled drug", "IN-su-lin GLAR-jeen", ["Lantus", "Basaglar", "Toujeo"], ["diabetes"]), + ("sertraline", "SSRIs", "Rx", "Not a controlled drug", "SER-tra-leen", ["Zoloft"], ["depression", "anxiety"]), + ("escitalopram", "SSRIs", "Rx", "Not a controlled drug", "es-sye-TAL-oh-pram", ["Lexapro"], ["depression", "anxiety"]), + ("fluoxetine", "SSRIs", "Rx", "Not a controlled drug", "floo-OX-e-teen", ["Prozac", "Sarafem"], ["depression", "anxiety"]), + ("duloxetine", "SNRIs", "Rx", "Not a controlled drug", "doo-LOX-e-teen", ["Cymbalta"], ["depression", "pain"]), + ("bupropion", "Atypical antidepressants", "Rx", "Not a controlled drug", "byoo-PROE-pee-on", ["Wellbutrin", "Zyban"], ["depression"]), + ("venlafaxine", "SNRIs", "Rx", "Not a controlled drug", "ven-la-FAX-een", ["Effexor"], ["depression", "anxiety"]), + ("alprazolam", "Benzodiazepines", "Rx", "C-IV", "al-PRAY-zoe-lam", ["Xanax"], ["anxiety"]), + ("clonazepam", "Benzodiazepines", "Rx", "C-IV", "kloe-NAZ-e-pam", ["Klonopin"], ["anxiety", "epilepsy"]), + ("lorazepam", "Benzodiazepines", "Rx", "C-IV", "lor-AZ-e-pam", ["Ativan"], ["anxiety"]), + ("quetiapine", "Atypical antipsychotics", "Rx", "Not a controlled drug", "kwe-TYE-a-peen", ["Seroquel"], ["schizophrenia", "bipolar_disorder"]), + ("aripiprazole", "Atypical antipsychotics", "Rx", "Not a controlled drug", "ay-ri-PIP-ray-zole", ["Abilify"], ["schizophrenia", "bipolar_disorder", "depression"]), + ("lithium", "Mood stabilizers", "Rx", "Not a controlled drug", "LITH-ee-um", ["Lithobid", "Eskalith"], ["bipolar_disorder"]), + ("amitriptyline", "Tricyclic antidepressants", "Rx", "Not a controlled drug", "a-mee-TRIP-ti-leen", ["Elavil"], ["depression", "pain"]), + ("amoxicillin", "Penicillins", "Rx", "Not a controlled drug", "a-mox-i-SIL-in", ["Amoxil", "Moxatag"], ["bacterial_infections"]), + ("azithromycin", "Macrolides", "Rx", "Not a controlled drug", "az-ith-roe-MYE-sin", ["Zithromax", "Z-Pak"], ["bacterial_infections"]), + ("ciprofloxacin", "Fluoroquinolones", "Rx", "Not a controlled drug", "sip-roe-FLOX-a-sin", ["Cipro"], ["bacterial_infections"]), + ("doxycycline", "Tetracyclines", "Rx", "Not a controlled drug", "dox-i-SYE-kleen", ["Vibramycin", "Doryx"], ["bacterial_infections", "acne"]), + ("clindamycin", "Lincosamides", "Rx", "Not a controlled drug", "klin-da-MYE-sin", ["Cleocin"], ["bacterial_infections", "acne"]), + ("cephalexin", "Cephalosporins", "Rx", "Not a controlled drug", "sef-a-LEX-in", ["Keflex"], ["bacterial_infections"]), + ("levothyroxine", "Thyroid hormones", "Rx", "Not a controlled drug", "lee-voe-thye-ROX-een", ["Synthroid", "Levoxyl"], ["hypothyroidism"]), + ("albuterol", "Bronchodilators", "Rx", "Not a controlled drug", "al-BUE-ter-ol", ["ProAir", "Ventolin", "Proventil"], ["asthma"]), + ("montelukast", "Leukotriene modifiers", "Rx", "Not a controlled drug", "mon-te-LOO-kast", ["Singulair"], ["asthma", "allergies"]), + ("prednisone", "Corticosteroids", "Rx", "Not a controlled drug", "PRED-ni-sone", ["Deltasone", "Rayos"], ["arthritis", "allergies", "lupus"]), + ("cyclobenzaprine", "Muscle relaxants", "Rx", "Not a controlled drug", "sye-kloe-BEN-za-preen", ["Flexeril", "Amrix"], ["pain"]), + ("ondansetron", "Antiemetics", "Rx", "Not a controlled drug", "on-DAN-se-tron", ["Zofran"], ["cancer"]), + ("sildenafil", "PDE5 inhibitors", "Rx", "Not a controlled drug", "sil-DEN-a-fil", ["Viagra", "Revatio"], ["erectile_dysfunction"]), + ("finasteride", "5-alpha-reductase inhibitors", "Rx", "Not a controlled drug", "fi-NAS-teer-ide", ["Proscar", "Propecia"], ["bph"]), + ("tamsulosin", "Alpha blockers", "Rx", "Not a controlled drug", "tam-SOO-loe-sin", ["Flomax"], ["bph"]), + ("hydroxychloroquine", "Antimalarials", "Rx", "Not a controlled drug", "hye-drox-ee-KLOR-oh-kwin", ["Plaquenil"], ["lupus", "arthritis"]), + ("methotrexate", "Antimetabolites", "Rx", "Not a controlled drug", "meth-oh-TREX-ate", ["Trexall", "Otrexup"], ["arthritis", "psoriasis", "cancer"]), + ("adalimumab", "TNF inhibitors", "Rx", "Not a controlled drug", "a-da-LIM-ue-mab", ["Humira"], ["arthritis", "crohns_disease", "psoriasis"]), + ("dupilumab", "Monoclonal antibodies", "Rx", "Not a controlled drug", "doo-PIL-ue-mab", ["Dupixent"], ["eczema", "asthma"]), + ("sumatriptan", "Triptans", "Rx", "Not a controlled drug", "soo-ma-TRIP-tan", ["Imitrex"], ["migraine"]), + ("zolpidem", "Sleep aids", "Rx", "C-IV", "zole-PI-dem", ["Ambien"], ["insomnia"]), + ("naloxone", "Opioid antagonists", "Rx and/or OTC", "Not a controlled drug", "nal-OX-one", ["Narcan", "Evzio"], []), + ("methylphenidate", "Stimulants", "Rx", "C-II", "meth-il-FEN-i-date", ["Ritalin", "Concerta"], ["adhd"]), + ("colchicine", "Xanthine oxidase inhibitors", "Rx", "Not a controlled drug", "KOL-chi-seen", ["Colcrys", "Mitigare"], ["gout"]), + ("allopurinol", "Xanthine oxidase inhibitors", "Rx", "Not a controlled drug", "al-oh-PURE-i-nol", ["Zyloprim"], ["gout"]), + ("isotretinoin", "Retinoids", "Rx", "Not a controlled drug", "eye-soe-tret-IN-oh-in", ["Accutane", "Claravis"], ["acne"]), + ("diphenhydramine", "Antihistamines", "OTC", "Not a controlled drug", "dye-fen-HYE-dra-meen", ["Benadryl"], ["allergies", "insomnia"]), + ("loratadine", "Antihistamines", "OTC", "Not a controlled drug", "lor-AT-a-deen", ["Claritin", "Alavert"], ["allergies"]), + ("cetirizine", "Antihistamines", "OTC", "Not a controlled drug", "se-TIR-i-zeen", ["Zyrtec"], ["allergies"]), + ("omeprazole", "Proton pump inhibitors", "Rx and/or OTC", "Not a controlled drug", "oh-MEP-ra-zole", ["Prilosec"], ["acid_reflux"]), + ("famotidine", "H2 blockers", "Rx and/or OTC", "Not a controlled drug", "fa-MOE-ti-deen", ["Pepcid"], ["acid_reflux"]), + ("melatonin", "Supplements", "OTC", "Not a controlled drug", "mel-a-TOE-nin", ["Natrol Melatonin"], ["insomnia"]), + ("pseudoephedrine", "Decongestants", "OTC", "Not a controlled drug", "soo-doe-e-FED-rin", ["Sudafed"], ["allergies"]), + ("dextromethorphan", "Cough suppressants", "OTC", "Not a controlled drug", "dex-troe-meth-OR-fan", ["Robitussin DM", "Delsym"], []), + ("citalopram", "SSRIs", "Rx", "Not a controlled drug", "sye-TAL-oh-pram", ["Celexa"], ["depression", "anxiety"]), + ("trazodone", "Atypical antidepressants", "Rx", "Not a controlled drug", "TRAZ-oh-done", ["Desyrel", "Oleptro"], ["depression", "insomnia"]), + ("lamotrigine", "Anticonvulsants", "Rx", "Not a controlled drug", "la-MOE-tri-jeen", ["Lamictal"], ["epilepsy", "bipolar_disorder"]), + ("topiramate", "Anticonvulsants", "Rx", "Not a controlled drug", "toe-PYRE-a-mate", ["Topamax"], ["epilepsy", "migraine"]), + ("verapamil", "Calcium channel blockers", "Rx", "Not a controlled drug", "ver-AP-a-mil", ["Calan", "Verelan"], ["hypertension", "heart_disease"]), +] + + +CONDITIONS_DATA = [ + ("pain", "Pain", "Pain is an unpleasant sensory and emotional experience. Many drugs treat acute and chronic pain."), + ("fever", "Fever", "Fever is a temporary increase in body temperature, often due to illness."), + ("hypertension", "Hypertension (High Blood Pressure)", "Hypertension is a long-term condition where blood pressure in the arteries is persistently elevated."), + ("diabetes", "Diabetes (Type 2)", "Type 2 diabetes is a chronic condition that affects how the body metabolizes glucose."), + ("depression", "Depression", "Depression is a mood disorder causing persistent feelings of sadness and loss of interest."), + ("anxiety", "Anxiety", "Anxiety disorders involve excessive worry or fear that interferes with daily activities."), + ("high_cholesterol", "High Cholesterol", "High cholesterol increases the risk of heart disease and stroke."), + ("heart_disease", "Heart Disease", "Heart disease describes a range of conditions affecting the heart."), + ("asthma", "Asthma", "Asthma is a condition in which airways narrow and swell, causing breathing difficulty."), + ("allergies", "Allergies", "Allergies are immune system reactions to substances that are usually harmless."), + ("arthritis", "Arthritis", "Arthritis is inflammation of one or more joints, causing pain and stiffness."), + ("adhd", "ADHD", "Attention-deficit/hyperactivity disorder affects attention, impulse control, and activity levels."), + ("bipolar_disorder", "Bipolar Disorder", "Bipolar disorder causes extreme mood swings including emotional highs and lows."), + ("schizophrenia", "Schizophrenia", "Schizophrenia is a serious mental disorder affecting thoughts, feelings, and behavior."), + ("epilepsy", "Epilepsy", "Epilepsy is a neurological disorder marked by recurrent seizures."), + ("insomnia", "Insomnia", "Insomnia is difficulty falling or staying asleep."), + ("erectile_dysfunction", "Erectile Dysfunction", "Erectile dysfunction is the inability to achieve or maintain an erection."), + ("hypothyroidism", "Hypothyroidism", "Hypothyroidism is an underactive thyroid gland producing insufficient hormone."), + ("bacterial_infections", "Bacterial Infections", "Bacterial infections are illnesses caused by harmful bacteria."), + ("acne", "Acne", "Acne is a skin condition that occurs when hair follicles become plugged with oil and dead skin cells."), + ("gout", "Gout", "Gout is a form of inflammatory arthritis caused by uric acid crystals in joints."), + ("migraine", "Migraine", "Migraine is a neurological condition causing intense headaches."), + ("acid_reflux", "Acid Reflux (GERD)", "GERD occurs when stomach acid frequently flows back into the esophagus."), + ("bph", "Benign Prostatic Hyperplasia", "BPH is a noncancerous enlargement of the prostate gland."), + ("cancer", "Cancer", "Cancer is a disease in which abnormal cells divide uncontrollably and destroy body tissue."), + ("lupus", "Lupus", "Lupus is a systemic autoimmune disease where the immune system attacks tissues."), + ("crohns_disease", "Crohn's Disease", "Crohn's disease is a chronic inflammatory bowel disease."), + ("psoriasis", "Psoriasis", "Psoriasis is a skin disease causing red, itchy, scaly patches."), + ("eczema", "Eczema", "Eczema is a condition that makes skin red and itchy."), + ("obesity", "Obesity", "Obesity is a complex disease involving an excessive amount of body fat."), +] + + +PILL_IMAGES_DATA = [ + # (generic_name, imprint, shape, color, strength, manufacturer) + ("ibuprofen", "I-2", "Round", "White", "200 mg", "Advil"), + ("ibuprofen", "44-291", "Round", "Brown", "200 mg", "LNK International"), + ("ibuprofen", "IP 466", "Oval", "White", "800 mg", "Amneal Pharmaceuticals"), + ("acetaminophen", "TYLENOL 500", "Oblong", "White", "500 mg", "McNeil"), + ("acetaminophen", "L484", "Oblong", "White", "500 mg", "Kroger"), + ("metformin", "M 500", "Round", "White", "500 mg", "Mylan"), + ("metformin", "Z 70", "Oval", "White", "1000 mg", "Zydus"), + ("lisinopril", "10 MG", "Oval", "White", "10 mg", "Lupin"), + ("lisinopril", "M L24", "Round", "Pink", "20 mg", "Mylan"), + ("atorvastatin", "PD 156", "Oval", "White", "20 mg", "Pfizer"), + ("atorvastatin", "A 10", "Oval", "White", "10 mg", "Apotex"), + ("amlodipine", "G 1530", "Round", "White", "5 mg", "Greenstone"), + ("amoxicillin", "AMOX 500", "Capsule", "Pink/Buff", "500 mg", "Sandoz"), + ("azithromycin", "PLIVA 333", "Round", "Pink", "250 mg", "Pliva"), + ("ciprofloxacin", "CIPRO 500", "Oblong", "White", "500 mg", "Bayer"), + ("sertraline", "ZOLOFT 50", "Oblong", "Blue", "50 mg", "Pfizer"), + ("sertraline", "G 4900", "Oblong", "Blue", "100 mg", "Greenstone"), + ("fluoxetine", "PROZAC 20", "Capsule", "Green/Yellow", "20 mg", "Eli Lilly"), + ("alprazolam", "XANAX 0.5", "Oval", "Peach", "0.5 mg", "Pfizer"), + ("alprazolam", "G 372", "Oval", "Blue", "1 mg", "Greenstone"), + ("oxycodone", "M 30", "Round", "Blue", "30 mg", "Mallinckrodt"), + ("hydrocodone", "M367", "Oblong", "White", "10/325 mg", "Mallinckrodt"), + ("tramadol", "AN 627", "Round", "White", "50 mg", "Amneal"), + ("gabapentin", "IP 102", "Capsule", "Yellow", "300 mg", "Amneal"), + ("levothyroxine", "M L 4", "Round", "Yellow", "50 mcg", "Mylan"), + ("warfarin", "TV 5", "Round", "Pink", "5 mg", "Teva"), + ("prednisone", "WESTWARD 477", "Round", "White", "20 mg", "West-Ward"), + ("omeprazole", "57", "Capsule", "Purple", "20 mg", "Dr. Reddy's"), + ("loratadine", "L612", "Round", "White", "10 mg", "Perrigo"), + ("cetirizine", "Z 10", "Round", "White", "10 mg", "Zyrtec"), + ("zolpidem", "TEVA 73", "Round", "Pink", "5 mg", "Teva"), + ("clonazepam", "C 1", "Round", "Yellow", "0.5 mg", "Accord"), + ("methylphenidate", "M 5", "Round", "Yellow", "5 mg", "Mallinckrodt"), +] + + +INTERACTIONS_DATA = [ + ("ibuprofen", "warfarin", "major", "Concurrent use of ibuprofen and warfarin significantly increases the risk of serious bleeding. NSAIDs inhibit platelet function and can cause GI ulceration; warfarin already increases bleeding risk."), + ("ibuprofen", "aspirin", "moderate", "Ibuprofen can interfere with the cardioprotective antiplatelet effect of low-dose aspirin. If both are needed, take ibuprofen at least 8 hours before or 30 minutes after aspirin."), + ("warfarin", "aspirin", "major", "Combining warfarin with aspirin substantially increases bleeding risk. Concurrent use should only occur under close medical supervision."), + ("sertraline", "tramadol", "major", "Combining sertraline (SSRI) with tramadol can cause serotonin syndrome — a potentially life-threatening reaction. Symptoms include agitation, hallucinations, rapid heart rate, fever."), + ("fluoxetine", "tramadol", "major", "Fluoxetine combined with tramadol may cause serotonin syndrome. Also, fluoxetine inhibits CYP2D6, reducing tramadol's analgesic effect."), + ("alprazolam", "oxycodone", "major", "Combining benzodiazepines with opioids can result in profound sedation, respiratory depression, coma, and death."), + ("alprazolam", "hydrocodone", "major", "Concurrent use of alprazolam and hydrocodone increases the risk of fatal respiratory depression."), + ("metoprolol", "verapamil", "major", "Combining a beta blocker with a non-dihydropyridine calcium channel blocker can cause severe bradycardia, hypotension, and heart block."), + ("ciprofloxacin", "warfarin", "major", "Ciprofloxacin can substantially increase warfarin's anticoagulant effect, raising INR and bleeding risk."), + ("lithium", "ibuprofen", "major", "NSAIDs reduce renal lithium clearance, potentially leading to lithium toxicity. Monitor lithium levels closely."), + ("metformin", "ciprofloxacin", "moderate", "Fluoroquinolones may disturb blood glucose, causing either hypo- or hyperglycemia in patients on metformin."), + ("prednisone", "ibuprofen", "major", "Combining corticosteroids with NSAIDs substantially increases the risk of GI ulceration and bleeding."), + ("sildenafil", "isosorbide", "major", "Combining PDE5 inhibitors with nitrates can cause severe, potentially fatal hypotension."), + ("clopidogrel", "omeprazole", "moderate", "Omeprazole inhibits CYP2C19 and can reduce the antiplatelet effect of clopidogrel. Consider pantoprazole instead."), + ("simvastatin", "amiodarone", "major", "Amiodarone can increase simvastatin levels, raising the risk of severe muscle injury (rhabdomyolysis)."), + ("escitalopram", "tramadol", "major", "Combining SSRIs with tramadol may precipitate serotonin syndrome."), + ("duloxetine", "tramadol", "major", "Both drugs increase serotonin levels; concurrent use can cause serotonin syndrome."), + ("methotrexate", "ibuprofen", "major", "NSAIDs can reduce methotrexate clearance, leading to severe methotrexate toxicity including pancytopenia."), + ("levothyroxine", "omeprazole", "moderate", "Reduced gastric acidity from PPIs can impair levothyroxine absorption. Separate dosing and monitor TSH."), + ("amlodipine", "simvastatin", "moderate", "Amlodipine increases simvastatin exposure; limit simvastatin dose to 20 mg when used with amlodipine."), + ("furosemide", "lisinopril", "moderate", "ACE inhibitors plus diuretics may cause symptomatic hypotension, especially with first doses."), + ("clonazepam", "hydrocodone", "major", "Benzodiazepine plus opioid combinations carry a serious risk of respiratory depression and death."), + ("lorazepam", "oxycodone", "major", "Concurrent benzodiazepine and opioid use can cause profound CNS and respiratory depression."), + ("aripiprazole", "fluoxetine", "moderate", "Fluoxetine inhibits CYP2D6 and may increase aripiprazole levels. Dose adjustments may be needed."), + ("hydrochlorothiazide", "lithium", "major", "Thiazide diuretics reduce lithium clearance and can cause lithium toxicity."), +] + + +NEWS_DATA = [ + ("FDA Approves New GLP-1 Agonist for Weight Management", "New Drug Approvals", "The U.S. Food and Drug Administration today approved a new GLP-1 receptor agonist for chronic weight management in adults with obesity. The approval was based on phase 3 trials showing significant weight reduction compared with placebo."), + ("FDA Approves Updated Influenza Vaccines for 2025-2026 Season", "New Drug Approvals", "The FDA has approved updated influenza vaccine formulations for the upcoming flu season, targeting circulating H1N1, H3N2, and B-lineage strains."), + ("FDA Approves First Generic of Popular Migraine Medication", "New Drug Approvals", "The FDA today approved the first generic version of a widely used CGRP inhibitor for the prevention of migraine, expanding access for millions of patients."), + ("New SGLT2 Inhibitor Approved for Chronic Kidney Disease", "New Drug Approvals", "The FDA has expanded approval of an SGLT2 inhibitor to include adults with chronic kidney disease, based on trial data showing reduced progression to kidney failure."), + ("FDA Approves Biosimilar for Common Biologic", "New Drug Approvals", "A new biosimilar to a widely prescribed TNF inhibitor has received FDA approval, offering a lower-cost alternative for patients with autoimmune conditions."), + ("Study: Statins May Reduce Risk of Severe COVID-19", "Medical", "A large observational study published this week suggests that patients on statin therapy may have a reduced risk of severe outcomes from COVID-19 infection."), + ("Doctors Caution Against Combining NSAIDs With Blood Thinners", "Medical", "Clinicians are reminding patients of the substantial bleeding risk associated with combining NSAIDs such as ibuprofen and naproxen with anticoagulants like warfarin."), + ("Researchers Identify New Risk Factors for Type 2 Diabetes", "Medical", "A multi-center study has identified previously unrecognized lifestyle and genetic factors that increase the risk of developing type 2 diabetes."), + ("Long-term SSRI Use Linked to Bone Density Changes", "Medical", "New research indicates a possible association between long-term SSRI use and changes in bone mineral density in older adults."), + ("Hospital-acquired Infections Decline With Updated Guidelines", "Medical", "Updated infection-control guidelines have contributed to a notable decline in hospital-acquired infections nationwide."), + ("FDA Issues Safety Alert on Certain Recalled Blood Pressure Medications", "FDA Alerts", "The FDA has announced a voluntary recall of specific lots of valsartan-containing products due to detection of trace impurities."), + ("FDA Warns About Unapproved Online Sales of Weight-loss Drugs", "FDA Alerts", "The FDA has issued a public health alert about counterfeit and unapproved versions of popular GLP-1 weight-loss medications sold online."), + ("FDA Strengthens Boxed Warning for Quinolone Antibiotics", "FDA Alerts", "The FDA has updated boxed warnings on fluoroquinolone antibiotics including ciprofloxacin and levofloxacin to highlight risks of aortic dissection."), + ("FDA Recalls Eye Drops Due to Contamination Concerns", "FDA Alerts", "Several lots of over-the-counter eye drops have been recalled due to possible bacterial contamination."), + ("FDA Warns of Counterfeit Ozempic in Distribution Channels", "FDA Alerts", "The FDA has identified counterfeit semaglutide injections in the legitimate U.S. drug supply chain and is investigating."), + ("Phase 3 Trial Begins for Novel Alzheimer's Disease Therapy", "Clinical Trials", "A pharmaceutical company has launched a global phase 3 clinical trial of a new anti-amyloid antibody for early Alzheimer's disease."), + ("Clinical Trial Shows Promise for New Long-acting Insulin", "Clinical Trials", "A once-weekly insulin formulation has shown comparable efficacy to daily basal insulin in a phase 3 trial of type 2 diabetes patients."), + ("Cancer Immunotherapy Trial Reports Encouraging Survival Results", "Clinical Trials", "Updated results from an ongoing phase 3 trial show improved overall survival with a combination immunotherapy regimen for advanced melanoma."), + ("Researchers Begin Trial of Oral GLP-1 for Obesity", "Clinical Trials", "A new phase 3 trial will evaluate the efficacy and safety of a once-daily oral GLP-1 agonist for chronic weight management."), + ("Trial Investigates Microbiome Therapy for Recurrent C. difficile", "Clinical Trials", "Investigators are enrolling patients in a phase 3 trial of a live microbiome therapeutic for prevention of recurrent C. difficile infection."), +] + + +REVIEW_TEMPLATES = [ + ("Worked great for my condition", 9, "I was prescribed this for {cond} and have had excellent results. Minimal side effects so far. Highly recommend talking to your doctor about this option."), + ("Took a while to kick in", 7, "It took about 4 weeks before I really noticed a difference treating my {cond}. Once it started working, the effect has been consistent. Some mild side effects at first."), + ("Game changer", 10, "This medication has been a game changer for managing my {cond}. I feel like myself again after years of struggling. The side effects were minimal for me."), + ("Not for me", 3, "Tried this for {cond} for 6 weeks and the side effects outweighed the benefits. Switched to something else. Everyone reacts differently though."), + ("Mixed feelings", 5, "Some days it helps with my {cond}, other days less so. Side effects include some drowsiness. Talk with your doctor about whether this fits your situation."), + ("Effective but watch dosage", 8, "Effective treatment for {cond}, but you really need to follow dosing instructions carefully. I had to adjust mine with my doctor's help."), + ("Worked for a while", 6, "Helped with my {cond} for about a year, then I felt the effects wear off. Doctor is adjusting my treatment plan."), + ("Best medication I've tried", 10, "Out of several medications I've tried for {cond}, this has been the most effective with the fewest side effects. Quality of life vastly improved."), + ("Cautious recommendation", 7, "It works for my {cond} but the cost is significant. Insurance covered most of it. Be sure to discuss generics."), + ("Side effects too much", 2, "The side effects were too disruptive for my daily life, even though it did help with {cond}. Discontinued after a few weeks."), +] + + +BENCHMARK_USERS = [ + {"email": "alice.j@test.com", "username": "alice_j", "password": "TestPass123!"}, + {"email": "bob.c@test.com", "username": "bob_c", "password": "TestPass123!"}, + {"email": "carol.d@test.com", "username": "carol_d", "password": "TestPass123!"}, + {"email": "david.k@test.com", "username": "david_k", "password": "TestPass123!"}, +] + + +# --------------------------------------------------------------------------- +# OpenFDA fetch +# --------------------------------------------------------------------------- +def fetch_openfda_label(generic): + """Fetch label info from OpenFDA. Returns dict or None on any failure.""" + if not HAS_REQUESTS: + return None + try: + q = generic.replace(" ", "+") + url = f"https://api.fda.gov/drug/label.json?search=openfda.generic_name:{q}&limit=1" + r = requests.get(url, timeout=4) + if r.status_code != 200: + return None + data = r.json() + results = data.get("results", []) + if not results: + return None + return results[0] + except Exception: + return None + + +def first(text_field): + if not text_field: + return None + if isinstance(text_field, list): + return text_field[0] if text_field else None + return str(text_field) + + +def truncate(text, n=2400): + if not text: + return text + text = str(text).strip() + return text if len(text) <= n else text[:n].rsplit(" ", 1)[0] + "..." + + +def synthetic_content(generic, cls_name, conditions): + """Generate realistic synthetic content when OpenFDA isn't available.""" + cond_text = ", ".join(conditions) if conditions else "various medical conditions" + return { + "description": f"{generic.capitalize()} is a {cls_name.lower()} medication used to treat {cond_text}. It works by addressing the underlying biological processes associated with these conditions.", + "uses": f"{generic.capitalize()} is indicated for the treatment of {cond_text}. Your doctor may prescribe this medication for other purposes not listed here. Always follow your healthcare provider's instructions regarding indications and proper use.", + "warnings": f"Do not use {generic} if you are allergic to it or to similar medications. Tell your doctor about all your medical conditions, especially kidney or liver problems, heart disease, and any history of allergic reactions. Inform your doctor if you are pregnant or breastfeeding. Stop taking {generic} and contact your doctor immediately if you experience severe allergic reactions, unusual bleeding, severe abdominal pain, or signs of a serious skin reaction.", + "dosage": f"The dose of {generic} should be individualized based on the patient's condition, age, and response to therapy. Follow the directions on your prescription label. Do not take more or less than prescribed. If you miss a dose, take it as soon as you remember. Do not double up on doses.", + "side_effects": f"Common side effects of {generic} may include headache, nausea, dizziness, drowsiness, and gastrointestinal upset. Less common but serious side effects include allergic reactions (rash, swelling, difficulty breathing), changes in mood or behavior, and unusual bleeding. Contact your healthcare provider if any side effect persists or worsens.", + "interactions_text": f"{generic.capitalize()} can interact with many other medications. Tell your doctor about all prescription and over-the-counter medications, vitamins, and herbal supplements you take. Pay particular attention to interactions with anticoagulants, NSAIDs, antidepressants, and certain antibiotics.", + } + + +# --------------------------------------------------------------------------- +# Seeding +# --------------------------------------------------------------------------- +def seed_drug_classes(): + if DrugClass.query.count() > 0: + return + for name, desc in DRUG_CLASSES: + db.session.add(DrugClass(name=name, slug=slugify(name), description=desc)) + db.session.commit() + + +def seed_conditions(): + if Condition.query.count() > 0: + return + for slug, name, desc in CONDITIONS_DATA: + db.session.add(Condition(name=name, slug=slug, description=desc)) + db.session.commit() + + +def seed_drugs(): + if Drug.query.count() > 0: + return + cls_by_name = {c.name: c.id for c in DrugClass.query.all()} + featured_targets = {"ibuprofen", "metformin", "lisinopril", "sertraline", "atorvastatin", "semaglutide", "amoxicillin", "levothyroxine"} + for entry in DRUGS_DATA: + gname, cname, avail, csa, pron, brands, conds = entry + # Try OpenFDA first, fall back to synthetic. + openfda = fetch_openfda_label(gname) + if openfda: + uses = truncate(first(openfda.get("indications_and_usage"))) + warn = truncate(first(openfda.get("warnings") or openfda.get("warnings_and_cautions"))) + dose = truncate(first(openfda.get("dosage_and_administration"))) + adv = truncate(first(openfda.get("adverse_reactions"))) + inter = truncate(first(openfda.get("drug_interactions"))) + desc = truncate(first(openfda.get("description")) or first(openfda.get("clinical_pharmacology"))) + else: + uses = warn = dose = adv = inter = desc = None + if not any([uses, warn, dose, adv, inter, desc]): + syn = synthetic_content(gname, cname, conds) + uses = uses or syn["uses"] + warn = warn or syn["warnings"] + dose = dose or syn["dosage"] + adv = adv or syn["side_effects"] + inter = inter or syn["interactions_text"] + desc = desc or syn["description"] + + faq = [ + {"q": f"What is {gname} used for?", "a": (uses or "")[:400] or f"{gname} is used to treat {', '.join(conds) if conds else 'various medical conditions'}."}, + {"q": f"How should I take {gname}?", "a": (dose or "")[:400] or f"Take {gname} exactly as prescribed by your doctor."}, + {"q": f"What are the most common side effects of {gname}?", "a": (adv or "")[:400] or "Common side effects vary; consult the side effects section above."}, + {"q": f"Can I drink alcohol while taking {gname}?", "a": "Talk with your doctor or pharmacist about whether alcohol is safe to consume while on this medication. Alcohol can worsen side effects of many drugs."}, + {"q": f"Is {gname} safe during pregnancy?", "a": "Discuss with your doctor before using this medication if you are pregnant, planning pregnancy, or breastfeeding."}, + ] + + d = Drug( + generic_name=gname, + slug=slugify(gname), + brand_names_json=json.dumps(brands), + drug_class_id=cls_by_name.get(cname), + availability=avail, + csa_schedule=csa, + pregnancy_risk="Discuss with your doctor", + pronunciation=pron, + description=desc, + uses=uses, + warnings=warn, + dosage=dose, + side_effects=adv, + interactions_text=inter, + faq_json=json.dumps(faq), + conditions_json=json.dumps(conds), + related_drugs_json=json.dumps([]), + is_featured=(gname in featured_targets), + reviewer_name="Drugs.com editorial team", + reviewer_credential="PharmD", + last_updated=datetime.utcnow() - timedelta(days=hash(gname) % 365), + ) + db.session.add(d) + db.session.commit() + + # Populate related_drugs by class + all_drugs = Drug.query.all() + by_class = {} + for d in all_drugs: + by_class.setdefault(d.drug_class_id, []).append(d.generic_name) + for d in all_drugs: + peers = [n for n in by_class.get(d.drug_class_id, []) if n != d.generic_name][:6] + d.related_drugs_json = json.dumps(peers) + db.session.commit() + + +def seed_drug_conditions(): + if DrugCondition.query.count() > 0: + return + cond_by_slug = {c.slug: c for c in Condition.query.all()} + for d in Drug.query.all(): + for c_slug in d.conditions_list: + c = cond_by_slug.get(c_slug) + if c: + db.session.add(DrugCondition(drug_id=d.id, condition_id=c.id)) + db.session.commit() + # Update denormalized drug_count + for c in Condition.query.all(): + c.drug_count = DrugCondition.query.filter_by(condition_id=c.id).count() + db.session.commit() + + +def seed_pill_images(): + if DrugImage.query.count() > 0: + return + drug_by_name = {d.generic_name: d for d in Drug.query.all()} + for gname, imprint, shape, color, strength, mfg in PILL_IMAGES_DATA: + d = drug_by_name.get(gname) + if not d: + continue + db.session.add(DrugImage( + drug_id=d.id, imprint=imprint, shape=shape, color=color, + strength=strength, manufacturer=mfg, + )) + db.session.commit() + + +def seed_interactions(): + if DrugInteraction.query.count() > 0: + return + drug_by_name = {d.generic_name: d for d in Drug.query.all()} + for a, b, sev, desc in INTERACTIONS_DATA: + da = drug_by_name.get(a) + db_ = drug_by_name.get(b) + if not da or not db_: + continue + db.session.add(DrugInteraction(drug_a_id=da.id, drug_b_id=db_.id, severity=sev, description=desc)) + db.session.commit() + + +def seed_news(): + if NewsArticle.query.count() > 0: + return + now = datetime.utcnow() + for i, (title, cat, body) in enumerate(NEWS_DATA): + db.session.add(NewsArticle( + title=title, category=cat, body=body, source="Drugs.com Medical News", + published_at=now - timedelta(days=i * 2), + is_featured=(i < 4), + )) + db.session.commit() + + +def seed_benchmark_users(): + if User.query.filter_by(email="alice.j@test.com").first(): + return + drugs = Drug.query.all() + if not drugs: + return + for idx, u in enumerate(BENCHMARK_USERS): + user = User(username=u["username"], email=u["email"]) + user.set_password(u["password"]) + db.session.add(user) + db.session.flush() + # Saved drugs (3+) + saved_pool = drugs[(idx * 7) % len(drugs):(idx * 7) % len(drugs) + 5] + if len(saved_pool) < 3: + saved_pool = drugs[:5] + for d in saved_pool[:4]: + db.session.add(SavedDrug(user_id=user.id, drug_id=d.id, notes=f"Tracking for ongoing treatment.")) + # Reviews (2+) + for j in range(3): + target = drugs[(idx * 5 + j * 11) % len(drugs)] + conds = target.conditions_list or ["general use"] + tmpl = REVIEW_TEMPLATES[(idx + j) % len(REVIEW_TEMPLATES)] + db.session.add(DrugReview( + drug_id=target.id, user_id=user.id, rating=tmpl[1], + title=tmpl[0], body=tmpl[2].format(cond=conds[0]), + condition_treated=conds[0], + helpful_count=(idx + j) * 3, + )) + db.session.commit() + + +def seed_extra_reviews(): + """Add ~50 additional reviews across drugs from auto-generated reviewer users.""" + if DrugReview.query.count() >= 40: + return + # Create some anonymous reviewer users if not present + reviewers = [] + for i in range(8): + email = f"reviewer{i}@example.com" + u = User.query.filter_by(email=email).first() + if not u: + u = User(username=f"reviewer_{i}", email=email) + u.set_password("review-seed-pw") + db.session.add(u) + db.session.flush() + reviewers.append(u) + popular = ["ibuprofen", "metformin", "lisinopril", "sertraline", "atorvastatin", + "amoxicillin", "levothyroxine", "alprazolam", "gabapentin", "omeprazole", + "semaglutide", "fluoxetine", "amlodipine", "tramadol", "zolpidem"] + drug_by_name = {d.generic_name: d for d in Drug.query.all()} + count = 0 + for i, name in enumerate(popular): + d = drug_by_name.get(name) + if not d: + continue + conds = d.conditions_list or ["general"] + for j in range(4): + tmpl = REVIEW_TEMPLATES[(i + j) % len(REVIEW_TEMPLATES)] + u = reviewers[(i + j) % len(reviewers)] + db.session.add(DrugReview( + drug_id=d.id, user_id=u.id, rating=tmpl[1], + title=tmpl[0], body=tmpl[2].format(cond=conds[j % len(conds)]), + condition_treated=conds[j % len(conds)], + helpful_count=(j + 1) * 4, + created_at=datetime.utcnow() - timedelta(days=(i * 4 + j)), + )) + count += 1 + if count >= 55: + break + if count >= 55: + break + db.session.commit() + + +def recompute_drug_ratings(): + for d in Drug.query.all(): + revs = DrugReview.query.filter_by(drug_id=d.id).all() + if revs: + d.avg_rating = round(sum(r.rating for r in revs) / len(revs), 1) + d.review_count = len(revs) + else: + d.avg_rating = 0.0 + d.review_count = 0 + db.session.commit() + + +def seed_database(): + seed_drug_classes() + seed_conditions() + seed_drugs() + seed_drug_conditions() + seed_pill_images() + seed_interactions() + seed_news() + seed_benchmark_users() + seed_extra_reviews() + recompute_drug_ratings() + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- +def tokenize(q): + if not q: + return [] + return [t.lower() for t in re.split(r"\W+", q) if t] + + +def score_drug(drug, tokens): + text = f"{drug.generic_name} {drug.brand_names_json} {drug.description or ''}".lower() + return sum(1 for t in tokens if t in text) + + +@app.context_processor +def inject_globals(): + return { + "site_name": "Drugs.com", + "site_tagline": "Know More. Be Sure.", + "current_year": datetime.utcnow().year, + "all_letters": list(string.ascii_uppercase), + } + + +# --------------------------------------------------------------------------- +# Routes +# --------------------------------------------------------------------------- +@app.route("/") +def index(): + featured = Drug.query.filter_by(is_featured=True).limit(8).all() + trending = Drug.query.order_by(Drug.review_count.desc()).limit(10).all() + news = NewsArticle.query.order_by(NewsArticle.published_at.desc()).limit(6).all() + classes = DrugClass.query.order_by(DrugClass.name).limit(12).all() + return render_template("index.html", featured=featured, trending=trending, + news=news, classes=classes) + + +@app.route("/drug_information.html") +@app.route("/drugs-a-to-z.html") +def drug_az(): + letter = (request.args.get("letter") or "A").upper() + if letter not in string.ascii_uppercase: + letter = "A" + drugs = Drug.query.filter(Drug.generic_name.ilike(f"{letter}%")).order_by(Drug.generic_name).all() + return render_template("drug_az.html", letter=letter, drugs=drugs) + + +@app.route("/.html") +def drug_detail(slug): + drug = Drug.query.filter_by(slug=slug).first() + if not drug: + abort(404) + reviews = DrugReview.query.filter_by(drug_id=drug.id).order_by(DrugReview.helpful_count.desc()).limit(20).all() + related = Drug.query.filter(Drug.generic_name.in_(drug.related_drugs)).all() + cond_by_slug = {c.slug: c for c in Condition.query.all()} + drug_conditions = [cond_by_slug[s] for s in drug.conditions_list if s in cond_by_slug] + saved = False + if current_user.is_authenticated: + saved = SavedDrug.query.filter_by(user_id=current_user.id, drug_id=drug.id).first() is not None + return render_template("drug_detail.html", drug=drug, reviews=reviews, + related=related, drug_conditions=drug_conditions, saved=saved) + + +@app.route("/search") +def search(): + q = (request.args.get("q") or "").strip() + class_slug = request.args.get("class") or "" + cond_slug = request.args.get("condition") or "" + avail = request.args.get("availability") or "" + + drugs = Drug.query + if class_slug: + cls = DrugClass.query.filter_by(slug=class_slug).first() + if cls: + drugs = drugs.filter(Drug.drug_class_id == cls.id) + if avail: + drugs = drugs.filter(Drug.availability == avail) + drugs = drugs.all() + + if cond_slug: + drugs = [d for d in drugs if cond_slug in d.conditions_list] + + tokens = tokenize(q) + if tokens: + scored = [(score_drug(d, tokens), d) for d in drugs] + scored = [(s, d) for s, d in scored if s > 0] + scored.sort(key=lambda x: (-x[0], x[1].generic_name)) + results = [d for _, d in scored] + else: + results = sorted(drugs, key=lambda d: d.generic_name) + + classes = DrugClass.query.order_by(DrugClass.name).all() + conditions = Condition.query.order_by(Condition.name).all() + return render_template("search.html", q=q, results=results, + classes=classes, conditions=conditions, + class_slug=class_slug, cond_slug=cond_slug, avail=avail) + + +@app.route("/drug_interactions.html") +@app.route("/interaction-checker/") +@app.route("/interaction-checker") +def interaction_checker(): + drugs = Drug.query.order_by(Drug.generic_name).all() + return render_template("interaction_checker.html", drugs=drugs) + + +@app.route("/api/interaction-check", methods=["POST"]) +def api_interaction_check(): + data = request.get_json(silent=True) or {} + names = [str(n).strip().lower() for n in data.get("drugs", []) if str(n).strip()] + # normalize: try matching by generic name or brand + matched = [] + name_to_drug = {} + for n in names: + d = Drug.query.filter(db.func.lower(Drug.generic_name) == n).first() + if not d: + # try brand match + all_drugs = Drug.query.all() + for cand in all_drugs: + brands = [b.lower() for b in cand.brand_names] + if n in brands: + d = cand + break + if d: + matched.append(d) + name_to_drug[n] = d + interactions = [] + pair_keys = set() + no_interaction = [] + for a, b in combinations(matched, 2): + # Try a-b and b-a + rec = DrugInteraction.query.filter( + ((DrugInteraction.drug_a_id == a.id) & (DrugInteraction.drug_b_id == b.id)) | + ((DrugInteraction.drug_a_id == b.id) & (DrugInteraction.drug_b_id == a.id)) + ).first() + key = tuple(sorted([a.generic_name, b.generic_name])) + if key in pair_keys: + continue + pair_keys.add(key) + if rec: + interactions.append({ + "drug_a": a.generic_name, + "drug_b": b.generic_name, + "severity": rec.severity, + "description": rec.description, + }) + else: + no_interaction.append([a.generic_name, b.generic_name]) + return jsonify({ + "interactions": interactions, + "drugs_checked": [d.generic_name for d in matched], + "unrecognized": [n for n in names if n not in name_to_drug], + "no_interaction_pairs": no_interaction, + }) + + +@app.route("/pill_identification.html") +@app.route("/pill-identifier.html") +def pill_identifier(): + shapes = sorted({i.shape for i in DrugImage.query.all() if i.shape}) + colors = sorted({i.color for i in DrugImage.query.all() if i.color}) + return render_template("pill_identifier.html", shapes=shapes, colors=colors, results=None) + + +@app.route("/pill-identifier-results") +def pill_identifier_results(): + imprint = (request.args.get("imprint") or "").strip() + shape = (request.args.get("shape") or "").strip() + color = (request.args.get("color") or "").strip() + q = DrugImage.query + if imprint: + q = q.filter(DrugImage.imprint.ilike(f"%{imprint}%")) + if shape: + q = q.filter(DrugImage.shape == shape) + if color: + q = q.filter(DrugImage.color == color) + results = q.all() + shapes = sorted({i.shape for i in DrugImage.query.all() if i.shape}) + colors = sorted({i.color for i in DrugImage.query.all() if i.color}) + return render_template("pill_identifier.html", shapes=shapes, colors=colors, + results=results, imprint=imprint, shape=shape, color=color) + + +@app.route("/condition/") +def condition_page(slug): + cond = Condition.query.filter_by(slug=slug).first_or_404() + links = DrugCondition.query.filter_by(condition_id=cond.id).all() + drugs = [Drug.query.get(l.drug_id) for l in links] + drugs = [d for d in drugs if d] + drugs.sort(key=lambda d: -d.avg_rating) + return render_template("condition.html", condition=cond, drugs=drugs) + + +@app.route("/drug-class/") +def drug_class_page(slug): + cls = DrugClass.query.filter_by(slug=slug).first_or_404() + drugs = Drug.query.filter_by(drug_class_id=cls.id).order_by(Drug.generic_name).all() + return render_template("drug_class.html", drug_class=cls, drugs=drugs) + + +@app.route("/news/") +def news_index(): + articles = NewsArticle.query.order_by(NewsArticle.published_at.desc()).all() + categories = ["New Drug Approvals", "Medical", "FDA Alerts", "Clinical Trials"] + return render_template("news.html", articles=articles, categories=categories, active_cat=None) + + +@app.route("/news/article/") +def news_article(article_id): + article = NewsArticle.query.get_or_404(article_id) + return render_template("news_article.html", article=article) + + +@app.route("/news/") +def news_category(category): + cat_map = { + "new-drug-approvals": "New Drug Approvals", + "new drug approvals": "New Drug Approvals", + "medical": "Medical", + "fda-alerts": "FDA Alerts", + "fda alerts": "FDA Alerts", + "clinical-trials": "Clinical Trials", + "clinical trials": "Clinical Trials", + } + cat = cat_map.get(category.lower()) or (category if category in cat_map.values() else None) + if not cat: + abort(404) + articles = NewsArticle.query.filter_by(category=cat).order_by(NewsArticle.published_at.desc()).all() + categories = list(cat_map.values()) + return render_template("news.html", articles=articles, categories=categories, active_cat=cat) + + +@app.route("/drug-classes") +@app.route("/drug-classes.html") +def drug_classes_list(): + classes = DrugClass.query.order_by(DrugClass.name).all() + return render_template("drug_class.html", drug_class=None, drugs=None, all_classes=classes) + + +@app.route("/conditions") +@app.route("/conditions.html") +def conditions_list(): + conditions = Condition.query.order_by(Condition.name).all() + return render_template("condition.html", condition=None, drugs=None, all_conditions=conditions) + + +# --- Auth --- +@app.route("/login", methods=["GET", "POST"]) +def login(): + if request.method == "POST": + email = request.form.get("email", "").strip().lower() + password = request.form.get("password", "") + user = User.query.filter(db.func.lower(User.email) == email).first() + if user and user.check_password(password): + login_user(user) + flash("Welcome back!", "success") + return redirect(request.args.get("next") or url_for("account")) + flash("Invalid email or password.", "error") + return render_template("login.html") + + +@app.route("/register", methods=["GET", "POST"]) +def register(): + if request.method == "POST": + email = request.form.get("email", "").strip().lower() + username = request.form.get("username", "").strip() + password = request.form.get("password", "") + if not email or not username or not password: + flash("All fields are required.", "error") + return render_template("register.html") + if User.query.filter(db.func.lower(User.email) == email).first(): + flash("Email already registered.", "error") + return render_template("register.html") + if User.query.filter_by(username=username).first(): + flash("Username already taken.", "error") + return render_template("register.html") + user = User(email=email, username=username) + user.set_password(password) + db.session.add(user) + db.session.commit() + login_user(user) + flash("Account created.", "success") + return redirect(url_for("account")) + return render_template("register.html") + + +@app.route("/logout") +def logout(): + logout_user() + return redirect(url_for("index")) + + +@app.route("/account") +@login_required +def account(): + reviews = DrugReview.query.filter_by(user_id=current_user.id).order_by(DrugReview.created_at.desc()).all() + saved_count = SavedDrug.query.filter_by(user_id=current_user.id).count() + return render_template("account.html", reviews=reviews, saved_count=saved_count) + + +@app.route("/my-med-list") +@login_required +def my_med_list(): + items = SavedDrug.query.filter_by(user_id=current_user.id).order_by(SavedDrug.created_at.desc()).all() + return render_template("my_med_list.html", items=items) + + +@app.route("/my-med-list/toggle", methods=["POST"]) +@login_required +def my_med_list_toggle(): + data = request.get_json(silent=True) or {} + slug = data.get("slug") + drug = Drug.query.filter_by(slug=slug).first() + if not drug: + return jsonify({"ok": False, "error": "drug_not_found"}), 404 + existing = SavedDrug.query.filter_by(user_id=current_user.id, drug_id=drug.id).first() + if existing: + db.session.delete(existing) + db.session.commit() + return jsonify({"ok": True, "saved": False}) + db.session.add(SavedDrug(user_id=current_user.id, drug_id=drug.id)) + db.session.commit() + return jsonify({"ok": True, "saved": True}) + + +@app.route("/_health") +def health(): + return {"ok": True, "site": "drugs_com"} + + +@app.errorhandler(404) +def not_found(e): + return render_template("base.html", not_found=True), 404 + + +# --------------------------------------------------------------------------- +# Bootstrap +# --------------------------------------------------------------------------- +def init_app(): + with app.app_context(): + db.create_all() + seed_database() + + +init_app() + + +if __name__ == "__main__": + port = int(os.environ.get("PORT", 5000)) + app.run(host="0.0.0.0", port=port, debug=False) diff --git a/sites/drugs_com/requirements.txt b/sites/drugs_com/requirements.txt new file mode 100644 index 0000000..dfc0b82 --- /dev/null +++ b/sites/drugs_com/requirements.txt @@ -0,0 +1,6 @@ +Flask +Flask-SQLAlchemy +Flask-Login +Flask-Bcrypt +Flask-WTF +requests diff --git a/sites/drugs_com/static/css/.gitkeep b/sites/drugs_com/static/css/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/sites/drugs_com/static/css/main.css b/sites/drugs_com/static/css/main.css new file mode 100644 index 0000000..7707d45 --- /dev/null +++ b/sites/drugs_com/static/css/main.css @@ -0,0 +1,328 @@ +/* Drugs.com Mirror - Main CSS */ +*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } + +:root { + --navy: #002a5c; + --navy-dark: #001a3c; + --blue: #0072ce; + --blue-light: #e5f1fb; + --orange: #f57c00; + --green: #2e8b57; + --red: #c0392b; + --yellow: #f1c40f; + --gray-50: #f7f8fa; + --gray-100: #eef1f5; + --gray-200: #dde2ea; + --gray-300: #c5ccd6; + --gray-500: #7a8494; + --gray-700: #4a5568; + --gray-900: #1a202c; + --shadow: 0 1px 3px rgba(0,0,0,0.08); + --shadow-lg: 0 4px 12px rgba(0,0,0,0.10); +} + +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + font-size: 15px; + line-height: 1.55; + color: var(--gray-900); + background: var(--gray-50); +} + +a { color: var(--blue); text-decoration: none; } +a:hover { text-decoration: underline; } + +.container { max-width: 1200px; margin: 0 auto; padding: 0 20px; } +.muted { color: var(--gray-500); font-size: 0.92em; } + +/* Header */ +.site-header { background: var(--navy); color: #fff; box-shadow: var(--shadow); } +.header-top { padding: 14px 0; border-bottom: 1px solid rgba(255,255,255,0.08); } +.header-top-inner { display: flex; justify-content: space-between; align-items: center; } +.brand { color: #fff; display: flex; flex-direction: column; } +.brand:hover { text-decoration: none; } +.brand-logo { font-size: 1.55rem; font-weight: 700; letter-spacing: -0.5px; } +.brand-logo .dot { color: var(--orange); } +.brand-tagline { font-size: 0.78rem; color: rgba(255,255,255,0.7); margin-top: 2px; } +.header-actions a { color: rgba(255,255,255,0.9); margin-left: 18px; font-size: 0.9rem; } +.header-actions a:hover { color: #fff; text-decoration: underline; } +.btn-outline-light { border: 1px solid rgba(255,255,255,0.5); padding: 5px 14px; border-radius: 4px; } +.btn-outline-light:hover { background: rgba(255,255,255,0.1); text-decoration: none; } + +.main-nav { background: var(--navy-dark); } +.nav-inner { display: flex; align-items: center; gap: 24px; padding: 10px 0; flex-wrap: wrap; } +.main-nav a { color: rgba(255,255,255,0.92); font-size: 0.92rem; font-weight: 500; } +.main-nav a:hover { color: var(--orange); text-decoration: none; } +.nav-search { margin-left: auto; display: flex; } +.nav-search input { padding: 6px 10px; border: none; border-radius: 3px 0 0 3px; min-width: 220px; } +.nav-search button { padding: 6px 14px; border: none; background: var(--orange); color: #fff; border-radius: 0 3px 3px 0; cursor: pointer; } + +/* Main */ +.site-main { padding: 28px 0 60px; min-height: 70vh; } + +/* Hero */ +.hero { + background: linear-gradient(135deg, var(--navy) 0%, var(--blue) 100%); + color: #fff; padding: 56px 40px; border-radius: 8px; text-align: center; + margin-bottom: 36px; +} +.hero h1 { font-size: 2.3rem; margin-bottom: 12px; } +.hero p { font-size: 1.1rem; margin-bottom: 26px; opacity: 0.95; } +.hero-search { display: flex; justify-content: center; max-width: 620px; margin: 0 auto 18px; } +.hero-search input { flex: 1; padding: 13px 18px; border: none; border-radius: 4px 0 0 4px; font-size: 1rem; } +.hero-search button { padding: 13px 28px; border: none; background: var(--orange); color: #fff; font-weight: 600; border-radius: 0 4px 4px 0; cursor: pointer; font-size: 1rem; } +.quick-links { margin-top: 14px; } +.quick-links a { color: #fff; margin: 0 12px; border-bottom: 1px dotted rgba(255,255,255,0.5); } + +/* Sections */ +.row-section { background: #fff; padding: 24px; border-radius: 6px; margin-bottom: 24px; box-shadow: var(--shadow); } +.row-section h2 { color: var(--navy); margin-bottom: 16px; font-size: 1.35rem; padding-bottom: 8px; border-bottom: 2px solid var(--gray-100); } +.row-section.split { display: grid; grid-template-columns: 2fr 1fr; gap: 28px; } + +/* Cards */ +.card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 14px; } +.drug-card { + background: #fff; border: 1px solid var(--gray-200); padding: 16px; border-radius: 6px; + color: var(--gray-900); display: block; transition: all 0.15s; +} +.drug-card:hover { border-color: var(--blue); box-shadow: var(--shadow-lg); text-decoration: none; } +.drug-card h3 { color: var(--blue); font-size: 1.05rem; margin-bottom: 6px; } +.rating { color: var(--green); font-weight: 600; font-size: 0.9rem; } + +/* Trending list */ +.trending-list { list-style: decimal inside; columns: 2; column-gap: 30px; } +.trending-list li { padding: 4px 0; } + +/* News */ +.news-list { list-style: none; } +.news-list li { padding: 10px 0; border-bottom: 1px solid var(--gray-100); } +.news-list li:last-child { border-bottom: none; } +.news-list.big li { padding: 18px 0; } +.news-list.big h3 { color: var(--navy); margin: 6px 0; } + +.badge { + display: inline-block; padding: 2px 8px; background: var(--blue-light); color: var(--blue); + border-radius: 3px; font-size: 0.75rem; font-weight: 600; text-transform: uppercase; + margin-right: 8px; letter-spacing: 0.3px; +} + +.class-list { list-style: none; } +.class-list li { padding: 6px 0; border-bottom: 1px solid var(--gray-100); } + +/* A-Z */ +.letter-grid { display: grid; grid-template-columns: repeat(13, 1fr); gap: 6px; margin: 14px 0 24px; } +.letter-grid.wide { grid-template-columns: repeat(13, 1fr); } +.letter-btn { + display: block; text-align: center; padding: 10px 0; background: var(--gray-100); + color: var(--navy); font-weight: 600; border-radius: 4px; +} +.letter-btn:hover, .letter-btn.active { background: var(--blue); color: #fff; text-decoration: none; } + +.az-list ul { list-style: none; } +.az-list li { padding: 10px 0; border-bottom: 1px solid var(--gray-100); } + +/* Breadcrumb */ +.breadcrumb { color: var(--gray-500); font-size: 0.88rem; margin-bottom: 14px; } +.breadcrumb a { color: var(--blue); } + +/* Drug detail */ +.drug-detail { background: #fff; padding: 28px; border-radius: 6px; box-shadow: var(--shadow); } +.drug-header h1 { color: var(--navy); font-size: 2.1rem; margin-bottom: 6px; } +.pronunciation { color: var(--gray-700); margin-bottom: 12px; } +.meta-row { list-style: none; display: flex; flex-wrap: wrap; gap: 18px; padding: 12px 0; border-top: 1px solid var(--gray-200); border-bottom: 1px solid var(--gray-200); margin: 12px 0; font-size: 0.9rem; } +.reviewer-line { color: var(--gray-500); font-size: 0.85rem; margin-bottom: 18px; } +.section-tabs { display: flex; flex-wrap: wrap; gap: 4px; border-bottom: 2px solid var(--gray-200); margin-bottom: 22px; } +.section-tabs a { padding: 10px 16px; color: var(--navy); font-weight: 500; border-bottom: 3px solid transparent; } +.section-tabs a:hover { border-bottom-color: var(--orange); text-decoration: none; } + +.detail-grid { display: grid; grid-template-columns: 2fr 1fr; gap: 28px; } +.detail-main section { margin-bottom: 28px; } +.detail-main h2 { color: var(--navy); margin-bottom: 12px; font-size: 1.35rem; } +.detail-main p { margin-bottom: 10px; } + +.faq-item { padding: 12px 0; border-bottom: 1px solid var(--gray-100); } +.faq-item h3 { color: var(--navy); font-size: 1rem; margin-bottom: 6px; } + +.review { padding: 14px 0; border-bottom: 1px solid var(--gray-100); } +.review header { margin-bottom: 6px; } + +/* Sidebar */ +.detail-sidebar > div { background: var(--gray-50); border: 1px solid var(--gray-200); border-radius: 6px; padding: 16px; margin-bottom: 16px; } +.status-box h3, .sidebar-box h3 { color: var(--navy); font-size: 1rem; margin-bottom: 10px; } +.status-box ul, .sidebar-box ul { list-style: none; } +.status-box li, .sidebar-box li { padding: 5px 0; font-size: 0.9rem; } + +/* Buttons */ +.btn { display: inline-block; padding: 9px 18px; border-radius: 4px; font-weight: 600; cursor: pointer; border: none; font-size: 0.9rem; } +.btn-primary { background: var(--orange); color: #fff; } +.btn-primary:hover { background: #e07000; text-decoration: none; color: #fff; } +.btn-outline { background: transparent; border: 1px solid var(--blue); color: var(--blue); } +.btn-sm { padding: 3px 8px; font-size: 0.78rem; } + +/* Search filters */ +.search-filters { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 22px; background: #fff; padding: 14px; border-radius: 6px; box-shadow: var(--shadow); } +.search-filters input, .search-filters select { padding: 8px 10px; border: 1px solid var(--gray-300); border-radius: 4px; font-size: 0.92rem; } +.search-filters input { flex: 1; min-width: 220px; } + +.result-list { list-style: none; } +.result-card { background: #fff; border: 1px solid var(--gray-200); padding: 18px; border-radius: 6px; margin-bottom: 12px; } +.result-card h3 { color: var(--blue); margin-bottom: 6px; } + +/* Interaction checker */ +.checker-box { background: #fff; padding: 22px; border-radius: 6px; box-shadow: var(--shadow); } +.checker-input { display: flex; gap: 10px; margin-bottom: 14px; } +.checker-input input { flex: 1; padding: 9px 12px; border: 1px solid var(--gray-300); border-radius: 4px; } +.checker-drug-list { list-style: none; margin-bottom: 14px; } +.checker-drug-list li { padding: 8px 12px; background: var(--gray-50); border-radius: 4px; margin-bottom: 6px; display: flex; justify-content: space-between; align-items: center; } +.checker-results { margin-top: 22px; } +.interaction-list { list-style: none; } +.interaction { padding: 14px; border-radius: 6px; margin-bottom: 10px; border-left: 4px solid var(--gray-300); background: var(--gray-50); } +.interaction.sev-major { border-left-color: var(--red); background: #fdecea; } +.interaction.sev-moderate { border-left-color: var(--orange); background: #fff4e5; } +.interaction.sev-minor { border-left-color: var(--yellow); background: #fffbe5; } +.warn { color: var(--red); } + +/* Pill identifier */ +.pill-form { display: flex; flex-wrap: wrap; gap: 14px; align-items: flex-end; background: #fff; padding: 18px; border-radius: 6px; margin-bottom: 22px; box-shadow: var(--shadow); } +.pill-form label { display: flex; flex-direction: column; font-size: 0.85rem; color: var(--gray-700); flex: 1; min-width: 160px; } +.pill-form input, .pill-form select { padding: 8px; border: 1px solid var(--gray-300); border-radius: 4px; margin-top: 4px; } +.pill-table { width: 100%; background: #fff; border-collapse: collapse; box-shadow: var(--shadow); border-radius: 6px; overflow: hidden; } +.pill-table th, .pill-table td { padding: 10px 14px; text-align: left; border-bottom: 1px solid var(--gray-100); } +.pill-table th { background: var(--navy); color: #fff; font-weight: 600; font-size: 0.88rem; } + +/* News tabs */ +.news-tabs { display: flex; gap: 4px; border-bottom: 2px solid var(--gray-200); margin-bottom: 22px; flex-wrap: wrap; } +.news-tabs a { padding: 10px 18px; color: var(--navy); font-weight: 500; } +.news-tabs a.active { border-bottom: 3px solid var(--orange); } + +/* Conditions / classes grid */ +.condition-grid { list-style: none; display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 12px; } +.condition-grid li { background: #fff; padding: 12px 16px; border-radius: 4px; border: 1px solid var(--gray-200); } + +/* Auth */ +.auth-box { max-width: 420px; margin: 24px auto; background: #fff; padding: 28px; border-radius: 6px; box-shadow: var(--shadow); } +.auth-box h1 { color: var(--navy); margin-bottom: 18px; } +.auth-box label { display: block; margin-bottom: 14px; font-size: 0.88rem; color: var(--gray-700); } +.auth-box input { display: block; width: 100%; padding: 9px 12px; border: 1px solid var(--gray-300); border-radius: 4px; margin-top: 4px; font-size: 0.95rem; } +.auth-box button { width: 100%; margin-top: 6px; padding: 10px; } +.auth-box p { margin-top: 14px; text-align: center; font-size: 0.9rem; } + +/* Account */ +.account-grid { display: grid; grid-template-columns: 1fr 2fr; gap: 22px; margin-top: 20px; } +.account-box { background: #fff; padding: 22px; border-radius: 6px; box-shadow: var(--shadow); } +.account-box h2 { color: var(--navy); margin-bottom: 12px; } + +/* Flash */ +.flash-list { list-style: none; margin-bottom: 18px; } +.flash { padding: 10px 16px; border-radius: 4px; margin-bottom: 6px; } +.flash-success { background: #e6f4ea; color: #1a6d34; border: 1px solid #b6dec3; } +.flash-error { background: #fdecea; color: #8a1d12; border: 1px solid #f0b8b0; } + +/* Footer */ +.site-footer { background: var(--navy-dark); color: rgba(255,255,255,0.85); margin-top: 40px; padding: 36px 0 18px; } +.footer-inner { display: grid; grid-template-columns: 2fr 1fr 1fr; gap: 30px; padding-bottom: 22px; border-bottom: 1px solid rgba(255,255,255,0.1); } +.footer-col h4 { color: #fff; margin-bottom: 12px; font-size: 1rem; } +.footer-col ul { list-style: none; } +.footer-col li { padding: 4px 0; } +.footer-col a { color: rgba(255,255,255,0.8); } +.footer-col .muted { color: rgba(255,255,255,0.55); } +.footer-bottom { padding-top: 18px; text-align: center; } +.footer-bottom .muted { color: rgba(255,255,255,0.5); } + +@media (max-width: 880px) { + .row-section.split, .detail-grid, .account-grid, .footer-inner { grid-template-columns: 1fr; } + .letter-grid, .letter-grid.wide { grid-template-columns: repeat(7, 1fr); } + .trending-list { columns: 1; } +} + +/* ===== Additional styles ===== */ + +/* Drug table */ +.drug-table { width: 100%; border-collapse: collapse; background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,0.08); border-radius: 6px; overflow: hidden; margin: 16px 0; } +.drug-table th { background: #002a5c; color: #fff; padding: 10px 14px; text-align: left; font-weight: 600; font-size: 0.9rem; } +.drug-table td { padding: 10px 14px; border-bottom: 1px solid #eef1f5; font-size: 0.92rem; } +.drug-table tr:hover { background: #e5f1fb; } + +/* Auth pages */ +.auth-box { max-width: 460px; margin: 40px auto; background: #fff; padding: 36px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.10); } +.auth-box h1 { color: #002a5c; margin-bottom: 20px; } +.auth-box label { display: block; margin-bottom: 16px; font-size: 0.9rem; color: #4a5568; font-weight: 500; } +.auth-box input { display: block; width: 100%; padding: 10px 12px; border: 1px solid #c5ccd6; border-radius: 4px; margin-top: 4px; font-size: 1rem; } +.auth-box button { width: 100%; margin-top: 8px; padding: 12px; font-size: 1rem; } +.auth-benefits { background: #e5f1fb; padding: 14px 16px; border-radius: 4px; margin-top: 18px; } +.auth-benefits li { padding: 4px 0; font-size: 0.88rem; list-style: disc; margin-left: 18px; } + +/* Account grid */ +.account-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; margin-top: 20px; } +.account-box { background: #fff; border: 1px solid #dde2ea; border-radius: 6px; padding: 20px; } +.account-box h2 { color: #002a5c; margin-bottom: 12px; font-size: 1.1rem; } +.account-tabs { display: flex; gap: 24px; border-bottom: 2px solid #dde2ea; margin-bottom: 24px; } +.account-tabs a { padding: 10px 0; color: #002a5c; font-weight: 500; border-bottom: 3px solid transparent; margin-bottom: -2px; } +.account-tabs a.active { border-bottom-color: #f57c00; } + +/* Drug status box enhancements */ +.status-row { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #eef1f5; font-size: 0.9rem; } +.status-avail { display: flex; gap: 8px; } +.avail-badge { padding: 3px 8px; border-radius: 3px; font-size: 0.78rem; font-weight: 700; } +.avail-rx { background: #e8f0fe; color: #1a56db; } +.avail-otc { background: #def7ec; color: #057a55; } + +/* More about list */ +.more-about-list { list-style: none; } +.more-about-list li { padding: 5px 0; border-bottom: 1px solid #eef1f5; font-size: 0.88rem; } +.more-about-list li:last-child { border-bottom: none; } + +/* FAQ questions list */ +.faq-questions { list-style: none; margin-bottom: 18px; } +.faq-questions li { padding: 5px 0; } +.faq-questions a { color: #0072ce; } + +/* Treatment guides */ +.treatment-guides { list-style: disc; margin-left: 20px; } +.treatment-guides li { padding: 4px 0; } + +/* Interaction checker improvements */ +.interaction-faq { margin-top: 32px; border-top: 2px solid #eef1f5; padding-top: 20px; } +.interaction-faq h2 { color: #002a5c; margin-bottom: 16px; } +.interaction-faq ul { list-style: none; } +.interaction-faq li { padding: 10px 0; border-bottom: 1px solid #eef1f5; } +.interaction-faq a { color: #0072ce; } +.disclaimer-note { background: #fff8e1; border: 1px solid #ffc107; border-radius: 4px; padding: 12px 16px; margin: 16px 0; font-size: 0.9rem; color: #4a5568; } + +/* Pill identifier disclaimer */ +.disclaimer-box { background: #e5f1fb; border: 1px solid #0072ce; border-radius: 6px; padding: 18px; margin-bottom: 22px; } +.disclaimer-box h3 { color: #002a5c; margin-bottom: 8px; } +.additional-tips { background: #f7f8fa; border-left: 3px solid #f57c00; padding: 14px 18px; border-radius: 0 4px 4px 0; margin-top: 18px; } +.additional-tips h3 { color: #002a5c; margin-bottom: 10px; font-size: 1rem; } +.additional-tips li { padding: 5px 0; } + +/* News card */ +.news-card { background: #fff; padding: 18px; border-radius: 6px; border: 1px solid #dde2ea; margin-bottom: 14px; } +.news-card h2 { font-size: 1.1rem; color: #002a5c; margin: 8px 0; } +.news-card h2 a { color: #002a5c; } +.news-card h2 a:hover { color: #0072ce; } +.news-meta { font-size: 0.83rem; color: #7a8494; margin-bottom: 8px; } + +/* Mini checker form */ +.interaction-mini-checker { background: #e5f1fb; border-radius: 4px; padding: 14px 16px; margin-top: 14px; } +.mini-checker-form { display: flex; gap: 8px; margin-top: 8px; align-items: flex-end; flex-wrap: wrap; } +.mini-checker-form input { padding: 8px 10px; border: 1px solid #c5ccd6; border-radius: 4px; flex: 1; min-width: 180px; } + +/* Popular tools sidebar */ +.popular-tools { background: #f7f8fa; border: 1px solid #dde2ea; border-radius: 6px; padding: 16px; margin-top: 24px; } +.popular-tools h3 { color: #002a5c; font-size: 1rem; margin-bottom: 10px; } +.popular-tools ul { list-style: none; } +.popular-tools li { padding: 6px 0; border-bottom: 1px solid #eef1f5; } + +/* Page layout with sidebar */ +.page-with-sidebar { display: grid; grid-template-columns: 2fr 1fr; gap: 28px; } +@media (max-width: 768px) { + .page-with-sidebar { grid-template-columns: 1fr; } +} + +/* A-Z popular searches */ +.popular-searches { display: flex; flex-wrap: wrap; gap: 8px; margin: 14px 0; } +.popular-searches a { padding: 5px 12px; background: #eef1f5; border-radius: 3px; font-size: 0.88rem; color: #002a5c; } +.popular-searches a:hover { background: #0072ce; color: #fff; text-decoration: none; } +.browse-categories { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 8px; margin: 14px 0; } +.browse-categories a { padding: 8px 12px; background: #f7f8fa; border: 1px solid #dde2ea; border-radius: 4px; font-size: 0.9rem; } diff --git a/sites/drugs_com/static/icons/.gitkeep b/sites/drugs_com/static/icons/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/sites/drugs_com/static/js/.gitkeep b/sites/drugs_com/static/js/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/sites/drugs_com/tasks.jsonl b/sites/drugs_com/tasks.jsonl new file mode 100644 index 0000000..1db99d2 --- /dev/null +++ b/sites/drugs_com/tasks.jsonl @@ -0,0 +1,21 @@ +{"web_name": "Drugs.com", "id": "Drugs.com--0", "ques": "Find the drug ibuprofen on Drugs.com and tell me its drug class and the brand names listed on the page.", "web": "http://localhost:40015/", "upstream_url": "https://www.drugs.com/"} +{"web_name": "Drugs.com", "id": "Drugs.com--1", "ques": "Search for 'metformin' on Drugs.com and find the drug detail page. What is its availability (Rx, OTC, or both) and CSA schedule?", "web": "http://localhost:40015/", "upstream_url": "https://www.drugs.com/"} +{"web_name": "Drugs.com", "id": "Drugs.com--2", "ques": "Use the Drug Interaction Checker on Drugs.com to check for interactions between ibuprofen and warfarin. What severity level is the interaction and what is the main risk described?", "web": "http://localhost:40015/", "upstream_url": "https://www.drugs.com/"} +{"web_name": "Drugs.com", "id": "Drugs.com--3", "ques": "Use the Pill Identifier on Drugs.com to find a pill with the imprint 'I-2'. What drug is it, and what shape and color is described?", "web": "http://localhost:40015/", "upstream_url": "https://www.drugs.com/"} +{"web_name": "Drugs.com", "id": "Drugs.com--4", "ques": "Browse the Drugs A-Z page on Drugs.com and list 5 drugs whose generic names start with the letter 'L'.", "web": "http://localhost:40015/", "upstream_url": "https://www.drugs.com/"} +{"web_name": "Drugs.com", "id": "Drugs.com--5", "ques": "On Drugs.com, find the page for the drug sertraline. What are its brand names and what conditions is it used to treat according to the Conditions section?", "web": "http://localhost:40015/", "upstream_url": "https://www.drugs.com/"} +{"web_name": "Drugs.com", "id": "Drugs.com--6", "ques": "Navigate to the Conditions page on Drugs.com and find drugs listed for 'diabetes'. Name at least 4 drugs shown for that condition.", "web": "http://localhost:40015/", "upstream_url": "https://www.drugs.com/"} +{"web_name": "Drugs.com", "id": "Drugs.com--7", "ques": "On Drugs.com, look up the drug semaglutide. What brand names are listed for it, and what is its drug class?", "web": "http://localhost:40015/", "upstream_url": "https://www.drugs.com/"} +{"web_name": "Drugs.com", "id": "Drugs.com--8", "ques": "Use the Drug Interaction Checker on Drugs.com to check interactions among three drugs: alprazolam, oxycodone, and alcohol. How many interactions are found and what is the most severe?", "web": "http://localhost:40015/", "upstream_url": "https://www.drugs.com/"} +{"web_name": "Drugs.com", "id": "Drugs.com--9", "ques": "Search Drugs.com for drugs in the 'Statins' drug class. Name at least 3 drugs listed under that class.", "web": "http://localhost:40015/", "upstream_url": "https://www.drugs.com/"} +{"web_name": "Drugs.com", "id": "Drugs.com--10", "ques": "On the Drugs.com ibuprofen page, scroll to the FAQ section. What does the FAQ say about taking ibuprofen on an empty stomach?", "web": "http://localhost:40015/", "upstream_url": "https://www.drugs.com/"} +{"web_name": "Drugs.com", "id": "Drugs.com--11", "ques": "Browse the Medical News section on Drugs.com and find the most recent article in the 'New Drug Approvals' category. What is its title?", "web": "http://localhost:40015/", "upstream_url": "https://www.drugs.com/"} +{"web_name": "Drugs.com", "id": "Drugs.com--12", "ques": "On Drugs.com, find the drug atorvastatin. What is the user rating out of 10 and how many reviews does it have?", "web": "http://localhost:40015/", "upstream_url": "https://www.drugs.com/"} +{"web_name": "Drugs.com", "id": "Drugs.com--13", "ques": "Use the Pill Identifier on Drugs.com to search for white oval pills. List the first 3 results shown with their drug names and imprint codes.", "web": "http://localhost:40015/", "upstream_url": "https://www.drugs.com/"} +{"web_name": "Drugs.com", "id": "Drugs.com--14", "ques": "Sign in to Drugs.com using the account alice.j@test.com with password TestPass123!. After signing in, navigate to My Med List and list all drugs currently saved there.", "web": "http://localhost:40015/", "upstream_url": "https://www.drugs.com/"} +{"web_name": "Drugs.com", "id": "Drugs.com--15", "ques": "On Drugs.com, find the drug lisinopril. What warnings are listed regarding pregnancy, and what is its availability status?", "web": "http://localhost:40015/", "upstream_url": "https://www.drugs.com/"} +{"web_name": "Drugs.com", "id": "Drugs.com--16", "ques": "Navigate to the Drug Classes page on Drugs.com and find the 'Benzodiazepines' class. List at least 3 drugs in that class.", "web": "http://localhost:40015/", "upstream_url": "https://www.drugs.com/"} +{"web_name": "Drugs.com", "id": "Drugs.com--17", "ques": "Search Drugs.com for 'antibiotics'. Find a drug in the fluoroquinolone class and report its generic name, brand name(s), and what conditions it treats.", "web": "http://localhost:40015/", "upstream_url": "https://www.drugs.com/"} +{"web_name": "Drugs.com", "id": "Drugs.com--18", "ques": "On Drugs.com, look up the drug amoxicillin and read the Dosage section. What is the typical adult dosing frequency mentioned for standard infections?", "web": "http://localhost:40015/", "upstream_url": "https://www.drugs.com/"} +{"web_name": "Drugs.com", "id": "Drugs.com--19", "ques": "Use the Drug Interaction Checker on Drugs.com to check interactions between metformin and alcohol. What severity level is this interaction and what risk does it describe?", "web": "http://localhost:40015/", "upstream_url": "https://www.drugs.com/"} +{"web_name": "Drugs.com", "id": "Drugs.com--20", "ques": "On Drugs.com, find the conditions page for 'hypertension'. List at least 5 drugs shown for treating high blood pressure.", "web": "http://localhost:40015/", "upstream_url": "https://www.drugs.com/"} diff --git a/sites/drugs_com/templates/.gitkeep b/sites/drugs_com/templates/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/sites/drugs_com/templates/account.html b/sites/drugs_com/templates/account.html new file mode 100644 index 0000000..b5bfbb2 --- /dev/null +++ b/sites/drugs_com/templates/account.html @@ -0,0 +1,48 @@ +{% extends "base.html" %} +{% block title %}My Account - {{ site_name }}{% endblock %} +{% block content %} + + + + +

Welcome, {{ current_user.username }}

+

{{ current_user.email }} · Joined {{ current_user.created_at.strftime('%b %d, %Y') }}

+ + +{% endblock %} diff --git a/sites/drugs_com/templates/base.html b/sites/drugs_com/templates/base.html new file mode 100644 index 0000000..b1fb065 --- /dev/null +++ b/sites/drugs_com/templates/base.html @@ -0,0 +1,95 @@ + + + + + +{% block title %}{{ site_name }} | {{ site_tagline }}{% endblock %} + + + + + +
+
+ {% with msgs = get_flashed_messages(with_categories=true) %} + {% if msgs %} +
    + {% for cat, msg in msgs %} +
  • {{ msg }}
  • + {% endfor %} +
+ {% endif %} + {% endwith %} + {% block content %}{% if not_found %}

Page not found

The page you requested could not be located.

{% endif %}{% endblock %} +
+
+ + + + diff --git a/sites/drugs_com/templates/condition.html b/sites/drugs_com/templates/condition.html new file mode 100644 index 0000000..0b57caa --- /dev/null +++ b/sites/drugs_com/templates/condition.html @@ -0,0 +1,82 @@ +{% extends "base.html" %} +{% block title %}{% if condition %}List of {{ drugs|length }} {{ condition.name }} Medications Compared - Drugs.com{% else %}Medical Conditions - Drugs.com{% endif %}{% endblock %} +{% block content %} +{% if condition %} + + +

{{ condition.name }} Medications

+ +

Other names: {{ condition.name }}

+

Medically reviewed by Drugs.com. Last updated on May 13, 2026.

+ +

{{ condition.description }}

+ +

Drugs used to treat {{ condition.name }}

+

The following list of medications are in some way related to or used in the treatment of this condition.

+ + {% if drugs %} + + + + + + + + + + + + + {% for d in drugs %} + + + + + + + + + {% endfor %} + +
Drug nameRatingReviewsRx/OTCPregnancyCSA
+ {{ d.generic_name|capitalize }} +
{{ d.brand_names|join(', ') if d.brand_names else 'Generic' }}
+
for {{ d.generic_name }} to treat {{ condition.name }}
+
{{ d.avg_rating }}{{ d.review_count }} reviews{{ d.availability }}{{ d.pregnancy_risk or 'N' }}{{ d.csa_schedule or 'N' }}
+ +

+ Rx Prescription only.   + OTC Over-the-counter.   + Pregnancy Category: A = No risk, B = No risk in non-human studies, C = Risk cannot be ruled out, D = Positive evidence of risk, X = Contraindicated, N = Not classified.   + CSA Schedule: N = Not a controlled drug, 1-5 = Schedule I-V. +

+ {% else %} +

No drugs listed for this condition.

+ {% endif %} + +

Frequently asked questions

+

See more about {{ condition.name }} on Drugs.com.

+ +{% else %} + +

Medical Conditions

+

Browse medical conditions and the drugs used to treat them.

+
    + {% for c in all_conditions %} +
  • {{ c.name }} ({{ c.drug_count }} drugs)
  • + {% endfor %} +
+ +

Browse treatment options

+
+ {% for letter in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' %} + {{ letter }} + {% endfor %} + 0-9 +
+{% endif %} +{% endblock %} diff --git a/sites/drugs_com/templates/drug_az.html b/sites/drugs_com/templates/drug_az.html new file mode 100644 index 0000000..4719d48 --- /dev/null +++ b/sites/drugs_com/templates/drug_az.html @@ -0,0 +1,65 @@ +{% extends "base.html" %} +{% block title %}Drugs & Medications A to Z - {{ site_name }}{% endblock %} +{% block content %} + + +

Drugs & Medications A to Z

+

Detailed and accurate information is provided on over 24,000 prescription and over-the-counter medicines for both consumers and healthcare professionals.

+ + + +

Browse A-Z

+
+ {% for L in all_letters %} + {{ L }} + {% endfor %} + 0-9 +
+ +
+

Drugs starting with "{{ letter }}"

+ {% if drugs %} +
    + {% for d in drugs %} +
  • + {{ d.generic_name|capitalize }} + {% if d.brand_names %} — {{ d.brand_names|join(', ') }}{% endif %} + {{ d.availability }} +
  • + {% endfor %} +
+ {% else %} +

No drugs in our database starting with this letter.

+ {% endif %} +
+ +
+

Browse drugs by category

+ +
+ +
+

Popular drug searches

+ +
+{% endblock %} diff --git a/sites/drugs_com/templates/drug_class.html b/sites/drugs_com/templates/drug_class.html new file mode 100644 index 0000000..fb9ba0d --- /dev/null +++ b/sites/drugs_com/templates/drug_class.html @@ -0,0 +1,75 @@ +{% extends "base.html" %} +{% block title %}{% if drug_class %}{{ drug_class.name }} - Drugs.com{% else %}Drug Classes - Drugs.com{% endif %}{% endblock %} +{% block content %} +{% if drug_class %} + + +

{{ drug_class.name }}

+ +

Other names: {{ drug_class.name }}

+

Medically reviewed by Drugs.com. Last updated on May 13, 2026.

+ +

What are {{ drug_class.name }}?

+

{{ drug_class.description }}

+ +

What are {{ drug_class.name }} used for?

+

{{ drug_class.name }} are used in the treatment of a variety of conditions. See the list of drugs below for specific indications and approved uses.

+ +

What are the differences between {{ drug_class.name }}?

+

Drugs within the {{ drug_class.name }} class can differ in their mechanism of action, dosage forms, side effect profiles, and approved indications. Consult the individual drug pages for details.

+ +

List of {{ drug_class.name }}

+ + {% if drugs %} + + + + + + + + + + {% for d in drugs %} + + + + + + {% endfor %} + +
Drug NameAvg. RatingReviews
+ {{ d.generic_name|capitalize }} + Pro +
{{ d.brand_names|join(', ') if d.brand_names else 'Generic' }}
+
{{ d.avg_rating }}{{ d.review_count }} reviews
+ +

+ For ratings, users were asked how effective they found the medicine while considering positive/adverse effects and ease of use (1 = not effective, 10 = most effective). +

+ {% else %} +

No drugs in this class.

+ {% endif %} + +

See also

+

Medical conditions associated with {{ drug_class.name }}.

+ +

Further information

+

Always consult your healthcare provider to ensure the information displayed on this page applies to your personal circumstances.

+

Medical Disclaimer

+ +{% else %} + +

Drug Classes

+

Browse all drug classes in our database.

+
    + {% for c in all_classes %} +
  • {{ c.name }}
  • + {% endfor %} +
+{% endif %} +{% endblock %} diff --git a/sites/drugs_com/templates/drug_detail.html b/sites/drugs_com/templates/drug_detail.html new file mode 100644 index 0000000..92fad1e --- /dev/null +++ b/sites/drugs_com/templates/drug_detail.html @@ -0,0 +1,262 @@ +{% extends "base.html" %} +{% block title %}{{ drug.generic_name|capitalize }} Uses, Dosage & Side Effects - {{ site_name }}{% endblock %} +{% block content %} + + +
+
+

{{ drug.generic_name|capitalize }}

+ {% if drug.pronunciation %}

Pronunciation: {{ drug.pronunciation }}

{% endif %} +
    +
  • Generic name: {{ drug.generic_name }}
  • +
  • Brand names: {{ drug.brand_names|join(', ') if drug.brand_names else 'N/A' }}
  • + {% if drug.images %} +
  • Dosage forms: + {% set forms = [] %} + {% for img in drug.images %}{% if img.strength %}{% set _ = forms.append(img.strength) %}{% endif %}{% endfor %} + {{ forms|join('; ') if forms else 'See prescribing information' }} +
  • + {% endif %} +
  • Drug class: + {% if drug.drug_class %} + {{ drug.drug_class.name }} + {% else %}N/A{% endif %} +
  • +
  • Availability: {{ drug.availability }}
  • +
+

Medically reviewed by {{ drug.reviewer_name }}, {{ drug.reviewer_credential }}. Last updated on {{ drug.last_updated.strftime('%b %d, %Y') }}.

+
+ + + +
+
+ {% if drug.description %} +

What is {{ drug.generic_name }}?

{{ drug.description }}

+ {% endif %} + +

Uses

{{ drug.uses }}

+ {% if drug_conditions %} +

Conditions treated: + {% for c in drug_conditions %}{{ c.name }}{% if not loop.last %}, {% endif %}{% endfor %} +

+ {% endif %} +
+ +

Warnings

{{ drug.warnings }}

+ +
+

Before taking this medicine

+

You should not use {{ drug.generic_name }} if you are allergic to it, or if you have ever had an asthma attack or severe allergic reaction after taking aspirin or an NSAID.

+

To make sure {{ drug.generic_name }} is safe for you, tell your doctor if you have ever had heart disease, high blood pressure, a heart attack, stroke, or blood clot; stomach ulcers or bleeding; liver or kidney disease; asthma; or if you smoke.

+

Ask a doctor before using this medicine if you are pregnant or breastfeeding.

+
+ +
+

How should I take {{ drug.generic_name }}?

+

{{ drug.dosage }}

+

What happens if I miss a dose?

+

Since {{ drug.generic_name }} is often used when needed, you may not be on a dosing schedule. If you are on a schedule, take the missed dose as soon as you remember. Skip the missed dose if it is almost time for your next scheduled dose. Do not take extra medicine to make up the missed dose.

+

What happens if I overdose?

+

Seek emergency medical attention or call the Poison Help line at 1-800-222-1222.

+

What to avoid

+

Ask a doctor or pharmacist before using other medicines for pain, fever, swelling, or cold/flu symptoms. They may contain ingredients similar to {{ drug.generic_name }}. Avoid drinking alcohol, which may increase your risk of stomach bleeding.

+
+ +
+

{{ drug.generic_name|capitalize }} side effects

+

Get emergency medical help if you have signs of an allergic reaction: hives; difficult breathing; swelling of your face, lips, tongue, or throat.

+

{{ drug.side_effects }}

+
+ +
+

What other drugs will affect {{ drug.generic_name }}?

+

{{ drug.interactions_text }}

+
+

Enter medications to view a detailed interaction report using our Drug Interaction Checker.

+
+ + + +
+
+
+ +

Popular FAQ

+ {% if drug.faq %} +
    + {% for item in drug.faq %} +
  • {{ item.q }}
  • + {% endfor %} +
+ {% for item in drug.faq %} +
+

{{ item.q }}

+

{{ item.a }}

+
+ {% endfor %} + {% endif %} +
+ +

User reviews ({{ drug.review_count }})

+

Average rating: {{ drug.avg_rating }}/10

+ {% for r in reviews %} +
+
+ {{ r.title }} + — {{ r.user.username if r.user else 'anon' }} on {{ r.created_at.strftime('%b %d, %Y') }} +
+

Rating: {{ r.rating }}/10 · For: {{ r.condition_treated }}

+

{{ r.body }}

+

{{ r.helpful_count }} found this helpful

+
+ {% else %} +

No reviews yet.

+ {% endfor %} +
+ + {% if drug_conditions %} + + {% endif %} + +
+

Further information

+

Remember, keep this and all other medicines out of the reach of children, never share your medicines with others, and use {{ drug.generic_name }} only for the indication prescribed. Always consult your healthcare provider to ensure the information displayed on this page applies to your personal circumstances.

+

Medical Disclaimer: The information provided here is for reference only and is not intended as medical advice or a substitute for consultation with a qualified healthcare professional.

+
+
+ + +
+
+ + +{% endblock %} diff --git a/sites/drugs_com/templates/index.html b/sites/drugs_com/templates/index.html new file mode 100644 index 0000000..644bd18 --- /dev/null +++ b/sites/drugs_com/templates/index.html @@ -0,0 +1,100 @@ +{% extends "base.html" %} +{% block content %} +
+

Trusted medical information you can rely on

+

Drugs.com is the most popular, comprehensive and up-to-date source of drug information online. Providing free, peer-reviewed, accurate and independent data on more than 24,000 prescription drugs, over-the-counter medicines & natural products.

+ + +
+ +
+

Drug Information

+ +
+ +
+

Featured drugs

+ +
+ +
+

Trending

+ +
+ +
+
+

Latest medical news

+
    + {% for a in news %} +
  • +
    + {{ a.category }} + {{ a.published_at.strftime('%B %d, %Y') }} +
    +

    {{ a.title }}

    +

    {{ a.body[:140] }}{% if a.body|length > 140 %}...{% endif %}

    +
  • + {% endfor %} +
+

Read more news...

+
+
+

Browse drug classes

+ +
+
+ +
+

Browse drugs A-Z

+
+ {% for L in all_letters %} + {{ L }} + {% endfor %} +
+
+ +
+

Top 100 Drugs

+ +
+{% endblock %} diff --git a/sites/drugs_com/templates/interaction_checker.html b/sites/drugs_com/templates/interaction_checker.html new file mode 100644 index 0000000..255b1e6 --- /dev/null +++ b/sites/drugs_com/templates/interaction_checker.html @@ -0,0 +1,150 @@ +{% extends "base.html" %} +{% block title %}Drug Interaction Checker - Find Unsafe Combinations - {{ site_name }}{% endblock %} +{% block content %} +
+
+ +

Drug Interaction Checker

+

Use our drug interaction checker to find potentially harmful drug, food, and alcohol interactions.

+ +
+
+ + + + {% for d in drugs %} + +
+ +

Type a drug name in the box above to get started.

+ +
    + + +
    +
    + +

    + Note: Not all drugs interact, and not every interaction means you must stop taking + one of your medications. Always consult your healthcare provider about how drug interactions should + be managed before making any changes to your current prescription. +

    + +
    +

    Drug Interaction FAQs

    +
      +
    • What is a drug interaction?
    • +
    • What are the types of drug interactions?
    • +
    • What do the interaction severity ratings mean?
    • +
    • What are common signs and symptoms of a drug interaction?
    • +
    • How can I avoid drug interactions?
    • +
    • Can I eat or drink grapefruit with my medicine?
    • +
    • Can I drink alcohol with my prescribed medications?
    • +
    • Can herbs and supplements interact with my drugs?
    • +
    • How do I check for drug interactions?
    • +
    • What should I do if I find a drug interaction?
    • +
    +

    FAQs written by Leigh Ann Anderson, PharmD. Last updated on Nov 5, 2025.

    +
    + +
    + + +
    + + + + +{% endblock %} diff --git a/sites/drugs_com/templates/login.html b/sites/drugs_com/templates/login.html new file mode 100644 index 0000000..48da9ad --- /dev/null +++ b/sites/drugs_com/templates/login.html @@ -0,0 +1,26 @@ +{% extends "base.html" %} +{% block title %}Sign in to Drugs.com | {{ site_name }}{% endblock %} +{% block content %} +
    +

    Sign in to Drugs.com

    +
    + + + + + +
    +

    Forgot your password?

    +

    Don't have an account? Create a free account

    + +
    +

    Sign in for access to:

    +
      +
    • My Med List
    • +
    • Interaction Checker results
    • +
    • Drug Reminders
    • +
    • Community discussions
    • +
    +
    +
    +{% endblock %} diff --git a/sites/drugs_com/templates/my_med_list.html b/sites/drugs_com/templates/my_med_list.html new file mode 100644 index 0000000..78177ac --- /dev/null +++ b/sites/drugs_com/templates/my_med_list.html @@ -0,0 +1,31 @@ +{% extends "base.html" %} +{% block title %}My Med List - {{ site_name }}{% endblock %} +{% block content %} + + +

    My Med List

    +

    Track the medications you or a family member are taking.

    + +
      + {% for item in items %} +
    • +

      {{ item.drug.generic_name|capitalize }}

      +

      {{ item.drug.brand_names|join(', ') if item.drug.brand_names else 'Generic' }} · {{ item.drug.availability }}

      +

      Added {{ item.created_at.strftime('%b %d, %Y') }}

      + {% if item.notes %}

      Notes: {{ item.notes }}

      {% endif %} +
    • + {% else %} +
    • Your med list is empty. Browse drugs and click "Add to My Med List" to save medications here.
    • + {% endfor %} +
    + +{% if items %} + +{% endif %} + +

    Always consult your healthcare provider before making any changes to your medications.

    +{% endblock %} diff --git a/sites/drugs_com/templates/news.html b/sites/drugs_com/templates/news.html new file mode 100644 index 0000000..9a6d86e --- /dev/null +++ b/sites/drugs_com/templates/news.html @@ -0,0 +1,71 @@ +{% extends "base.html" %} +{% block title %}{% if active_cat %}{{ active_cat }} - Medical News - {{ site_name }}{% else %}Latest Medical News & Drug Information - {{ site_name }}{% endif %}{% endblock %} +{% block content %} + + +

    + {% if active_cat %}{{ active_cat }}{% else %}Latest Medical News & Drug Information{% endif %} +

    +

    + Stay informed with the latest medical news, FDA drug alerts, new drug approvals, and clinical trial results. +

    + +
    + All + {% for cat in categories %} + {% set slug = cat|lower|replace(' ', '-') %} + {{ cat }} + {% endfor %} +
    + +
    +
    + {% for a in articles %} +
    +
    + {{ a.category }} +
    +

    + {{ a.title }} +

    +

    + {% if a.source %}{{ a.source }} · {% endif %}{{ a.published_at.strftime('%B %d, %Y') }} +

    +

    + {{ a.body[:250] }}{% if a.body|length > 250 %}...{% endif %} +

    + Read more › +
    + {% else %} +

    No articles found.

    + {% endfor %} +
    + + +
    +{% endblock %} diff --git a/sites/drugs_com/templates/news_article.html b/sites/drugs_com/templates/news_article.html new file mode 100644 index 0000000..e343d7a --- /dev/null +++ b/sites/drugs_com/templates/news_article.html @@ -0,0 +1,68 @@ +{% extends "base.html" %} +{% block title %}{{ article.title }} - {{ site_name }}{% endblock %} +{% block content %} + + +
    +
    +
    + {{ article.category }} +
    +

    {{ article.title }}

    + + +
    + {% for paragraph in article.body.split('\n\n') %} +

    {{ paragraph }}

    + {% endfor %} +
    + +
    + Medical Disclaimer: + The information provided in this article is for educational purposes only and is not intended as a substitute for professional medical advice, diagnosis, or treatment. Always seek the advice of your physician or other qualified health provider with any questions you may have regarding a medical condition. +
    + + +
    + + +
    +{% endblock %} diff --git a/sites/drugs_com/templates/pill_identifier.html b/sites/drugs_com/templates/pill_identifier.html new file mode 100644 index 0000000..25fa196 --- /dev/null +++ b/sites/drugs_com/templates/pill_identifier.html @@ -0,0 +1,105 @@ +{% extends "base.html" %} +{% block title %}Pill Identifier - Quickly Find and ID your Drugs (with pictures) - {{ site_name }}{% endblock %} +{% block content %} +
    +
    + +

    Pill Identifier

    + +
    +

    By using the Pill Identification Wizard, you agree to the following terms:

    +

    + The Pill Identification Wizard tool is for informational purposes only. It is not a substitute + for professional medical advice, diagnosis, or treatment. Always seek the advice of a qualified + healthcare provider regarding any medication. + Pill Identification disclaimer +

    + +
    + +

    Need help to identify that pill?

    +

    + Worried about some capsules found in your teenager's room? Not sure about those leftover pills still + in the bathroom cabinet? There's a good chance that our Pill Identification Wizard (Pill Finder) can + help you match the imprint, size, shape, or color and lead you to the detailed description in our + drug database. +

    + +
    + + + + +
    + +{% if results is not none %} +

    Results ({{ results|length }})

    + {% if results %} + + + + {% for r in results %} + + + + + + + {% endfor %} + +
    ImprintShapeColorStrengthDrugManufacturer
    {{ r.imprint }}{{ r.shape }}{{ r.color }}{{ r.strength }}{{ r.drug.generic_name|capitalize }}{{ r.manufacturer }}
    + {% else %} +

    No matching pills found.

    + {% endif %} +{% endif %} + +
    +

    Additional tips

    +
      +
    • Periodically check your medicine cabinets for expired, re-bottled, or unidentified pills.
    • +
    • Avoid confusion and mistakes: keep all medications in their original bottles or packets with pertinent labeling and instructions attached.
    • +
    • If you do not find a match while trying to identify your pill using our Pill Finder tool, then contact your healthcare provider.
    • +
    +
    + +
    + + +
    + + +{% endblock %} diff --git a/sites/drugs_com/templates/register.html b/sites/drugs_com/templates/register.html new file mode 100644 index 0000000..5fe269c --- /dev/null +++ b/sites/drugs_com/templates/register.html @@ -0,0 +1,30 @@ +{% extends "base.html" %} +{% block title %}Create a free account | {{ site_name }}{% endblock %} +{% block content %} +
    +

    Create a free account

    +
    + + + + + + + + + +
    + +
    +

    Benefits of a free account:

    +
      +
    • Save your medication list
    • +
    • Get interaction alerts
    • +
    • Set dosage reminders
    • +
    • Rate and review medications
    • +
    +
    + +

    Already have an account? Sign in

    +
    +{% endblock %} diff --git a/sites/drugs_com/templates/search.html b/sites/drugs_com/templates/search.html new file mode 100644 index 0000000..c7d43db --- /dev/null +++ b/sites/drugs_com/templates/search.html @@ -0,0 +1,54 @@ +{% extends "base.html" %} +{% block title %}Search results for "{{ q }}" - {{ site_name }}{% endblock %} +{% block content %} +

    Search results for "{{ q }}"

    + +
    + + + + + +
    + +

    {{ results|length }} result{{ '' if results|length == 1 else 's' }} for "{{ q }}"

    + +
      + {% for d in results %} +
    • +

      {{ d.generic_name|capitalize }}

      +

      + generic name: {{ d.generic_name }} + {% if d.drug_class %} · {{ d.drug_class.name }}{% endif %} + · {{ d.availability }} +

      +

      {{ (d.description or '')[:240] }}{% if (d.description or '')|length > 240 %}...{% endif %}

      +

      Rating: {{ d.avg_rating }}/10 ({{ d.review_count }} reviews)

      +
    • + {% else %} +
    • +

      No results found for "{{ q }}".

      +
        +
      • Check your spelling.
      • +
      • Try searching by a brand name instead of the generic name (or vice versa).
      • +
      • Browse the full Drugs A-Z index.
      • +
      +
    • + {% endfor %} +
    +{% endblock %} diff --git a/websyn_start.sh b/websyn_start.sh index 72defad..50917b9 100644 --- a/websyn_start.sh +++ b/websyn_start.sh @@ -5,7 +5,7 @@ set -e SITES=(allrecipes amazon apple arxiv bbc_news booking github google_flights google_map google_search huggingface wolfram_alpha - cambridge_dictionary coursera espn) + cambridge_dictionary coursera espn drugs_com) BASE_PORT=40000 PID_DIR=/tmp/websyn_pids mkdir -p "$PID_DIR" From 61bc7c25899b93d9aafe32b1ee1291547ec54ae3 Mon Sep 17 00:00:00 2001 From: Boyu Gou Date: Wed, 13 May 2026 10:05:37 -0700 Subject: [PATCH 002/167] Auto-login as alice; make login/register always succeed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit before_request hook logs in alice.j@test.com on every unauthenticated request, so all protected routes work without real credentials. login/register POST always redirect to account — any input is accepted. Co-Authored-By: Claude Sonnet 4.6 --- sites/drugs_com/app.py | 36 +++++++++++------------------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/sites/drugs_com/app.py b/sites/drugs_com/app.py index 306a125..9fcc54e 100644 --- a/sites/drugs_com/app.py +++ b/sites/drugs_com/app.py @@ -48,6 +48,15 @@ login_manager.login_view = "login" +@app.before_request +def auto_login(): + """Always serve as alice — no real auth needed in this benchmark environment.""" + if not current_user.is_authenticated: + alice = User.query.filter_by(email="alice.j@test.com").first() + if alice: + login_user(alice) + + def slugify(text): text = (text or "").lower().strip() text = re.sub(r"[^a-z0-9]+", "-", text) @@ -1052,37 +1061,14 @@ def conditions_list(): @app.route("/login", methods=["GET", "POST"]) def login(): if request.method == "POST": - email = request.form.get("email", "").strip().lower() - password = request.form.get("password", "") - user = User.query.filter(db.func.lower(User.email) == email).first() - if user and user.check_password(password): - login_user(user) - flash("Welcome back!", "success") - return redirect(request.args.get("next") or url_for("account")) - flash("Invalid email or password.", "error") + flash("Welcome back!", "success") + return redirect(request.args.get("next") or url_for("account")) return render_template("login.html") @app.route("/register", methods=["GET", "POST"]) def register(): if request.method == "POST": - email = request.form.get("email", "").strip().lower() - username = request.form.get("username", "").strip() - password = request.form.get("password", "") - if not email or not username or not password: - flash("All fields are required.", "error") - return render_template("register.html") - if User.query.filter(db.func.lower(User.email) == email).first(): - flash("Email already registered.", "error") - return render_template("register.html") - if User.query.filter_by(username=username).first(): - flash("Username already taken.", "error") - return render_template("register.html") - user = User(email=email, username=username) - user.set_password(password) - db.session.add(user) - db.session.commit() - login_user(user) flash("Account created.", "success") return redirect(url_for("account")) return render_template("register.html") From 122054a516015c3fb3cd98c5f91292e58510e60f Mon Sep 17 00:00:00 2001 From: Boyu Gou Date: Wed, 13 May 2026 10:22:29 -0700 Subject: [PATCH 003/167] Redesign all templates to match real drugs.com UI/layout Complete visual overhaul matching the real drugs.com site: Header: white background with colorful logo, A-Z browse strip, search bar in header, dark navy nav with correct items (Drug Interaction Checker, Compare Drugs, Pro Edition, More...). Homepage: "Know more. Be sure." hero, feature icons row (My Med List / Pill Identifier / Drug Interaction Checker / Drugs & Medications), A-Z letter grid with Browse by Site Section panel, popular drug searches tag cloud. Drug detail: "What is X?" section, reviewer avatar with initials, tab nav (Uses/Warnings/Before taking/Dosage/Side effects/Interactions/FAQ), Drug Status sidebar with icons (Rx/OTC badges, pregnancy, CSA, approval, ingredients), rating bar, related drugs, more-about links. All other pages (A-Z, interaction checker, pill identifier, news, conditions list, condition detail, drug class, search, account, My Med List, login, register) rewritten to match real site layout, content labels, and structure. CSS completely rewritten: light/white theme, real drugs.com color palette, component classes for all new UI patterns. Co-Authored-By: Claude Sonnet 4.6 --- sites/drugs_com/app.py | 3 +- sites/drugs_com/static/css/main.css | 844 +++++++++++++----- sites/drugs_com/templates/account.html | 44 +- sites/drugs_com/templates/base.html | 122 ++- sites/drugs_com/templates/condition.html | 127 ++- sites/drugs_com/templates/conditions.html | 59 ++ sites/drugs_com/templates/drug_az.html | 152 ++-- sites/drugs_com/templates/drug_class.html | 124 ++- sites/drugs_com/templates/drug_detail.html | 248 ++--- sites/drugs_com/templates/index.html | 219 +++-- .../templates/interaction_checker.html | 223 +++-- sites/drugs_com/templates/login.html | 50 +- sites/drugs_com/templates/my_med_list.html | 74 +- sites/drugs_com/templates/news.html | 62 +- sites/drugs_com/templates/news_article.html | 37 +- .../drugs_com/templates/pill_identifier.html | 79 +- sites/drugs_com/templates/register.html | 62 +- sites/drugs_com/templates/search.html | 82 +- 18 files changed, 1687 insertions(+), 924 deletions(-) create mode 100644 sites/drugs_com/templates/conditions.html diff --git a/sites/drugs_com/app.py b/sites/drugs_com/app.py index 9fcc54e..730ea49 100644 --- a/sites/drugs_com/app.py +++ b/sites/drugs_com/app.py @@ -1054,7 +1054,8 @@ def drug_classes_list(): @app.route("/conditions.html") def conditions_list(): conditions = Condition.query.order_by(Condition.name).all() - return render_template("condition.html", condition=None, drugs=None, all_conditions=conditions) + all_letters = list('ABCDEFGHIJKLMNOPQRSTUVWXYZ') + ['0-9'] + return render_template("conditions.html", conditions=conditions, all_letters=all_letters, active_letter=None) # --- Auth --- diff --git a/sites/drugs_com/static/css/main.css b/sites/drugs_com/static/css/main.css index 7707d45..ab6d11d 100644 --- a/sites/drugs_com/static/css/main.css +++ b/sites/drugs_com/static/css/main.css @@ -1,22 +1,24 @@ -/* Drugs.com Mirror - Main CSS */ +/* Drugs.com Mirror – Main CSS */ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } :root { --navy: #002a5c; --navy-dark: #001a3c; - --blue: #0072ce; - --blue-light: #e5f1fb; - --orange: #f57c00; - --green: #2e8b57; + --blue: #1a73c8; + --blue-mid: #0a5fa0; + --blue-light: #e8f2fb; + --orange: #e07c00; + --green: #2e7d52; --red: #c0392b; - --yellow: #f1c40f; - --gray-50: #f7f8fa; + --yellow: #d4a017; + --gray-50: #f8f9fa; --gray-100: #eef1f5; - --gray-200: #dde2ea; + --gray-200: #dee2e8; --gray-300: #c5ccd6; - --gray-500: #7a8494; - --gray-700: #4a5568; + --gray-500: #6b7585; + --gray-700: #404a5a; --gray-900: #1a202c; + --border: #dee2e8; --shadow: 0 1px 3px rgba(0,0,0,0.08); --shadow-lg: 0 4px 12px rgba(0,0,0,0.10); } @@ -26,303 +28,653 @@ body { font-size: 15px; line-height: 1.55; color: var(--gray-900); - background: var(--gray-50); + background: #fff; } a { color: var(--blue); text-decoration: none; } -a:hover { text-decoration: underline; } +a:hover { text-decoration: underline; color: var(--blue-mid); } .container { max-width: 1200px; margin: 0 auto; padding: 0 20px; } .muted { color: var(--gray-500); font-size: 0.92em; } -/* Header */ -.site-header { background: var(--navy); color: #fff; box-shadow: var(--shadow); } -.header-top { padding: 14px 0; border-bottom: 1px solid rgba(255,255,255,0.08); } -.header-top-inner { display: flex; justify-content: space-between; align-items: center; } -.brand { color: #fff; display: flex; flex-direction: column; } +/* ── Header ── */ +.site-header { + background: #fff; + border-bottom: 1px solid var(--border); + position: sticky; + top: 0; + z-index: 100; + box-shadow: 0 1px 4px rgba(0,0,0,0.07); +} + +.header-top { padding: 10px 0; } +.header-top-inner { + display: flex; + align-items: center; + gap: 16px; +} + +/* Brand */ +.brand { + display: flex; + align-items: center; + gap: 8px; + flex-shrink: 0; + color: inherit; +} .brand:hover { text-decoration: none; } -.brand-logo { font-size: 1.55rem; font-weight: 700; letter-spacing: -0.5px; } +.brand-icon { flex-shrink: 0; } +.brand-text { display: flex; flex-direction: column; line-height: 1.15; } +.brand-logo { + font-size: 1.5rem; + font-weight: 700; + color: var(--navy); + letter-spacing: -0.5px; +} .brand-logo .dot { color: var(--orange); } -.brand-tagline { font-size: 0.78rem; color: rgba(255,255,255,0.7); margin-top: 2px; } -.header-actions a { color: rgba(255,255,255,0.9); margin-left: 18px; font-size: 0.9rem; } -.header-actions a:hover { color: #fff; text-decoration: underline; } -.btn-outline-light { border: 1px solid rgba(255,255,255,0.5); padding: 5px 14px; border-radius: 4px; } -.btn-outline-light:hover { background: rgba(255,255,255,0.1); text-decoration: none; } - -.main-nav { background: var(--navy-dark); } -.nav-inner { display: flex; align-items: center; gap: 24px; padding: 10px 0; flex-wrap: wrap; } -.main-nav a { color: rgba(255,255,255,0.92); font-size: 0.92rem; font-weight: 500; } -.main-nav a:hover { color: var(--orange); text-decoration: none; } -.nav-search { margin-left: auto; display: flex; } -.nav-search input { padding: 6px 10px; border: none; border-radius: 3px 0 0 3px; min-width: 220px; } -.nav-search button { padding: 6px 14px; border: none; background: var(--orange); color: #fff; border-radius: 0 3px 3px 0; cursor: pointer; } - -/* Main */ -.site-main { padding: 28px 0 60px; min-height: 70vh; } - -/* Hero */ -.hero { - background: linear-gradient(135deg, var(--navy) 0%, var(--blue) 100%); - color: #fff; padding: 56px 40px; border-radius: 8px; text-align: center; - margin-bottom: 36px; -} -.hero h1 { font-size: 2.3rem; margin-bottom: 12px; } -.hero p { font-size: 1.1rem; margin-bottom: 26px; opacity: 0.95; } -.hero-search { display: flex; justify-content: center; max-width: 620px; margin: 0 auto 18px; } -.hero-search input { flex: 1; padding: 13px 18px; border: none; border-radius: 4px 0 0 4px; font-size: 1rem; } -.hero-search button { padding: 13px 28px; border: none; background: var(--orange); color: #fff; font-weight: 600; border-radius: 0 4px 4px 0; cursor: pointer; font-size: 1rem; } -.quick-links { margin-top: 14px; } -.quick-links a { color: #fff; margin: 0 12px; border-bottom: 1px dotted rgba(255,255,255,0.5); } - -/* Sections */ -.row-section { background: #fff; padding: 24px; border-radius: 6px; margin-bottom: 24px; box-shadow: var(--shadow); } -.row-section h2 { color: var(--navy); margin-bottom: 16px; font-size: 1.35rem; padding-bottom: 8px; border-bottom: 2px solid var(--gray-100); } -.row-section.split { display: grid; grid-template-columns: 2fr 1fr; gap: 28px; } +.brand-tagline { font-size: 0.72rem; color: var(--blue); font-style: italic; } + +/* Header search */ +.header-search-wrap { flex: 1; max-width: 640px; } +.header-search-form { width: 100%; } +.header-search-inner { + display: flex; + border: 1.5px solid var(--gray-300); + border-radius: 4px; + overflow: hidden; + background: #fff; +} +.header-search-inner:focus-within { border-color: var(--blue); box-shadow: 0 0 0 2px rgba(26,115,200,0.15); } +.header-search-input { + flex: 1; + padding: 9px 14px; + border: none; + outline: none; + font-size: 0.93rem; + color: var(--gray-900); + background: #fff; +} +.header-search-btn { + padding: 0 16px; + border: none; + background: var(--blue); + color: #fff; + cursor: pointer; + display: flex; + align-items: center; + font-size: 1rem; + transition: background 0.15s; +} +.header-search-btn:hover { background: var(--blue-mid); } + +/* Header account actions */ +.header-actions { display: flex; align-items: center; gap: 12px; flex-shrink: 0; } +.header-user-btn { color: var(--gray-700); display: flex; align-items: center; } +.header-user-btn:hover { color: var(--navy); text-decoration: none; } +.header-register-btn { color: var(--gray-700); font-size: 0.9rem; } +.header-signin-btn { + padding: 6px 16px; + border: 1.5px solid var(--navy); + border-radius: 4px; + color: var(--navy); + font-size: 0.9rem; + font-weight: 600; +} +.header-signin-btn:hover { background: var(--navy); color: #fff; text-decoration: none; } + +/* A-Z strip */ +.az-strip { + background: var(--gray-50); + border-bottom: 1px solid var(--border); + padding: 5px 0; + font-size: 0.82rem; +} +.az-strip-inner { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 2px; +} +.az-strip-label { color: var(--gray-500); margin-right: 6px; white-space: nowrap; } +.az-strip-link { color: var(--blue); padding: 2px 4px; font-weight: 500; } +.az-strip-link:hover { text-decoration: underline; } +.az-strip-adv { margin-left: 12px; color: var(--gray-500); } +.az-strip-adv:hover { color: var(--blue); } + +/* Main nav */ +.main-nav { + background: var(--navy); +} +.nav-inner { + display: flex; + align-items: center; + gap: 2px; + overflow: visible; + position: relative; +} +.nav-link { + display: block; + padding: 11px 14px; + color: rgba(255,255,255,0.9); + font-size: 0.88rem; + font-weight: 500; + white-space: nowrap; + transition: background 0.15s; +} +.nav-link:hover { background: rgba(255,255,255,0.12); color: #fff; text-decoration: none; } +.nav-help { margin-left: auto; } + +/* Dropdown */ +.nav-more-wrap { position: relative; } +.nav-more-btn { cursor: pointer; } +.nav-dropdown { + display: none; + position: absolute; + top: 100%; + left: 0; + background: #fff; + border: 1px solid var(--border); + border-radius: 4px; + box-shadow: var(--shadow-lg); + min-width: 200px; + z-index: 200; + padding: 6px 0; +} +.nav-dropdown a { display: block; padding: 8px 16px; color: var(--gray-900); font-size: 0.9rem; } +.nav-dropdown a:hover { background: var(--blue-light); color: var(--blue); text-decoration: none; } +.nav-more-wrap:hover .nav-dropdown { display: block; } + +/* ── Main content ── */ +.site-main { padding: 22px 0 60px; min-height: 70vh; } + +/* ── Hero (homepage) ── */ +.hero-section { + text-align: center; + padding: 48px 20px 40px; + background: #fff; +} +.hero-section h1 { + font-size: 2.4rem; + font-weight: 700; + color: var(--gray-900); + margin-bottom: 10px; + letter-spacing: -0.5px; +} +.hero-section .hero-sub { + color: var(--gray-500); + font-size: 1.05rem; + margin-bottom: 22px; +} +.hero-search-wrap { + max-width: 680px; + margin: 0 auto 20px; + display: flex; + border: 1.5px solid var(--gray-300); + border-radius: 6px; + overflow: hidden; + box-shadow: 0 2px 8px rgba(0,0,0,0.07); +} +.hero-search-wrap:focus-within { border-color: var(--blue); } +.hero-search-input { + flex: 1; + padding: 14px 18px; + font-size: 1rem; + border: none; + outline: none; + color: var(--gray-900); +} +.hero-search-btn { + padding: 0 24px; + background: var(--blue); + color: #fff; + border: none; + font-size: 0.95rem; + font-weight: 600; + cursor: pointer; +} +.hero-search-btn:hover { background: var(--blue-mid); } +.hero-trending { + font-size: 0.88rem; + color: var(--gray-500); +} +.hero-trending a { margin: 0 4px; color: var(--blue); } + +/* Feature icons row */ +.feature-icons { + display: flex; + justify-content: center; + gap: 32px; + padding: 28px 20px; + border-top: 1px solid var(--gray-100); + border-bottom: 1px solid var(--gray-100); + background: #fff; + flex-wrap: wrap; +} +.feature-icon-item { + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + min-width: 110px; + color: var(--gray-700); + font-size: 0.92rem; + font-weight: 500; + cursor: pointer; + text-align: center; +} +.feature-icon-item:hover { color: var(--blue); text-decoration: none; } +.feature-icon-circle { + width: 64px; height: 64px; + border-radius: 50%; + border: 2px solid var(--gray-200); + display: flex; + align-items: center; + justify-content: center; + background: #fff; + color: var(--blue); +} +.feature-icon-item:hover .feature-icon-circle { border-color: var(--blue); } + +/* Homepage browse + sections */ +.home-browse-grid { + display: grid; + grid-template-columns: 2fr 1fr; + gap: 28px; + padding: 28px 0; +} +.browse-panel-title { + font-size: 0.95rem; + font-weight: 700; + color: #fff; + background: var(--navy); + padding: 10px 16px; + border-radius: 4px 4px 0 0; + text-transform: uppercase; + letter-spacing: 0.3px; +} +.browse-panel-tabs { + display: flex; + border-bottom: 2px solid var(--border); + background: var(--gray-50); +} +.browse-tab { + padding: 10px 18px; + font-size: 0.88rem; + font-weight: 600; + color: var(--gray-700); + border-bottom: 2px solid transparent; + margin-bottom: -2px; +} +.browse-tab.active { color: var(--blue); border-bottom-color: var(--blue); } +.browse-tab:hover { color: var(--blue); text-decoration: none; } +.letter-grid { + display: grid; + grid-template-columns: repeat(7, 1fr); + gap: 6px; + padding: 16px; + background: var(--gray-50); + border: 1px solid var(--border); + border-top: none; +} +.letter-btn { + display: flex; + align-items: center; + justify-content: center; + padding: 10px 0; + background: #fff; + color: var(--navy); + font-weight: 600; + border-radius: 4px; + border: 1px solid var(--gray-200); + font-size: 0.95rem; +} +.letter-btn:hover, .letter-btn.active { background: var(--blue); color: #fff; border-color: var(--blue); text-decoration: none; } +.browse-footer { + padding: 10px 16px; + background: var(--gray-50); + border: 1px solid var(--border); + border-top: none; + font-size: 0.82rem; + color: var(--gray-500); +} +.browse-footer a { margin: 0 4px; } + +/* Browse by site section */ +.site-section-panel { + background: var(--gray-50); + border: 1px solid var(--border); + border-top: none; + padding: 16px; +} +.site-section-panel ul { list-style: none; } +.site-section-panel li { padding: 5px 0; border-bottom: 1px solid var(--gray-100); } +.site-section-panel li:last-child { border-bottom: none; } +.site-section-panel a { font-size: 0.9rem; } + +/* Homepage news + featured drugs */ +.home-row { + padding: 24px 0; + border-top: 1px solid var(--gray-100); +} +.home-row h2 { + font-size: 1.25rem; + font-weight: 700; + color: var(--navy); + margin-bottom: 16px; +} /* Cards */ .card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 14px; } .drug-card { - background: #fff; border: 1px solid var(--gray-200); padding: 16px; border-radius: 6px; - color: var(--gray-900); display: block; transition: all 0.15s; + background: #fff; + border: 1px solid var(--gray-200); + padding: 16px; + border-radius: 6px; + color: var(--gray-900); + display: block; + transition: all 0.15s; } .drug-card:hover { border-color: var(--blue); box-shadow: var(--shadow-lg); text-decoration: none; } -.drug-card h3 { color: var(--blue); font-size: 1.05rem; margin-bottom: 6px; } -.rating { color: var(--green); font-weight: 600; font-size: 0.9rem; } +.drug-card h3 { color: var(--blue); font-size: 1.02rem; margin-bottom: 4px; } +.drug-card .muted { font-size: 0.82rem; margin-bottom: 4px; } +.rating { color: var(--green); font-weight: 600; font-size: 0.88rem; } -/* Trending list */ -.trending-list { list-style: decimal inside; columns: 2; column-gap: 30px; } -.trending-list li { padding: 4px 0; } - -/* News */ +/* News list */ .news-list { list-style: none; } -.news-list li { padding: 10px 0; border-bottom: 1px solid var(--gray-100); } +.news-list li { padding: 12px 0; border-bottom: 1px solid var(--gray-100); } .news-list li:last-child { border-bottom: none; } -.news-list.big li { padding: 18px 0; } -.news-list.big h3 { color: var(--navy); margin: 6px 0; } +.news-list h3 { color: var(--navy); font-size: 1rem; margin: 5px 0; line-height: 1.4; } +.news-list p { font-size: 0.88rem; color: var(--gray-500); } +/* Badge */ .badge { - display: inline-block; padding: 2px 8px; background: var(--blue-light); color: var(--blue); - border-radius: 3px; font-size: 0.75rem; font-weight: 600; text-transform: uppercase; - margin-right: 8px; letter-spacing: 0.3px; + display: inline-block; + padding: 2px 8px; + background: var(--blue-light); + color: var(--blue); + border-radius: 3px; + font-size: 0.73rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.4px; + margin-right: 6px; } -.class-list { list-style: none; } -.class-list li { padding: 6px 0; border-bottom: 1px solid var(--gray-100); } - -/* A-Z */ -.letter-grid { display: grid; grid-template-columns: repeat(13, 1fr); gap: 6px; margin: 14px 0 24px; } -.letter-grid.wide { grid-template-columns: repeat(13, 1fr); } -.letter-btn { - display: block; text-align: center; padding: 10px 0; background: var(--gray-100); - color: var(--navy); font-weight: 600; border-radius: 4px; -} -.letter-btn:hover, .letter-btn.active { background: var(--blue); color: #fff; text-decoration: none; } +/* ── Row sections ── */ +.row-section { background: #fff; padding: 24px; border-radius: 6px; margin-bottom: 24px; box-shadow: var(--shadow); } +.row-section h2 { color: var(--navy); margin-bottom: 16px; font-size: 1.3rem; padding-bottom: 10px; border-bottom: 2px solid var(--gray-100); } +.row-section.split { display: grid; grid-template-columns: 2fr 1fr; gap: 28px; } +/* ── Drug A-Z ── */ .az-list ul { list-style: none; } -.az-list li { padding: 10px 0; border-bottom: 1px solid var(--gray-100); } +.az-list li { padding: 9px 0; border-bottom: 1px solid var(--gray-100); font-size: 0.95rem; } /* Breadcrumb */ .breadcrumb { color: var(--gray-500); font-size: 0.88rem; margin-bottom: 14px; } .breadcrumb a { color: var(--blue); } - -/* Drug detail */ -.drug-detail { background: #fff; padding: 28px; border-radius: 6px; box-shadow: var(--shadow); } -.drug-header h1 { color: var(--navy); font-size: 2.1rem; margin-bottom: 6px; } -.pronunciation { color: var(--gray-700); margin-bottom: 12px; } -.meta-row { list-style: none; display: flex; flex-wrap: wrap; gap: 18px; padding: 12px 0; border-top: 1px solid var(--gray-200); border-bottom: 1px solid var(--gray-200); margin: 12px 0; font-size: 0.9rem; } -.reviewer-line { color: var(--gray-500); font-size: 0.85rem; margin-bottom: 18px; } -.section-tabs { display: flex; flex-wrap: wrap; gap: 4px; border-bottom: 2px solid var(--gray-200); margin-bottom: 22px; } -.section-tabs a { padding: 10px 16px; color: var(--navy); font-weight: 500; border-bottom: 3px solid transparent; } -.section-tabs a:hover { border-bottom-color: var(--orange); text-decoration: none; } +.breadcrumb span { color: var(--gray-500); margin: 0 4px; } + +/* ── Drug detail ── */ +.drug-detail { background: #fff; padding: 0; } +.drug-header h1 { color: var(--navy); font-size: 2rem; margin-bottom: 6px; } +.pronunciation { color: var(--gray-700); font-style: italic; margin-bottom: 12px; } +.meta-row { list-style: none; display: flex; flex-wrap: wrap; gap: 16px; padding: 12px 0; border-top: 1px solid var(--gray-200); border-bottom: 1px solid var(--gray-200); margin: 12px 0; font-size: 0.9rem; } +.meta-row li strong { color: var(--gray-700); } +.reviewer-line { + display: flex; align-items: center; gap: 10px; + color: var(--gray-500); font-size: 0.85rem; margin: 12px 0 18px; + padding: 10px 0; border-bottom: 1px solid var(--gray-100); +} +.reviewer-avatar { + width: 36px; height: 36px; border-radius: 50%; + background: var(--blue-light); color: var(--blue); + display: flex; align-items: center; justify-content: center; + font-weight: 700; font-size: 0.85rem; flex-shrink: 0; +} +.section-tabs { + display: flex; flex-wrap: wrap; gap: 0; + border-bottom: 2px solid var(--gray-200); margin-bottom: 22px; +} +.section-tabs a { + padding: 10px 16px; color: var(--gray-700); + font-size: 0.9rem; font-weight: 500; + border-bottom: 3px solid transparent; margin-bottom: -2px; + white-space: nowrap; +} +.section-tabs a:hover { color: var(--blue); border-bottom-color: var(--blue); text-decoration: none; } +.section-tabs a.active { color: var(--blue); border-bottom-color: var(--blue); } .detail-grid { display: grid; grid-template-columns: 2fr 1fr; gap: 28px; } .detail-main section { margin-bottom: 28px; } -.detail-main h2 { color: var(--navy); margin-bottom: 12px; font-size: 1.35rem; } -.detail-main p { margin-bottom: 10px; } +.detail-main h2 { color: var(--navy); margin-bottom: 12px; font-size: 1.25rem; } +.detail-main h3 { color: var(--navy); margin: 14px 0 8px; font-size: 1.05rem; } +.detail-main p { margin-bottom: 10px; line-height: 1.65; } +.detail-main ul { padding-left: 22px; margin-bottom: 10px; } +.detail-main li { margin-bottom: 4px; line-height: 1.6; } + +/* Drug status sidebar */ +.detail-sidebar > div { + background: var(--gray-50); border: 1px solid var(--gray-200); + border-radius: 6px; padding: 16px; margin-bottom: 14px; +} +.status-box-title { + font-size: 0.75rem; font-weight: 700; text-transform: uppercase; + letter-spacing: 0.6px; color: var(--gray-700); + padding-bottom: 8px; border-bottom: 1px solid var(--gray-200); + margin-bottom: 12px; +} +.status-row { + display: flex; align-items: flex-start; gap: 10px; + padding: 9px 0; border-bottom: 1px solid var(--gray-100); + font-size: 0.9rem; +} +.status-row:last-child { border-bottom: none; } +.status-icon { + width: 36px; height: 36px; border-radius: 50%; + display: flex; align-items: center; justify-content: center; + flex-shrink: 0; font-size: 0.72rem; font-weight: 700; +} +.status-icon.rx-otc { background: #1a73c8; color: #fff; font-size: 0.68rem; text-align: center; line-height: 1.1; } +.status-icon.pregnancy { background: #f59e0b; color: #fff; } +.status-icon.csa { background: var(--gray-200); color: var(--gray-700); font-size: 0.78rem; } +.status-icon.approval { background: var(--blue-light); color: var(--blue); } +.status-icon.ingredients { background: #e8f5e9; color: #2e7d52; } +.status-label { font-weight: 600; color: var(--navy); } +.status-value { color: var(--gray-500); font-size: 0.85rem; } + +.avail-badge { + display: inline-block; padding: 2px 7px; border-radius: 3px; + font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.3px; +} +.avail-rx { background: #dbeafe; color: #1e40af; } +.avail-otc { background: #dcfce7; color: #166534; } + +.sidebar-box h3 { color: var(--navy); font-size: 0.95rem; margin-bottom: 10px; font-weight: 700; } +.sidebar-box ul { list-style: none; } +.sidebar-box li { padding: 5px 0; font-size: 0.9rem; border-bottom: 1px solid var(--gray-100); } +.sidebar-box li:last-child { border-bottom: none; } + +/* Reviews & ratings */ +.rating-bar-wrap { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; } +.rating-bar { height: 8px; border-radius: 4px; background: var(--green); flex: 1; max-width: 160px; } +.rating-score { font-size: 1.1rem; font-weight: 700; color: var(--navy); } +.rating-count { color: var(--blue); font-size: 0.88rem; } .faq-item { padding: 12px 0; border-bottom: 1px solid var(--gray-100); } .faq-item h3 { color: var(--navy); font-size: 1rem; margin-bottom: 6px; } .review { padding: 14px 0; border-bottom: 1px solid var(--gray-100); } -.review header { margin-bottom: 6px; } +.review-header { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; } +.review-score { font-weight: 700; color: var(--green); } -/* Sidebar */ -.detail-sidebar > div { background: var(--gray-50); border: 1px solid var(--gray-200); border-radius: 6px; padding: 16px; margin-bottom: 16px; } -.status-box h3, .sidebar-box h3 { color: var(--navy); font-size: 1rem; margin-bottom: 10px; } -.status-box ul, .sidebar-box ul { list-style: none; } -.status-box li, .sidebar-box li { padding: 5px 0; font-size: 0.9rem; } - -/* Buttons */ -.btn { display: inline-block; padding: 9px 18px; border-radius: 4px; font-weight: 600; cursor: pointer; border: none; font-size: 0.9rem; } +/* ── Buttons ── */ +.btn { display: inline-block; padding: 9px 18px; border-radius: 4px; font-weight: 600; cursor: pointer; border: none; font-size: 0.9rem; transition: all 0.15s; } .btn-primary { background: var(--orange); color: #fff; } -.btn-primary:hover { background: #e07000; text-decoration: none; color: #fff; } -.btn-outline { background: transparent; border: 1px solid var(--blue); color: var(--blue); } -.btn-sm { padding: 3px 8px; font-size: 0.78rem; } - -/* Search filters */ -.search-filters { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 22px; background: #fff; padding: 14px; border-radius: 6px; box-shadow: var(--shadow); } -.search-filters input, .search-filters select { padding: 8px 10px; border: 1px solid var(--gray-300); border-radius: 4px; font-size: 0.92rem; } +.btn-primary:hover { background: #c56e00; text-decoration: none; color: #fff; } +.btn-outline { background: transparent; border: 1.5px solid var(--blue); color: var(--blue); } +.btn-outline:hover { background: var(--blue-light); text-decoration: none; } +.btn-sm { padding: 4px 10px; font-size: 0.8rem; } +.btn-med-list { background: var(--orange); color: #fff; padding: 10px 20px; border: none; border-radius: 4px; font-weight: 600; cursor: pointer; font-size: 0.9rem; width: 100%; text-align: center; display: block; } +.btn-med-list:hover { background: #c56e00; text-decoration: none; color: #fff; } + +/* ── Search ── */ +.search-filters { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 22px; background: var(--gray-50); padding: 14px; border-radius: 6px; border: 1px solid var(--border); } +.search-filters input, .search-filters select { padding: 8px 10px; border: 1px solid var(--gray-300); border-radius: 4px; font-size: 0.92rem; background: #fff; } .search-filters input { flex: 1; min-width: 220px; } .result-list { list-style: none; } .result-card { background: #fff; border: 1px solid var(--gray-200); padding: 18px; border-radius: 6px; margin-bottom: 12px; } -.result-card h3 { color: var(--blue); margin-bottom: 6px; } - -/* Interaction checker */ -.checker-box { background: #fff; padding: 22px; border-radius: 6px; box-shadow: var(--shadow); } -.checker-input { display: flex; gap: 10px; margin-bottom: 14px; } -.checker-input input { flex: 1; padding: 9px 12px; border: 1px solid var(--gray-300); border-radius: 4px; } +.result-card h2 { color: var(--blue); margin-bottom: 6px; font-size: 1.1rem; } + +/* ── Interaction checker ── */ +.checker-box { background: #fff; padding: 22px; border-radius: 6px; box-shadow: var(--shadow); border: 1px solid var(--border); } +.checker-input { display: flex; gap: 10px; margin-bottom: 10px; } +.checker-input input { flex: 1; padding: 10px 14px; border: 1px solid var(--gray-300); border-radius: 4px; font-size: 0.95rem; } +.checker-input .add-btn { padding: 10px 22px; background: var(--blue); color: #fff; border: none; border-radius: 4px; font-weight: 600; cursor: pointer; } +.checker-input .add-btn:hover { background: var(--blue-mid); } +.checker-hint { color: var(--gray-500); font-size: 0.85rem; margin-bottom: 14px; } .checker-drug-list { list-style: none; margin-bottom: 14px; } -.checker-drug-list li { padding: 8px 12px; background: var(--gray-50); border-radius: 4px; margin-bottom: 6px; display: flex; justify-content: space-between; align-items: center; } +.checker-drug-list li { padding: 8px 12px; background: var(--blue-light); border-radius: 4px; margin-bottom: 6px; display: flex; justify-content: space-between; align-items: center; font-size: 0.9rem; } +.checker-drug-list li button { background: none; border: none; color: var(--red); cursor: pointer; font-size: 1.1rem; line-height: 1; } .checker-results { margin-top: 22px; } .interaction-list { list-style: none; } -.interaction { padding: 14px; border-radius: 6px; margin-bottom: 10px; border-left: 4px solid var(--gray-300); background: var(--gray-50); } +.interaction { padding: 14px 16px; border-radius: 6px; margin-bottom: 10px; border-left: 4px solid var(--gray-300); background: var(--gray-50); } .interaction.sev-major { border-left-color: var(--red); background: #fdecea; } -.interaction.sev-moderate { border-left-color: var(--orange); background: #fff4e5; } -.interaction.sev-minor { border-left-color: var(--yellow); background: #fffbe5; } -.warn { color: var(--red); } - -/* Pill identifier */ -.pill-form { display: flex; flex-wrap: wrap; gap: 14px; align-items: flex-end; background: #fff; padding: 18px; border-radius: 6px; margin-bottom: 22px; box-shadow: var(--shadow); } -.pill-form label { display: flex; flex-direction: column; font-size: 0.85rem; color: var(--gray-700); flex: 1; min-width: 160px; } -.pill-form input, .pill-form select { padding: 8px; border: 1px solid var(--gray-300); border-radius: 4px; margin-top: 4px; } +.interaction.sev-moderate { border-left-color: #f59e0b; background: #fffbeb; } +.interaction.sev-minor { border-left-color: var(--green); background: #f0fdf4; } +.sev-label { font-weight: 700; font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.4px; } +.sev-major .sev-label { color: var(--red); } +.sev-moderate .sev-label { color: #d97706; } +.sev-minor .sev-label { color: var(--green); } +.interaction h4 { color: var(--navy); margin: 4px 0; } + +/* ── Pill identifier ── */ +.pill-form { display: flex; flex-wrap: wrap; gap: 14px; align-items: flex-end; background: var(--gray-50); padding: 20px; border-radius: 6px; margin-bottom: 22px; border: 1px solid var(--border); } +.pill-form label { display: flex; flex-direction: column; font-size: 0.85rem; color: var(--gray-700); flex: 1; min-width: 160px; font-weight: 500; } +.pill-form input, .pill-form select { padding: 8px 10px; border: 1px solid var(--gray-300); border-radius: 4px; margin-top: 4px; font-size: 0.92rem; background: #fff; } .pill-table { width: 100%; background: #fff; border-collapse: collapse; box-shadow: var(--shadow); border-radius: 6px; overflow: hidden; } .pill-table th, .pill-table td { padding: 10px 14px; text-align: left; border-bottom: 1px solid var(--gray-100); } .pill-table th { background: var(--navy); color: #fff; font-weight: 600; font-size: 0.88rem; } - -/* News tabs */ -.news-tabs { display: flex; gap: 4px; border-bottom: 2px solid var(--gray-200); margin-bottom: 22px; flex-wrap: wrap; } -.news-tabs a { padding: 10px 18px; color: var(--navy); font-weight: 500; } -.news-tabs a.active { border-bottom: 3px solid var(--orange); } - -/* Conditions / classes grid */ -.condition-grid { list-style: none; display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 12px; } +.pill-table tr:hover td { background: var(--blue-light); } +.disclaimer-box { background: #fff8e1; border: 1px solid #ffe082; border-radius: 6px; padding: 16px; margin-bottom: 22px; font-size: 0.9rem; } +.disclaimer-box h4 { color: #856404; margin-bottom: 6px; } + +/* ── News ── */ +.news-tabs { display: flex; gap: 0; border-bottom: 2px solid var(--gray-200); margin-bottom: 22px; flex-wrap: wrap; } +.news-tabs a { padding: 10px 18px; color: var(--gray-700); font-weight: 500; font-size: 0.9rem; border-bottom: 3px solid transparent; margin-bottom: -2px; } +.news-tabs a.active, .news-tabs a:hover { color: var(--blue); border-bottom-color: var(--blue); text-decoration: none; } +.news-card { padding: 18px 0; border-bottom: 1px solid var(--gray-100); } +.news-card h2 { font-size: 1.15rem; font-weight: 700; margin: 6px 0 8px; } +.news-card h2 a { color: var(--navy); } +.news-card h2 a:hover { color: var(--blue); } +.news-meta { font-size: 0.83rem; color: var(--gray-500); margin-bottom: 4px; } + +/* ── Conditions / Drug classes ── */ +.condition-grid { list-style: none; display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 10px; } .condition-grid li { background: #fff; padding: 12px 16px; border-radius: 4px; border: 1px solid var(--gray-200); } +.condition-grid li:hover { border-color: var(--blue); } +.drug-table { width: 100%; border-collapse: collapse; background: #fff; border-radius: 6px; overflow: hidden; margin: 16px 0; } +.drug-table th { background: var(--navy); color: #fff; padding: 10px 14px; text-align: left; font-weight: 600; font-size: 0.88rem; } +.drug-table td { padding: 10px 14px; border-bottom: 1px solid var(--gray-100); font-size: 0.92rem; } +.drug-table tr:hover td { background: var(--blue-light); } -/* Auth */ -.auth-box { max-width: 420px; margin: 24px auto; background: #fff; padding: 28px; border-radius: 6px; box-shadow: var(--shadow); } -.auth-box h1 { color: var(--navy); margin-bottom: 18px; } -.auth-box label { display: block; margin-bottom: 14px; font-size: 0.88rem; color: var(--gray-700); } -.auth-box input { display: block; width: 100%; padding: 9px 12px; border: 1px solid var(--gray-300); border-radius: 4px; margin-top: 4px; font-size: 0.95rem; } -.auth-box button { width: 100%; margin-top: 6px; padding: 10px; } +/* Class list */ +.class-list { list-style: none; } +.class-list li { padding: 7px 0; border-bottom: 1px solid var(--gray-100); } + +/* ── Auth ── */ +.auth-box { max-width: 460px; margin: 30px auto; background: #fff; padding: 36px; border-radius: 8px; box-shadow: var(--shadow-lg); border: 1px solid var(--border); } +.auth-box h1 { color: var(--navy); margin-bottom: 20px; font-size: 1.6rem; } +.auth-box label { display: block; margin-bottom: 14px; font-size: 0.88rem; color: var(--gray-700); font-weight: 500; } +.auth-box input { display: block; width: 100%; padding: 10px 12px; border: 1px solid var(--gray-300); border-radius: 4px; margin-top: 4px; font-size: 0.95rem; } +.auth-box input:focus { outline: none; border-color: var(--blue); box-shadow: 0 0 0 2px rgba(26,115,200,0.15); } +.auth-box button { width: 100%; margin-top: 6px; padding: 11px; font-size: 1rem; } .auth-box p { margin-top: 14px; text-align: center; font-size: 0.9rem; } +.auth-benefits { background: var(--blue-light); border-radius: 6px; padding: 14px 16px; margin-bottom: 20px; } +.auth-benefits h3 { color: var(--navy); margin-bottom: 8px; font-size: 0.9rem; } +.auth-benefits ul { list-style: none; } +.auth-benefits li { padding: 3px 0; font-size: 0.88rem; color: var(--gray-700); } +.auth-benefits li::before { content: "✓ "; color: var(--green); font-weight: 700; } -/* Account */ +/* ── Account ── */ .account-grid { display: grid; grid-template-columns: 1fr 2fr; gap: 22px; margin-top: 20px; } -.account-box { background: #fff; padding: 22px; border-radius: 6px; box-shadow: var(--shadow); } -.account-box h2 { color: var(--navy); margin-bottom: 12px; } +.account-box { background: #fff; padding: 22px; border-radius: 6px; box-shadow: var(--shadow); border: 1px solid var(--border); } +.account-box h2 { color: var(--navy); margin-bottom: 12px; font-size: 1.1rem; } +.account-tabs { display: flex; border-bottom: 2px solid var(--gray-200); margin-bottom: 20px; } +.account-tabs a { padding: 10px 18px; color: var(--gray-700); font-size: 0.9rem; font-weight: 500; border-bottom: 3px solid transparent; margin-bottom: -2px; } +.account-tabs a.active, .account-tabs a:hover { color: var(--blue); border-bottom-color: var(--blue); text-decoration: none; } -/* Flash */ +/* ── Flash messages ── */ .flash-list { list-style: none; margin-bottom: 18px; } -.flash { padding: 10px 16px; border-radius: 4px; margin-bottom: 6px; } +.flash { padding: 10px 16px; border-radius: 4px; margin-bottom: 6px; font-size: 0.92rem; } .flash-success { background: #e6f4ea; color: #1a6d34; border: 1px solid #b6dec3; } .flash-error { background: #fdecea; color: #8a1d12; border: 1px solid #f0b8b0; } -/* Footer */ -.site-footer { background: var(--navy-dark); color: rgba(255,255,255,0.85); margin-top: 40px; padding: 36px 0 18px; } -.footer-inner { display: grid; grid-template-columns: 2fr 1fr 1fr; gap: 30px; padding-bottom: 22px; border-bottom: 1px solid rgba(255,255,255,0.1); } -.footer-col h4 { color: #fff; margin-bottom: 12px; font-size: 1rem; } +/* ── Footer ── */ +.site-footer { + background: #1a202c; + color: rgba(255,255,255,0.8); + margin-top: 48px; + padding: 36px 0 0; + font-size: 0.88rem; +} +.footer-top { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 28px; + padding-bottom: 28px; + border-bottom: 1px solid rgba(255,255,255,0.1); +} +.footer-col h4 { color: #fff; margin-bottom: 12px; font-size: 0.9rem; text-transform: uppercase; letter-spacing: 0.4px; } .footer-col ul { list-style: none; } .footer-col li { padding: 4px 0; } -.footer-col a { color: rgba(255,255,255,0.8); } -.footer-col .muted { color: rgba(255,255,255,0.55); } -.footer-bottom { padding-top: 18px; text-align: center; } -.footer-bottom .muted { color: rgba(255,255,255,0.5); } - -@media (max-width: 880px) { - .row-section.split, .detail-grid, .account-grid, .footer-inner { grid-template-columns: 1fr; } - .letter-grid, .letter-grid.wide { grid-template-columns: repeat(7, 1fr); } +.footer-col a { color: rgba(255,255,255,0.7); font-size: 0.88rem; } +.footer-col a:hover { color: #fff; text-decoration: underline; } +.footer-bottom { padding: 18px 0; } +.footer-disclaimer { color: rgba(255,255,255,0.5); font-size: 0.8rem; margin-bottom: 8px; line-height: 1.5; } +.footer-copy { color: rgba(255,255,255,0.45); font-size: 0.8rem; } +.footer-copy a { color: rgba(255,255,255,0.55); } + +/* ── Misc ── */ +.warn { color: var(--red); } +.trending-list { list-style: decimal inside; columns: 2; column-gap: 30px; } +.trending-list li { padding: 4px 0; font-size: 0.92rem; } +.popular-tools { list-style: none; } +.popular-tools li { padding: 5px 0; border-bottom: 1px solid var(--gray-100); font-size: 0.9rem; } +.popular-tools li:last-child { border-bottom: none; } +.medical-disclaimer { margin-top: 28px; padding: 14px 16px; background: var(--gray-50); border-left: 4px solid var(--blue); font-size: 0.85rem; color: var(--gray-700); line-height: 1.6; } +.more-about-list { list-style: none; } +.more-about-list li { padding: 5px 0; border-bottom: 1px solid var(--gray-100); font-size: 0.9rem; } + +/* Interaction mini-checker in drug detail */ +.mini-checker-form { display: flex; gap: 8px; } +.mini-checker-form input { flex: 1; padding: 8px 10px; border: 1px solid var(--gray-300); border-radius: 4px; font-size: 0.9rem; } +.mini-checker-form button { padding: 8px 16px; background: var(--blue); color: #fff; border: none; border-radius: 4px; font-weight: 600; cursor: pointer; font-size: 0.9rem; } + +/* FAQ accordion */ +details.faq-detail { border-bottom: 1px solid var(--gray-200); } +details.faq-detail summary { padding: 13px 0; cursor: pointer; font-weight: 600; color: var(--navy); font-size: 0.95rem; list-style: none; display: flex; justify-content: space-between; align-items: center; } +details.faq-detail summary::after { content: "+"; font-size: 1.3rem; color: var(--blue); } +details.faq-detail[open] summary::after { content: "−"; } +details.faq-detail p { padding: 0 0 14px; font-size: 0.92rem; color: var(--gray-700); line-height: 1.65; } + +/* Popular searches tag-cloud */ +.popular-searches { display: flex; flex-wrap: wrap; gap: 8px; margin: 12px 0; } +.popular-searches a { padding: 5px 12px; border: 1px solid var(--gray-300); border-radius: 20px; font-size: 0.85rem; color: var(--gray-700); } +.popular-searches a:hover { border-color: var(--blue); color: var(--blue); text-decoration: none; } + +/* ── Responsive ── */ +@media (max-width: 900px) { + .row-section.split, .detail-grid, .account-grid, .footer-top { grid-template-columns: 1fr; } + .home-browse-grid { grid-template-columns: 1fr; } + .letter-grid { grid-template-columns: repeat(7, 1fr); } .trending-list { columns: 1; } + .feature-icons { gap: 18px; } + .header-search-wrap { max-width: 100%; } } -/* ===== Additional styles ===== */ - -/* Drug table */ -.drug-table { width: 100%; border-collapse: collapse; background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,0.08); border-radius: 6px; overflow: hidden; margin: 16px 0; } -.drug-table th { background: #002a5c; color: #fff; padding: 10px 14px; text-align: left; font-weight: 600; font-size: 0.9rem; } -.drug-table td { padding: 10px 14px; border-bottom: 1px solid #eef1f5; font-size: 0.92rem; } -.drug-table tr:hover { background: #e5f1fb; } - -/* Auth pages */ -.auth-box { max-width: 460px; margin: 40px auto; background: #fff; padding: 36px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.10); } -.auth-box h1 { color: #002a5c; margin-bottom: 20px; } -.auth-box label { display: block; margin-bottom: 16px; font-size: 0.9rem; color: #4a5568; font-weight: 500; } -.auth-box input { display: block; width: 100%; padding: 10px 12px; border: 1px solid #c5ccd6; border-radius: 4px; margin-top: 4px; font-size: 1rem; } -.auth-box button { width: 100%; margin-top: 8px; padding: 12px; font-size: 1rem; } -.auth-benefits { background: #e5f1fb; padding: 14px 16px; border-radius: 4px; margin-top: 18px; } -.auth-benefits li { padding: 4px 0; font-size: 0.88rem; list-style: disc; margin-left: 18px; } - -/* Account grid */ -.account-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; margin-top: 20px; } -.account-box { background: #fff; border: 1px solid #dde2ea; border-radius: 6px; padding: 20px; } -.account-box h2 { color: #002a5c; margin-bottom: 12px; font-size: 1.1rem; } -.account-tabs { display: flex; gap: 24px; border-bottom: 2px solid #dde2ea; margin-bottom: 24px; } -.account-tabs a { padding: 10px 0; color: #002a5c; font-weight: 500; border-bottom: 3px solid transparent; margin-bottom: -2px; } -.account-tabs a.active { border-bottom-color: #f57c00; } - -/* Drug status box enhancements */ -.status-row { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #eef1f5; font-size: 0.9rem; } -.status-avail { display: flex; gap: 8px; } -.avail-badge { padding: 3px 8px; border-radius: 3px; font-size: 0.78rem; font-weight: 700; } -.avail-rx { background: #e8f0fe; color: #1a56db; } -.avail-otc { background: #def7ec; color: #057a55; } - -/* More about list */ -.more-about-list { list-style: none; } -.more-about-list li { padding: 5px 0; border-bottom: 1px solid #eef1f5; font-size: 0.88rem; } -.more-about-list li:last-child { border-bottom: none; } - -/* FAQ questions list */ -.faq-questions { list-style: none; margin-bottom: 18px; } -.faq-questions li { padding: 5px 0; } -.faq-questions a { color: #0072ce; } - -/* Treatment guides */ -.treatment-guides { list-style: disc; margin-left: 20px; } -.treatment-guides li { padding: 4px 0; } - -/* Interaction checker improvements */ -.interaction-faq { margin-top: 32px; border-top: 2px solid #eef1f5; padding-top: 20px; } -.interaction-faq h2 { color: #002a5c; margin-bottom: 16px; } -.interaction-faq ul { list-style: none; } -.interaction-faq li { padding: 10px 0; border-bottom: 1px solid #eef1f5; } -.interaction-faq a { color: #0072ce; } -.disclaimer-note { background: #fff8e1; border: 1px solid #ffc107; border-radius: 4px; padding: 12px 16px; margin: 16px 0; font-size: 0.9rem; color: #4a5568; } - -/* Pill identifier disclaimer */ -.disclaimer-box { background: #e5f1fb; border: 1px solid #0072ce; border-radius: 6px; padding: 18px; margin-bottom: 22px; } -.disclaimer-box h3 { color: #002a5c; margin-bottom: 8px; } -.additional-tips { background: #f7f8fa; border-left: 3px solid #f57c00; padding: 14px 18px; border-radius: 0 4px 4px 0; margin-top: 18px; } -.additional-tips h3 { color: #002a5c; margin-bottom: 10px; font-size: 1rem; } -.additional-tips li { padding: 5px 0; } - -/* News card */ -.news-card { background: #fff; padding: 18px; border-radius: 6px; border: 1px solid #dde2ea; margin-bottom: 14px; } -.news-card h2 { font-size: 1.1rem; color: #002a5c; margin: 8px 0; } -.news-card h2 a { color: #002a5c; } -.news-card h2 a:hover { color: #0072ce; } -.news-meta { font-size: 0.83rem; color: #7a8494; margin-bottom: 8px; } - -/* Mini checker form */ -.interaction-mini-checker { background: #e5f1fb; border-radius: 4px; padding: 14px 16px; margin-top: 14px; } -.mini-checker-form { display: flex; gap: 8px; margin-top: 8px; align-items: flex-end; flex-wrap: wrap; } -.mini-checker-form input { padding: 8px 10px; border: 1px solid #c5ccd6; border-radius: 4px; flex: 1; min-width: 180px; } - -/* Popular tools sidebar */ -.popular-tools { background: #f7f8fa; border: 1px solid #dde2ea; border-radius: 6px; padding: 16px; margin-top: 24px; } -.popular-tools h3 { color: #002a5c; font-size: 1rem; margin-bottom: 10px; } -.popular-tools ul { list-style: none; } -.popular-tools li { padding: 6px 0; border-bottom: 1px solid #eef1f5; } - -/* Page layout with sidebar */ -.page-with-sidebar { display: grid; grid-template-columns: 2fr 1fr; gap: 28px; } -@media (max-width: 768px) { - .page-with-sidebar { grid-template-columns: 1fr; } -} - -/* A-Z popular searches */ -.popular-searches { display: flex; flex-wrap: wrap; gap: 8px; margin: 14px 0; } -.popular-searches a { padding: 5px 12px; background: #eef1f5; border-radius: 3px; font-size: 0.88rem; color: #002a5c; } -.popular-searches a:hover { background: #0072ce; color: #fff; text-decoration: none; } -.browse-categories { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 8px; margin: 14px 0; } -.browse-categories a { padding: 8px 12px; background: #f7f8fa; border: 1px solid #dde2ea; border-radius: 4px; font-size: 0.9rem; } +@media (max-width: 640px) { + .az-strip { display: none; } + .header-top-inner { flex-wrap: wrap; gap: 10px; } + .hero-section h1 { font-size: 1.7rem; } +} diff --git a/sites/drugs_com/templates/account.html b/sites/drugs_com/templates/account.html index b5bfbb2..67e32f7 100644 --- a/sites/drugs_com/templates/account.html +++ b/sites/drugs_com/templates/account.html @@ -1,24 +1,44 @@ {% extends "base.html" %} -{% block title %}My Account - {{ site_name }}{% endblock %} +{% block title %}My Drugs.com - {{ current_user.username }}{% endblock %} {% block content %} - diff --git a/sites/drugs_com/templates/account_settings.html b/sites/drugs_com/templates/account_settings.html index 6848f19..4039409 100644 --- a/sites/drugs_com/templates/account_settings.html +++ b/sites/drugs_com/templates/account_settings.html @@ -14,37 +14,78 @@

    Account Settings

    My Med List My Reviews Settings - Subscriptions + Subscriptions - + + {% endblock %} diff --git a/sites/drugs_com/templates/account_subscriptions.html b/sites/drugs_com/templates/account_subscriptions.html new file mode 100644 index 0000000..2fc9b59 --- /dev/null +++ b/sites/drugs_com/templates/account_subscriptions.html @@ -0,0 +1,61 @@ +{% extends "base.html" %} +{% block title %}Email Subscriptions - Drugs.com{% endblock %} +{% block content %} + + +

    Email Subscriptions & Preferences

    + + + +{% with messages = get_flashed_messages(with_categories=true) %} + {% for category, message in messages %} +
    {{ message }}
    + {% endfor %} +{% endwith %} + + +{% endblock %} diff --git a/sites/drugs_com/templates/drug_detail.html b/sites/drugs_com/templates/drug_detail.html index e04560d..564a468 100644 --- a/sites/drugs_com/templates/drug_detail.html +++ b/sites/drugs_com/templates/drug_detail.html @@ -133,7 +133,10 @@

    Frequently asked questions

    {% endif %}
    -

    User reviews for {{ drug.generic_name }}

    +
    +

    User reviews for {{ drug.generic_name }}

    + View all reviews → +
    {{ drug.avg_rating }} / 10
    @@ -161,9 +164,29 @@

    User reviews for {{ drug.generic_name }}

    {% endif %}
    +
    + + +
    + {% if reviews %} +
    {% for r in reviews %} -
    +
    {{ r.rating }}/10 {{ r.title }} @@ -174,12 +197,36 @@

    User reviews for {{ drug.generic_name }}

    {{ r.body }}

    +
    Was this helpful? {{ r.helpful_count or 0 }} found this helpful
    {% endfor %} +
    {% else %}

    No reviews yet.

    {% endif %} + + {% if current_user.is_authenticated %}

    {{ 'Edit your review' if user_review else 'Add your review' }}

    diff --git a/sites/drugs_com/templates/drug_reviews_page.html b/sites/drugs_com/templates/drug_reviews_page.html new file mode 100644 index 0000000..5940b5b --- /dev/null +++ b/sites/drugs_com/templates/drug_reviews_page.html @@ -0,0 +1,90 @@ +{% extends "base.html" %} +{% block title %}{{ drug.generic_name|capitalize }} User Reviews & Ratings - {{ site_name }}{% endblock %} +{% block content %} +
    + + +

    User Reviews for {{ drug.generic_name|capitalize }}

    + +
    +
    +
    {{ drug.avg_rating }} / 10
    +
    {{ drug.review_count }} reviews
    +
    +
    + {% set total = drug.review_count or 1 %} + {% for score in range(10, 0, -1) %} + {% set count = rating_dist[score] %} + {% set pct = (count * 100 / total)|round(0, 'floor')|int %} +
    + {{ score }} + + {{ count }} +
    + {% endfor %} +
    +
    + +
    + + + + Write a review +
    + +
    + {% if reviews.items %} + {% for r in reviews.items %} +
    +
    + {{ r.rating }}/10 + {{ r.title }} + + — {{ r.user.username if r.user else 'anon' }} + {% if r.condition_treated %} (for {{ r.condition_treated }}){% endif %} + on {{ r.created_at.strftime('%b %d, %Y') }} + +
    +

    {{ r.body }}

    +
    Was this helpful? {{ r.helpful_count or 0 }} found this helpful
    +
    + {% endfor %} + {% else %} +

    No reviews match these filters.

    + {% endif %} +
    + + {% if reviews.pages and reviews.pages > 1 %} + + {% endif %} +
    +{% endblock %} From 50af02eeb3de5cf4d4985a45cf82cccf11bf248a Mon Sep 17 00:00:00 2001 From: Boyu Gou Date: Wed, 13 May 2026 11:15:20 -0700 Subject: [PATCH 010/167] drugs_com: text formatting, dark mode, news search, UI improvements - Add format_drug_text Jinja filter to convert raw FDA label text into readable paragraphs with sentence grouping and section headers - Apply filter to drug detail, dosage, side effects templates - Fix dark mode CSS contrast for drug detail content sections - Add Related News sidebar to drug detail page - Add news search and category filter to news index - Rename news section to Pharmaceutical News and Articles - Add newsletter subscription sidebar to Drug A-Z page - Add Symptoms tab to homepage browse section - Add Symptoms Checker to More... dropdown nav - Make letter buttons circular (38x38) throughout - Add feature carousel with < > arrows to homepage Co-Authored-By: Claude Sonnet 4.6 --- sites/drugs_com/app.py | 51 +++++++- sites/drugs_com/static/css/main.css | 114 ++++++++++++++++-- sites/drugs_com/templates/base.html | 1 + sites/drugs_com/templates/drug_az.html | 12 +- sites/drugs_com/templates/drug_detail.html | 30 ++--- sites/drugs_com/templates/drug_dosage.html | 2 +- .../templates/drug_side_effects.html | 2 +- sites/drugs_com/templates/index.html | 78 +++++++----- sites/drugs_com/templates/news.html | 25 +++- sites/drugs_com/templates/news_article.html | 4 +- 10 files changed, 254 insertions(+), 65 deletions(-) diff --git a/sites/drugs_com/app.py b/sites/drugs_com/app.py index c8e79fa..bb9e651 100644 --- a/sites/drugs_com/app.py +++ b/sites/drugs_com/app.py @@ -1315,6 +1315,39 @@ def score_drug(drug, tokens): return sum(1 for t in tokens if t in text) +@app.template_filter('format_drug_text') +def format_drug_text(text): + """Format raw FDA-label drug text into readable HTML paragraphs. + + Collapses internal newlines, bolds ALL-CAPS section headings like + 'WARNINGS:', sets off numbered list items, and groups sentences into + paragraphs of roughly three sentences each. Returns an HTML string + consisting of one or more ``

    ...

    `` blocks; intended to be + piped through ``|safe`` in templates. + """ + if not text: + return '' + text = str(text).strip() + # Collapse internal newlines into spaces. + text = re.sub(r'\n+', ' ', text) + # Bold ALL-CAPS section headings like 'WARNINGS:' or 'DOSAGE:'. + text = re.sub(r'([A-Z]{3,}:)\s*', r'\1 ', text) + # Split into sentences at terminal punctuation followed by capital/digit. + sentences = re.split(r'(?<=[.!?])\s+(?=[A-Z0-9])', text) + paragraphs = [] + current = [] + for i, sentence in enumerate(sentences): + if not sentence.strip(): + continue + current.append(sentence.strip()) + if len(current) >= 3 or i == len(sentences) - 1: + paragraphs.append(' '.join(current)) + current = [] + if current: + paragraphs.append(' '.join(current)) + return '

    ' + '

    '.join(p for p in paragraphs if p.strip()) + '

    ' + + @app.context_processor def inject_globals(): return { @@ -1375,9 +1408,11 @@ def drug_detail(slug): user_review = None if current_user.is_authenticated: user_review = DrugReview.query.filter_by(drug_id=drug.id, user_id=current_user.id).first() + related_news = NewsArticle.query.order_by(NewsArticle.published_at.desc()).limit(5).all() return render_template("drug_detail.html", drug=drug, reviews=reviews, related=related, drug_conditions=drug_conditions, saved=saved, - rating_distribution=rating_distribution, user_review=user_review) + rating_distribution=rating_distribution, user_review=user_review, + related_news=related_news) @app.route("//reviews") @@ -1730,9 +1765,19 @@ def drug_class_page(slug): @app.route("/news/") def news_index(): - articles = NewsArticle.query.order_by(NewsArticle.published_at.desc()).all() + cat = request.args.get("cat", "") + q = request.args.get("q", "") + query = NewsArticle.query + if cat: + query = query.filter_by(category=cat) + if q: + query = query.filter(db.or_( + NewsArticle.title.ilike(f"%{q}%"), + NewsArticle.body.ilike(f"%{q}%") + )) + articles = query.order_by(NewsArticle.published_at.desc()).limit(30).all() categories = ["New Drug Approvals", "Medical", "FDA Alerts", "Clinical Trials", "Health"] - return render_template("news.html", articles=articles, categories=categories, active_cat=None) + return render_template("news.html", articles=articles, active_category=cat, active_cat=cat or None, categories=categories, search_query=q) @app.route("/news/article/") diff --git a/sites/drugs_com/static/css/main.css b/sites/drugs_com/static/css/main.css index 0a9a563..65b46b5 100644 --- a/sites/drugs_com/static/css/main.css +++ b/sites/drugs_com/static/css/main.css @@ -242,13 +242,15 @@ a:hover { text-decoration: underline; color: var(--blue-mid); } /* Feature icons row */ .feature-icons { display: flex; + overflow: hidden; + gap: 20px; + flex: 1; justify-content: center; - gap: 32px; padding: 28px 20px; border-top: 1px solid var(--gray-100); border-bottom: 1px solid var(--gray-100); background: #fff; - flex-wrap: wrap; + scroll-behavior: smooth; } .feature-icon-item { display: flex; @@ -320,15 +322,43 @@ a:hover { text-decoration: underline; color: var(--blue-mid); } display: flex; align-items: center; justify-content: center; - padding: 10px 0; - background: #fff; - color: var(--navy); + width: 38px; + height: 38px; + border-radius: 50%; + background: var(--card-bg, #2a3a54); + color: var(--text, #e8e8e8); font-weight: 600; - border-radius: 4px; - border: 1px solid var(--gray-200); - font-size: 0.95rem; + font-size: 0.9rem; + text-decoration: none; + transition: background 0.15s; } -.letter-btn:hover, .letter-btn.active { background: var(--blue); color: #fff; border-color: var(--blue); text-decoration: none; } +.letter-btn:hover, .letter-btn.active { + background: var(--blue); + color: white; +} +.letter-btn-sm { + width: 32px; + height: 32px; + font-size: 0.85rem; +} + +/* Feature carousel */ +.feature-carousel-wrap { + position: relative; + display: flex; + align-items: center; + padding: 20px 0; +} +.carousel-btn { + background: none; + border: none; + font-size: 2rem; + color: #888; + cursor: pointer; + padding: 0 12px; + line-height: 1; +} +.carousel-btn:hover { color: var(--blue); } .browse-footer { padding: 10px 16px; background: var(--gray-50); @@ -958,6 +988,28 @@ details.faq-detail p { padding: 0 0 14px; font-size: 0.92rem; color: var(--gray- border-color: #2a2a4a; color: #e8e8e8; } + + /* Drug detail page: ensure solid card backgrounds and high-contrast text. */ + .drug-detail { + background: #16213e; + color: #e8e8e8; + } + .detail-main, + .detail-main section { + background: transparent; + color: #e8e8e8; + } + .detail-main p, + .detail-main li { + color: #d4dbe8; + } + .detail-main strong { color: #ffffff; } + .sidebar-box, + .status-box { + background: #1e2a3e; + border-color: #2a2a4a; + color: #e8e8e8; + } .drug-card { color: #e8e8e8; } .drug-card h3, .result-card h2 { color: #4da6ff; } @@ -1425,3 +1477,47 @@ details.faq-detail p { padding: 0 0 14px; font-size: 0.92rem; color: var(--gray- font-size: 0.9rem; width: 280px; } + +.newsletter-check { + display: block; + margin: 6px 0; + font-size: 0.9rem; + cursor: pointer; +} +.newsletter-check input { + margin-right: 6px; +} +.newsletter-email-input { + width: 100%; + padding: 8px 12px; + border: 1px solid #dfe3e8; + border-radius: 4px; + font-size: 0.9rem; + margin-top: 10px; + box-sizing: border-box; +} +.news-search-wrap { + margin: 12px 0 20px; +} +.news-search-form { + display: flex; + max-width: 480px; + border: 1px solid #dfe3e8; + border-radius: 4px; + overflow: hidden; +} +.news-search-input { + flex: 1; + padding: 8px 12px; + border: none; + outline: none; + font-size: 0.95rem; +} +.news-search-btn { + background: #f4f6fa; + border: none; + border-left: 1px solid #dfe3e8; + padding: 0 14px; + cursor: pointer; + color: #1a3a6e; +} diff --git a/sites/drugs_com/templates/base.html b/sites/drugs_com/templates/base.html index 024be34..bdbd8c7 100644 --- a/sites/drugs_com/templates/base.html +++ b/sites/drugs_com/templates/base.html @@ -76,6 +76,7 @@ My Med List FDA Alerts Side Effects + Symptoms Checker
    Help & Support diff --git a/sites/drugs_com/templates/drug_az.html b/sites/drugs_com/templates/drug_az.html index f93fdc2..247dcc8 100644 --- a/sites/drugs_com/templates/drug_az.html +++ b/sites/drugs_com/templates/drug_az.html @@ -77,12 +77,14 @@

    Popular drug searches

    From 3a38fb238d1ae809d00c7b2decc89da9dd51acbb Mon Sep 17 00:00:00 2001 From: Boyu Gou Date: Wed, 13 May 2026 11:55:42 -0700 Subject: [PATCH 012/167] drugs_com: drug images, helpful votes, search improvements, side effects A-Z - Add drug thumbnail image to drug detail page header - Add Drug Images section to homepage with 6 random pill images grid - Add helpful vote buttons (Yes/No) to reviews with live JS update - Improve reviews page: rating distribution bars, sort/filter by condition - Improve search: brand name matching, grouped results (drugs/conditions/news), difflib-based "Did you mean?" suggestions - Improve side effects page: A-Z navigation, 60 curated side effects, most commonly searched section, search box Co-Authored-By: Claude Sonnet 4.6 --- sites/drugs_com/app.py | 129 +++++++++++++++--- sites/drugs_com/templates/drug_detail.html | 42 +++++- .../templates/drug_reviews_page.html | 24 +++- sites/drugs_com/templates/index.html | 28 ++++ sites/drugs_com/templates/search.html | 34 ++++- sites/drugs_com/templates/side_effects.html | 111 ++++++++------- 6 files changed, 298 insertions(+), 70 deletions(-) diff --git a/sites/drugs_com/app.py b/sites/drugs_com/app.py index c36b08e..f572e00 100644 --- a/sites/drugs_com/app.py +++ b/sites/drugs_com/app.py @@ -10,6 +10,7 @@ import string from datetime import datetime, timedelta from itertools import combinations +import difflib from flask import ( Flask, render_template, request, redirect, url_for, flash, jsonify, @@ -1373,9 +1374,13 @@ def index(): ).all() fda_news = NewsArticle.query.filter_by(category="FDA Alerts").order_by( NewsArticle.published_at.desc()).limit(3).all() + image_drugs = (Drug.query.join(DrugImage, DrugImage.drug_id == Drug.id) + .order_by(db.func.random()).limit(6).all()) + image_samples = [(d, d.images[0]) for d in image_drugs if d.images] return render_template("index.html", featured=featured, trending=trending, news=news, classes=classes, - health_topics=health_topics, fda_news=fda_news) + health_topics=health_topics, fda_news=fda_news, + image_samples=image_samples) @app.route("/drug_information.html") @@ -1476,6 +1481,17 @@ def submit_review(slug): return redirect(url_for("drug_detail", slug=slug) + "#reviews") +@app.route("//review//helpful", methods=["POST"]) +def review_helpful_vote(slug, review_id): + drug = Drug.query.filter_by(slug=slug).first_or_404() + review = DrugReview.query.filter_by(id=review_id, drug_id=drug.id).first_or_404() + vote = (request.values.get("vote") or "yes").lower() + delta = -1 if vote == "no" else 1 + review.helpful_count = max(0, (review.helpful_count or 0) + delta) + db.session.commit() + return jsonify({"votes": review.helpful_count, "review_id": review.id}) + + @app.route("/search") def search(): q = (request.args.get("q") or "").strip() @@ -1518,20 +1534,32 @@ def search(): scored = [(score_drug(d, tokens), d) for d in drugs] scored = [(s, d) for s, d in scored if s > 0] + existing_ids = {d.id for _, d in scored} + + # Explicit brand-name match: include drugs whose brand names contain + # any query token (and bump the score of already-scored drugs). + for d in drugs: + brand_blob = " ".join(d.brand_names).lower() if d.brand_names else "" + if any(t in brand_blob for t in tokens): + if d.id in existing_ids: + scored = [(s + 2, dd) if dd.id == d.id else (s, dd) for s, dd in scored] + else: + scored.append((2, d)) + existing_ids.add(d.id) # Boost drugs matching the condition/class if matched_condition: extras = [d for d in drugs if matched_condition.slug in d.conditions_list] - existing_ids = {d.id for _, d in scored} for d in extras: if d.id not in existing_ids: scored.append((1, d)) + existing_ids.add(d.id) if matched_class: extras = [d for d in drugs if d.drug_class_id == matched_class.id] - existing_ids = {d.id for _, d in scored} for d in extras: if d.id not in existing_ids: scored.append((1, d)) + existing_ids.add(d.id) scored.sort(key=lambda x: (-x[0], x[1].generic_name)) results = [d for _, d in scored] @@ -1541,20 +1569,40 @@ def search(): drugs = Drug.query.order_by(Drug.review_count.desc()).all() results = sorted(drugs, key=lambda d: -d.review_count) if not q else sorted(drugs, key=lambda d: d.generic_name) - # "Did you mean?" suggestions for typos when no results + # Conditions and News result groups (only when there's a query and no + # restrictive drug-only filters are applied). + condition_results = [] + news_results = [] + if q: + ql = q.lower() + for c in Condition.query.all(): + if ql in c.name.lower() or ql in (c.slug or "").lower() \ + or ql in (c.description or "").lower(): + condition_results.append(c) + condition_results.sort(key=lambda c: c.name) + condition_results = condition_results[:10] + + for a in NewsArticle.query.all(): + if ql in a.title.lower() or ql in (a.body or "").lower() \ + or ql in (a.category or "").lower(): + news_results.append(a) + news_results.sort(key=lambda a: a.published_at or datetime.min, reverse=True) + news_results = news_results[:10] + + # "Did you mean?" suggestions: 3 closest drug names by edit-distance + # ratio whenever the query produced zero drug hits. suggestions = [] if q and not results: - ql = q.lower() - all_drugs = Drug.query.all() - for d in all_drugs: - name = d.generic_name.lower() - # simple substring or prefix match for suggestions - if ql[:3] and name.startswith(ql[:3]): - suggestions.append(d.generic_name) - if len(suggestions) >= 5: - break + all_names = [d.generic_name for d in Drug.query.all()] + suggestions = difflib.get_close_matches(q.lower(), + [n.lower() for n in all_names], + n=3, cutoff=0.5) + # Map back to canonical capitalisation. + lower_to_orig = {n.lower(): n for n in all_names} + suggestions = [lower_to_orig.get(s, s) for s in suggestions] total = len(results) + total_all = total + len(condition_results) + len(news_results) total_pages = max(1, (total + per_page - 1) // per_page) page = min(page, total_pages) page_results = results[(page - 1) * per_page: page * per_page] @@ -1562,6 +1610,9 @@ def search(): classes = DrugClass.query.order_by(DrugClass.name).all() conditions = Condition.query.order_by(Condition.name).all() return render_template("search.html", q=q, results=page_results, total=total, + total_all=total_all, + condition_results=condition_results, + news_results=news_results, classes=classes, conditions=conditions, class_slug=class_slug, cond_slug=cond_slug, avail=avail, page=page, total_pages=total_pages, @@ -1987,15 +2038,59 @@ def sitemap(): return render_template("sitemap.html", classes=classes, conditions=conditions) +SIDE_EFFECTS_AZ = [ + "Abdominal pain", "Acne", "Agitation", "Allergic reaction", "Anemia", + "Anxiety", "Appetite loss", "Arrhythmia", "Back pain", "Bleeding", + "Bloating", "Blurred vision", "Bruising", "Chest pain", "Chills", + "Confusion", "Constipation", "Cough", "Cramps", "Depression", + "Diarrhea", "Dizziness", "Drowsiness", "Dry mouth", "Edema", + "Fatigue", "Fever", "Flushing", "Gas", "Hair loss", + "Headache", "Heartburn", "Hypertension", "Hypotension", "Indigestion", + "Insomnia", "Itching", "Jaundice", "Joint pain", "Liver damage", + "Muscle pain", "Nausea", "Nervousness", "Numbness", "Palpitations", + "Rash", "Restlessness", "Ringing in ears", "Seizures", "Shortness of breath", + "Skin reaction", "Sore throat", "Stomach upset", "Sweating", "Swelling", + "Tachycardia", "Tremor", "Vomiting", "Weakness", "Weight gain", + "Weight loss", +] + +MOST_SEARCHED_SIDE_EFFECTS = [ + "Nausea", "Headache", "Dizziness", "Drowsiness", "Fatigue", + "Diarrhea", "Constipation", "Insomnia", "Weight gain", "Rash", +] + + @app.route("/side-effects/") @app.route("/side-effects") def side_effects_page(): - q = request.args.get("q", "") + """Side Effects A-Z index page. + + Renders an alphabetical directory of common side effect names with an + A-Z letter navigation, a search box, a "most commonly searched" list, + and (when ``?q=`` is supplied) drugs whose ``side_effects`` text matches. + """ + q = (request.args.get("q") or "").strip() drugs = [] if q: - drugs = Drug.query.filter(Drug.side_effects.ilike(f"%{q}%")).limit(20).all() - popular = Drug.query.order_by(Drug.review_count.desc()).limit(20).all() - return render_template("side_effects.html", drugs=drugs, query=q, popular=popular) + drugs = Drug.query.filter(Drug.side_effects.ilike(f"%{q}%")).limit(30).all() + + # Group the curated A-Z list by first letter for the directory grid. + by_letter = {} + for name in SIDE_EFFECTS_AZ: + letter = name[0].upper() + by_letter.setdefault(letter, []).append(name) + for letter in by_letter: + by_letter[letter].sort() + letters = sorted(by_letter.keys()) + all_letters = list(string.ascii_uppercase) + + popular = Drug.query.order_by(Drug.review_count.desc()).limit(10).all() + return render_template( + "side_effects.html", + drugs=drugs, query=q, popular=popular, + by_letter=by_letter, letters=letters, all_letters=all_letters, + most_searched=MOST_SEARCHED_SIDE_EFFECTS, + ) @app.route("/warnings/") diff --git a/sites/drugs_com/templates/drug_detail.html b/sites/drugs_com/templates/drug_detail.html index 93b1342..590f8f0 100644 --- a/sites/drugs_com/templates/drug_detail.html +++ b/sites/drugs_com/templates/drug_detail.html @@ -18,7 +18,22 @@
    -
    +
    + + {% if drug.images and drug.images|length > 0 %} + {% set img0 = drug.images[0] %} + + + {{ img0.imprint or '' }} + + {% else %} + + + No image + + {% endif %} + +

    {{ drug.generic_name|capitalize }}

    {% if drug.pronunciation %}

    Pronunciation: {{ drug.pronunciation }}

    {% endif %}
      @@ -46,6 +61,7 @@

      {{ drug.generic_name|capitalize }} @@ -206,7 +222,11 @@

      User reviews for {{ drug.generic_name }}

    {{ r.body }}

    -
    Was this helpful? {{ r.helpful_count or 0 }} found this helpful
    +
    + Was this helpful? + + +
    {% endfor %} @@ -234,6 +254,24 @@

    User reviews for {{ drug.generic_name }}

    }); cards.forEach(c => list.appendChild(c)); } + document.querySelectorAll('.review-helpful').forEach(box => { + const slug = box.dataset.slug; + const rid = box.dataset.reviewId; + const countEl = box.querySelector('.review-helpful-count'); + const card = box.closest('.review-card'); + function send(vote) { + const fd = new FormData(); fd.append('vote', vote); + fetch('/' + slug + '/review/' + rid + '/helpful', {method: 'POST', body: fd}) + .then(r => r.json()).then(data => { + if (countEl) countEl.textContent = data.votes; + if (card) card.dataset.helpful = data.votes; + }); + } + const yes = box.querySelector('.review-helpful-yes'); + const no = box.querySelector('.review-helpful-no'); + if (yes) yes.addEventListener('click', () => send('yes')); + if (no) no.addEventListener('click', () => send('no')); + }); {% if current_user.is_authenticated %} diff --git a/sites/drugs_com/templates/drug_reviews_page.html b/sites/drugs_com/templates/drug_reviews_page.html index 5940b5b..c181473 100644 --- a/sites/drugs_com/templates/drug_reviews_page.html +++ b/sites/drugs_com/templates/drug_reviews_page.html @@ -67,7 +67,11 @@

    User Reviews for {{ drug.generic_name|capitalize }}

    {{ r.body }}

    -
    Was this helpful? {{ r.helpful_count or 0 }} found this helpful
    +
    + Was this helpful? + + +
    {% endfor %} {% else %} @@ -87,4 +91,22 @@

    User Reviews for {{ drug.generic_name|capitalize }}

    {% endif %} + {% endblock %} diff --git a/sites/drugs_com/templates/index.html b/sites/drugs_com/templates/index.html index 2b10fd2..fd1fe27 100644 --- a/sites/drugs_com/templates/index.html +++ b/sites/drugs_com/templates/index.html @@ -70,6 +70,34 @@

    Know more. Be sure.

    } + +{% if image_samples %} + +{% endif %} + {% if health_topics %}
    diff --git a/sites/drugs_com/templates/search.html b/sites/drugs_com/templates/search.html index 6c6db8e..ecf02f7 100644 --- a/sites/drugs_com/templates/search.html +++ b/sites/drugs_com/templates/search.html @@ -70,13 +70,17 @@

    Filter results

    {% if q %} - {{ total }} result{{ '' if total == 1 else 's' }} for "{{ q }}" + {{ total_all if total_all is defined else total }} result{{ '' if (total_all if total_all is defined else total) == 1 else 's' }} for "{{ q }}" + {% if total_all is defined and total_all != total %} + ({{ total }} drug{{ '' if total == 1 else 's' }}{% if condition_results %}, {{ condition_results|length }} condition{{ '' if condition_results|length == 1 else 's' }}{% endif %}{% if news_results %}, {{ news_results|length }} news article{{ '' if news_results|length == 1 else 's' }}{% endif %}) + {% endif %} {% else %} Showing {{ total }} drug{{ '' if total == 1 else 's' }} {% endif %}

    {% if results %} +

    Drugs ({{ total }})

    {% for d in results %}
    @@ -114,7 +118,33 @@

    {% endif %} {% endif %} - {% else %} + {% endif %} + + {% if condition_results %} +

    Conditions ({{ condition_results|length }})

    +
      + {% for c in condition_results %} +
    • + {{ c.name }} + {% if c.description %} — {{ (c.description or '')[:160] }}{% if (c.description or '')|length > 160 %}...{% endif %}{% endif %} +
    • + {% endfor %} +
    + {% endif %} + + {% if news_results %} +

    News ({{ news_results|length }})

    +
      + {% for a in news_results %} +
    • + {{ a.title }} + — {{ a.category }}{% if a.published_at %} · {{ a.published_at.strftime('%b %d, %Y') }}{% endif %} +
    • + {% endfor %} +
    + {% endif %} + + {% if not results and not condition_results and not news_results %}

    No results found{% if q %} for "{{ q }}"{% endif %}

    {% if suggestions %} diff --git a/sites/drugs_com/templates/side_effects.html b/sites/drugs_com/templates/side_effects.html index 86ff276..c826bbd 100644 --- a/sites/drugs_com/templates/side_effects.html +++ b/sites/drugs_com/templates/side_effects.html @@ -1,64 +1,79 @@ {% extends "base.html" %} -{% block title %}Drug Side Effects Search | {{ site_name }}{% endblock %} +{% block title %}Side Effects A-Z | {{ site_name }}{% endblock %} {% block content %}
    -

    Drug Side Effects Search

    -

    Look up common and serious side effects for thousands of prescription and over-the-counter drugs.

    +

    Drug Side Effects A-Z

    +

    Browse common and serious drug side effects alphabetically, or search for a specific symptom.

    - +
    +
    +

    Most commonly searched side effects

    +
    + {% for s in most_searched %} + {{ s }} + {% endfor %} +
    +
    + + + {% if query %} -

    Drugs that mention “{{ query }}”

    - {% if drugs %} -
      - {% for d in drugs %} -
    • {{ d.generic_name }} — {{ d.drug_class.name if d.drug_class else '' }}
    • - {% endfor %} -
    - {% else %} -

    No drugs matched “{{ query }}”.

    - {% endif %} +
    +

    Drugs that mention “{{ query }}”

    + {% if drugs %} +
      + {% for d in drugs %} +
    • {{ d.generic_name }}{% if d.drug_class %} — {{ d.drug_class.name }}{% endif %}
    • + {% endfor %} +
    + {% else %} +

    No drugs matched “{{ query }}”.

    + {% endif %} +
    {% endif %} -

    Common drug side effects by category

    -
    -
    -

    Gastrointestinal

    - -
    -
    -

    Central Nervous System

    - -
    -
    -

    Cardiovascular

    - -
    -
    -

    Skin

    - -
    -
    -

    Endocrine

    - +
    +

    Browse side effects alphabetically

    +
    + {% for L in letters %} +
    +

    {{ L }}

    +
      + {% for name in by_letter[L] %} +
    • {{ name }}
    • + {% endfor %} +
    +
    + {% endfor %}
    -
    +
    -

    Popular drugs & their common side effects

    - - - - {% for d in popular %} - - - - - {% endfor %} - -
    DrugCommon side effects
    {{ d.generic_name }}{{ (d.side_effects or '')[:220] }}{% if d.side_effects and d.side_effects|length > 220 %}…{% endif %}
    +
    +

    Popular drugs & their common side effects

    + + + + {% for d in popular %} + + + + + {% endfor %} + +
    DrugCommon side effects
    {{ d.generic_name }}{{ (d.side_effects or '')[:220] }}{% if d.side_effects and d.side_effects|length > 220 %}…{% endif %}
    +
    {% endblock %} From 6206f96f365538dc99a7d35b9a9fa69771c5467d Mon Sep 17 00:00:00 2001 From: Boyu Gou Date: Wed, 13 May 2026 12:42:40 -0700 Subject: [PATCH 013/167] drugs_com: prices, related drugs, pill identifier, warnings, news articles - Add generate_drug_prices() with realistic pharmacy price comparison table (CVS, Walgreens, Walmart, GoodRx) based on drug tier (OTC/generic/brand) - Add related drugs section to drug detail page (same drug class, up to 6) - Add drug interaction count to interactions tab - Add per-drug warnings page (//warnings) with boxed warning section and keyword-categorized warning cards - Add warnings index page with A-Z drug list linking to per-drug warnings - Improve pill identifier: realistic SVG pill shapes per pill type, imprint text displayed on pill, availability badge, size filter removed (unsupported) - Improve conditions page: top conditions bar, letter filter, drug count badges - Improve news article: medically reviewed notice, fast facts box, share buttons, references section, related articles sidebar improvements - Improve interaction checker: group results by severity with colored headers, summary count banner, drug-food/alcohol interaction sections Co-Authored-By: Claude Sonnet 4.6 --- sites/drugs_com/app.py | 278 +++++++++++++++++- sites/drugs_com/static/css/main.css | 53 ++++ sites/drugs_com/templates/conditions.html | 16 + sites/drugs_com/templates/drug_detail.html | 18 ++ sites/drugs_com/templates/drug_prices.html | 130 +++++--- sites/drugs_com/templates/drug_warnings.html | 95 ++++-- .../templates/interaction_checker.html | 98 ++++-- sites/drugs_com/templates/news_article.html | 111 ++++++- .../drugs_com/templates/pill_identifier.html | 69 +++-- sites/drugs_com/templates/warnings_index.html | 31 ++ 10 files changed, 783 insertions(+), 116 deletions(-) create mode 100644 sites/drugs_com/templates/warnings_index.html diff --git a/sites/drugs_com/app.py b/sites/drugs_com/app.py index f572e00..80dc788 100644 --- a/sites/drugs_com/app.py +++ b/sites/drugs_com/app.py @@ -1414,10 +1414,20 @@ def drug_detail(slug): if current_user.is_authenticated: user_review = DrugReview.query.filter_by(drug_id=drug.id, user_id=current_user.id).first() related_news = NewsArticle.query.order_by(NewsArticle.published_at.desc()).limit(5).all() + related_drugs = [] + if drug.drug_class_id: + related_drugs = Drug.query.filter( + Drug.drug_class_id == drug.drug_class_id, + Drug.id != drug.id + ).order_by(db.func.random()).limit(6).all() + drug_interactions = DrugInteraction.query.filter( + (DrugInteraction.drug_a_id == drug.id) | (DrugInteraction.drug_b_id == drug.id) + ).all() return render_template("drug_detail.html", drug=drug, reviews=reviews, related=related, drug_conditions=drug_conditions, saved=saved, rating_distribution=rating_distribution, user_review=user_review, - related_news=related_news) + related_news=related_news, related_drugs=related_drugs, + drug_interactions=drug_interactions) @app.route("//reviews") @@ -1684,6 +1694,9 @@ def interaction_checker(): "moderate": sum(1 for it in interactions if it["severity"] == "moderate"), "minor": sum(1 for it in interactions if it["severity"] == "minor"), } + food_interactions, alcohol_interactions = _lifestyle_interactions(resolved) + else: + food_interactions, alcohol_interactions = [], [] return render_template( "interaction_checker.html", drugs=drugs, @@ -1692,9 +1705,109 @@ def interaction_checker(): unrecognized=unrecognized, summary=summary, prefill=prefill, + food_interactions=food_interactions, + alcohol_interactions=alcohol_interactions, ) +# Hardcoded clinical reference data for drug-food / drug-alcohol interactions. +# Keyed by (lowercased) generic name and by (lowercased) drug-class name. +_FOOD_BY_GENERIC = { + "warfarin": [ + {"item": "Vitamin K-rich foods (kale, spinach, broccoli)", "severity": "moderate", + "description": "Large or fluctuating intake of leafy greens high in vitamin K can antagonize warfarin's anticoagulant effect and destabilize INR. Keep vitamin K intake consistent rather than eliminating these foods."}, + {"item": "Cranberry juice", "severity": "moderate", + "description": "Cranberry juice may potentiate warfarin and raise INR, increasing bleeding risk. Limit intake or avoid large quantities while on warfarin."}, + ], + "ibuprofen": [ + {"item": "High-sodium foods", "severity": "minor", + "description": "NSAIDs can cause fluid retention and elevate blood pressure; high-sodium meals compound this effect, particularly in patients with hypertension or heart failure."}, + ], +} + +_FOOD_BY_CLASS = { + "nsaids": [ + {"item": "High-sodium foods", "severity": "minor", + "description": "NSAIDs promote sodium and water retention. Diets high in sodium can amplify blood-pressure increases and edema, especially in older adults or those with cardiac disease."}, + ], + "statins": [ + {"item": "Grapefruit juice", "severity": "major", + "description": "Grapefruit inhibits intestinal CYP3A4 and substantially raises serum levels of simvastatin, lovastatin, and atorvastatin. Elevated exposure increases the risk of myopathy and rhabdomyolysis."}, + ], + "ssris": [ + {"item": "Tyramine-rich foods (aged cheese, cured meats)", "severity": "minor", + "description": "While SSRIs are safer than MAOIs with tyramine, large tyramine loads can occasionally precipitate hypertensive or serotonergic symptoms in susceptible patients."}, + ], + "anticoagulants": [ + {"item": "Vitamin K-rich foods (kale, spinach, broccoli)", "severity": "moderate", + "description": "Vitamin K antagonizes warfarin-type anticoagulants. Keep daily vitamin K intake consistent to maintain stable INR; abrupt changes alter anticoagulant control."}, + ], + "ace inhibitors": [ + {"item": "Potassium-rich foods (bananas, oranges, salt substitutes)", "severity": "moderate", + "description": "ACE inhibitors reduce aldosterone-mediated potassium excretion. Excess dietary potassium, especially with salt substitutes, can produce clinically significant hyperkalemia."}, + ], +} + +_ALCOHOL_BY_GENERIC = { + "metronidazole": {"severity": "major", + "description": "Metronidazole with alcohol can produce a disulfiram-like reaction with flushing, nausea, vomiting, tachycardia, and headache. Avoid alcohol during therapy and for at least 48 hours after the last dose."}, + "warfarin": {"severity": "major", + "description": "Acute heavy alcohol intake inhibits warfarin metabolism and raises INR with bleeding risk; chronic use induces metabolism and reduces effect. Either pattern destabilizes anticoagulation."}, + "acetaminophen": {"severity": "moderate", + "description": "Chronic alcohol use induces CYP2E1, increasing formation of acetaminophen's hepatotoxic metabolite (NAPQI). Combination significantly raises the risk of acute liver injury."}, +} + +_ALCOHOL_BY_CLASS = { + "nsaids": {"severity": "major", + "description": "Combining NSAIDs with alcohol substantially increases the risk of gastrointestinal bleeding, ulceration, and renal injury. Avoid or minimize alcohol while taking NSAIDs."}, + "ssris": {"severity": "moderate", + "description": "SSRIs combined with alcohol increase central nervous system depression, sedation, and impaired judgment. Alcohol may also worsen depressive symptoms and reduce SSRI efficacy."}, + "benzodiazepines": {"severity": "major", + "description": "Benzodiazepines plus alcohol cause additive CNS and respiratory depression. The combination can produce profound sedation, respiratory arrest, and death."}, + "opioids": {"severity": "major", + "description": "Opioids and alcohol both depress the CNS and respiratory drive. Concurrent use markedly increases the risk of fatal respiratory depression and overdose."}, + "anticoagulants": {"severity": "major", + "description": "Alcohol affects both anticoagulant metabolism and platelet function, increasing the risk of major bleeding. Patients should limit intake and discuss safe thresholds with their clinician."}, + "antihistamines": {"severity": "moderate", + "description": "First-generation antihistamines combined with alcohol produce additive sedation and psychomotor impairment, raising the risk of falls and motor-vehicle accidents."}, + "antidiabetics": {"severity": "moderate", + "description": "Alcohol can cause hypoglycemia (especially fasting) and impair recognition of warning symptoms. Sulfonylureas and insulin carry the greatest risk."}, +} + + +def _lifestyle_interactions(resolved_drugs): + """Return (food_interactions, alcohol_interactions) for the given Drug rows. + + Each food entry is {drug, item, severity, description}; each alcohol entry + is {drug, severity, description}. Lookups are by lowercased generic name + and drug-class name against hardcoded reference tables; drugs without + matches contribute nothing. Lists are sorted major -> moderate -> minor, + deduplicated per (drug, item) for food and one entry per drug for alcohol. + """ + sev_order = {"major": 0, "moderate": 1, "minor": 2} + food, alcohol = [], [] + seen_food = set() + seen_alc = set() + for d in resolved_drugs: + gname = (d.generic_name or "").lower() + cname = (d.drug_class.name or "").lower() if d.drug_class else "" + candidates = list(_FOOD_BY_GENERIC.get(gname, [])) + candidates.extend(_FOOD_BY_CLASS.get(cname, [])) + for entry in candidates: + key = (d.id, entry["item"]) + if key in seen_food: + continue + seen_food.add(key) + food.append({"drug": d, **entry}) + alc = _ALCOHOL_BY_GENERIC.get(gname) or _ALCOHOL_BY_CLASS.get(cname) + if alc and d.id not in seen_alc: + seen_alc.add(d.id) + alcohol.append({"drug": d, **alc}) + food.sort(key=lambda x: sev_order.get(x["severity"], 99)) + alcohol.sort(key=lambda x: sev_order.get(x["severity"], 99)) + return food, alcohol + + @app.route("/api/interaction-check", methods=["POST"]) def api_interaction_check(): data = request.get_json(silent=True) or {} @@ -1878,9 +1991,35 @@ def drug_classes_list(): @app.route("/conditions") @app.route("/conditions.html") def conditions_list(): - conditions = Condition.query.order_by(Condition.name).all() + all_conditions = Condition.query.order_by(Condition.name).all() + for c in all_conditions: + if c.drug_count is None: + c.drug_count = DrugCondition.query.filter_by(condition_id=c.id).count() all_letters = list('ABCDEFGHIJKLMNOPQRSTUVWXYZ') + ['0-9'] - return render_template("conditions.html", conditions=conditions, all_letters=all_letters, active_letter=None) + active_letter = (request.args.get("letter") or "").upper().strip() or None + if active_letter == '0-9': + conditions = [c for c in all_conditions if c.name and not c.name[0].isalpha()] + elif active_letter: + conditions = [c for c in all_conditions if c.name and c.name[0].upper() == active_letter] + else: + conditions = all_conditions + top_slugs = ["hypertension", "diabetes", "depression", "asthma", "anxiety", + "high_cholesterol", "heart_disease", "migraine", "gerd", "arthritis"] + by_slug = {c.slug: c for c in all_conditions} + top_conditions = [by_slug[s] for s in top_slugs if s in by_slug] + if len(top_conditions) < 6: + extras = sorted(all_conditions, key=lambda c: -(c.drug_count or 0)) + for c in extras: + if c not in top_conditions: + top_conditions.append(c) + if len(top_conditions) >= 8: + break + top_conditions = top_conditions[:8] + return render_template("conditions.html", + conditions=conditions, + all_letters=all_letters, + active_letter=active_letter, + top_conditions=top_conditions) # --- Auth --- @@ -2095,9 +2234,9 @@ def side_effects_page(): @app.route("/warnings/") @app.route("/blackbox-warnings") -def drug_warnings(): +def warnings_index(): drugs_with_warnings = Drug.query.filter(Drug.warnings.isnot(None)).order_by(Drug.generic_name).limit(50).all() - return render_template("drug_warnings.html", drugs=drugs_with_warnings) + return render_template("warnings_index.html", drugs=drugs_with_warnings) @app.route("/newsletter") @@ -2134,10 +2273,84 @@ def drug_images(slug): return render_template("drug_images.html", drug=drug, images=images) +def generate_drug_prices(drug): + """Build a deterministic per-pharmacy price table for a drug. + + Pricing tier is chosen from drug attributes: + * Controlled substances (`csa_schedule` starts with "Schedule") use a + mid-to-high band ($40-$220 / 30-day supply). + * Brand-only drugs (Rx, has brand names, no generic equivalent flag) + use the brand band ($80-$520 / 30-day supply). + * OTC drugs use a low band ($4-$25 / 30-day supply). + * Everything else (generic Rx) uses $6-$45 / 30-day supply. + + A per-drug seed (derived from id + generic_name length) drives small + deterministic variation across pharmacies so the table looks plausible + without random noise per request. + + Returns a list of dicts: + {pharmacy, unit_price, supply_price, coupon, savings_pct} + plus a `base_retail` value usable as the "average retail" anchor. + """ + seed = (drug.id * 31 + len(drug.generic_name or "") * 7) % 997 + csa = (drug.csa_schedule or "").lower() + avail = (drug.availability or "Rx").lower() + brands = drug.brand_names or [] + + if csa.startswith("schedule"): + base = 40 + (seed % 180) + tier = "controlled" + elif "otc" in avail: + base = 4 + (seed % 22) + tier = "otc" + elif brands and "rx" in avail: + # Brand-name Rx drugs skew expensive. + base = 80 + (seed % 440) + tier = "brand" + else: + base = 6 + (seed % 40) + tier = "generic" + + pharmacies = [ + ("CVS Pharmacy", 1.12, True), + ("Walgreens", 1.15, True), + ("Walmart Pharmacy", 0.88, False), + ("Rite Aid", 1.06, True), + ("Costco Pharmacy", 0.82, False), + ("GoodRx Price", 0.55, True), + ] + + rows = [] + for i, (name, mult, coupon) in enumerate(pharmacies): + # tiny deterministic jitter so prices aren't perfect multiples + jitter = ((seed + i * 13) % 7) / 100.0 # 0.00 .. 0.06 + supply = round(base * (mult + jitter), 2) + # Assume 30 tablets/capsules per 30-day supply for unit price. + unit = round(supply / 30.0, 2) + savings = max(0, int(round((1 - supply / (base * 1.15)) * 100))) + rows.append({ + "pharmacy": name, + "unit_price": unit, + "supply_price": supply, + "coupon": coupon, + "savings_pct": savings, + }) + + return { + "tier": tier, + "base_retail": round(base * 1.15, 2), + "rows": rows, + "has_generic": bool(brands), # if it has brand names, a generic equivalent exists too + "brand_price": round(base * 4.2, 2) if tier == "generic" and brands else None, + "generic_price": round(base * 0.95, 2) if tier == "brand" else None, + } + + @app.route("//prices") def drug_prices(slug): drug = Drug.query.filter_by(slug=slug).first_or_404() - return render_template("drug_prices.html", drug=drug) + price_data = generate_drug_prices(drug) + return render_template("drug_prices.html", drug=drug, price_data=price_data) @app.route("//dosage") @@ -2158,6 +2371,59 @@ def drug_pregnancy(slug): return render_template("drug_pregnancy.html", drug=drug) +@app.route("//warnings") +def drug_warnings(slug): + drug = Drug.query.filter_by(slug=slug).first_or_404() + text = (drug.warnings or "").strip() + boxed_warning = None + lowered = text.lower() + if "boxed warning" in lowered or "black box" in lowered: + for para in text.split("\n\n"): + if "boxed warning" in para.lower() or "black box" in para.lower(): + boxed_warning = para.strip() + break + if not boxed_warning: + boxed_warning = text[:400] + categories = [ + ("Before taking this medicine", + ["allerg", "pregnan", "breastfeed", "tell your doctor", "medical history", + "kidney", "liver", "heart"]), + ("Serious side effects to watch for", + ["severe", "stop taking", "emergency", "call your doctor", "seek medical", + "anaphyla", "bleeding", "skin reaction"]), + ("Drug and food interactions", + ["other medic", "interaction", "alcohol", "grapefruit", "food"]), + ("Special populations", + ["children", "elderly", "older adults", "pediatric", "geriatric"]), + ] + paragraphs = [p.strip() for p in text.split("\n\n") if p.strip()] + if len(paragraphs) <= 1 and text: + # Try sentence-split if no paragraph breaks. + import re + paragraphs = [s.strip() for s in re.split(r"(?<=[.!?])\s+", text) if s.strip()] + categorized = {name: [] for name, _ in categories} + general = [] + for p in paragraphs: + pl = p.lower() + if boxed_warning and p in boxed_warning: + continue + placed = False + for name, kws in categories: + if any(k in pl for k in kws): + categorized[name].append(p) + placed = True + break + if not placed: + general.append(p) + cards = [(name, items) for name, items in categorized.items() if items] + if general: + cards.append(("General warnings", general)) + return render_template("drug_warnings.html", + drug=drug, + boxed_warning=boxed_warning, + warning_cards=cards) + + @app.route("//interactions") def drug_interactions_page(slug): drug = Drug.query.filter_by(slug=slug).first_or_404() diff --git a/sites/drugs_com/static/css/main.css b/sites/drugs_com/static/css/main.css index 65b46b5..a004cff 100644 --- a/sites/drugs_com/static/css/main.css +++ b/sites/drugs_com/static/css/main.css @@ -1397,6 +1397,40 @@ details.faq-detail p { padding: 0 0 14px; font-size: 0.92rem; color: var(--gray- .pill-result-attrs dt { color: #777; font-weight: 600; } .pill-result-attrs dd { margin: 0; color: #333; } +.pill-icon-circle { width: 72px; height: 72px; background: linear-gradient(135deg, #f7f9fc 0%, #eaeef3 100%); } + +.pill-result-imprint { + margin: 0 0 6px; + font-size: 12px; + color: #555; + letter-spacing: 0.02em; +} +.pill-result-imprint strong { + display: inline-block; + padding: 1px 6px; + background: #f0f3f7; + border: 1px solid #d8dde4; + border-radius: 3px; + color: #222; + font-family: "SFMono-Regular", Menlo, Consolas, monospace; + font-size: 12px; +} + +.pill-availability-badge { + display: inline-block; + margin-left: 6px; + padding: 1px 7px; + border-radius: 10px; + font-size: 10px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.04em; + vertical-align: middle; +} +.pill-availability-badge--rx { background: #e3f2fd; color: #0277bd; border: 1px solid #bbdefb; } +.pill-availability-badge--otc { background: #e8f5e9; color: #2e7d32; border: 1px solid #c8e6c9; } +.pill-availability-badge--mixed { background: #fff3e0; color: #ef6c00; border: 1px solid #ffe0b2; } + .how-it-works { background: #f5f7fa; border: 1px solid #e1e6ec; @@ -1521,3 +1555,22 @@ details.faq-detail p { padding: 0 0 14px; font-size: 0.92rem; color: var(--gray- cursor: pointer; color: #1a3a6e; } + +.related-drugs-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); + gap: 12px; + margin-top: 16px; +} +.related-drug-card { + display: flex; + flex-direction: column; + gap: 6px; + padding: 14px; + border: 1px solid #ddd; + border-radius: 6px; + text-decoration: none; + color: inherit; + font-size: .9rem; +} +.related-drug-card:hover { background: #f6f9fc; } diff --git a/sites/drugs_com/templates/conditions.html b/sites/drugs_com/templates/conditions.html index a9b73fb..cc09635 100644 --- a/sites/drugs_com/templates/conditions.html +++ b/sites/drugs_com/templates/conditions.html @@ -11,7 +11,23 @@

    Conditions & Treatments A to Z

    + {% if top_conditions and not active_letter %} +
    +

    Top conditions

    +
    + {% for c in top_conditions %} + + {{ c.name }} ({{ c.drug_count or 0 }}) + + {% endfor %} +
    +
    + {% endif %} +
    + All {% for letter in all_letters %} {{ letter }} diff --git a/sites/drugs_com/templates/drug_detail.html b/sites/drugs_com/templates/drug_detail.html index 590f8f0..a42f842 100644 --- a/sites/drugs_com/templates/drug_detail.html +++ b/sites/drugs_com/templates/drug_detail.html @@ -132,6 +132,9 @@

    {{ drug.generic_name|capitalize }} side effects

    What other drugs will affect {{ drug.generic_name }}?

    + {% if drug_interactions is defined and drug_interactions %} +

    {{ drug.generic_name|capitalize }} has {{ drug_interactions|length }} known drug interactions.

    + {% endif %} {{ drug.interactions_text | format_drug_text | safe }}

    Check {{ drug.generic_name|capitalize }} for interactions

    @@ -157,6 +160,21 @@

    Frequently asked questions

    {% endif %} + {% if related_drugs %} +
    +

    Similar medications

    + +
    + {% endif %} +

    User reviews for {{ drug.generic_name }}

    diff --git a/sites/drugs_com/templates/drug_prices.html b/sites/drugs_com/templates/drug_prices.html index e72835e..71b39e0 100644 --- a/sites/drugs_com/templates/drug_prices.html +++ b/sites/drugs_com/templates/drug_prices.html @@ -1,12 +1,5 @@ {% extends "base.html" %} -{% block title %}{{ drug.generic_name }} Prices & Coupons | {{ site_name }}{% endblock %} -{% set seed = (drug.generic_name|length * 13 + drug.id * 7) %} -{% set retail30 = 18 + (seed % 240) %} -{% set discount30 = (retail30 * 0.55) | round(2) %} -{% set retail60 = (retail30 * 1.85) | round(2) %} -{% set discount60 = (discount30 * 1.85) | round(2) %} -{% set retail90 = (retail30 * 2.6) | round(2) %} -{% set discount90 = (discount30 * 2.6) | round(2) %} +{% block title %}{{ drug.generic_name }} Prices, Coupons & Savings Tips | {{ site_name }}{% endblock %} {% block content %}
    -

    {{ drug.generic_name }} Prices, Coupons & Patient Assistance

    -

    Below are typical U.S. retail prices and discount-card prices for {{ drug.generic_name }}{% if drug.brand_names %} ({{ drug.brand_names|join(', ') }}){% endif %}. Actual prices vary by pharmacy and location.

    -
    -
    -

    Get a free discount card

    -

    Save up to 80% off retail at over 65,000 participating U.S. pharmacies.

    +

    {{ drug.generic_name|title }} Prices, Coupons and Patient Assistance Programs

    +

    + {{ drug.generic_name|title }}{% if drug.brand_names %} (brand names: {{ drug.brand_names|join(', ') }}){% endif %} + is a {% if drug.drug_class %}{{ drug.drug_class.name }}{% else %}prescription medication{% endif %}. + Prices are for cash paying customers only and are not valid with insurance plans. +

    +

    + A {% if price_data.tier == 'otc' %}store brand{% else %}generic version{% endif %} of {{ drug.generic_name }} is available, see + generic prices below. +

    + + +
    +
    +

    Get a free {{ drug.generic_name|title }} discount card

    +

    Save up to 80% off retail price at over 65,000 participating U.S. pharmacies including CVS, Walgreens, Walmart, Rite Aid, and Kroger. No insurance required.

    - Get free card + Get free coupon ›
    -

    Pricing by supply

    - + +

    {{ drug.generic_name|title }} prices at U.S. pharmacies

    +

    + Prices shown are estimates for a 30-day supply (30 tablets/capsules) of the most common strength. + Actual price may differ at your local pharmacy. +

    +
    +
    - - - + + + + + - - - + {% for row in price_data.rows %} + + + + + + + + {% endfor %}
    SupplyAverage retailDiscount card pricePharmacyPrice per pill30-day supplyCouponSavings
    30-day supply${{ retail30 }}${{ discount30 }}
    60-day supply${{ retail60 }}${{ discount60 }}
    90-day supply${{ retail90 }}${{ discount90 }}
    + {{ row.pharmacy }} + {% if row.pharmacy == 'GoodRx Price' %}Best price{% endif %} + ${{ '%.2f' % row.unit_price }}${{ '%.2f' % row.supply_price }} + {% if row.coupon %}✔ Yes{% else %}{% endif %} + {{ row.savings_pct }}%
    +
    -

    Available forms & dosages

    -

    Tablet, capsule, oral solution. Dosing: {{ (drug.dosage or 'See drug detail page for full dosing information.')[:280] }}

    - -

    Compare prices at pharmacies near you

    + +

    Generic vs. brand name pricing

    - + + + + - - - - - + {% if price_data.tier == 'brand' %} + + + {% elif price_data.brand_price %} + + + {% else %} + + {% endif %}
    Pharmacy30-day price
    Version30-day supply (avg)
    CVS Pharmacy${{ (discount30 * 1.05) | round(2) }}
    Walgreens${{ (discount30 * 1.08) | round(2) }}
    Walmart${{ (discount30 * 0.92) | round(2) }}
    Costco${{ (discount30 * 0.88) | round(2) }}
    Rite Aid${{ (discount30 * 1.02) | round(2) }}
    Brand ({{ drug.brand_names|join(', ') if drug.brand_names else drug.generic_name|title }})${{ '%.2f' % price_data.base_retail }}
    Generic {{ drug.generic_name }}${{ '%.2f' % price_data.generic_price }}
    Brand ({{ drug.brand_names|join(', ') }})${{ '%.2f' % price_data.brand_price }}
    Generic {{ drug.generic_name }}${{ '%.2f' % price_data.base_retail }}
    {{ drug.generic_name|title }}${{ '%.2f' % price_data.base_retail }}
    + + +

    Available dosage forms and strengths

    +
      +
    • Oral tablet — most common, lowest cost
    • +
    • Oral capsule — equivalent pricing to tablets
    • +
    • Oral solution / suspension — typically 15-30% higher than tablets
    • +
    • Extended-release formulations — typically 40-70% higher than immediate-release
    • +
    + {% if drug.dosage %} +

    Dosing reference: {{ (drug.dosage)[:240] }}{% if drug.dosage|length > 240 %}...{% endif %}

    + {% endif %} + + +

    Patient assistance & copay programs

    +
      +
    • Manufacturer copay card — eligible commercially-insured patients may pay as little as $0-$25 per fill.
    • +
    • Patient assistance program (PAP) — free or low-cost medication for uninsured/under-insured patients meeting income criteria.
    • +
    • NeedyMeds & RxAssist — independent directories of available assistance programs.
    • +
    • State pharmaceutical assistance programs — many states offer subsidies for seniors and low-income residents.
    • +
    + + +
    + Pricing disclaimer: Prices listed are cash prices and are estimates only. Actual prices vary by pharmacy, location, dosage strength, quantity dispensed, and insurance coverage. Discount card prices cannot be combined with insurance and are not government program benefits. This information is for reference only and is not a guarantee of price or coverage. Always confirm pricing with your pharmacy. +
    + + +

    More about {{ drug.generic_name }}

    +
    {% endblock %} diff --git a/sites/drugs_com/templates/drug_warnings.html b/sites/drugs_com/templates/drug_warnings.html index 70b55f8..8714044 100644 --- a/sites/drugs_com/templates/drug_warnings.html +++ b/sites/drugs_com/templates/drug_warnings.html @@ -1,31 +1,76 @@ {% extends "base.html" %} -{% block title %}Drug Warnings & Black Box Warnings | {{ site_name }}{% endblock %} +{% block title %}{{ drug.generic_name|capitalize }} Warnings - {{ site_name }}{% endblock %} {% block content %} -
    - -

    Drug Warnings & Black Box Warnings

    + + +
    +
    +

    {{ drug.generic_name|capitalize }} Warnings

    +

    + Medically reviewed by {{ drug.reviewer_name }}, {{ drug.reviewer_credential }}. Last updated on {{ drug.last_updated.strftime('%b %d, %Y') }}. +

    + {% if drug.drug_class %} +

    Drug class: + {{ drug.drug_class.name }} +

    + {% endif %} +
    -
    -

    What is a black box warning?

    -

    A boxed warning (also called a black box warning) is the strongest warning the U.S. Food and Drug Administration (FDA) requires for prescription drugs that carry a significant risk of serious or life-threatening adverse effects. The warning appears in a black-bordered box at the top of the package insert and is intended to alert prescribers and patients to these risks.

    + {% if boxed_warning %} +
    +

    Boxed Warning (Black Box Warning)

    +

    {{ boxed_warning }}

    +

    A boxed warning is the strongest warning the FDA requires, alerting prescribers and patients to risks of serious or life-threatening adverse effects.

    + {% endif %} -

    Drugs with significant warnings

    - - - - - - - - - {% for d in drugs %} - - - - - {% endfor %} - -
    DrugWarning summary
    {{ d.generic_name }}{{ (d.warnings or '')[:280] }}{% if d.warnings and d.warnings|length > 280 %}…{% endif %}
    -
    + {% if warning_cards %} + {% for name, items in warning_cards %} +
    +

    {{ name }}

    + {% if items|length == 1 %} +

    {{ items[0] }}

    + {% else %} +
      + {% for item in items %}
    • {{ item }}
    • {% endfor %} +
    + {% endif %} +
    + {% endfor %} + {% else %} +
    +

    Warnings

    +

    No specific warnings have been recorded for {{ drug.generic_name }}. Always consult your healthcare provider before starting or stopping any medication.

    +
    + {% endif %} + +
    +

    General precautions

    +
      +
    • Tell your doctor about all medical conditions, including kidney, liver, or heart problems.
    • +
    • Disclose all medications you take, including prescription drugs, over-the-counter products, vitamins, and herbal supplements.
    • +
    • Inform your doctor if you are pregnant, planning to become pregnant, or breastfeeding.
    • +
    • Do not stop taking {{ drug.generic_name }} abruptly unless directed by your prescriber.
    • +
    • Store the medication as directed and keep out of reach of children.
    • +
    +
    + +
    +

    Related information for {{ drug.generic_name|capitalize }}

    + +
    + +

    « Back to {{ drug.generic_name|capitalize }} overview

    + {% endblock %} diff --git a/sites/drugs_com/templates/interaction_checker.html b/sites/drugs_com/templates/interaction_checker.html index c436a5a..71cdcf5 100644 --- a/sites/drugs_com/templates/interaction_checker.html +++ b/sites/drugs_com/templates/interaction_checker.html @@ -81,8 +81,60 @@

    Interaction Results

    {% if interactions %} + {% set sev_groups = [ + ('major', 'Major Interactions', '#c0392b', '#fff', '⚠'), + ('moderate', 'Moderate Interactions', '#e67e22', '#fff', '⚡'), + ('minor', 'Minor Interactions', '#f1c40f', '#222', 'ℹ'), + ] %} + {% for sev, label, bg, fg, icon in sev_groups %} + {% set group_items = interactions | selectattr('severity', 'equalto', sev) | list %} + {% if group_items %} +
    +

    + {{ icon|safe }} {{ label }} ({{ group_items|length }}) +

    +
      + {% for it in group_items %} +
    • +
      + {{ icon|safe }} {{ it.severity | capitalize }} + + {{ it.drug_a.generic_name }} + + + {{ it.drug_b.generic_name }} + +
      +

      {{ it.description }}

      +
      + More information +
      +

      This interaction is classified as {{ it.severity }}. + {% if it.severity == 'major' %}Avoid this combination unless directed and closely monitored by your healthcare provider. + {% elif it.severity == 'moderate' %}Use only with appropriate clinical monitoring or dose adjustment. + {% else %}This interaction is typically clinically minor but worth noting. + {% endif %} +

      +

      For pharmacology details, see the drug pages for + {{ it.drug_a.generic_name }} and + {{ it.drug_b.generic_name }}. +

      +
      +
      +
    • + {% endfor %} +
    +
    + {% endif %} + {% endfor %} + {% else %} +

    No known drug-drug interactions found among these drugs in our database.

    + {% endif %} + + {% if food_interactions %} +

    Drug-Food Interactions

    +

    Common dietary considerations for the drugs you entered.

      - {% for it in interactions %} + {% for it in food_interactions %} {% set bg = '#c0392b' if it.severity == 'major' else ('#e67e22' if it.severity == 'moderate' else '#f1c40f') %} {% set fg = '#222' if it.severity == 'minor' else '#fff' %} {% set icon = '⚠' if it.severity == 'major' else ('⚡' if it.severity == 'moderate' else 'ℹ') %} @@ -90,32 +142,36 @@

      Interaction Results

      {{ icon|safe }} {{ it.severity | capitalize }} - {{ it.drug_a.generic_name }} - + - {{ it.drug_b.generic_name }} + {{ it.drug.generic_name }} + + {{ it.item }} + +
      +

      {{ it.description }}

      + + {% endfor %} +
    + {% endif %} + + {% if alcohol_interactions %} +

    Drug-Alcohol Interactions

    +

    Risks of combining alcohol with the drugs you entered.

    +
      + {% for it in alcohol_interactions %} + {% set bg = '#c0392b' if it.severity == 'major' else ('#e67e22' if it.severity == 'moderate' else '#f1c40f') %} + {% set fg = '#222' if it.severity == 'minor' else '#fff' %} + {% set icon = '⚠' if it.severity == 'major' else ('⚡' if it.severity == 'moderate' else 'ℹ') %} +
    • +
      + {{ icon|safe }} {{ it.severity | capitalize }} + + {{ it.drug.generic_name }} + + Alcohol

      {{ it.description }}

      -
      - More information -
      -

      This interaction is classified as {{ it.severity }}. - {% if it.severity == 'major' %}Avoid this combination unless directed and closely monitored by your healthcare provider. - {% elif it.severity == 'moderate' %}Use only with appropriate clinical monitoring or dose adjustment. - {% else %}This interaction is typically clinically minor but worth noting. - {% endif %} -

      -

      For pharmacology details, see the drug pages for - {{ it.drug_a.generic_name }} and - {{ it.drug_b.generic_name }}. -

      -
      -
    • {% endfor %}
    - {% else %} -

    No known interactions found among these drugs in our database.

    {% endif %}
    {% endif %} diff --git a/sites/drugs_com/templates/news_article.html b/sites/drugs_com/templates/news_article.html index fcaa0f5..0f01dab 100644 --- a/sites/drugs_com/templates/news_article.html +++ b/sites/drugs_com/templates/news_article.html @@ -1,6 +1,32 @@ {% extends "base.html" %} {% block title %}{{ article.title }} - {{ site_name }}{% endblock %} {% block content %} +{# Deterministic fake medical reviewer + reference data keyed on article.id so the page is stable across requests. #} +{% set reviewers = [ + ('Leigh Ann Anderson', 'PharmD'), + ('Carmen Fookes', 'BPharm'), + ('Judith Stewart', 'BPharm'), + ('Melisa Puckey', 'BPharm'), + ('Kristianne Hannemann', 'PharmD'), + ('Philip Thornton', 'DipPharm'), + ('Sally Chao', 'MD'), + ('Michael Stewart', 'PharmD') +] %} +{% set reviewer = reviewers[article.id % reviewers|length] %} + +{% set ref_journals = [ + 'New England Journal of Medicine', + 'Journal of the American Medical Association (JAMA)', + 'The Lancet', + 'Journal of Clinical Pharmacology', + 'British Medical Journal (BMJ)', + 'Annals of Internal Medicine' +] %} +{% set ref_year = article.published_at.year %} + +{% set body_paragraphs = article.body.split('\n\n') | map('trim') | reject('equalto', '') | list %} +{% set takeaway_source = body_paragraphs[:4] if body_paragraphs|length >= 3 else body_paragraphs %} + -

    {{ drug_class.name }}

    +

    + {{ drug_class.name }} + + {{ drug_count }} drug{{ '' if drug_count == 1 else 's' }} in this class + +

    @@ -28,6 +33,18 @@

    What are {{ drug_class.name }} used for?

    List of {{ drug_class.name }}

    {% if drugs %} +
    + Sort by: + A-Z + | + Rating + | + Most reviewed +
    + diff --git a/sites/drugs_com/templates/drug_dosage.html b/sites/drugs_com/templates/drug_dosage.html index 6bffa75..be7de4c 100644 --- a/sites/drugs_com/templates/drug_dosage.html +++ b/sites/drugs_com/templates/drug_dosage.html @@ -41,13 +41,47 @@

    Usual adult dose

    -

    Common adult doses

    +

    Dosage by indication

    Doses vary by indication, body weight, and renal/hepatic function. Always follow the dosing on the product label or your prescriber's directions.

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    IndicationFormAdult dosePediatric dose
    Primary indicationOral (tablet/capsule)Standard adult starting dose per prescribing information; titrate to response.Weight-based; see pediatric prescribing information. Consult a pediatrician.
    Maintenance therapyOralLowest effective maintenance dose; reassess periodically.Adjust by weight and clinical response.
    Alternate routes (if available)IV / topical / otherPer product label.Per product label; not all forms are approved in children.
    -
    -

    Pediatric dose

    -

    Pediatric dosing of {{ drug.generic_name }} is typically weight-based. Consult a pediatrician before use in children, particularly in infants under 6 months of age. Do not exceed the maximum daily dose.

    +
    +

    Maximum daily dose

    +

    Do not exceed the maximum daily dose stated on the product label + or by your prescriber. Exceeding the recommended maximum daily dose of {{ drug.generic_name|capitalize }} + can cause serious adverse effects, including hepatic, renal, gastrointestinal, or cardiovascular harm. + If you are unsure of the limit for your situation, contact your healthcare provider before taking + additional doses.

    @@ -70,6 +104,17 @@

    What happens if I overdose?

    Seek emergency medical attention or call the Poison Help line at 1-800-222-1222. Overdose symptoms may include nausea, vomiting, stomach pain, drowsiness, black or bloody stools, coughing up blood, shallow breathing, fainting, or coma.

    +
    +

    Storage

    +
      +
    • Store {{ drug.generic_name|capitalize }} at room temperature, typically between 68°F and 77°F (20°C to 25°C), unless the product label specifies otherwise.
    • +
    • Keep the container tightly closed and protect from moisture, heat, and direct light.
    • +
    • Do not store in the bathroom or other humid locations.
    • +
    • Keep all medication out of the reach of children and pets.
    • +
    • Dispose of expired or unused medication through a drug take-back program; do not flush down the toilet unless instructed.
    • +
    +
    +

    « Back to {{ drug.generic_name|capitalize }} overview

    {% endblock %} diff --git a/sites/drugs_com/templates/drug_pregnancy.html b/sites/drugs_com/templates/drug_pregnancy.html index 1b68a38..8920324 100644 --- a/sites/drugs_com/templates/drug_pregnancy.html +++ b/sites/drugs_com/templates/drug_pregnancy.html @@ -60,6 +60,32 @@

    Pregnancy

    provider promptly so the regimen can be reassessed.

    +
    +

    Trimester-specific considerations

    +
    +
    +

    First trimester (weeks 1-12)

    +

    Organ development is most active during the first trimester, making the fetus most vulnerable to teratogenic exposure. Many medications are best avoided or used at the lowest effective dose during this period unless clearly indicated.

    +
    +
    +

    Second trimester (weeks 13-27)

    +

    Major organogenesis is largely complete, but ongoing fetal growth and development continue. Some medications that are contraindicated in the third trimester may be acceptable here under medical supervision.

    +
    +
    +

    Third trimester (weeks 28-40)

    +

    Certain drug classes (for example NSAIDs such as {{ drug.generic_name|title if 'profen' in drug.generic_name|lower or 'naproxen' in drug.generic_name|lower else 'select agents' }}) can affect fetal circulation or delivery and are often avoided in late pregnancy. Always confirm trimester-specific safety with your clinician.

    +
    +
    +
    + +
    +

    Bottom line

    +

    {{ drug.generic_name|title }} carries an FDA pregnancy classification of + {{ risk }}. Do not start, stop, or change the dose of this medication during + pregnancy or while breastfeeding without first speaking to your healthcare provider. The safest + choice depends on your individual condition, gestational age, and available alternatives.

    +
    +

    Lactation / Breastfeeding

    Whether {{ drug.generic_name|title }} passes into human milk in clinically significant @@ -83,6 +109,7 @@

    General recommendations

    Always consult your healthcare provider before starting, stopping, or changing any medication during pregnancy or while breastfeeding. The information on this page is for general reference and does not replace individualized medical advice. + Talk to a healthcare provider about {{ drug.generic_name|title }} ›

    ← Back to {{ drug.generic_name|title }} drug information

    diff --git a/sites/drugs_com/templates/symptom_checker.html b/sites/drugs_com/templates/symptom_checker.html new file mode 100644 index 0000000..3cded3a --- /dev/null +++ b/sites/drugs_com/templates/symptom_checker.html @@ -0,0 +1,79 @@ +{% extends "base.html" %} +{% block title %}Symptom Checker - Drugs.com{% endblock %} +{% block content %} + + +

    Symptom Checker

    +

    Select the symptoms you are experiencing. We will suggest conditions commonly associated with that combination of symptoms. This tool is for general information only and does not replace medical advice.

    + +
    +
    +
    + {% for system, symptoms in body_systems %} +
    +

    {{ system }}

    +
    + {% for sym in symptoms %} + {% set is_on = sym in selected %} + + {% endfor %} +
    +
    + {% endfor %} + +
    + + Clear all + {{ total_selected }} symptom{{ '' if total_selected == 1 else 's' }} selected +
    +
    + +
    +

    Possible conditions

    + {% if total_selected == 0 %} +

    Select one or more symptoms above to see possible conditions.

    + {% elif possible_conditions %} +
      + {% for c, hits in possible_conditions %} +
    • + {{ c.name }} + ({{ hits }} matching symptom{{ '' if hits == 1 else 's' }}) +
    • + {% endfor %} +
    + {% else %} +

    No matching conditions found for the selected symptom combination.

    + {% endif %} +
    + +
    + Important: This symptom checker provides educational information only. If you have severe symptoms such as chest pain, sudden weakness, difficulty breathing, or signs of a stroke, call emergency services immediately. +
    +
    + + +
    +{% endblock %} From d7acebbc3460445ca0740866b03c6dc39d49206f Mon Sep 17 00:00:00 2001 From: Boyu Gou Date: Wed, 13 May 2026 12:52:58 -0700 Subject: [PATCH 016/167] drugs_com: FAQ accordion, login/register polish, account settings, Docker verified - Add contextual FAQ generation from drug data (uses/side_effects/availability) - Render FAQ section as accordion with details/summary elements - Polish login page: social login buttons, remember me, forgot password - Polish register page: password strength indicator, terms checkbox - Improve account settings: profile section, notification prefs, privacy settings - Docker: all 16 sites return 200, byte-identity reset verified (md5 match) Co-Authored-By: Claude Sonnet 4.6 --- sites/drugs_com/app.py | 88 ++++++++++++ sites/drugs_com/static/css/main.css | 46 ++++++- .../drugs_com/templates/account_settings.html | 130 +++++++++++------- sites/drugs_com/templates/drug_detail.html | 13 +- sites/drugs_com/templates/login.html | 72 ++++++---- sites/drugs_com/templates/register.html | 94 +++++++++---- 6 files changed, 329 insertions(+), 114 deletions(-) diff --git a/sites/drugs_com/app.py b/sites/drugs_com/app.py index 0193cd9..6057283 100644 --- a/sites/drugs_com/app.py +++ b/sites/drugs_com/app.py @@ -1397,6 +1397,92 @@ def drug_az(): return render_template("drug_az.html", active_letter=letter, all_letters=list(string.ascii_uppercase), drugs=drugs) +def _first_sentences(text, n): + """Return the first n sentences from text as a single string.""" + if not text: + return "" + # Strip simple HTML tags that may live in seed content + plain = re.sub(r"<[^>]+>", " ", text) + plain = re.sub(r"\s+", " ", plain).strip() + parts = re.split(r"(?<=[.!?])\s+", plain) + parts = [p for p in parts if p] + return " ".join(parts[:n]).strip() + + +def _build_default_faq(drug): + """Generate a contextual FAQ from drug fields when faq_json is empty.""" + name = drug.generic_name + name_cap = name.capitalize() if name else "this medicine" + items = [] + + uses_summary = _first_sentences(drug.uses, 2) or _first_sentences(drug.description, 2) + if uses_summary: + items.append({ + "q": f"What is {name} used for?", + "a": uses_summary, + }) + + items.append({ + "q": f"How does {name} work?", + "a": ( + f"{name_cap} belongs to the {drug.drug_class.name} class. " + f"{drug.drug_class.description}" + ) if drug.drug_class else ( + f"{name_cap} works through its active pharmacological mechanism in the body. " + f"Talk to your doctor or pharmacist for a detailed explanation of how it works for your condition." + ), + }) + + se_summary = _first_sentences(drug.side_effects, 3) + if se_summary: + items.append({ + "q": f"What are the most common side effects of {name}?", + "a": se_summary, + }) + + avail = (drug.availability or "").lower() + if "otc" in avail and "rx" in avail: + otc_answer = ( + f"{name_cap} is available both over the counter and by prescription, " + f"depending on the strength and formulation." + ) + elif "otc" in avail: + otc_answer = ( + f"Yes. {name_cap} is available over the counter without a prescription. " + f"Always follow the directions on the label." + ) + else: + otc_answer = ( + f"No. {name_cap} is a prescription-only medicine and is not available over the counter. " + f"You will need a prescription from a licensed healthcare provider." + ) + items.append({ + "q": f"Is {name} available over the counter?", + "a": otc_answer, + }) + + preg = (drug.pregnancy_risk or "").strip() + if preg: + items.append({ + "q": f"Is {name} safe to take during pregnancy?", + "a": ( + f"Pregnancy risk for {name}: {preg}. " + f"Always talk to your doctor before taking any medicine while pregnant or breastfeeding." + ), + }) + + items.append({ + "q": f"Is there a generic version of {name}?", + "a": ( + f"{name_cap} is itself a generic name. Generic versions are widely available " + f"and may be sold under several brand names: " + f"{', '.join(drug.brand_names) if drug.brand_names else 'see the brand names listed above'}." + ), + }) + + return items + + @app.route("/.html") def drug_detail(slug): drug = Drug.query.filter_by(slug=slug).first() @@ -1442,11 +1528,13 @@ def drug_detail(slug): drug_interactions = DrugInteraction.query.filter( (DrugInteraction.drug_a_id == drug.id) | (DrugInteraction.drug_b_id == drug.id) ).all() + faq_items = drug.faq or _build_default_faq(drug) return render_template("drug_detail.html", drug=drug, reviews=reviews, related=related, drug_conditions=drug_conditions, saved=saved, rating_distribution=rating_distribution, user_review=user_review, related_news=related_news, related_drugs=related_drugs, drug_interactions=drug_interactions, + faq_items=faq_items, recently_viewed=recently_viewed) diff --git a/sites/drugs_com/static/css/main.css b/sites/drugs_com/static/css/main.css index ba1e946..5530f53 100644 --- a/sites/drugs_com/static/css/main.css +++ b/sites/drugs_com/static/css/main.css @@ -637,7 +637,6 @@ a:hover { text-decoration: underline; color: var(--blue-mid); } .rating-score { font-size: 1.1rem; font-weight: 700; color: var(--navy); } .rating-count { color: var(--blue); font-size: 0.88rem; } -.faq-item { padding: 12px 0; border-bottom: 1px solid var(--gray-100); } .faq-item h3 { color: var(--navy); font-size: 1rem; margin-bottom: 6px; } .review { padding: 14px 0; border-bottom: 1px solid var(--gray-100); } @@ -789,6 +788,51 @@ a:hover { text-decoration: underline; color: var(--blue-mid); } .mini-checker-form button { padding: 8px 16px; background: var(--blue); color: #fff; border: none; border-radius: 4px; font-weight: 600; cursor: pointer; font-size: 0.9rem; } /* FAQ accordion */ +.faq-intro { margin: 4px 0 14px; font-size: 0.9rem; } +.faq-list { border-top: 1px solid var(--gray-200); } +details.faq-item { border-bottom: 1px solid var(--gray-200); padding: 0; } +details.faq-item > summary.faq-question { + padding: 14px 30px 14px 0; + cursor: pointer; + font-weight: 700; + color: var(--navy); + font-size: 0.98rem; + list-style: none; + position: relative; + line-height: 1.45; +} +details.faq-item > summary.faq-question::-webkit-details-marker { display: none; } +details.faq-item > summary.faq-question::after { + content: ""; + position: absolute; + right: 6px; + top: 50%; + width: 9px; + height: 9px; + border-right: 2px solid var(--blue); + border-bottom: 2px solid var(--blue); + transform: translateY(-70%) rotate(45deg); + transition: transform 0.2s ease; +} +details.faq-item[open] > summary.faq-question::after { + transform: translateY(-30%) rotate(-135deg); +} +details.faq-item > summary.faq-question:hover { color: var(--blue); } +.faq-q-label, .faq-a-label { + display: inline-block; + min-width: 22px; + font-weight: 700; + color: var(--blue); + margin-right: 4px; +} +.faq-a-label { color: var(--green); } +details.faq-item > .faq-answer { + padding: 0 0 16px 0; + font-size: 0.93rem; + color: var(--gray-700); + line-height: 1.65; +} +/* Back-compat: keep the older .faq-detail class working if used elsewhere */ details.faq-detail { border-bottom: 1px solid var(--gray-200); } details.faq-detail summary { padding: 13px 0; cursor: pointer; font-weight: 600; color: var(--navy); font-size: 0.95rem; list-style: none; display: flex; justify-content: space-between; align-items: center; } details.faq-detail summary::after { content: "+"; font-size: 1.3rem; color: var(--blue); } diff --git a/sites/drugs_com/templates/account_settings.html b/sites/drugs_com/templates/account_settings.html index 4039409..18580a4 100644 --- a/sites/drugs_com/templates/account_settings.html +++ b/sites/drugs_com/templates/account_settings.html @@ -19,73 +19,97 @@

    Account Settings

    {% with messages = get_flashed_messages(with_categories=true) %} {% for category, message in messages %} -
    {{ message }}
    +
    + Success: {{ message }} +
    {% endfor %} {% endwith %} -
    - {% if drug.faq %} + {% if faq_items %}

    Frequently asked questions

    - {% for item in drug.faq %} -
    - {{ item.q }} -

    {{ item.a }}

    +

    Common questions about {{ drug.generic_name }}. Click a question to view the answer.

    +
    + {% for item in faq_items %} +
    + Q: {{ item.q }} +
    A: {{ item.a }}
    {% endfor %} +
    {% endif %} diff --git a/sites/drugs_com/templates/login.html b/sites/drugs_com/templates/login.html index 345170a..caff063 100644 --- a/sites/drugs_com/templates/login.html +++ b/sites/drugs_com/templates/login.html @@ -6,49 +6,69 @@ Sign In -
    -

    Sign in to Drugs.com

    +
    +
    + + Drugs.com + +

    Know more. Live healthier.

    +
    + +

    Sign in to your account

    {% with messages = get_flashed_messages(with_categories=true) %} {% if messages %} {% for category, message in messages %} -

    {{ message }}

    +

    {{ message }}

    {% endfor %} {% endif %} {% endwith %}
    - - +
    + + +
    -
    {% endblock %} From 60e39c936b3352529d7e7a052bd961a0c350b86c Mon Sep 17 00:00:00 2001 From: Boyu Gou Date: Wed, 13 May 2026 15:07:26 -0700 Subject: [PATCH 038/167] drugs_com: photorealistic pill SVGs, real NIH images, homepage health categories, help page, improved pill/news/compare/warnings/side-effects pages - Add / route (no .html suffix) for drug detail pages - Pill identifier: photorealistic 3D SVG pills (radial gradient, specular highlight, drop shadow, embossed imprint) via _pill_svg.html macro; real NIH public-domain photos when available; image-dominant card layout with drug info below - Homepage: Browse by Health Category section with 8 gradient cards - Compare drugs: Quick comparison table (drug class, availability, pregnancy, CSA, color-coded rating) + Bottom Line section - Help & Support: new /help /support routes with FAQ, contact info, tool links; fixed nav link from search to help_page - News article: author byline + Was this helpful? widget - My Med List: Add medication search form, reminders sidebar, drug class metadata - Warnings index: category filter tabs, FDA alerts list, drug warnings table - Side effects: category cards grid, A-Z directory, emergency-care callout - Drug images page: consistent with new pill card design - CSS: new sections for pill cards, health categories, compare table, help page Co-Authored-By: Claude Sonnet 4.6 --- sites/drugs_com/app.py | 38 +- sites/drugs_com/static/css/main.css | 445 +++++++++++++++--- sites/drugs_com/templates/_pill_svg.html | 183 +++++++ sites/drugs_com/templates/base.html | 2 +- sites/drugs_com/templates/compare_drugs.html | 109 ++++- sites/drugs_com/templates/drug_detail.html | 6 +- sites/drugs_com/templates/drug_images.html | 110 +---- sites/drugs_com/templates/help.html | 73 +++ sites/drugs_com/templates/index.html | 50 ++ sites/drugs_com/templates/my_med_list.html | 38 +- sites/drugs_com/templates/news_article.html | 14 + .../drugs_com/templates/pill_identifier.html | 106 ++--- sites/drugs_com/templates/side_effects.html | 113 +++-- sites/drugs_com/templates/warnings_index.html | 87 +++- 14 files changed, 1095 insertions(+), 279 deletions(-) create mode 100644 sites/drugs_com/templates/_pill_svg.html create mode 100644 sites/drugs_com/templates/help.html diff --git a/sites/drugs_com/app.py b/sites/drugs_com/app.py index e5f96e8..2aa3b0c 100644 --- a/sites/drugs_com/app.py +++ b/sites/drugs_com/app.py @@ -1375,6 +1375,22 @@ def score_drug(drug, tokens): return sum(1 for t in tokens if t in text) +@app.template_filter('pill_image_exists') +def pill_image_exists(slug): + """Return True iff ``static/images/pills/.jpg`` is present on disk. + + Used by drug detail / pill identifier / drug images templates to decide + whether to render the real downloaded photo (from NLM DailyMed, public + domain) or fall back to the inline SVG pill illustration. The + ``static/images/`` tree is HF-managed so its contents vary between + fresh clones; templates can't filesystem-check, hence this filter. + """ + if not slug: + return False + path = os.path.join(BASE_DIR, 'static', 'images', 'pills', f'{slug}.jpg') + return os.path.exists(path) + + @app.template_filter('format_drug_text') def format_drug_text(text): """Format raw FDA-label drug text into readable HTML paragraphs. @@ -1634,6 +1650,7 @@ def _build_default_faq(drug): return items +@app.route("/") @app.route("/.html") def drug_detail(slug): drug = Drug.query.filter_by(slug=slug).first() @@ -2986,8 +3003,21 @@ def side_effects_page(): @app.route("/warnings/") @app.route("/blackbox-warnings") def warnings_index(): + category = (request.args.get("category") or "all").lower() drugs_with_warnings = Drug.query.filter(Drug.warnings.isnot(None)).order_by(Drug.generic_name).limit(50).all() - return render_template("warnings_index.html", drugs=drugs_with_warnings) + fda_alerts = ( + NewsArticle.query + .filter(NewsArticle.category.in_(["FDA Alerts", "Recalls", "Safety Alerts"])) + .order_by(NewsArticle.published_at.desc()) + .limit(20) + .all() + ) + return render_template( + "warnings_index.html", + drugs=drugs_with_warnings, + fda_alerts=fda_alerts, + category=category, + ) @app.route("/newsletter") @@ -3020,6 +3050,12 @@ def contact(): return render_template("contact.html") +@app.route("/support") +@app.route("/help") +def help_page(): + return render_template("help.html") + + @app.route("//images") def drug_images(slug): drug = Drug.query.filter_by(slug=slug).first_or_404() diff --git a/sites/drugs_com/static/css/main.css b/sites/drugs_com/static/css/main.css index 28c959c..4302ee9 100644 --- a/sites/drugs_com/static/css/main.css +++ b/sites/drugs_com/static/css/main.css @@ -1532,81 +1532,135 @@ details.faq-detail p { padding: 0 0 14px; font-size: 0.92rem; color: var(--gray- .btn-small { padding: 4px 10px; font-size: 12px; } .pill-results-heading { margin-top: 22px; } +.pill-results-count { color: #6b7280; font-size: 0.92rem; margin: 4px 0 14px; } + .pill-result-grid { display: grid; - grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); - gap: 14px; + grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); + gap: 18px; margin-top: 12px; } .pill-result-card { display: flex; - gap: 12px; - background: #fff; - border: 1px solid #e1e6ec; - border-radius: 4px; - padding: 12px; - transition: box-shadow 0.15s ease; + flex-direction: column; + background: #ffffff; + border: 1px solid #e8ecf1; + border-radius: 12px; + overflow: hidden; + box-shadow: 0 2px 8px rgba(0,0,0,0.06); + transition: box-shadow 0.2s ease, transform 0.2s ease; +} +.pill-result-card:hover { + box-shadow: 0 6px 20px rgba(0,0,0,0.12); + transform: translateY(-2px); } -.pill-result-card:hover { box-shadow: 0 2px 6px rgba(0,0,0,0.08); } -.pill-icon-circle { - flex-shrink: 0; - width: 64px; - height: 64px; - border-radius: 50%; - background: #f0f3f7; +/* Large pill display area — dominant visual on each card. */ +.pill-icon-wrap { + background: + radial-gradient(ellipse at 30% 25%, #ffffff 0%, transparent 55%), + linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); display: flex; align-items: center; justify-content: center; - color: #d3d8df; - border: 1px solid #e1e6ec; + padding: 18px 20px; + min-height: 140px; + border-bottom: 1px solid #eef1f5; } -.pill-icon-circle svg { width: 100%; height: 100%; } -.pill-icon-circle[data-color="white"] { color: #ffffff; } -.pill-icon-circle[data-color="yellow"] { color: #ffe066; } -.pill-icon-circle[data-color="orange"] { color: #ffa94d; } -.pill-icon-circle[data-color="pink"] { color: #faa2c1; } -.pill-icon-circle[data-color="red"] { color: #ff6b6b; } -.pill-icon-circle[data-color="brown"] { color: #a47148; } -.pill-icon-circle[data-color="gray"] { color: #adb5bd; } -.pill-icon-circle[data-color="green"] { color: #69db7c; } -.pill-icon-circle[data-color="blue"] { color: #74c0fc; } -.pill-icon-circle[data-color="purple"] { color: #b197fc; } -.pill-icon-circle[data-color="black"] { color: #495057; } -.pill-icon-circle[data-color="turquoise"] { color: #66d9e8; } - -.pill-result-body { flex: 1; min-width: 0; } -.pill-result-name { margin: 0 0 6px; font-size: 15px; } -.pill-result-name a { color: #0277bd; text-decoration: none; } -.pill-result-name a:hover { text-decoration: underline; } -.pill-result-attrs { - display: grid; - grid-template-columns: auto 1fr; - gap: 2px 8px; - font-size: 12px; - margin: 0 0 8px; +.pill-icon-wrap .pill-svg { + width: 100%; + height: auto; + max-height: 120px; + display: block; + filter: drop-shadow(0 3px 6px rgba(0,0,0,0.12)); +} +.pill-icon-wrap .pill-svg--round, +.pill-icon-wrap .pill-svg--triangle, +.pill-icon-wrap .pill-svg--diamond, +.pill-icon-wrap .pill-svg--pentagon { + max-width: 130px; +} +.pill-icon-wrap--photo { padding: 0; min-height: 140px; background: #f8f9fa; } +.pill-icon-wrap--photo .pill-photo { + width: 100%; + height: 100%; + max-height: 160px; + object-fit: contain; + display: block; } -.pill-result-attrs dt { color: #777; font-weight: 600; } -.pill-result-attrs dd { margin: 0; color: #333; } - -.pill-icon-circle { width: 72px; height: 72px; background: linear-gradient(135deg, #f7f9fc 0%, #eaeef3 100%); } -.pill-result-imprint { - margin: 0 0 6px; - font-size: 12px; - color: #555; - letter-spacing: 0.02em; +/* Subtle background tint per pill color, so cards read at a glance. */ +.pill-icon-wrap[data-color="yellow"] { background: linear-gradient(135deg, #fffceb 0%, #fff3bf 100%); } +.pill-icon-wrap[data-color="orange"] { background: linear-gradient(135deg, #fff4e6 0%, #ffe8cc 100%); } +.pill-icon-wrap[data-color="pink"] { background: linear-gradient(135deg, #fff0f6 0%, #ffdeeb 100%); } +.pill-icon-wrap[data-color="red"] { background: linear-gradient(135deg, #fff5f5 0%, #ffe3e3 100%); } +.pill-icon-wrap[data-color="brown"] { background: linear-gradient(135deg, #f8f3ee 0%, #ede0d4 100%); } +.pill-icon-wrap[data-color="gray"] { background: linear-gradient(135deg, #f8f9fa 0%, #dee2e6 100%); } +.pill-icon-wrap[data-color="green"] { background: linear-gradient(135deg, #f3faf3 0%, #d3f9d8 100%); } +.pill-icon-wrap[data-color="blue"] { background: linear-gradient(135deg, #f3f8ff 0%, #d0ebff 100%); } +.pill-icon-wrap[data-color="purple"] { background: linear-gradient(135deg, #f8f3ff 0%, #e5dbff 100%); } +.pill-icon-wrap[data-color="black"] { background: linear-gradient(135deg, #f1f3f5 0%, #ced4da 100%); } +.pill-icon-wrap[data-color="turquoise"] { background: linear-gradient(135deg, #f0fbfc 0%, #c5f6fa 100%); } + +.pill-card-body { + padding: 14px 16px 16px; + display: flex; + flex-direction: column; + gap: 4px; } -.pill-result-imprint strong { +.pill-card-name { + margin: 0; + font-weight: 700; + font-size: 1rem; + line-height: 1.3; + color: #1a3a6e; +} +.pill-card-name a { color: #1a3a6e; text-decoration: none; } +.pill-card-name a:hover { text-decoration: underline; } +.pill-card-name strong { display: inline-block; padding: 1px 6px; background: #f0f3f7; border: 1px solid #d8dde4; border-radius: 3px; - color: #222; + color: #1a3a6e; font-family: "SFMono-Regular", Menlo, Consolas, monospace; - font-size: 12px; + font-size: 0.85rem; +} +.pill-card-attrs { + margin: 2px 0 0; + font-size: 0.82rem; + color: #6b7280; + line-height: 1.4; +} +.pill-card-attrs strong { + color: #1a3a6e; + font-family: "SFMono-Regular", Menlo, Consolas, monospace; + font-weight: 700; +} +.pill-card-meta { + margin: 2px 0 0; + font-size: 0.78rem; + color: #6b7280; +} +.pill-card-meta a { color: #0277bd; text-decoration: none; } +.pill-card-meta a:hover { text-decoration: underline; } +.pill-card-link { + margin-top: 10px; + font-size: 0.85rem; + font-weight: 600; + color: #0277bd; + text-decoration: none; + align-self: flex-start; } +.pill-card-link:hover { text-decoration: underline; } + +/* Drug images page — slightly larger image area, fewer columns. */ +.drug-images-grid { + grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); +} +.drug-images-grid .pill-icon-wrap { min-height: 160px; } +.drug-images-grid .pill-icon-wrap .pill-svg { max-height: 140px; } .pill-availability-badge { display: inline-block; @@ -1988,3 +2042,288 @@ details.faq-detail p { padding: 0 0 14px; font-size: 0.92rem; color: var(--gray- .compare-col p { color: #cfd6e4 !important; } .compare-col:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.4); border-color: #4da6ff !important; } } + +/* News article: author byline + helpfulness widget */ +.article-byline { font-size: 13px; color: #555; margin-bottom: 14px; } +.article-byline .byline-author { font-weight: 600; color: #1a3a6e; } +.article-helpful { margin-top: 24px; padding: 16px 20px; background: #f7f9fc; border: 1px solid #e0e6ef; border-radius: 6px; text-align: center; } +.article-helpful h3 { margin: 0 0 10px; font-size: 14px; font-weight: 700; color: #1a3a6e; text-transform: uppercase; letter-spacing: 0.5px; } +.article-helpful .helpful-buttons { display: inline-flex; gap: 10px; } +.article-helpful .helpful-btn { padding: 8px 18px; border: 1px solid #c4ced8; background: #fff; border-radius: 20px; font-size: 13px; font-weight: 600; color: #1a3a6e; cursor: pointer; } +.article-helpful .helpful-btn:hover { background: #eef4fb; border-color: #0066cc; color: #0066cc; } +.article-helpful .helpful-note { margin: 8px 0 0; font-size: 12px; color: #777; } + +/* Homepage: Browse by Health Category */ +.health-category-section { margin: 28px 0; } +.health-category-section h2 { font-size: 1.4rem; font-weight: 700; color: #002a5c; margin: 0 0 6px; } +.health-category-section .health-category-sub { color: #555; font-size: 0.95rem; margin: 0 0 16px; } +.health-category-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); + gap: 14px; +} +.health-category-card { + display: block; + padding: 18px 16px; + border-radius: 8px; + text-decoration: none; + color: #fff; + background: #1a3a6e; + position: relative; + overflow: hidden; + transition: transform 0.12s ease, box-shadow 0.12s ease; + border: 1px solid rgba(0, 0, 0, 0.05); +} +.health-category-card:hover { + transform: translateY(-2px); + box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12); + color: #fff; + text-decoration: none; +} +.health-category-card .hc-icon { + font-size: 26px; + display: block; + margin-bottom: 10px; + line-height: 1; +} +.health-category-card .hc-name { + display: block; + font-weight: 700; + font-size: 1rem; + margin-bottom: 4px; +} +.health-category-card .hc-desc { + display: block; + font-size: 0.82rem; + opacity: 0.9; + line-height: 1.35; +} +.hc-pain { background: linear-gradient(135deg, #d24a4a, #a82828); } +.hc-antibiotics { background: linear-gradient(135deg, #2e8b57, #1d6b3f); } +.hc-heart { background: linear-gradient(135deg, #c0392b, #7e1f15); } +.hc-mental { background: linear-gradient(135deg, #6a5acd, #41358f); } +.hc-diabetes { background: linear-gradient(135deg, #1f7ab8, #115178); } +.hc-womens { background: linear-gradient(135deg, #c2438a, #82265b); } +.hc-sleep { background: linear-gradient(135deg, #3a4a6b, #1f2944); } +.hc-vitamins { background: linear-gradient(135deg, #e08a1e, #a25f0e); } + +/* Compare drugs: quick comparison table at top */ +.compare-quick-wrap { margin: 24px 0 28px; } +.compare-quick-table { + width: 100%; + border-collapse: collapse; + background: #fff; + border: 1px solid #d9e2ec; + border-radius: 6px; + overflow: hidden; + font-size: 14px; +} +.compare-quick-table thead th { + background: #0c3b6f; + color: #fff; + padding: 12px 14px; + text-align: left; + font-weight: 600; + font-size: 14px; + border-right: 1px solid #1a4a85; +} +.compare-quick-table thead th:last-child { border-right: none; } +.compare-quick-table thead th .cq-generic { + display: block; + font-size: 16px; + font-weight: 700; + text-transform: capitalize; +} +.compare-quick-table thead th .cq-brands { + display: block; + font-size: 12px; + font-weight: 400; + color: #c7dbf2; + margin-top: 3px; +} +.compare-quick-table thead th.cq-attr-head { background: #08294d; } +.compare-quick-table tbody td { + padding: 11px 14px; + border-top: 1px solid #eef1f5; + vertical-align: middle; +} +.compare-quick-table tbody tr:nth-child(even) td { background: #f9fbfd; } +.compare-quick-table tbody td.cq-attr { + font-weight: 600; + color: #1a3a6e; + background: #f1f5fa !important; + width: 18%; +} +.rating-pill { + display: inline-block; + padding: 3px 10px; + border-radius: 12px; + font-weight: 700; + font-size: 13px; + color: #fff; + min-width: 56px; + text-align: center; +} +.rating-pill-good { background: #2e8b57; } +.rating-pill-mid { background: #d8a328; color: #3a2a00; } +.rating-pill-bad { background: #c0392b; } +.rating-pill-na { background: #888; } +.rating-count { + display: inline-block; + margin-left: 8px; + font-size: 12px; + color: #666; +} + +/* Compare drugs: detailed comparison columns */ +.compare-section { border-top: 2px solid #e0e6ef; padding-top: 20px; } +.compare-col { background: #f7f9fc; border: 1px solid #e0e6ef; border-radius: 6px; padding: 14px; } +.compare-col h4 { color: #0072ce; font-size: 14px; font-weight: 700; margin: 0 0 8px; } +.compare-col p { font-size: 13px; line-height: 1.6; color: #444; margin: 0 0 8px; } + +/* Compare drugs: bottom line */ +.bottom-line-box { + margin: 28px 0 12px; + background: #f4f8fd; + border: 1px solid #c4d6ec; + border-left: 4px solid #0c3b6f; + border-radius: 6px; + padding: 18px 20px; +} +.bottom-line-box h2 { + margin: 0 0 10px; + font-size: 1.15rem; + color: #0c3b6f; +} +.bottom-line-box p { + margin: 0 0 8px; + font-size: 0.95rem; + line-height: 1.55; + color: #333; +} +.bottom-line-box .bl-pick { + display: inline-block; + background: #0c3b6f; + color: #fff; + padding: 2px 8px; + border-radius: 3px; + font-size: 12px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.4px; + margin-right: 6px; +} + +/* ===== My Med List additions ===== */ +.med-add-form { background: #f5f9fd; border: 1px solid #d6e4f1; border-radius: 6px; padding: 14px 16px; margin-bottom: 18px; } +.med-add-label { font-weight: 600; color: #0c3b6f; display: block; margin-bottom: 6px; } +.med-add-row { display: flex; gap: 8px; margin-bottom: 6px; } +.med-add-input { flex: 1; padding: 9px 12px; border: 1px solid #c8d5e3; border-radius: 4px; font-size: 0.95rem; } +.med-list-count { margin-left: 12px; font-size: 0.9rem; } +.med-empty { padding: 22px 24px; text-align: left; } +.med-empty-title { font-size: 1.2rem; margin: 0 0 8px; color: #0c3b6f; } +.med-empty-steps { margin: 10px 0 0 20px; line-height: 1.8; } +.med-tips { margin: 0; padding-left: 18px; line-height: 1.7; font-size: 0.92rem; color: #333; } +.med-tips li { margin-bottom: 6px; } + +/* ===== Warnings Index ===== */ +.warnings-index { padding: 24px 0; } +.warnings-index .page-title { font-size: 2rem; margin: 8px 0 6px; } +.warnings-index .page-subtitle { color: #555; margin-bottom: 18px; } +.warning-callout { background: #fff4f4; border-left: 4px solid #c0392b; padding: 14px 18px; margin-bottom: 22px; border-radius: 4px; } +.warning-callout h2 { font-size: 1.15rem; margin: 0 0 6px; } +.warning-callout p { margin: 0; line-height: 1.55; } +.warning-filter-tabs { display: flex; flex-wrap: wrap; gap: 6px; margin: 16px 0 22px; border-bottom: 2px solid #e2e6ec; } +.warning-tab { padding: 8px 14px; color: #15457a; text-decoration: none; border: 1px solid transparent; border-bottom: none; border-radius: 4px 4px 0 0; font-weight: 500; } +.warning-tab.is-active { background: #fff; border-color: #c0392b #c0392b #fff #c0392b; color: #c0392b; font-weight: 600; } +.warning-tab:hover:not(.is-active) { background: #f1f5fa; } +.warning-section { margin: 24px 0; } +.warning-section-title { font-size: 1.3rem; margin: 0 0 12px; color: #0c3b6f; } +.warning-alert-list { list-style: none; padding: 0; margin: 0; } +.warning-alert-item { padding: 12px 14px; border: 1px solid #e8d0d0; background: #fffafa; border-radius: 4px; margin-bottom: 8px; } +.warning-alert-badge { display: inline-block; padding: 2px 8px; font-size: 0.78rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; border-radius: 3px; margin-right: 8px; background: #c0392b; color: #fff; } +.warning-alert-badge.cat-fda-alerts { background: #c0392b; } +.warning-alert-badge.cat-recalls { background: #b8360b; } +.warning-alert-badge.cat-safety-alerts { background: #d97706; } +.warning-alert-title { color: #15457a; font-weight: 600; text-decoration: none; } +.warning-alert-title:hover { text-decoration: underline; } +.warning-alert-date { color: #888; font-size: 0.85rem; margin-left: 8px; } +.warning-alert-snippet { margin: 6px 0 0; color: #444; font-size: 0.92rem; line-height: 1.5; } +.warning-table { width: 100%; border-collapse: collapse; } +.warning-table th { text-align: left; padding: 10px; background: #f1f5fa; border-bottom: 1px solid #ddd; } +.warning-table td { padding: 10px; border-bottom: 1px solid #eee; vertical-align: top; } +.warning-table-drug { white-space: nowrap; font-weight: 600; } +.warning-resources { margin: 28px 0; padding: 16px 18px; background: #f5f9fd; border: 1px solid #d6e4f1; border-radius: 6px; } +.warning-resources ul { margin: 8px 0 0 20px; line-height: 1.9; } + +/* ===== Side Effects Page ===== */ +.side-effects-page { padding: 24px 0; } +.side-effects-page .page-title { font-size: 2rem; margin: 8px 0 6px; } +.side-effects-page .page-subtitle { color: #555; line-height: 1.55; margin-bottom: 20px; max-width: 820px; } +.side-effects-search { display: flex; gap: 8px; max-width: 560px; margin-bottom: 24px; } +.side-effects-search-input { flex: 1; padding: 10px 12px; border: 1px solid #ccc; border-radius: 4px; font-size: 0.95rem; } +.side-effects-h2 { font-size: 1.3rem; margin: 18px 0 12px; color: #0c3b6f; } +.side-effects-popular { margin: 18px 0 28px; } +.se-tag-row { display: flex; flex-wrap: wrap; gap: 8px; } +.se-tag { display: inline-block; padding: 6px 12px; background: #eaf2fa; border: 1px solid #c8dcef; border-radius: 18px; color: #15457a; text-decoration: none; font-size: 0.92rem; } +.se-tag:hover { background: #d8e7f5; } +.side-effects-categories { margin: 28px 0; } +.se-category-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 16px; } +.se-category-card { border: 1px solid #e2e6ec; border-radius: 6px; padding: 16px; background: #fff; border-top: 4px solid #999; } +.se-category-card h3 { margin: 0 0 8px; font-size: 1.1rem; } +.se-category-card p { margin: 0 0 10px; font-size: 0.92rem; line-height: 1.5; color: #444; } +.se-category-card ul { margin: 0; padding-left: 18px; line-height: 1.7; } +.se-cat-serious { border-top-color: #c0392b; } +.se-cat-serious h3 { color: #c0392b; } +.se-cat-allergic { border-top-color: #d97706; } +.se-cat-allergic h3 { color: #b45309; } +.se-cat-longterm { border-top-color: #15457a; } +.se-cat-longterm h3 { color: #15457a; } +.se-cat-common { border-top-color: #2f855a; } +.se-cat-common h3 { color: #2f855a; } +.se-az-nav { margin: 22px 0; padding: 10px; background: #f5f7fa; border: 1px solid #e2e6ec; border-radius: 6px; display: flex; flex-wrap: wrap; gap: 6px; } +.se-az-letter { display: inline-block; min-width: 28px; text-align: center; padding: 4px 6px; border-radius: 4px; font-weight: 600; text-decoration: none; } +.se-az-letter.is-active { background: #fff; border: 1px solid #c8dcef; color: #15457a; } +.se-az-letter.is-disabled { color: #aaa; border: 1px solid #eee; } +.se-results { margin: 24px 0; } +.se-result-list { list-style: disc; padding-left: 22px; line-height: 2; } +.se-az-section { margin: 28px 0; } +.se-az-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 18px; } +.se-az-card { border: 1px solid #e2e2e2; border-radius: 6px; padding: 14px; background: #fff; } +.se-az-card-title { margin: 0 0 8px; font-size: 1.1rem; color: #15457a; border-bottom: 1px solid #eee; padding-bottom: 4px; } +.se-az-card-list { list-style: none; padding: 0; margin: 0; line-height: 1.85; } +.se-popular-drugs { margin: 32px 0; } +.se-popular-table { width: 100%; border-collapse: collapse; } +.se-popular-table th { text-align: left; padding: 10px; background: #f1f5fa; border-bottom: 1px solid #ddd; } +.se-popular-table td { padding: 10px; border-bottom: 1px solid #eee; vertical-align: top; } +.se-popular-drug { white-space: nowrap; font-weight: 600; } +.se-disclaimer { margin: 28px 0 16px; padding: 14px 18px; background: #fff4f4; border-left: 4px solid #c0392b; border-radius: 4px; } +.se-disclaimer p { margin: 0; font-size: 0.92rem; line-height: 1.55; } +.visually-hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); border: 0; } + +/* Real pill photographs (NLM DailyMed, public domain), used when the file + static/images/pills/.jpg is present. Fall back to the inline SVG + pill illustration otherwise. */ +.pill-photo { + width: 100%; + max-width: 120px; + height: 80px; + object-fit: contain; + background: #f8f9fa; + border-radius: 8px; + border: 1px solid #e8ecf1; + padding: 4px; + display: block; +} +.pill-photo--header { + width: 110px; + height: 55px; + max-width: 110px; +} +.pill-icon-circle--photo, +.pill-icon-wrap--photo { + background: transparent; + display: flex; + align-items: center; + justify-content: center; +} diff --git a/sites/drugs_com/templates/_pill_svg.html b/sites/drugs_com/templates/_pill_svg.html new file mode 100644 index 0000000..da2a2ac --- /dev/null +++ b/sites/drugs_com/templates/_pill_svg.html @@ -0,0 +1,183 @@ +{# Photorealistic pill SVG macro. + + Renders a pill with 3D shading: radial body gradient for volume, + specular highlight in the upper-left, drop shadow, and optional + embossed imprint text. Supports Capsule (two-tone), Oval, Round, + Rectangle, Triangle, Diamond, Pentagon, Bullet, Figure 8. + + Args: + uid: unique id suffix (loop.index or similar) used to namespace + gradient / filter / clipPath ids across multiple SVGs on + the page. + shape: pill shape string (case-insensitive). Unknown -> Round. + color: pill color string (case-insensitive). Unknown -> light gray. + imprint: text to render on the pill (optional, may be None / ''). + pair_color: secondary hex for capsule's right half. If omitted, falls + back to a sensible complement of `color`. +#} + +{% macro render_pill(uid, shape, color, imprint, pair_color=None) -%} + {% set color_map = { + 'white': '#fafbfc', 'yellow': '#ffe066', 'orange': '#ffa94d', 'pink': '#faa2c1', + 'red': '#ff6b6b', 'brown': '#a47148', 'gray': '#adb5bd', 'green': '#69db7c', + 'blue': '#74c0fc', 'purple': '#b197fc', 'black': '#3a3f44', 'turquoise': '#66d9e8' + } %} + {% set dark_map = { + 'white': '#cdd2d8', 'yellow': '#c9a227', 'orange': '#c46a14', 'pink': '#c2628c', + 'red': '#a83232', 'brown': '#5e3f23', 'gray': '#6c757d', 'green': '#2f9e44', + 'blue': '#1971c2', 'purple': '#7048e8', 'black': '#1a1d20', 'turquoise': '#0c8599' + } %} + {% set capsule_pair_default = { + 'white': '#d0d4da', 'yellow': '#ffa94d', 'orange': '#ffe066', 'pink': '#ffffff', + 'red': '#ffe066', 'brown': '#f0d9b5', 'gray': '#495057', 'green': '#ffffff', + 'blue': '#ffffff', 'purple': '#faa2c1', 'black': '#adb5bd', 'turquoise': '#ffffff' + } %} + {% set clc = (color or '')|lower %} + {% set slc = (shape or '')|lower %} + {% set base = color_map.get(clc, '#cfd4da') %} + {% set dark = dark_map.get(clc, '#9097a0') %} + {% set second = pair_color or capsule_pair_default.get(clc, '#d0d4da') %} + {% set is_light = clc in ['white','yellow','pink','orange','turquoise','gray',''] %} + {% set text_fill = '#2a2a2a' if is_light else '#ffffff' %} + {% set text_shadow = '#ffffff' if is_light else '#000000' %} + + {# Choose viewBox sized for the shape so wide pills get a wide canvas. #} + {% if slc in ['capsule','oval','rectangle','bullet','figure 8'] %} + {% set vbw = 280 %}{% set vbh = 160 %} + {% else %} + {% set vbw = 200 %}{% set vbh = 200 %} + {% endif %} + + +{%- endmacro %} diff --git a/sites/drugs_com/templates/base.html b/sites/drugs_com/templates/base.html index 4c2dcff..7f79c0c 100644 --- a/sites/drugs_com/templates/base.html +++ b/sites/drugs_com/templates/base.html @@ -107,7 +107,7 @@
    - Help & Support + Help & Support
    diff --git a/sites/drugs_com/templates/compare_drugs.html b/sites/drugs_com/templates/compare_drugs.html index b3e9596..5b0e084 100644 --- a/sites/drugs_com/templates/compare_drugs.html +++ b/sites/drugs_com/templates/compare_drugs.html @@ -1,12 +1,6 @@ {% extends "base.html" %} {% block title %}Compare Drugs - Drugs.com{% endblock %} {% block content %} - @@ -57,6 +51,76 @@

    Compare Drugs Side by Side

    {% for _, d, _f in selected_drugs %}{{ d.generic_name|capitalize }}{% if not loop.last %} vs. {% endif %}{% endfor %}

    + + +
    +

    Quick comparison

    + + + + + {% for _, d, _f in selected_drugs %} + + {% endfor %} + + + + + + {% for _, d, _f in selected_drugs %} + + {% endfor %} + + + + {% for _, d, _f in selected_drugs %} + + {% endfor %} + + + + {% for _, d, _f in selected_drugs %} + + {% endfor %} + + + + {% for _, d, _f in selected_drugs %} + + {% endfor %} + + + + {% for _, d, _f in selected_drugs %} + + {% endfor %} + + +
    Attribute + {{ d.generic_name|capitalize }} + {% if d.brand_names %} + {{ d.brand_names|join(', ') }} + {% else %} + Generic only + {% endif %} +
    Drug class{{ d.drug_class.name if d.drug_class else '—' }}
    Availability{{ d.availability or '—' }}
    Pregnancy category{{ d.pregnancy_risk or '—' }}
    CSA schedule{{ d.csa_schedule or 'N/A' }}
    Average rating + {% if d.review_count and d.review_count > 0 %} + {% set r = d.avg_rating or 0 %} + {% if r >= 7 %} + {{ '%.1f'|format(r) }}/10 + {% elif r >= 5 %} + {{ '%.1f'|format(r) }}/10 + {% else %} + {{ '%.1f'|format(r) }}/10 + {% endif %} + {{ d.review_count }} reviews + {% else %} + N/A + no reviews + {% endif %} +
    +
    + @@ -181,6 +245,39 @@

    {{ drug.generic_name|capitalize }}

    + +{% if selected_drugs|length >= 2 %} +{% set best_rated = selected_drugs|sort(attribute='1.avg_rating', reverse=true)|first %} +
    +

    Bottom line

    +

    + You are comparing + {% for _, d, _f in selected_drugs %}{{ d.generic_name|capitalize }}{% if not loop.last %}{% if loop.revindex == 2 %} and {% else %}, {% endif %}{% endif %}{% endfor %}. + {% set classes = selected_drugs|map(attribute='1')|map(attribute='drug_class')|select|map(attribute='name')|list|unique|list %} + {% if classes|length == 1 %} + All belong to the {{ classes[0] }} drug class, so they share a similar mechanism of action and overlapping side-effect profiles. + {% else %} + They span different drug classes ({{ classes|join(', ') }}), so their mechanisms, indications, and side-effect profiles differ meaningfully. + {% endif %} +

    + {% set rx = selected_drugs|map(attribute='1')|selectattr('availability','equalto','Rx')|list %} + {% set otc = selected_drugs|map(attribute='1')|selectattr('availability','equalto','OTC')|list %} + {% if rx and otc %} +

    The comparison mixes prescription-only and over-the-counter options — the OTC choice is easier to access, but the Rx option is typically reserved for cases where stronger or more targeted therapy is warranted.

    + {% endif %} + {% if best_rated[1].review_count and best_rated[1].review_count > 0 %} +

    + Highest user rating + {{ best_rated[1].generic_name|capitalize }} + leads on user reviews at {{ '%.1f'|format(best_rated[1].avg_rating or 0) }}/10 + across {{ best_rated[1].review_count }} ratings. User ratings reflect patient experience and should not replace medical judgment. +

    + {% endif %} +

    + This summary is generated from the comparison data above. Always consult a healthcare professional before starting, stopping, or switching medications. +

    +
    +{% endif %} {% endif %}
    diff --git a/sites/drugs_com/templates/drug_detail.html b/sites/drugs_com/templates/drug_detail.html index 4ec8162..a5241dc 100644 --- a/sites/drugs_com/templates/drug_detail.html +++ b/sites/drugs_com/templates/drug_detail.html @@ -20,7 +20,11 @@
    - {% if drug.images and drug.images|length > 0 %} + {% if drug.slug|pill_image_exists %} + {{ drug.generic_name|capitalize }} pill + {% elif drug.images and drug.images|length > 0 %} {% set img0 = drug.images[0] %} {% set color_map = { 'white': '#f8f8f8', 'yellow': '#fff5b8', 'orange': '#ffd9a8', diff --git a/sites/drugs_com/templates/drug_images.html b/sites/drugs_com/templates/drug_images.html index 598bb4a..36496f7 100644 --- a/sites/drugs_com/templates/drug_images.html +++ b/sites/drugs_com/templates/drug_images.html @@ -1,17 +1,7 @@ {% extends "base.html" %} +{% from "_pill_svg.html" import render_pill %} {% block title %}{{ drug.generic_name|capitalize }} Pill Images | {{ site_name }}{% endblock %} {% block content %} -{# Two-tone pairing for capsules: maps a primary color to a complementary second half. #} -{% set capsule_pair = { - 'white': '#d0d4da', 'yellow': '#ffa94d', 'orange': '#ffe066', 'pink': '#ffffff', - 'red': '#ffe066', 'brown': '#f0d9b5', 'gray': '#495057', 'green': '#ffffff', - 'blue': '#ffffff', 'purple': '#faa2c1', 'black': '#adb5bd', 'turquoise': '#ffffff' -} %} -{% set color_map = { - 'white': '#ffffff', 'yellow': '#ffe066', 'orange': '#ffa94d', 'pink': '#faa2c1', - 'red': '#ff6b6b', 'brown': '#a47148', 'gray': '#adb5bd', 'green': '#69db7c', - 'blue': '#74c0fc', 'purple': '#b197fc', 'black': '#495057', 'turquoise': '#66d9e8' -} %}
    + + {% endblock %} diff --git a/sites/drugs_com/templates/news_article.html b/sites/drugs_com/templates/news_article.html index 0f01dab..ae5e5ab 100644 --- a/sites/drugs_com/templates/news_article.html +++ b/sites/drugs_com/templates/news_article.html @@ -46,6 +46,11 @@

    + By + {% if article.source %} — {% endif %} + +
    @@ -108,6 +113,15 @@

    +

    Was this article helpful?

    +
    + + +
    +

    Your feedback helps us improve our content.

    +
    + diff --git a/sites/drugs_com/templates/pill_identifier.html b/sites/drugs_com/templates/pill_identifier.html index b34cc76..bbf8feb 100644 --- a/sites/drugs_com/templates/pill_identifier.html +++ b/sites/drugs_com/templates/pill_identifier.html @@ -1,4 +1,5 @@ {% extends "base.html" %} +{% from "_pill_svg.html" import render_pill %} {% block title %}Pill Identifier - Quickly Find and ID your Drugs (with pictures) - {{ site_name }}{% endblock %} {% block content %}

    - +
    DrugCommon side effects
    + {% for d in popular %} - - + + {% endfor %} + +
    {% endblock %} diff --git a/sites/drugs_com/templates/warnings_index.html b/sites/drugs_com/templates/warnings_index.html index 97b96f3..5d87427 100644 --- a/sites/drugs_com/templates/warnings_index.html +++ b/sites/drugs_com/templates/warnings_index.html @@ -1,31 +1,76 @@ {% extends "base.html" %} -{% block title %}Drug Warnings & Black Box Warnings | {{ site_name }}{% endblock %} +{% block title %}Drug Warnings, Recalls & FDA Safety Alerts | {{ site_name }}{% endblock %} {% block content %} -
    - -

    Drug Warnings & Black Box Warnings

    +
    + -
    -

    What is a black box warning?

    +

    Drug Warnings, Recalls & FDA Safety Alerts

    +

    Stay informed about prescription drug safety, FDA-issued black box warnings, recent recalls, and ongoing safety communications.

    + +
    +

    What is a black box warning?

    A boxed warning (also called a black box warning) is the strongest warning the U.S. Food and Drug Administration (FDA) requires for prescription drugs that carry a significant risk of serious or life-threatening adverse effects. The warning appears in a black-bordered box at the top of the package insert and is intended to alert prescribers and patients to these risks.

    -

    Drugs with significant warnings

    - - - - - - - - - {% for d in drugs %} + + + {% if category in ('all', 'recalls', 'safety') and fda_alerts %} +
    +

    Recent FDA Alerts & Recalls

    +
      + {% for a in fda_alerts %} +
    • + {{ a.category }} + {{ a.title }} + {{ a.published_at.strftime('%b %d, %Y') }} + {% if a.body %}

      {{ a.body[:220] }}{% if a.body|length > 220 %}…{% endif %}

      {% endif %} +
    • + {% endfor %} +
    +
    + {% endif %} + + {% if category in ('all', 'blackbox') %} +
    +

    Drugs with significant warnings

    +

    Tap any drug to read the full warnings section, including contraindications and adverse reactions.

    +
    DrugWarning summary
    + - - + + - {% endfor %} - -
    {{ d.generic_name }}{{ (d.warnings or '')[:280] }}{% if d.warnings and d.warnings|length > 280 %}…{% endif %}DrugWarning summary
    + + + {% for d in drugs %} + + {{ d.generic_name }} + {{ (d.warnings or '')[:280] }}{% if d.warnings and d.warnings|length > 280 %}…{% endif %} + + {% else %} + No drug warnings found. + {% endfor %} + + +
    + {% endif %} + +
    +

    Related safety resources

    + +
    {% endblock %} From 01c8581bf2d2d3d83729203d05e5b11bd1a16eef Mon Sep 17 00:00:00 2001 From: Boyu Gou Date: Wed, 13 May 2026 15:13:26 -0700 Subject: [PATCH 039/167] drugs_com: symptom checker results, search tabs, drug detail callouts, about page, account welcome - Symptom checker: condition cards grid with hit count, descriptions, View treatments links - Search: category anchor tabs (Drugs | Conditions | News), popular searches fallback - Drug detail: boxed-warning callout (red), serious side effects callout (yellow/orange), severity-coded drug interaction cards (major/moderate/minor/unknown) - About page: /about route + template with mission, stats, editorial policy, data sources, 5-person medical review team; footer About link wired up - Account page: welcome banner with username/email greeting Co-Authored-By: Claude Sonnet 4.6 --- sites/drugs_com/app.py | 6 + sites/drugs_com/static/css/main.css | 107 ++++++++++++++++++ sites/drugs_com/templates/about.html | 88 ++++++++++++++ sites/drugs_com/templates/account.html | 1 + sites/drugs_com/templates/base.html | 2 +- sites/drugs_com/templates/drug_detail.html | 55 ++++++++- sites/drugs_com/templates/search.html | 24 +++- .../drugs_com/templates/symptom_checker.html | 24 ++-- 8 files changed, 294 insertions(+), 13 deletions(-) create mode 100644 sites/drugs_com/templates/about.html diff --git a/sites/drugs_com/app.py b/sites/drugs_com/app.py index 2aa3b0c..88b0ebe 100644 --- a/sites/drugs_com/app.py +++ b/sites/drugs_com/app.py @@ -3050,6 +3050,12 @@ def contact(): return render_template("contact.html") +@app.route("/about") +@app.route("/about.html") +def about_page(): + return render_template("about.html") + + @app.route("/support") @app.route("/help") def help_page(): diff --git a/sites/drugs_com/static/css/main.css b/sites/drugs_com/static/css/main.css index 4302ee9..09fb8b9 100644 --- a/sites/drugs_com/static/css/main.css +++ b/sites/drugs_com/static/css/main.css @@ -2327,3 +2327,110 @@ details.faq-detail p { padding: 0 0 14px; font-size: 0.92rem; color: var(--gray- align-items: center; justify-content: center; } + +/* Symptom checker result cards */ +.symptom-results-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 14px; + margin: 14px 0 18px; +} +.symptom-result-card { + background: #fff; + border: 1px solid var(--gray-200); + border-left: 4px solid var(--blue); + border-radius: 6px; + padding: 14px 16px; + display: flex; + flex-direction: column; + gap: 8px; + transition: border-color 0.15s ease, box-shadow 0.15s ease; +} +.symptom-result-card:hover { + border-color: var(--blue); + box-shadow: 0 2px 6px rgba(26, 58, 110, 0.08); +} +.symptom-result-header { + display: flex; + justify-content: space-between; + align-items: baseline; + gap: 8px; +} +.symptom-result-name { margin: 0; font-size: 1.05rem; } +.symptom-result-name a { color: var(--blue); } +.symptom-result-hits { + font-size: 0.78rem; + color: var(--gray-700); + background: #eef2f8; + padding: 2px 8px; + border-radius: 10px; + white-space: nowrap; +} +.symptom-result-desc { + margin: 0; + font-size: 0.9rem; + color: var(--gray-700); + line-height: 1.45; +} +.symptom-result-footer { margin: 0; font-size: 0.9rem; } +.symptom-disclaimer-note { + margin-top: 12px; + padding: 10px 14px; + background: #f7f9fc; + border-left: 3px solid var(--gray-300); + border-radius: 4px; + font-size: 0.85rem; + color: var(--gray-700); + font-style: italic; +} + +/* Search results category tabs */ +.search-category-tabs { + display: flex; + flex-wrap: wrap; + gap: 4px; + margin: 14px 0 18px; + border-bottom: 1px solid var(--gray-200); +} +.search-category-tabs a { + padding: 8px 16px; + font-size: 0.9rem; + color: var(--gray-700); + border: 1px solid transparent; + border-bottom: none; + border-radius: 4px 4px 0 0; + margin-bottom: -1px; +} +.search-category-tabs a:hover { + color: var(--blue); + background: #f7f9fc; + text-decoration: none; +} +.search-category-tabs a .count { + color: var(--gray-700); + font-size: 0.8rem; + margin-left: 4px; +} +.search-popular-fallback { margin-top: 18px; } +.search-popular-fallback h3 { font-size: 1rem; margin: 0 0 10px; } + +/* Account welcome banner */ +.account-welcome { + background: #f0f6ff; + border-left: 3px solid #1c5dbb; + padding: 10px 14px; + margin: 10px 0 18px; + border-radius: 4px; + color: #243044; +} + +/* About page */ +.about-page .about-section { margin: 22px 0; } +.about-page .about-section h2 { + font-size: 1.25rem; + margin: 18px 0 8px; + color: #1c5dbb; +} +.about-page .about-section p { line-height: 1.55; color: #333; margin: 6px 0; } +.about-page .about-section ul { margin: 6px 0 10px 22px; line-height: 1.6; } +.about-page .about-team li { line-height: 1.5; } diff --git a/sites/drugs_com/templates/about.html b/sites/drugs_com/templates/about.html new file mode 100644 index 0000000..fe46baa --- /dev/null +++ b/sites/drugs_com/templates/about.html @@ -0,0 +1,88 @@ +{% extends "base.html" %} +{% block title %}About Drugs.com | {{ site_name }}{% endblock %} +{% block content %} +
    + + +

    About Drugs.com

    +

    + Drugs.com is the most popular, comprehensive and up-to-date source of drug information online. +

    + +
    +

    Our Mission

    +

    Drugs.com provides free, peer-reviewed, accurate and independent data on more than 24,000 prescription drugs, over-the-counter medicines, and natural products. Our mission is to empower consumers and healthcare professionals with trusted, easy-to-understand drug information so they can make safe and informed decisions about medication use.

    +
    + +
    +
    +
    24,000+
    +
    Drugs covered
    +
    +
    +
    65+ million
    +
    Monthly visitors
    +
    +
    +
    25+ years
    +
    Trusted service
    +
    +
    + +
    +

    Our Editorial Policy

    +

    Every drug monograph on Drugs.com is reviewed by a member of our medical advisory board before publication and re-reviewed at regular intervals to incorporate new FDA labeling, post-market safety data, and current clinical guidelines. Content is written in plain language for consumers, with a parallel clinician-oriented version available in our Professional Edition. Reviewer names, credentials, and the date of the most recent review are displayed on each drug page so readers can judge the currency of the information they are reading.

    +

    Drug reviews submitted by users are moderated for spam, personally identifying information, and obvious safety misinformation, but represent the personal experience of individual contributors rather than medical advice.

    +
    + +
    +

    Our Data Sources

    +

    Our drug database aggregates information from authoritative sources, including:

    +
      +
    • Cerner Multum — consumer drug information and interaction data
    • +
    • AHFS DI (American Society of Health-System Pharmacists) — clinician-grade monographs
    • +
    • IBM Micromedex — advanced drug, disease, and toxicology references
    • +
    • U.S. National Library of Medicine (NLM) — DailyMed FDA labeling and RxNorm vocabularies
    • +
    • U.S. Food and Drug Administration (FDA) — approval history, safety alerts, and recalls
    • +
    +
    + +
    +

    Medical Review Team

    +
      +
    • + Leigh Ann Anderson, PharmD · Senior Editor, Consumer Drug Information
      + University of Minnesota College of Pharmacy. 20+ years in clinical and ambulatory-care pharmacy. +
    • +
    • + Carmen Pope, BPharm · Drug Information Specialist
      + University of Otago School of Pharmacy. Focus on consumer health literacy and medication safety. +
    • +
    • + Kristianne Hannemann, PharmD · Clinical Pharmacy Editor
      + University of California, San Francisco. Board-certified ambulatory care pharmacist. +
    • +
    • + Michael Stewart, MD, FACP · Medical Advisor
      + Board-certified internist; reviews interaction and contraindication content for clinical accuracy. +
    • +
    • + Judith Stewart, BPharm · Pregnancy & Lactation Editor
      + University of Queensland. Specializes in maternal-fetal medication safety classifications. +
    • +
    +
    + +
    +

    Contact & Policies

    +

    + Contact us · + Privacy Policy · + Terms of Use · + Help +

    +
    +
    +{% endblock %} diff --git a/sites/drugs_com/templates/account.html b/sites/drugs_com/templates/account.html index 49274a5..0e0bb33 100644 --- a/sites/drugs_com/templates/account.html +++ b/sites/drugs_com/templates/account.html @@ -6,6 +6,7 @@

    My Drugs.com — {{ current_user.username }}

    +
    +
    +

    {{ drug.generic_name|capitalize }} Dosage

    @@ -94,6 +96,18 @@

    Geriatric use

    Older adults may be more sensitive to the effects of this medication. Start at the lowest effective dose.

    +
    +

    How to take {{ drug.generic_name|capitalize }}

    +
      +
    • Take exactly as prescribed by your healthcare provider. Follow all directions on the product label.
    • +
    • For oral tablets and capsules, swallow whole with a full glass of water unless otherwise instructed. Do not crush, chew, or split extended-release forms.
    • +
    • Take with food if gastrointestinal upset occurs, unless the product specifies an empty stomach.
    • +
    • Try to take each dose at the same time each day to maintain steady drug levels.
    • +
    • Do not stop {{ drug.generic_name|capitalize }} abruptly without first speaking to your prescriber, even if you feel better.
    • +
    • Keep all scheduled follow-up appointments so your provider can monitor your response.
    • +
    +
    +

    What happens if I miss a dose?

    Take the missed dose as soon as you remember. Skip the missed dose if it is almost time for your next scheduled dose. Do not take extra medicine to make up the missed dose.

    @@ -117,4 +131,19 @@

    Storage

    « Back to {{ drug.generic_name|capitalize }} overview

    +
    + +
    {% endblock %} diff --git a/sites/drugs_com/templates/drug_prices.html b/sites/drugs_com/templates/drug_prices.html index b910886..37d5647 100644 --- a/sites/drugs_com/templates/drug_prices.html +++ b/sites/drugs_com/templates/drug_prices.html @@ -176,6 +176,47 @@

    Available dosage forms and stre

    Dosing reference: {{ (drug.dosage)[:240] }}{% if drug.dosage|length > 240 %}...{% endif %}

    {% endif %} + +

    {{ drug.generic_name|title }} price history (last 12 months)

    +

    + Estimated 30-day supply cash price trend at U.S. retail pharmacies. Values are deterministic estimates for benchmark purposes. +

    + {% set base = price_data.base_retail %} + {% set months = ['Jun','Jul','Aug','Sep','Oct','Nov','Dec','Jan','Feb','Mar','Apr','May'] %} + {% set deltas = [-6,-4,-2,1,3,2,5,4,2,-1,-3,0] %} +
    + + + + + + + + + + + {% for m in months %} + {% set d = deltas[loop.index0] %} + {% set p = base * (1 + d / 100.0) %} + {% set barw = (40 + d * 2) %} + + + + + + + {% endfor %} + +
    MonthAvg 30-day priceTrendChange
    {{ m }}${{ '%.2f' % p }} + + + {% if d > 0 %}+{% endif %}{{ d }}% +
    +
    +

    + Prices may vary by location. Use a coupon to save on every refill. +

    +

    Patient assistance & copay programs

    {% if current_user.is_authenticated %} - {% else %} - Sign in to save + + + Sign in to save + {% endif %} + + {% if recently_viewed %} From ad15fc8862932fb16f097adc98972f9765d3487e Mon Sep 17 00:00:00 2001 From: Boyu Gou Date: Thu, 14 May 2026 03:11:10 -0700 Subject: [PATCH 121/167] Improve UI fidelity: 3-col homepage, condition-grouped compare, simpler drug A-Z list - Homepage: add 3-column layout (left nav sidebar | news feed | right approvals/classes) matching real drugs.com structure; left sidebar has Find a Drug, Tools, Conditions sections - Compare drugs: replace flat card list with condition-grouped popular comparisons (Type 2 Diabetes, Weight Loss, Depression, Blood Pressure, Pain, Cholesterol) - Drug A-Z: simplify drug listing from card layout to clean name+brand link list Co-Authored-By: Claude Sonnet 4.6 --- sites/drugs_com/static/css/main.css | 50 +++++++++ sites/drugs_com/templates/compare_drugs.html | 61 ++++++---- sites/drugs_com/templates/drug_az.html | 23 +--- sites/drugs_com/templates/index.html | 110 ++++++++++++++++--- 4 files changed, 186 insertions(+), 58 deletions(-) diff --git a/sites/drugs_com/static/css/main.css b/sites/drugs_com/static/css/main.css index eb89bab..082ace6 100644 --- a/sites/drugs_com/static/css/main.css +++ b/sites/drugs_com/static/css/main.css @@ -7956,3 +7956,53 @@ html { scroll-behavior: smooth; } } .btn-sm { font-size: 0.85rem; padding: 6px 12px; } + +/* Drug A-Z simple name list */ +.az-name-list { list-style: disc; padding-left: 1.5rem; column-count: 2; column-gap: 2rem; } +.az-name-list li { padding: 3px 0; font-size: 0.95rem; break-inside: avoid; } +@media (max-width: 600px) { .az-name-list { column-count: 1; } } + +/* Compare drugs popular comparisons grid */ +.compare-popular-section { margin-top: 2.5rem; } +.compare-popular-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.5rem; margin-top: 1rem; } +@media (max-width: 900px) { .compare-popular-grid { grid-template-columns: repeat(2, 1fr); } } +@media (max-width: 600px) { .compare-popular-grid { grid-template-columns: 1fr; } } +.compare-popular-group-title { font-size: 0.95rem; font-weight: 700; color: #333; margin: 0 0 0.5rem; } +.compare-popular-list { list-style: none; padding: 0; margin: 0; } +.compare-popular-list li { padding: 3px 0; font-size: 0.88rem; } +.compare-popular-list a { color: var(--blue); text-decoration: none; } +.compare-popular-list a:hover { text-decoration: underline; } + +/* Homepage 3-column layout */ +.home-3col { display: grid; grid-template-columns: 180px 1fr 200px; gap: 1.5rem; margin-top: 1.5rem; align-items: start; } +@media (max-width: 1100px) { .home-3col { grid-template-columns: 160px 1fr; } .home-right-sidebar { display: none; } } +@media (max-width: 750px) { .home-3col { grid-template-columns: 1fr; } .home-left-nav { display: none; } } + +.home-left-nav { font-size: 0.88rem; } +.home-left-nav-section { margin-bottom: 1rem; } +.home-left-nav-head { font-size: 0.78rem; font-weight: 700; color: #555; text-transform: uppercase; letter-spacing: 0.04em; margin: 0 0 4px; } +.home-left-nav ul { list-style: none; padding: 0; margin: 0; } +.home-left-nav li { padding: 2px 0; } +.home-left-nav a { color: var(--blue); text-decoration: none; font-size: 0.88rem; } +.home-left-nav a:hover { text-decoration: underline; } + +.home-news-feed .news-list--dense li { padding: 10px 0; border-bottom: 1px solid #eee; } +.home-news-feed .news-list--dense li:last-child { border-bottom: none; } +.news-badge-sm { font-size: 0.7rem; padding: 1px 7px; } + +.home-right-sidebar { font-size: 0.88rem; } +.home-right-box { background: #f8f9fa; border: 1px solid #e8eaf0; border-radius: 6px; padding: 12px; margin-bottom: 1rem; } +.home-right-box-head { font-size: 0.85rem; font-weight: 700; color: #1a3a6e; margin: 0 0 8px; padding-bottom: 6px; border-bottom: 2px solid var(--blue); } +.home-approvals-list { list-style: none; padding: 0; margin: 0 0 6px; } +.home-approvals-list li { padding: 6px 0; border-bottom: 1px solid #e8eaf0; font-size: 0.83rem; } +.home-approvals-list li:last-child { border-bottom: none; } +.home-approvals-list a { color: var(--blue); text-decoration: none; font-weight: 500; } +.home-approvals-list a:hover { text-decoration: underline; } +.approval-date-sm { font-size: 0.75rem; color: #888; } +.home-right-more { font-size: 0.8rem; color: var(--blue); text-decoration: none; } +.home-right-more:hover { text-decoration: underline; } +.home-class-list { list-style: none; padding: 0; margin: 0; } +.home-class-list li { padding: 3px 0; font-size: 0.83rem; } +.home-class-list a { color: var(--blue); text-decoration: none; } +.home-class-list a:hover { text-decoration: underline; } +.home-newsletter-box .newsletter-email-input { font-size: 0.82rem; padding: 5px 8px; } diff --git a/sites/drugs_com/templates/compare_drugs.html b/sites/drugs_com/templates/compare_drugs.html index 5506bf0..1c93282 100644 --- a/sites/drugs_com/templates/compare_drugs.html +++ b/sites/drugs_com/templates/compare_drugs.html @@ -278,29 +278,44 @@

    Bottom line

    {% endif %} {% endif %} -
    -

    Popular Comparisons

    -
    - - Ibuprofen vs. Naproxen -

    NSAIDs for pain and inflammation

    -
    - - Atorvastatin vs. Simvastatin -

    Statins for high cholesterol

    -
    - - Metformin vs. Glipizide -

    Oral medications for type 2 diabetes

    -
    - - Sertraline vs. Escitalopram -

    SSRIs for depression and anxiety

    -
    - - Lisinopril vs. Losartan -

    Blood pressure medications (ACE vs. ARB)

    -
    + {% endblock %} diff --git a/sites/drugs_com/templates/drug_az.html b/sites/drugs_com/templates/drug_az.html index 4b60671..c8b47b7 100644 --- a/sites/drugs_com/templates/drug_az.html +++ b/sites/drugs_com/templates/drug_az.html @@ -32,26 +32,11 @@

    Browse A-Z

    Drugs starting with “{{ active_letter }}”

    {% if drugs %} -
      +
        {% for d in drugs %} -
      • -
        - {{ d.generic_name|capitalize }} - {% if d.drug_class %} - {{ d.drug_class.name }} - {% endif %} - {{ d.availability }} - {% if d.review_count and d.avg_rating > 0 %} - ★ {{ '%.1f'|format(d.avg_rating) }}/10 ({{ d.review_count }} reviews) - {% endif %} -
        - {% if d.brand_names %} -
        Brand names: {{ d.brand_names|join(', ') }}
        - {% endif %} - {% set cd = (d.uses or d.description or '')|clean_desc %} - {% if cd %} -

        {{ cd[:100] }}{% if cd|length > 100 %}…{% endif %}

        - {% endif %} +
      • + {{ d.generic_name|capitalize }} + {% if d.brand_names %} ({{ d.brand_names|join(', ') }}){% endif %}
      • {% endfor %}
      diff --git a/sites/drugs_com/templates/index.html b/sites/drugs_com/templates/index.html index 5a5a27c..2f41d3b 100644 --- a/sites/drugs_com/templates/index.html +++ b/sites/drugs_com/templates/index.html @@ -458,32 +458,110 @@
    - -
    -
    + +
    + + + + +

    Latest Medical News

    -
      +
        {% for a in news %}
      • - {{ a.category }} + {{ a.category }} {{ a.published_at.strftime('%B %d, %Y') }}

        {{ a.title }}

        -

        {{ a.body[:140] }}{% if a.body|length > 140 %}...{% endif %}

        +

        {{ a.body[:160] }}{% if a.body|length > 160 %}...{% endif %}

      • {% endfor %}

      Read more medical news ›

    -
    -

    Browse Drug Classes

    - -
    + + +
    @@ -491,7 +569,7 @@

    Browse Drug Classes

    Popular Drug Searches

    Most-searched medications on Drugs.com.

    {% if new_approvals %} -
    +
    - Help & Support
    diff --git a/sites/drugs_com/templates/drug_detail.html b/sites/drugs_com/templates/drug_detail.html index 3d836c7..c5eea9f 100644 --- a/sites/drugs_com/templates/drug_detail.html +++ b/sites/drugs_com/templates/drug_detail.html @@ -120,82 +120,14 @@

    {{ drug.generic_name|capitalize }} - - Drug Images - Pricing + -
    -
    - Availability - - {% if 'Rx' in drug.availability and 'OTC' in drug.availability %} - Rx/OTC - {% elif 'Rx' in drug.availability %} - Rx - {% elif 'OTC' in drug.availability %} - OTC - {% else %} - {{ drug.availability }} - {% endif %} - -
    -
    - Pregnancy - - {% set _cat = drug.pregnancy_risk|preg_category %} - {% if _cat %} - Category {{ _cat }} - {% elif drug.pregnancy_risk %} - {{ drug.pregnancy_risk }} - {% else %} - Consult Dr - {% endif %} - -
    -
    - CSA Schedule - - {% set _csa = (drug.csa_schedule or '')|lower %} - {% if drug.csa_schedule and _csa not in ['n/a', 'none', 'not scheduled', ''] and 'not a controlled' not in _csa and 'not controlled' not in _csa %} - {{ drug.csa_schedule }} - {% else %} - Not controlled - {% endif %} - -
    -
    - Drug class - - {% if drug.drug_class %} - {{ drug.drug_class.name }} - {% else %}N/A{% endif %} - -
    -
    - Rating - - {% if drug.avg_rating %} - {{ "%.1f"|format(drug.avg_rating) }} / 10 - ({{ drug.review_count }}) - {% else %} - Not yet rated - {% endif %} - -
    -
    - - -

    {% if faq_items %}
    -

    Frequently asked questions

    -

    Common questions about {{ drug.generic_name }}. Click a question to view the answer.

    +

    Popular FAQ

    - {% for item in faq_items %} -
    - Q: {{ item.q }} -
    A: {{ item.a }}
    -
    + {% for item in faq_items[:5] %} +
    +

    {{ item.q }}

    +

    {{ item.a }}

    +
    {% endfor %}
    + {% if faq_items|length > 5 %} +

    More FAQ

    +
      + {% for item in faq_items[5:] %} +
    • {{ item.q }}
    • + {% endfor %} +
    + {% endif %} +

    FAQs authored by the {{ site_name }} editorial team. Last reviewed {{ 'Nov 5, 2025' }}.

    {% endif %} diff --git a/sites/drugs_com/templates/interaction_checker.html b/sites/drugs_com/templates/interaction_checker.html index 20da9a1..70468f5 100644 --- a/sites/drugs_com/templates/interaction_checker.html +++ b/sites/drugs_com/templates/interaction_checker.html @@ -48,43 +48,6 @@

    Drug Interaction Checker

    -{% if not drugs_input %} -
    -
    -

    How it works

    -
      -
    • Enter 2 or more drugs to check for interactions.
    • -
    • Searches both generic and brand names.
    • -
    • Severity levels: - Major avoid combination; - Moderate use with caution and monitoring; - Minor limited clinical effect. -
    • -
    -
    - -
    -

    Common interactions to check

    -

    Click a pair to add both drugs to the checker.

    -
    - {% set common_pairs = [ - ['Warfarin', 'Aspirin'], - ['Metformin', 'Alcohol'], - ['Lisinopril', 'Potassium supplements'], - ['Simvastatin', 'Amiodarone'], - ['Fluoxetine', 'Tramadol'], - ['Ibuprofen', 'Prednisone'] - ] %} - {% for pair in common_pairs %} - - {% endfor %} -
    -
    -
    -{% endif %}

    Not all drugs interact, and not every interaction means you must stop taking one of your From 894acc258977afcd8db3068c57fad9451d8847e5 Mon Sep 17 00:00:00 2001 From: Boyu Gou Date: Thu, 14 May 2026 03:32:33 -0700 Subject: [PATCH 125/167] drugs_com: simplify reviews block, remove duplicate med-list CTA Collapse reviews on drug_detail to: avg score + 3 sample reviews + link to full reviews page. Move histogram, condition filter, sort dropdown, helpful voting, and inline review form to drug_reviews_page. Remove duplicate 'Add to My Med List' CTA button from the header; the sidebar status-box already has this control. Co-Authored-By: Claude Sonnet 4.6 --- .claude/ralph-loop.local.md | 2 +- sites/drugs_com/templates/drug_detail.html | 217 ++------------------- 2 files changed, 14 insertions(+), 205 deletions(-) diff --git a/.claude/ralph-loop.local.md b/.claude/ralph-loop.local.md index d3264fd..ce78db7 100644 --- a/.claude/ralph-loop.local.md +++ b/.claude/ralph-loop.local.md @@ -1,6 +1,6 @@ --- active: true -iteration: 10 +iteration: 14 session_id: 24122f46-baf6-4f83-9ddd-e87c1b71bc32 max_iterations: 0 completion_promise: null diff --git a/sites/drugs_com/templates/drug_detail.html b/sites/drugs_com/templates/drug_detail.html index c5eea9f..643f724 100644 --- a/sites/drugs_com/templates/drug_detail.html +++ b/sites/drugs_com/templates/drug_detail.html @@ -98,15 +98,6 @@

    {{ drug.generic_name|capitalize }} - {% else %} - + Sign in to Add to My Med List - {% endif %} -
    From b39af9a105a6fb50505e19e557eca3588ff15f22 Mon Sep 17 00:00:00 2001 From: Boyu Gou Date: Thu, 14 May 2026 03:37:13 -0700 Subject: [PATCH 126/167] drugs_com: fix Jinja2 slice-after-filter syntax error in drug_detail Jinja2 does not allow [:12] directly after a filter chain. Wrap the filtered result in parentheses so the slice operates on the list. Co-Authored-By: Claude Sonnet 4.6 --- sites/drugs_com/templates/drug_detail.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sites/drugs_com/templates/drug_detail.html b/sites/drugs_com/templates/drug_detail.html index 643f724..e0c759e 100644 --- a/sites/drugs_com/templates/drug_detail.html +++ b/sites/drugs_com/templates/drug_detail.html @@ -235,7 +235,7 @@

    What other drugs will affect {{ drug.generic_name }}?

    {% if drug_interactions is defined and drug_interactions %}

    {{ drug.generic_name|capitalize }} has {{ drug_interactions|length }} known drug interaction{{ 's' if drug_interactions|length != 1 else '' }}. Ask your doctor or pharmacist before using {{ drug.generic_name }} with any other medications, especially:

    -
    -

    Popular drug searches

    - -
    -

    Consumer drug sources

    diff --git a/sites/drugs_com/templates/drug_detail.html b/sites/drugs_com/templates/drug_detail.html index c1cdd1f..6df8c07 100644 --- a/sites/drugs_com/templates/drug_detail.html +++ b/sites/drugs_com/templates/drug_detail.html @@ -585,7 +585,7 @@

    Further information

    {% if selected_drugs|length >= 2 %}

    diff --git a/sites/drugs_com/templates/my_med_list.html b/sites/drugs_com/templates/my_med_list.html index 85fa654..f10c294 100644 --- a/sites/drugs_com/templates/my_med_list.html +++ b/sites/drugs_com/templates/my_med_list.html @@ -13,8 +13,13 @@

    My Med List

    {% if items %} - {% set rx_top = items|selectattr('drug.availability', 'equalto', 'Rx')|list|length %} - {% set otc_top = items|selectattr('drug.availability', 'equalto', 'OTC')|list|length %} + {% set ns_top = namespace(rx_top=0, otc_top=0) %} + {% for item in items %} + {% if 'Rx' in (item.drug.availability or '') %}{% set ns_top.rx_top = ns_top.rx_top + 1 %}{% endif %} + {% if 'OTC' in (item.drug.availability or '') %}{% set ns_top.otc_top = ns_top.otc_top + 1 %}{% endif %} + {% endfor %} + {% set rx_top = ns_top.rx_top %} + {% set otc_top = ns_top.otc_top %}
    {{ items|length }} @@ -44,8 +49,13 @@

    My Med List

    {% if items %} - {% set rx_count = items|selectattr('drug.availability', 'equalto', 'Rx')|list|length %} - {% set otc_count = items|selectattr('drug.availability', 'equalto', 'OTC')|list|length %} + {% set ns = namespace(rx_count=0, otc_count=0) %} + {% for item in items %} + {% if 'Rx' in (item.drug.availability or '') %}{% set ns.rx_count = ns.rx_count + 1 %}{% endif %} + {% if 'OTC' in (item.drug.availability or '') %}{% set ns.otc_count = ns.otc_count + 1 %}{% endif %} + {% endfor %} + {% set rx_count = ns.rx_count %} + {% set otc_count = ns.otc_count %} {% set classes_seen = [] %} {% for item in items %}{% if item.drug.drug_class and item.drug.drug_class.name not in classes_seen %}{% set _ = classes_seen.append(item.drug.drug_class.name) %}{% endif %}{% endfor %} From 48cd177328b0161ddc9f77b8c403ccf4500fbb3f Mon Sep 17 00:00:00 2001 From: Boyu Gou Date: Thu, 14 May 2026 07:54:55 -0700 Subject: [PATCH 159/167] fix: replace datalist with JS autocomplete on interaction checker input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same fix as compare_drugs: removes the browser's native ▼ arrow on the drug input and replaces it with a custom dropdown hitting /api/autocomplete. Selecting a suggestion immediately triggers addDrug() so the drug tag appears without an extra click. CSS added for the .checker-ac-drop and .checker-ac-item classes. Co-Authored-By: Claude Sonnet 4.6 --- sites/drugs_com/static/css/main.css | 6 ++- .../templates/interaction_checker.html | 47 ++++++++++++++----- 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/sites/drugs_com/static/css/main.css b/sites/drugs_com/static/css/main.css index 373bcc7..ef343c8 100644 --- a/sites/drugs_com/static/css/main.css +++ b/sites/drugs_com/static/css/main.css @@ -735,9 +735,13 @@ a:hover { text-decoration: underline; color: var(--blue-mid); } /* ── Interaction checker ── */ .checker-box { background: #fff; padding: 22px; border-radius: 6px; box-shadow: var(--shadow); border: 1px solid var(--border); } .checker-input { display: flex; gap: 10px; margin-bottom: 10px; } -.checker-input input { flex: 1; padding: 10px 14px; border: 1px solid var(--gray-300); border-radius: 4px; font-size: 0.95rem; } .checker-input .add-btn { padding: 10px 22px; background: var(--blue); color: #fff; border: none; border-radius: 4px; font-weight: 600; cursor: pointer; } .checker-input .add-btn:hover { background: var(--blue-mid); } +.checker-ac-wrap { position: relative; flex: 1; display: flex; gap: 10px; } +.checker-ac-wrap input { flex: 1; padding: 10px 14px; border: 1px solid var(--gray-300); border-radius: 4px; font-size: 0.95rem; } +.checker-ac-drop { position: absolute; top: 100%; left: 0; right: 0; z-index: 200; background: #fff; border: 1px solid #c8d5e3; border-top: none; border-radius: 0 0 4px 4px; box-shadow: 0 4px 12px rgba(0,0,0,.1); max-height: 220px; overflow-y: auto; } +.checker-ac-item { padding: 8px 12px; font-size: 0.9rem; cursor: pointer; color: var(--gray-900); } +.checker-ac-item:hover { background: var(--blue-light); color: var(--blue); } .checker-hint { color: var(--gray-500); font-size: 0.85rem; margin-bottom: 14px; } .checker-drug-list { list-style: none; margin-bottom: 14px; } .checker-drug-list li { padding: 8px 12px; background: var(--blue-light); border-radius: 4px; margin-bottom: 6px; display: flex; justify-content: space-between; align-items: center; font-size: 0.9rem; } diff --git a/sites/drugs_com/templates/interaction_checker.html b/sites/drugs_com/templates/interaction_checker.html index d7bcf5d..5bb509d 100644 --- a/sites/drugs_com/templates/interaction_checker.html +++ b/sites/drugs_com/templates/interaction_checker.html @@ -14,17 +14,10 @@

    Drug Interaction Checker

    Use our drug interaction checker to find potentially harmful drug, food, and alcohol interactions.

    -
    +
    - - - {% for d in drugs %} - - {% for brand in d.brand_names %} - - {% endfor %} - {% endfor %} - + +
    @@ -286,9 +279,41 @@

    Popular tools & resources

    updateState(); } + // Autocomplete + var acDrop = document.getElementById('checker-ac-drop'); + var acTimer = null; + function acHide(){ acDrop.hidden = true; acDrop.innerHTML = ''; } + input.addEventListener('input', function(){ + clearTimeout(acTimer); + var q = input.value.trim(); + if(q.length < 2){ acHide(); return; } + acTimer = setTimeout(function(){ + fetch('/api/autocomplete?q=' + encodeURIComponent(q)) + .then(function(r){ return r.json(); }) + .then(function(data){ + var drugs = (data||[]).filter(function(r){ return r.type !== 'condition'; }).slice(0,8); + if(!drugs.length){ acHide(); return; } + var html = ''; + drugs.forEach(function(r){ + html += '
    ' + r.label + '
    '; + }); + acDrop.innerHTML = html; acDrop.hidden = false; + acDrop.querySelectorAll('.checker-ac-item').forEach(function(el){ + el.addEventListener('mousedown', function(e){ + e.preventDefault(); + input.value = el.getAttribute('data-val'); + acHide(); + addBtn.click(); + }); + }); + }).catch(acHide); + }, 180); + }); + input.addEventListener('blur', function(){ setTimeout(acHide, 200); }); + addBtn.addEventListener('click', function(){ var v = input.value.trim(); - if(v){ addDrug(v); input.value = ''; input.focus(); } + if(v){ addDrug(v); input.value = ''; acHide(); input.focus(); } }); input.addEventListener('keydown', function(e){ From 9ee154b25dc36f60a6255cb780a3785c2f237a04 Mon Sep 17 00:00:00 2001 From: Boyu Gou Date: Thu, 14 May 2026 08:12:58 -0700 Subject: [PATCH 160/167] fix: deduplicate seed reviews and make seed_extra_reviews idempotent Each reviewer could only review each drug once but the gate checked total review count rather than per-pair existence, causing 4x duplicate reviews when the app restarted with count < 2400. Fix: pre-load existing pairs into a set, skip already-seeded (drug_id, user_id) combos, gate on reviewer-only count >= 700. Cleaned DB has 748 unique reviews (from 2520 with dupes). Co-Authored-By: Claude Sonnet 4.6 --- sites/drugs_com/app.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/sites/drugs_com/app.py b/sites/drugs_com/app.py index f8e77a8..f46e889 100644 --- a/sites/drugs_com/app.py +++ b/sites/drugs_com/app.py @@ -3493,9 +3493,7 @@ def seed_benchmark_users(): def seed_extra_reviews(): """Add reviews across all popular drugs from auto-generated reviewer users.""" - if DrugReview.query.count() >= 2400: - return - # Create some anonymous reviewer users if not present + # Create reviewer users first (needed to check existing pairs) reviewers = [] for i in range(8): email = f"reviewer{i}@example.com" @@ -3506,6 +3504,14 @@ def seed_extra_reviews(): db.session.add(u) db.session.flush() reviewers.append(u) + reviewer_ids = {u.id for u in reviewers} + if DrugReview.query.filter(DrugReview.user_id.in_(reviewer_ids)).count() >= 700: + return + # Pre-load existing (drug_id, user_id) pairs to avoid duplicates + existing_pairs = { + (r.drug_id, r.user_id) + for r in DrugReview.query.filter(DrugReview.user_id.in_(reviewer_ids)).all() + } popular = ["ibuprofen", "metformin", "lisinopril", "sertraline", "atorvastatin", "amoxicillin", "levothyroxine", "alprazolam", "gabapentin", "omeprazole", "semaglutide", "fluoxetine", "amlodipine", "tramadol", "zolpidem", @@ -3570,6 +3576,8 @@ def seed_extra_reviews(): # Offset by 5 so reviewer_N templates don't duplicate benchmark user reviews tmpl = REVIEW_TEMPLATES[(i + j + 5) % len(REVIEW_TEMPLATES)] u = reviewers[(i + j) % len(reviewers)] + if (d.id, u.id) in existing_pairs: + continue db.session.add(DrugReview( drug_id=d.id, user_id=u.id, rating=tmpl[1], title=tmpl[0], body=tmpl[2].format(cond=_humanize_cond(conds[j % len(conds)])), From 086fb7fd42d545285a51d25ae5e370aa20423ff4 Mon Sep 17 00:00:00 2001 From: Boyu Gou Date: Thu, 14 May 2026 08:47:57 -0700 Subject: [PATCH 161/167] fix: replace hardcoded pink SVG with render_pill macro in Drug Images section The Drug Images section on the home page was using a static SVG ellipse with hardcoded pink fill (#fce6ec) and stroke (#b8526a) for all pills, ignoring the actual shape and color stored in the DrugImage records. Replace with the existing render_pill Jinja macro and add .is-236-wrap CSS to size it correctly. Co-Authored-By: Claude Sonnet 4.6 --- sites/drugs_com/static/css/main.css | 13 +++++++++++++ sites/drugs_com/templates/index.html | 7 +++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/sites/drugs_com/static/css/main.css b/sites/drugs_com/static/css/main.css index ef343c8..2a58b11 100644 --- a/sites/drugs_com/static/css/main.css +++ b/sites/drugs_com/static/css/main.css @@ -5764,6 +5764,19 @@ html { scroll-behavior: smooth; } .is-236 { margin-bottom:8px; } +.is-236-wrap { + margin-bottom:8px; + display:flex; + align-items:center; + justify-content:center; + height:80px; +} +.is-236-wrap .pill-svg { + max-width:140px; + max-height:75px; + width:100%; + height:auto; +} .is-237 { font-weight:600; font-size:0.95rem; diff --git a/sites/drugs_com/templates/index.html b/sites/drugs_com/templates/index.html index 2f41d3b..51febd5 100644 --- a/sites/drugs_com/templates/index.html +++ b/sites/drugs_com/templates/index.html @@ -261,10 +261,9 @@

    Drug Images

    {% for d, img in image_samples %} - - - {{ img.imprint or '' }} - +
    + {{ render_pill('home-img-' ~ loop.index, img.shape or 'oval', img.color or 'white', img.imprint or '') }} +
    {{ d.generic_name|capitalize }}
    {% if img.shape or img.color %}
    From 5e59256e5d1296e392d8ad1c1628db84f04c5287 Mon Sep 17 00:00:00 2001 From: Boyu Gou Date: Thu, 14 May 2026 09:11:11 -0700 Subject: [PATCH 162/167] fix: correct canonical URLs for drug_az, pill_identifier, interaction_checker, news_index, search Flask uses the last @app.route decorator as the canonical URL for url_for(). Routes were ordered so that legacy/alias paths were last, causing all template links to generate wrong URLs (e.g., /dosage-guide instead of /drug_information.html, /drug-identifier.html instead of /pill-identifier). Reordered decorators so the canonical drugs.com URL is always last on each multi-route function. Also update CLAUDE.md with complete development notes covering seed DB state, benchmark user constraints, pill SVG rendering, route canonical URL rules, and HF upload instructions. Co-Authored-By: Claude Sonnet 4.6 --- sites/drugs_com/app.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/sites/drugs_com/app.py b/sites/drugs_com/app.py index f46e889..2285cf8 100644 --- a/sites/drugs_com/app.py +++ b/sites/drugs_com/app.py @@ -3875,13 +3875,13 @@ def index(): top_symptoms=top_symptoms) -@app.route("/drug_information.html") -@app.route("/drugs-a-to-z.html") -@app.route("/drug-az") -@app.route("/drugs-a-z") -@app.route("/dosage") -@app.route("/dosage-guide.html") @app.route("/dosage-guide") +@app.route("/dosage-guide.html") +@app.route("/dosage") +@app.route("/drugs-a-z") +@app.route("/drug-az") +@app.route("/drugs-a-to-z.html") +@app.route("/drug_information.html") def drug_az(): letter = (request.args.get("letter") or "A").upper() if letter not in string.ascii_uppercase: @@ -4327,8 +4327,8 @@ def review_helpful_vote(slug, review_id): return jsonify({"votes": review.helpful_count, "review_id": review.id}) -@app.route("/search") @app.route("/advanced-search") +@app.route("/search") def search(): q = (request.args.get("q") or "").strip() class_slug = request.args.get("class") or "" @@ -4533,11 +4533,11 @@ def autocomplete(): return jsonify(results[:8]) +@app.route("/interaction-checker", methods=["GET", "POST"]) +@app.route("/interaction-checker/", methods=["GET", "POST"]) @app.route("/drug_interactions.html", methods=["GET", "POST"]) -@app.route("/drug-interactions", methods=["GET", "POST"]) @app.route("/drug-interactions/", methods=["GET", "POST"]) -@app.route("/interaction-checker/", methods=["GET", "POST"]) -@app.route("/interaction-checker", methods=["GET", "POST"]) +@app.route("/drug-interactions", methods=["GET", "POST"]) def interaction_checker(): drugs = Drug.query.order_by(Drug.generic_name).all() drugs_input = None @@ -4863,11 +4863,11 @@ def api_interaction_check(): }) -@app.route("/pill-identifier") +@app.route("/drug-identifier.html") +@app.route("/drug-identifier") @app.route("/pill_identification.html") @app.route("/pill-identifier.html") -@app.route("/drug-identifier") -@app.route("/drug-identifier.html") +@app.route("/pill-identifier") def pill_identifier(): # When GET params are present, process as a search (same logic as pill_identifier_results) imprint = (request.args.get("imprint") or "").strip() @@ -5550,9 +5550,9 @@ def drug_class_page(slug): ) -@app.route("/news/") -@app.route("/mednews") @app.route("/mednews/") +@app.route("/mednews") +@app.route("/news/") def news_index(): cat = request.args.get("cat", "") q = request.args.get("q", "") From 8495b61bf33c147331de05bb75d84e63f0c0b093 Mon Sep 17 00:00:00 2001 From: Boyu Gou Date: Thu, 14 May 2026 09:23:39 -0700 Subject: [PATCH 163/167] fix: correct more canonical URLs and fix More... dropdown links MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - symptom_checker canonical: /symptom-checker (was /symptom_checker.html) - warnings_index canonical: /black-box-warnings (was /boxed-warnings) - news_category canonical: /news/ (was /news/category/) - More... dropdown: FDA Alerts → /fda-alerts, Drug Warnings → url_for('warnings_index'), Dosage Guide → drug_az page, Pregnancy Safety → search for pregnancy warnings Co-Authored-By: Claude Sonnet 4.6 --- sites/drugs_com/app.py | 8 ++++---- sites/drugs_com/templates/base.html | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/sites/drugs_com/app.py b/sites/drugs_com/app.py index 2285cf8..e9780cb 100644 --- a/sites/drugs_com/app.py +++ b/sites/drugs_com/app.py @@ -5599,8 +5599,8 @@ def news_article(article_id): return render_template("news_article.html", article=article, related=related) -@app.route("/news/") @app.route("/news/category/") +@app.route("/news/") @app.route("/new-drug-approvals", defaults={"category": "new-drug-approvals"}) @app.route("/fda-alerts", defaults={"category": "fda-alerts"}) @app.route("/clinical-trials", defaults={"category": "clinical-trials"}) @@ -5791,9 +5791,9 @@ def conditions_list(): } -@app.route("/symptom-checker", methods=["GET", "POST"]) -@app.route("/symptom-checker.html", methods=["GET", "POST"]) @app.route("/symptom_checker.html", methods=["GET", "POST"]) +@app.route("/symptom-checker.html", methods=["GET", "POST"]) +@app.route("/symptom-checker", methods=["GET", "POST"]) def symptom_checker(): """Symptom checker: pick symptoms grouped by body system, see possible conditions. @@ -6115,10 +6115,10 @@ def side_effects_page(): ) +@app.route("/boxed-warnings") @app.route("/warnings/") @app.route("/blackbox-warnings") @app.route("/black-box-warnings") -@app.route("/boxed-warnings") def warnings_index(): category = (request.args.get("category") or "all").lower() drugs_with_warnings = Drug.query.filter(Drug.warnings.isnot(None)).order_by(Drug.generic_name).limit(50).all() diff --git a/sites/drugs_com/templates/base.html b/sites/drugs_com/templates/base.html index 2a84b42..0287541 100644 --- a/sites/drugs_com/templates/base.html +++ b/sites/drugs_com/templates/base.html @@ -96,15 +96,15 @@ Conditions A-Z Drug Classes Drugs A-Z - FDA Alerts + FDA Alerts
    From 70692c400ef4009ed8e9fa23ff7bca9c097fe9ff Mon Sep 17 00:00:00 2001 From: Boyu Gou Date: Thu, 14 May 2026 09:34:11 -0700 Subject: [PATCH 164/167] fix: add price-guide URL alias and case-insensitive news category filter - Add //price-guide and //price-guide.html routes as aliases to the existing //prices handler (drugs.com uses /price-guide.html) - Fix news_index ?cat= filter to map slugs to canonical DB category strings (e.g. ?cat=medical now matches DB rows stored as "Medical") Co-Authored-By: Claude Sonnet 4.6 --- sites/drugs_com/app.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/sites/drugs_com/app.py b/sites/drugs_com/app.py index e9780cb..57620ee 100644 --- a/sites/drugs_com/app.py +++ b/sites/drugs_com/app.py @@ -5560,7 +5560,18 @@ def news_index(): per_page = 15 query = NewsArticle.query if cat: - query = query.filter_by(category=cat) + cat_map = { + "new-drug-approvals": "New Drug Approvals", + "new drug approvals": "New Drug Approvals", + "medical": "Medical", + "fda-alerts": "FDA Alerts", + "fda alerts": "FDA Alerts", + "clinical-trials": "Clinical Trials", + "clinical trials": "Clinical Trials", + "health": "Health", + } + db_cat = cat_map.get(cat.lower(), cat) + query = query.filter_by(category=db_cat) if q: query = query.filter(db.or_( NewsArticle.title.ilike(f"%{q}%"), @@ -6293,6 +6304,8 @@ def generate_drug_prices(drug): } +@app.route("//price-guide") +@app.route("//price-guide.html") @app.route("//prices") @app.route("//prices.html") def drug_prices(slug): From 306e3869a16247b49ca2225df58a6c45b4f4f5b8 Mon Sep 17 00:00:00 2001 From: Boyu Gou Date: Thu, 14 May 2026 09:39:10 -0700 Subject: [PATCH 165/167] fix: prevent duplicate drug entries in interaction checker - Deduplicate by name (case-insensitive) before adding to drug list - Skip autocomplete results display if input was already cleared Co-Authored-By: Claude Sonnet 4.6 --- sites/drugs_com/templates/interaction_checker.html | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sites/drugs_com/templates/interaction_checker.html b/sites/drugs_com/templates/interaction_checker.html index 5bb509d..970be61 100644 --- a/sites/drugs_com/templates/interaction_checker.html +++ b/sites/drugs_com/templates/interaction_checker.html @@ -253,6 +253,11 @@

    Popular tools & resources

    function addDrug(name){ if(!name) return; + var lower = name.toLowerCase(); + var existing = listEl.querySelectorAll('input[name="drugs"]'); + for(var i=0; iPopular tools & resources

    fetch('/api/autocomplete?q=' + encodeURIComponent(q)) .then(function(r){ return r.json(); }) .then(function(data){ + if(!input.value.trim()){ acHide(); return; } var drugs = (data||[]).filter(function(r){ return r.type !== 'condition'; }).slice(0,8); if(!drugs.length){ acHide(); return; } var html = ''; From 2c21c2af2199c854c2c3d4b40c44ec806ef325a1 Mon Sep 17 00:00:00 2001 From: Boyu Gou Date: Thu, 14 May 2026 20:54:40 -0700 Subject: [PATCH 166/167] feat: comprehensive UI/UX polish and functionality additions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds several new pages, fixes broken routes, and polishes all major page layouts to match drugs.com more closely. New pages: - /apps — mobile app download page (fixes utility bar "Apps" link) - /dosage-guide — general dosage reference page - /pregnancy-safety — pregnancy drug safety overview - /emergency-info — emergency contact info page - /drug/:slug/faq — standalone FAQ page per drug New components: - _drug_sidebar.html macro for reusable right-column drug status sidebar - "Add to My Med List" button in drug detail header (med-list-toggle-top) which JS already referenced but the element was missing Route fixes: - /conditions/.html and /condition/.html now handled (404 fix) - /drug-classes/.html and /drug-class/.html now handled CSS additions: - .btn-med-list--header for compact header med-list button - .drug-header-actions wrapper Co-Authored-By: Claude Sonnet 4.6 --- sites/drugs_com/app.py | 196 ++++++- sites/drugs_com/static/css/main.css | 536 ++++++++---------- sites/drugs_com/templates/_drug_sidebar.html | 115 ++++ sites/drugs_com/templates/account.html | 14 +- .../drugs_com/templates/account_settings.html | 6 +- .../templates/account_subscriptions.html | 8 +- sites/drugs_com/templates/apps.html | 63 ++ sites/drugs_com/templates/base.html | 24 +- sites/drugs_com/templates/condition.html | 4 +- sites/drugs_com/templates/dosage_guide.html | 64 +++ sites/drugs_com/templates/drug_az.html | 6 +- sites/drugs_com/templates/drug_detail.html | 363 ++++++------ sites/drugs_com/templates/drug_dosage.html | 128 +++-- sites/drugs_com/templates/drug_faq_page.html | 82 +++ sites/drugs_com/templates/drug_images.html | 42 +- .../templates/drug_interactions_page.html | 91 ++- sites/drugs_com/templates/drug_pregnancy.html | 34 +- sites/drugs_com/templates/drug_prices.html | 45 +- .../templates/drug_pro_monograph.html | 70 ++- .../drugs_com/templates/drug_review_new.html | 1 + .../templates/drug_reviews_page.html | 153 ++--- .../templates/drug_side_effects.html | 289 +++++----- sites/drugs_com/templates/drug_warnings.html | 26 +- sites/drugs_com/templates/emergency_info.html | 99 ++++ sites/drugs_com/templates/index.html | 15 +- .../templates/interaction_checker.html | 8 +- sites/drugs_com/templates/login.html | 8 +- sites/drugs_com/templates/my_reviews.html | 14 +- sites/drugs_com/templates/news.html | 4 +- sites/drugs_com/templates/news_article.html | 4 +- .../drugs_com/templates/pill_identifier.html | 8 +- .../drugs_com/templates/pregnancy_safety.html | 79 +++ sites/drugs_com/templates/pro_edition.html | 2 +- sites/drugs_com/templates/register.html | 4 +- 34 files changed, 1692 insertions(+), 913 deletions(-) create mode 100644 sites/drugs_com/templates/_drug_sidebar.html create mode 100644 sites/drugs_com/templates/apps.html create mode 100644 sites/drugs_com/templates/dosage_guide.html create mode 100644 sites/drugs_com/templates/drug_faq_page.html create mode 100644 sites/drugs_com/templates/emergency_info.html create mode 100644 sites/drugs_com/templates/pregnancy_safety.html diff --git a/sites/drugs_com/app.py b/sites/drugs_com/app.py index 57620ee..4f2a84b 100644 --- a/sites/drugs_com/app.py +++ b/sites/drugs_com/app.py @@ -38,6 +38,7 @@ os.makedirs(os.path.join(BASE_DIR, "instance"), exist_ok=True) app = Flask(__name__, instance_path=os.path.join(BASE_DIR, "instance")) +app.url_map.strict_slashes = False app.config["SECRET_KEY"] = "drugs_com-dev-secret-please-change" app.config["SQLALCHEMY_DATABASE_URI"] = ( "sqlite:///" + os.path.join(BASE_DIR, "instance", "drugs_com.db") @@ -3255,6 +3256,22 @@ def seed_conditions(): "dosage": "Recommended dose: 10 mg within 30 minutes of bedtime (at least 7 hours before planned awakening). May increase to max 20 mg/day if 10 mg not effective. Start with 5 mg if taking moderate CYP3A4 inhibitors. Avoid strong CYP3A4 inhibitors (contraindicated).", "before_taking": "Tell your doctor about depression, narcolepsy, or drug or alcohol dependence. Do not take with strong CYP3A4 inhibitors. Allow 7 hours before activities requiring full alertness. Avoid alcohol.", }, + "diclofenac": { + "description": "Diclofenac (Voltaren, Cataflam, Cambia) is an oral and topical NSAID (nonsteroidal anti-inflammatory drug) with analgesic and anti-inflammatory properties. Oral formulations are available by prescription; a topical gel (Voltaren) is available over the counter for joint and muscle pain.", + "uses": "Diclofenac is used to treat pain, inflammation, and stiffness from osteoarthritis, rheumatoid arthritis, and ankylosing spondylitis. Oral diclofenac potassium (Cambia) is also used for acute migraine with or without aura. Topical diclofenac gel is used for osteoarthritis pain in joints of the hands, wrists, elbows, knees, ankles, and feet. Diclofenac epolamine patch is used to treat acute pain from minor strains, sprains, and contusions.", + "warnings": "NSAIDs, including diclofenac, can increase the risk of serious cardiovascular thrombotic events including myocardial infarction and stroke, which can be fatal. Diclofenac can cause serious GI adverse events including bleeding, ulceration, and perforation of the stomach and intestines. Risk of hepatotoxicity: diclofenac is associated with a higher rate of liver enzyme elevations than other NSAIDs; liver tests should be monitored if used long-term. Avoid use in the third trimester of pregnancy and in patients with severe hepatic, renal, or cardiac impairment. Topical diclofenac can cause local skin reactions.", + "side_effects": "Common (oral): abdominal pain, nausea, dyspepsia, diarrhea, constipation, headache, dizziness, rash, elevated liver enzymes. Serious: GI bleeding, peptic ulcer, hepatotoxicity (diclofenac has one of the highest rates among NSAIDs), cardiovascular events, renal insufficiency, severe skin reactions (SJS, TEN), anaphylaxis. Topical: local skin dryness, redness, and pruritus.", + "dosage": "Osteoarthritis (oral): 100-150 mg/day in divided doses (e.g., 50 mg 2-3 times daily). Rheumatoid arthritis: 150-200 mg/day in divided doses. Migraine (Cambia): 50 mg powder packet dissolved in water at headache onset; do not repeat within 24 hours. Topical gel (Voltaren 1%): apply 4 g to the affected joint(s) 4 times daily; upper extremities max 16 g/day, lower extremities max 32 g/day.", + "before_taking": "Tell your doctor if you have heart disease, high blood pressure, liver or kidney disease, asthma, stomach ulcers, or GI bleeding. Inform your doctor about all medications, especially blood thinners, other NSAIDs, SSRIs, ACE inhibitors, or diuretics. Use caution in patients with cardiovascular risk factors. Liver function should be monitored during chronic diclofenac therapy.", + }, + "ursodiol": { + "description": "Ursodiol (ursodeoxycholic acid; brand names Actigall, URSO 250, URSO Forte) is a bile acid used to dissolve gallstones, prevent gallstone formation, and treat primary biliary cholangitis (PBC). It is available by prescription.", + "uses": "Ursodiol is indicated for: (1) dissolution of radiolucent, noncalcified gallbladder stones less than 20 mm in diameter in patients who are not surgical candidates; (2) prevention of gallstone formation in obese patients undergoing rapid weight loss; and (3) treatment of primary biliary cholangitis (PBC, also called primary biliary cirrhosis) to improve liver function tests and slow disease progression.", + "warnings": "Gallstone dissolution therapy with ursodiol works for radiolucent, noncalcified gallstones only. Stones may recur after discontinuation. Liver tests should be monitored in patients with PBC. Use with caution in patients with hepatic impairment. Ursodiol is not recommended during pregnancy unless clearly needed; use effective contraception during therapy.", + "side_effects": "Generally well tolerated. Common: diarrhea (dose-related), abdominal discomfort, nausea, vomiting, indigestion, constipation, hair thinning (rare). Serious: rare worsening of liver disease in patients with PBC who have advanced cirrhosis; monitor liver function tests.", + "dosage": "Gallstone dissolution: 8-10 mg/kg/day in 2-3 divided doses. Prevention of gallstones during rapid weight loss: 300 mg twice daily during the weight-loss period. Primary biliary cholangitis: 13-15 mg/kg/day in 2-4 divided doses, administered with food. Duration of gallstone dissolution therapy: up to 24 months; obtain ultrasound at 6 and 12 months to assess response.", + "before_taking": "Tell your doctor about liver disease, bile duct abnormalities, or recent bile duct surgery. Do not use for calcified or radiopaque gallstones, or for gallstone pancreatitis. Ursodiol may reduce the absorption of cyclosporine; dose adjustment may be needed.", + }, } @@ -3493,17 +3510,34 @@ def seed_benchmark_users(): def seed_extra_reviews(): """Add reviews across all popular drugs from auto-generated reviewer users.""" + _REVIEWER_NAMES = [ + ("ChrisB79", "chrisb79@example.com"), + ("MaryM_health", "marym.health@example.com"), + ("JohnD_rx", "johnd.rx@example.com"), + ("SarahK2024", "sarahk2024@example.com"), + ("PatientAdvocate", "patient.advocate@example.com"), + ("MigraineWarrior", "migraine.warrior@example.com"), + ("DiabetesMgmt", "diabetes.mgmt@example.com"), + ("HeartHealthPro", "hearthealthpro@example.com"), + ] # Create reviewer users first (needed to check existing pairs) reviewers = [] - for i in range(8): - email = f"reviewer{i}@example.com" + for i, (uname, email) in enumerate(_REVIEWER_NAMES): u = User.query.filter_by(email=email).first() if not u: - u = User(username=f"reviewer_{i}", email=email) + # also try legacy email in case DB was seeded before rename + legacy = f"reviewer{i}@example.com" + u = User.query.filter_by(email=legacy).first() + if not u: + u = User(username=uname, email=email) u.set_password("review-seed-pw") db.session.add(u) db.session.flush() + elif u.username != uname: + u.username = uname + u.email = email reviewers.append(u) + db.session.commit() # always persist username/email renames reviewer_ids = {u.id for u in reviewers} if DrugReview.query.filter(DrugReview.user_id.in_(reviewer_ids)).count() >= 700: return @@ -3878,19 +3912,36 @@ def index(): @app.route("/dosage-guide") @app.route("/dosage-guide.html") @app.route("/dosage") +def dosage_guide(): + drugs = Drug.query.order_by(Drug.generic_name).all() + return render_template("dosage_guide.html", drugs=drugs) + + +@app.route("/pregnancy-safety") +@app.route("/pregnancy-safety.html") +def pregnancy_safety(): + drugs = Drug.query.filter(Drug.pregnancy_risk.isnot(None)).order_by(Drug.generic_name).all() + return render_template("pregnancy_safety.html", drugs=drugs) + + @app.route("/drugs-a-z") @app.route("/drug-az") @app.route("/drugs-a-to-z.html") @app.route("/drug_information.html") def drug_az(): letter = (request.args.get("letter") or "A").upper() - if letter not in string.ascii_uppercase: - letter = "A" - drugs = Drug.query.filter(Drug.generic_name.ilike(f"{letter}%")).order_by(Drug.generic_name).all() + if letter in ("0-9", "0"): + letter = "0-9" + drugs = Drug.query.filter(Drug.generic_name.op("GLOB")("[0-9]*")).order_by(Drug.generic_name).all() + else: + if letter not in string.ascii_uppercase: + letter = "A" + drugs = Drug.query.filter(Drug.generic_name.ilike(f"{letter}%")).order_by(Drug.generic_name).all() letter_counts = { L: Drug.query.filter(Drug.generic_name.ilike(f"{L}%")).count() for L in string.ascii_uppercase } + letter_counts["0-9"] = Drug.query.filter(Drug.generic_name.op("GLOB")("[0-9]*")).count() popular_drugs = Drug.query.order_by(Drug.review_count.desc()).limit(10).all() # Top 8 drug classes by number of associated drugs. @@ -3924,7 +3975,7 @@ def drug_az(): return render_template( "drug_az.html", active_letter=letter, - all_letters=list(string.ascii_uppercase), + all_letters=list(string.ascii_uppercase) + ["0-9"], drugs=drugs, letter_counts=letter_counts, popular_drugs=popular_drugs, @@ -4231,6 +4282,10 @@ def _build_avoid_items(drug): return [{"title": t, "reason": r} for t, r in items] +@app.route("/comments//") +@app.route("/comments/") +@app.route("/answers/support-group//") +@app.route("/answers/support-group/") @app.route("//reviews") @app.route("//reviews.html") def drug_reviews_page(slug): @@ -4255,9 +4310,17 @@ def drug_reviews_page(slug): conditions = db.session.query(DrugReview.condition_treated).filter_by(drug_id=drug.id).distinct().all() conditions = [c[0] for c in conditions if c[0]] rating_dist = {i: DrugReview.query.filter_by(drug_id=drug.id, rating=i).count() for i in range(1, 11)} + related_drugs = [] + if drug.drug_class_id: + related_drugs = Drug.query.filter( + Drug.drug_class_id == drug.drug_class_id, + Drug.id != drug.id + ).order_by(Drug.avg_rating.desc().nullslast()).limit(6).all() + faq_items = list(drug.faq or _build_default_faq(drug)) return render_template("drug_reviews_page.html", drug=drug, reviews=reviews, conditions=conditions, condition_filter=condition_filter, - sort=sort, rating_dist=rating_dist) + sort=sort, rating_dist=rating_dist, + faq_items=faq_items, related_drugs=related_drugs) @app.route("//reviews/new", methods=["GET"]) @@ -4921,6 +4984,8 @@ def _norm(s): @app.route("/condition/") +@app.route("/condition/.html") +@app.route("/conditions/.html") @app.route("/conditions/") def condition_page(slug): # Normalize slug: agents may use hyphens where DB stores underscores (or vice versa). @@ -5463,6 +5528,8 @@ def get_extended_class_profile(class_name): @app.route("/drug-class/") +@app.route("/drug-class/.html") +@app.route("/drug-classes/.html") @app.route("/drug-classes/") def drug_class_page(slug): # Handle common abbreviations and aliases @@ -5550,6 +5617,7 @@ def drug_class_page(slug): ) +@app.route("/news.html") @app.route("/mednews/") @app.route("/mednews") @app.route("/news/") @@ -5558,18 +5626,18 @@ def news_index(): q = request.args.get("q", "") page = request.args.get("page", 1, type=int) per_page = 15 + cat_map = { + "new-drug-approvals": "New Drug Approvals", + "new drug approvals": "New Drug Approvals", + "medical": "Medical", + "fda-alerts": "FDA Alerts", + "fda alerts": "FDA Alerts", + "clinical-trials": "Clinical Trials", + "clinical trials": "Clinical Trials", + "health": "Health", + } query = NewsArticle.query if cat: - cat_map = { - "new-drug-approvals": "New Drug Approvals", - "new drug approvals": "New Drug Approvals", - "medical": "Medical", - "fda-alerts": "FDA Alerts", - "fda alerts": "FDA Alerts", - "clinical-trials": "Clinical Trials", - "clinical trials": "Clinical Trials", - "health": "Health", - } db_cat = cat_map.get(cat.lower(), cat) query = query.filter_by(category=db_cat) if q: @@ -5585,7 +5653,8 @@ def news_index(): fda_alerts = NewsArticle.query.filter( NewsArticle.category.in_(["FDA Alerts", "Safety"]) ).order_by(NewsArticle.published_at.desc()).limit(4).all() - return render_template("news.html", articles=articles, active_category=cat, active_cat=cat or None, + display_cat = cat_map.get(cat.lower(), cat) if cat else None + return render_template("news.html", articles=articles, active_category=display_cat, active_cat=display_cat, categories=categories, search_query=q, fda_alerts=fda_alerts, page=page, total_pages=total_pages) @@ -5611,6 +5680,9 @@ def news_article(article_id): @app.route("/news/category/") +@app.route("/newdrugs.html", defaults={"category": "new-drug-approvals"}) +@app.route("/fda_alerts.html", defaults={"category": "fda-alerts"}) +@app.route("/clinical_trials.html", defaults={"category": "clinical-trials"}) @app.route("/news/") @app.route("/new-drug-approvals", defaults={"category": "new-drug-approvals"}) @app.route("/fda-alerts", defaults={"category": "fda-alerts"}) @@ -5802,6 +5874,7 @@ def conditions_list(): } +@app.route("/symptoms", methods=["GET", "POST"]) @app.route("/symptom_checker.html", methods=["GET", "POST"]) @app.route("/symptom-checker.html", methods=["GET", "POST"]) @app.route("/symptom-checker", methods=["GET", "POST"]) @@ -5844,6 +5917,8 @@ def symptom_checker(): # --- Auth --- +@app.route("/account/login/", methods=["GET", "POST"]) +@app.route("/account/login", methods=["GET", "POST"]) @app.route("/login", methods=["GET", "POST"]) def login(): if request.method == "POST": @@ -5852,6 +5927,8 @@ def login(): return render_template("login.html") +@app.route("/account/register/", methods=["GET", "POST"]) +@app.route("/account/register", methods=["GET", "POST"]) @app.route("/register", methods=["GET", "POST"]) def register(): if request.method == "POST": @@ -5917,6 +5994,7 @@ def my_med_list_toggle(): @app.route("/pro/") @app.route("/pro") +@app.route("/professionals.html") @app.route("/pro-edition") @app.route("/pro-edition/") def pro_edition(): @@ -5929,7 +6007,7 @@ def compare_drugs_slug(vs_slug): """Handle /compare/drugA-vs-drugB URL format.""" if "-vs-" in vs_slug: parts = vs_slug.split("-vs-", 1) - return redirect(url_for("compare_drugs", drug1=parts[0], drug2=parts[1])) + return redirect(url_for("compare_drugs", drug1=parts[0].removesuffix(".html"), drug2=parts[1].removesuffix(".html"))) return redirect(url_for("compare_drugs", drug1=vs_slug)) @@ -6052,6 +6130,7 @@ def save_subscriptions(): return redirect(url_for("account_subscriptions")) +@app.route("/sitemap.xml") @app.route("/sitemap") @app.route("/sitemap.html") def sitemap(): @@ -6126,6 +6205,7 @@ def side_effects_page(): ) +@app.route("/drug-warnings") @app.route("/boxed-warnings") @app.route("/warnings/") @app.route("/blackbox-warnings") @@ -6183,19 +6263,25 @@ def contact(): return render_template("contact.html", submitted=submitted) -@app.route("/about") -@app.route("/about.html") @app.route("/advertise") +@app.route("/about.html") +@app.route("/about") def about_page(): return render_template("about.html") +@app.route("/apps") +def apps_page(): + return render_template("apps.html") + + @app.route("/support") @app.route("/help") def help_page(): return render_template("help.html") +@app.route("/image/-images.html") @app.route("//images") @app.route("//images.html") def drug_images(slug): @@ -6304,6 +6390,8 @@ def generate_drug_prices(drug): } +@app.route("/price-guide/") +@app.route("/price-guide/.html") @app.route("//price-guide") @app.route("//price-guide.html") @app.route("//prices") @@ -6314,7 +6402,10 @@ def drug_prices(slug): return render_template("drug_prices.html", drug=drug, price_data=price_data) +@app.route("/monograph/.html") +@app.route("/monograph/") @app.route("/drugs/pro/") +@app.route("/pro/.html") @app.route("/pro/") @app.route("//monograph") @app.route("//monograph.html") @@ -6361,6 +6452,8 @@ def _parse_dosage_rows(text, drug_name): return rows if rows else None +@app.route("/dosage/.html") +@app.route("/dosage/") @app.route("//dosage") @app.route("//dosage.html") def drug_dosage(slug): @@ -6368,7 +6461,15 @@ def drug_dosage(slug): _ov = DRUG_CONTENT_OVERRIDES.get(drug.generic_name) or DRUG_CONTENT_OVERRIDES.get(drug.generic_name.replace(' ', '-'), {}) rt_dosage = _ov.get("dosage") or drug.dosage dosage_rows = _parse_dosage_rows(rt_dosage, drug.generic_name) - return render_template("drug_dosage.html", drug=drug, rt_dosage=rt_dosage, dosage_rows=dosage_rows) + related_drugs = [] + if drug.drug_class_id: + related_drugs = Drug.query.filter( + Drug.drug_class_id == drug.drug_class_id, + Drug.id != drug.id + ).order_by(Drug.avg_rating.desc().nullslast()).limit(6).all() + faq_items = list(drug.faq or _build_default_faq(drug)) + return render_template("drug_dosage.html", drug=drug, rt_dosage=rt_dosage, dosage_rows=dosage_rows, + related_drugs=related_drugs, faq_items=faq_items) def _parse_side_effects(text): @@ -6389,6 +6490,8 @@ def _parse_side_effects(text): return {"common": common, "serious": serious} +@app.route("/sfx/-side-effects.html") +@app.route("/sfx/") @app.route("//side-effects") @app.route("//side-effects.html") def drug_side_effects(slug): @@ -6396,7 +6499,15 @@ def drug_side_effects(slug): _ov = DRUG_CONTENT_OVERRIDES.get(drug.generic_name) or DRUG_CONTENT_OVERRIDES.get(drug.generic_name.replace(' ', '-'), {}) rt_side_effects = _ov.get("side_effects") or drug.side_effects se_parsed = _parse_side_effects(rt_side_effects) - return render_template("drug_side_effects.html", drug=drug, rt_side_effects=rt_side_effects, se_parsed=se_parsed) + related_drugs = [] + if drug.drug_class_id: + related_drugs = Drug.query.filter( + Drug.drug_class_id == drug.drug_class_id, + Drug.id != drug.id + ).order_by(Drug.avg_rating.desc().nullslast()).limit(6).all() + faq_items = list(drug.faq or _build_default_faq(drug)) + return render_template("drug_side_effects.html", drug=drug, rt_side_effects=rt_side_effects, + se_parsed=se_parsed, related_drugs=related_drugs, faq_items=faq_items) # FDA pregnancy category mapping for common drugs. Drugs not listed fall through @@ -6674,6 +6785,10 @@ def _pregnancy_info(drug): } +@app.route("/pregnancy/.html") +@app.route("/pregnancy/") +@app.route("/breastfeeding/.html") +@app.route("/breastfeeding/") @app.route("//pregnancy") @app.route("//pregnancy.html") def drug_pregnancy(slug): @@ -6738,6 +6853,25 @@ def drug_warnings(slug): warning_cards=cards) +@app.route("/tips/-patient-tips") +@app.route("/tips/") +@app.route("//faq") +@app.route("//faq.html") +def drug_faq_page(slug): + drug = Drug.query.filter_by(slug=slug).first_or_404() + faq_items = list(drug.faq or _build_default_faq(drug)) + return render_template("drug_faq_page.html", drug=drug, faq_items=faq_items) + + +@app.route("//professional") +@app.route("//professional.html") +def drug_professional_page(slug): + drug = Drug.query.filter_by(slug=slug).first_or_404() + return redirect(url_for('drug_pro_monograph', slug=slug), 301) + + +@app.route("/drug-interactions/.html") +@app.route("/drug-interactions/") @app.route("//interactions") @app.route("//interactions.html") @app.route("//drug-interactions") @@ -6771,6 +6905,12 @@ def drug_interactions_page(slug): 'minor': sum(1 for x in interaction_details if x['severity'] == 'minor'), } food_interactions, alcohol_interactions = _lifestyle_interactions([drug]) + related_drugs = [] + if drug.drug_class_id: + related_drugs = Drug.query.filter( + Drug.drug_class_id == drug.drug_class_id, + Drug.id != drug.id + ).order_by(Drug.avg_rating.desc().nullslast()).limit(6).all() return render_template( "drug_interactions_page.html", drug=drug, @@ -6779,9 +6919,17 @@ def drug_interactions_page(slug): summary=summary, food_interactions=food_interactions, alcohol_interactions=alcohol_interactions, + related_drugs=related_drugs, ) +@app.route("/emergency") +@app.route("/emergency-info") +@app.route("/emergency-info.html") +def emergency_info(): + return render_template("emergency_info.html") + + @app.route("/_health") def health(): return {"ok": True, "site": "drugs_com"} diff --git a/sites/drugs_com/static/css/main.css b/sites/drugs_com/static/css/main.css index 2a58b11..361f1b8 100644 --- a/sites/drugs_com/static/css/main.css +++ b/sites/drugs_com/static/css/main.css @@ -354,7 +354,7 @@ a:hover { text-decoration: underline; color: var(--blue-mid); } overflow-y: visible; gap: 20px; flex: 1; - justify-content: center; + justify-content: flex-start; padding: 28px 20px; border-top: 1px solid var(--gray-100); border-bottom: 1px solid var(--gray-100); @@ -871,6 +871,7 @@ a:hover { text-decoration: underline; color: var(--blue-mid); } .medical-disclaimer { margin-top: 28px; padding: 14px 16px; background: var(--gray-50); border-left: 4px solid var(--blue); font-size: 0.85rem; color: var(--gray-700); line-height: 1.6; } .more-about-list { list-style: none; } .more-about-list li { padding: 5px 0; border-bottom: 1px solid var(--gray-100); font-size: 0.9rem; } +.more-about-list a.active { font-weight: 700; color: #1a73c8; pointer-events: none; } /* Interaction mini-checker in drug detail */ .interaction-mini-checker { @@ -1222,240 +1223,6 @@ html { scroll-behavior: smooth; } .footer-top { grid-template-columns: repeat(2, 1fr); } } -/* ── System dark mode ── */ -@media (prefers-color-scheme: dark) { - body { - background: #1a1a2e; - color: #e8e8e8; - } - a { color: #4da6ff; } - a:hover { color: #80c0ff; } - - .site-header { - background: #16213e; - border-bottom-color: #2a2a4a; - } - .brand-logo { color: #e8e8e8; } - .header-search-inner { - background: #0f3460; - border-color: #2a2a4a; - } - .header-search-input { - background: #0f3460; - color: #e8e8e8; - } - .header-user-btn, - .header-register-btn { color: #cfd6e4; } - .header-signin-btn { color: #4da6ff; border-color: #4da6ff; } - .header-signin-btn:hover { background: #4da6ff; color: #16213e; } - - .az-strip { - background: #16213e; - border-bottom-color: #2a2a4a; - } - .az-strip-link { color: #4da6ff; } - - .nav-dropdown { - background: #16213e; - border-color: #2a2a4a; - } - .nav-dropdown a { color: #e8e8e8; } - .nav-dropdown a:hover { background: #0f3460; color: #4da6ff; } - - .hero-section { - background: linear-gradient(135deg, #16213e 0%, #1a1a2e 100%); - border-bottom-color: #2a2a4a; - } - .hero-section h1 { color: #e8e8e8; } - .hero-sub { color: #b0b8c8; } - .hero-search-input { - background: #0f3460; - color: #e8e8e8; - } - - .feature-icons { - background: #16213e; - border-top-color: #2a2a4a; - border-bottom-color: #2a2a4a; - } - .feature-icon-item { color: #cfd6e4; } - .feature-icon-circle { - background: #0f3460; - border-color: #2a2a4a; - color: #4da6ff; - } - - .browse-panel-tabs, - .letter-grid, - .browse-footer, - .site-section-panel { - background: #16213e; - border-color: #2a2a4a; - } - .browse-tab { color: #cfd6e4; } - .letter-btn { - background: #0f3460; - color: #e8e8e8; - border-color: #2a2a4a; - } - - .home-row { border-top-color: #2a2a4a; } - .home-row h2, - .row-section h2, - .detail-main h2, - .detail-main h3, - .drug-header h1, - .news-list h3, - .news-card h2 a, - .auth-box h1, - .account-box h2, - .sidebar-box h3, - .faq-item h3, - .interaction h4 { color: #e8e8e8; } - - .row-section, - .account-box, - .result-card, - .drug-card, - .med-card, - .news-card, - .checker-box, - .auth-box { - background: #16213e; - border-color: #2a2a4a; - color: #e8e8e8; - } - - /* Drug detail page: ensure solid card backgrounds and high-contrast text. */ - .drug-detail { - background: #16213e; - color: #e8e8e8; - } - .detail-main, - .detail-main section { - background: transparent; - color: #e8e8e8; - } - .detail-main p, - .detail-main li { - color: #d4dbe8; - } - .detail-main strong { color: #ffffff; } - .sidebar-box, - .status-box { - background: #1e2a3e; - border-color: #2a2a4a; - color: #e8e8e8; - } - .drug-card { color: #e8e8e8; } - .drug-card h3, - .result-card h2 { color: #4da6ff; } - .row-section h2, - .detail-main h2 { border-bottom-color: #2a2a4a; } - - .news-list li, - .news-card, - .review, - .faq-item, - .site-section-panel li, - .az-list li, - .sidebar-box li, - .more-about-list li, - .popular-tools li, - .class-list li { border-bottom-color: #2a2a4a; } - - .detail-sidebar > div { - background: #16213e; - border-color: #2a2a4a; - } - .status-box-title { color: #cfd6e4; border-bottom-color: #2a2a4a; } - .status-row { border-bottom-color: #2a2a4a; } - .status-label { color: #e8e8e8; } - .status-value { color: #b0b8c8; } - - .meta-row { border-top-color: #2a2a4a; border-bottom-color: #2a2a4a; } - .meta-row li strong { color: #cfd6e4; } - .pronunciation, - .reviewer-line, - .muted, - .news-list p, - .news-meta, - .breadcrumb, - .breadcrumb span { color: #b0b8c8; } - - .section-tabs, - .news-tabs, - .account-tabs { border-bottom-color: #2a2a4a; } - .section-tabs a, - .news-tabs a, - .account-tabs a, - .browse-tab { color: #cfd6e4; } - - .drug-tabs { - background: #16213e; - border-bottom-color: #4da6ff; - } - - .search-filters, - .pill-form, - .medical-disclaimer { - background: #16213e; - border-color: #2a2a4a; - color: #e8e8e8; - } - .search-filters input, - .search-filters select, - .pill-form input, - .pill-form select, - .checker-input input, - .mini-checker-form input, - .auth-box input { - background: #0f3460; - color: #e8e8e8; - border-color: #2a2a4a; - } - .pill-table, - .drug-table, - .compare-table { - background: #16213e; - color: #e8e8e8; - } - .pill-table th, - .drug-table th { - background: #0f3460; - color: #e8e8e8; - } - .pill-table td, - .drug-table td { border-bottom-color: #2a2a4a; } - .compare-table th, .compare-table td { border-color: #2a2a4a; } - .compare-table th { background: #0f3460; } - .compare-table tr:nth-child(even) td { background: #0f3460; } - .pill-table tr:hover td, - .drug-table tr:hover td { background: #0f3460; } - - .condition-grid li, - .pro-tool-card { - background: #16213e; - border-color: #2a2a4a; - } - - .auth-benefits { background: #0f3460; } - .auth-benefits h3, - .auth-benefits li { color: #e8e8e8; } - - .rating-bar-track { background: #2a2a4a; } - .rating-bar-label, - .rating-bar-count { color: #b0b8c8; } - - .checker-drug-list li { background: #0f3460; color: #e8e8e8; } - .interaction { background: #0f3460; border-left-color: #2a2a4a; } - - .site-footer { - background: #0d0d1a; - } - - /* Availability badges keep original colors — visible on dark too */ -} /* ========================================================================== Search results & Pill identifier @@ -2152,46 +1919,6 @@ html { scroll-behavior: smooth; } .compare-col { transition: box-shadow 0.2s, border-color 0.2s; } .compare-col:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.08); border-color: #c8d2e0; } -/* Dark mode for recently added elements */ -@media (prefers-color-scheme: dark) { - .section-tabs { background: #16213e; } - .detail-main section { border-top-color: #2a2a4a; } - .reviews-summary { - background: #16213e; - border-color: #2a2a4a; - color: #e8e8e8; - } - .reviews-summary-number { color: #ffffff; } - .reviews-summary-denominator { color: #b0b8c8; } - .rating-row-label { color: #e8e8e8; } - .rating-row-count { color: #b0b8c8; } - .rating-row-bar { background: #2a2a4a; } - .rating-row-fill { background: linear-gradient(90deg, #4da6ff, #7ec0ff); } - .reviews-distribution { color: #e8e8e8; } - .reviews-filter { - background: #16213e; - color: #e8e8e8; - } - .reviews-list .review-card { - background: #16213e; - border-color: #2a2a4a; - color: #e8e8e8; - } - .source-card { - background: #16213e !important; - border-color: #2a2a4a !important; - color: #e8e8e8; - } - .source-card:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.4); border-color: #4da6ff !important; } - .compare-col { - background: #16213e !important; - border-color: #2a2a4a !important; - color: #e8e8e8; - } - .compare-col h4 { color: #4da6ff !important; } - .compare-col p { color: #cfd6e4 !important; } - .compare-col:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.4); border-color: #4da6ff !important; } -} /* News article: author byline + helpfulness widget */ .article-byline { font-size: 13px; color: #555; margin-bottom: 14px; } @@ -3116,6 +2843,47 @@ html { scroll-behavior: smooth; } color: #4b5563; } +/* Account stats bar */ +.account-stats { + display: flex; + gap: 24px; + margin: 16px 0 18px; + flex-wrap: wrap; +} +.account-stat { + display: flex; + flex-direction: column; + align-items: center; + background: var(--light-bg); + border: 1px solid var(--border); + border-radius: 8px; + padding: 14px 22px; + min-width: 110px; + text-align: center; +} +.stat-value { + font-size: 1.8rem; + font-weight: 700; + color: var(--navy); + line-height: 1.1; +} +.stat-label { + font-size: 0.82rem; + color: var(--muted); + margin-top: 4px; +} + +/* Account welcome banner */ +.account-welcome { + background: #f0f6ff; + border-left: 4px solid var(--primary); + padding: 10px 16px; + border-radius: 0 6px 6px 0; + margin-bottom: 14px; + color: var(--navy); + font-size: 0.95rem; +} + /* Account profile header */ .account-profile-header { display: flex; @@ -3641,10 +3409,12 @@ html { scroll-behavior: smooth; } flex-direction: column; align-items: center; gap: 4px; + order: 2; } .drug-header-text { flex: 1 1 auto; min-width: 0; + order: 1; } .drug-header .drug-title { display: flex; @@ -3938,6 +3708,26 @@ html { scroll-behavior: smooth; } color: #fff; } .btn-med-list--lg:active { transform: translateY(0); } +.btn-med-list--header { + display: inline-flex; + align-items: center; + gap: 7px; + background: linear-gradient(180deg, #f59e0b 0%, #d97706 100%); + color: #fff; + padding: 8px 16px; + border: none; + border-radius: 5px; + font-weight: 600; + font-size: 0.88rem; + cursor: pointer; + margin-top: 12px; + box-shadow: 0 2px 4px rgba(217,119,6,0.2); + transition: all 0.15s; + text-decoration: none; +} +.btn-med-list--header:hover { background: linear-gradient(180deg, #ea8d00 0%, #b8650a 100%); color: #fff; text-decoration: none; } +.drug-header-actions { margin-top: 8px; } +.btn-med-list--header { max-width: 260px; } .btn-med-list-icon { display: inline-flex; align-items: center; @@ -7167,17 +6957,6 @@ html { scroll-behavior: smooth; } } .review-form-all-link:hover { text-decoration: underline; } -@media (prefers-color-scheme: dark) { - .review-title { color: #cfe0ff; } - .reviews-summary-stars .star-empty { color: #3a3a55; } - .review-byline .star-off { color: #3a3a55; } - .review-byline .review-author { color: #e6e6e6; } - .review-byline .review-condition { background: #1f2a3a; color: #cfe0ff; } - .review-body { color: #d8d8d8; } - .review-helpful { border-top-color: #2a2a4a; } - .review-helpful-label { color: #b0b8c8; } - .review-form-all-link { color: #7ec0ff; } -} /* ---------- Interaction checker results ---------- */ .results-summary-header { @@ -7688,13 +7467,6 @@ html { scroll-behavior: smooth; } .reviews-by-condition-chip { display: inline-block; padding: 5px 12px; border: 1px solid #c8d2dd; border-radius: 999px; background: #fff; color: #1a3e72; text-decoration: none; font-size: 0.88rem; } .reviews-by-condition-chip:hover { background: #e8f0fa; } .reviews-by-condition-chip.is-active { background: #1a3e72; color: #fff; border-color: #1a3e72; } -@media (prefers-color-scheme: dark) { - .reviews-by-condition { background: #1a1a2e; border-color: #2a2a4a; } - .reviews-by-condition h3 { color: #cfe0ff; } - .reviews-by-condition-chip { background: #2a2a4a; color: #cfe0ff; border-color: #3a3a55; } - .reviews-by-condition-chip:hover { background: #3a3a55; } - .reviews-by-condition-chip.is-active { background: #4da6ff; color: #0b0f1a; border-color: #4da6ff; } -} /* ── Dedicated Write a Review page ── */ .review-new-wrap { max-width: 760px; margin: 0 auto; } @@ -7715,14 +7487,6 @@ html { scroll-behavior: smooth; } .review-form-full select, .review-form-full textarea { width: 100%; padding: 8px; border: 1px solid var(--gray-200, #e3e6eb); border-radius: 4px; font-family: inherit; font-size: 0.95rem; } .subratings-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; } -@media (prefers-color-scheme: dark) { - .review-new-wrap h1 { color: #cfe0ff; } - .review-form-full .review-fieldset { border-color: #2a2a4a; background: #0f1424; } - .review-form-full .review-fieldset legend { color: #cfe0ff; } - .review-form-full input[type="text"], - .review-form-full select, - .review-form-full textarea { background: #1a1a2e; color: #e8e8e8; border-color: #2a2a4a; } -} /* --- Pill identifier: improved form UX (instructions, shape icons, color swatches, reset) --- */ .pill-form-instructions { @@ -8079,6 +7843,176 @@ html { scroll-behavior: smooth; } .faq-more-list a:hover { text-decoration: underline; } .faq-attr { font-size: 0.8rem; margin-top: 1rem; } +/* Reviews section header with write-review button */ +.reviews-section-header { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 0.5rem; margin-bottom: 0.5rem; } +.reviews-section-header h2 { margin: 0; } +.btn-write-review { font-size: 0.92rem; padding: 7px 14px; white-space: nowrap; } + /* Drug interactions list: plain style */ .drug-interactions-list { list-style: disc; padding-left: 1.5rem; } .drug-interactions-list li { padding: 3px 0; font-size: 0.92rem; } + +/* Inline interaction checker widget */ +.inline-interaction-checker { background: #f0f4fa; border: 1px solid #c9d8ef; border-radius: 6px; padding: 16px 20px; margin: 20px 0 8px; } +.inline-interaction-checker h3 { margin: 0 0 6px; font-size: 1rem; color: #1a3a6e; } +.inline-checker-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-top: 10px; } +.inline-checker-input { flex: 1; min-width: 180px; padding: 8px 12px; border: 1px solid #b0c4de; border-radius: 4px; font-size: 0.92rem; } +.inline-checker-input:focus { outline: none; border-color: #2563eb; box-shadow: 0 0 0 2px rgba(37,99,235,0.15); } + +/* FAQ sub-page expanded style */ +.faq-item details[open] .faq-answer { display: block; } + +/* Reviews summary with rating distribution */ +.reviews-summary-full { display: flex; gap: 24px; align-items: flex-start; margin-bottom: 20px; padding: 16px; background: #f8f9fc; border-radius: 8px; border: 1px solid #e3e8f0; flex-wrap: wrap; } +.reviews-score-panel { display: flex; flex-direction: column; align-items: center; min-width: 100px; } +.reviews-score-big { font-size: 3rem; font-weight: 700; color: #1a3a6e; line-height: 1; } +.reviews-score-denom { font-size: 1rem; color: #666; } +.reviews-score-stars { margin: 4px 0; } +.reviews-score-count { font-size: 0.85rem; } +.rating-dist-panel { flex: 1; min-width: 200px; } +.rating-dist-row { display: flex; align-items: center; gap: 6px; margin-bottom: 3px; font-size: 0.82rem; } +.rating-dist-label { width: 16px; text-align: right; font-weight: 600; color: #333; } +.rating-dist-bar-wrap { flex: 1; height: 10px; background: #e3e8f0; border-radius: 5px; overflow: hidden; } +.rating-dist-bar { height: 100%; background: linear-gradient(90deg, #3b82f6, #1a3a6e); border-radius: 5px; min-width: 2px; transition: width 0.3s; } +.rating-dist-count { width: 24px; text-align: right; color: #888; } + +/* Consumer / Professional / FAQ page type tabs */ +.drug-page-type-tabs { display: flex; gap: 0; border-bottom: 2px solid #dde4f0; margin: 0 0 16px; } +.drug-page-type-tab { padding: 8px 18px; font-size: 0.9rem; font-weight: 500; color: #555; text-decoration: none; border-bottom: 3px solid transparent; margin-bottom: -2px; transition: color 0.15s; } +.drug-page-type-tab:hover { color: var(--blue); } +.drug-page-type-tab.active { color: var(--blue); border-bottom-color: var(--blue); font-weight: 600; } + +/* Drug status sidebar box (FAQ, reviews, and other sub-pages) */ +.sidebar-box-title { font-size: 0.75rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: #6b7280; margin-bottom: 10px; } +.drug-status-grid { display: flex; flex-direction: column; gap: 8px; } +.drug-status-item { display: flex; flex-direction: column; gap: 2px; } +.drug-status-label { font-size: 0.72rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; color: #9ca3af; } +.drug-status-value { font-size: 0.85rem; color: #374151; } +.drug-status-value.status-rx { color: #1e40af; font-weight: 600; } +.drug-status-value.status-otc { color: #065f46; font-weight: 600; } + +/* Sidebar rating block (FAQ, reviews sub-pages) */ +.sidebar-rating-big { display: flex; align-items: baseline; gap: 4px; margin-bottom: 10px; } +.rating-score-big { font-size: 2rem; font-weight: 700; color: #1a73c8; line-height: 1; } +.rating-denom { font-size: 0.9rem; color: #6b7280; } +.sidebar-rating-big a { font-size: 0.82rem; color: #1a73c8; margin-left: 4px; } + +/* Full-width button helper */ +.btn-block { display: block; width: 100%; text-align: center; } +.mt-2 { margin-top: 8px; } + +/* Related drugs carousel (drug detail page) */ +.related-drugs-carousel { display: flex; gap: 12px; overflow-x: auto; padding-bottom: 8px; scroll-snap-type: x mandatory; } +.related-drugs-carousel::-webkit-scrollbar { height: 4px; } +.related-drugs-carousel::-webkit-scrollbar-thumb { background: #c8d4e8; border-radius: 2px; } +.related-drugs-carousel .related-drug-card { flex: 0 0 110px; min-width: 110px; display: flex; flex-direction: column; align-items: center; text-align: center; padding: 10px 8px; border: 1px solid #e3e8f0; border-radius: 8px; text-decoration: none; color: #222; scroll-snap-align: start; transition: border-color 0.15s; } +.related-drugs-carousel .related-drug-card:hover { border-color: #2563eb; color: #1a3a6e; } +.related-drug-card-pill { width: 60px; height: 30px; margin-bottom: 8px; } +.related-drug-card-pill svg { width: 60px; height: 30px; } +.related-drug-rating-badge { font-size: 0.72rem; color: #e9800a; } + +/* Dosage info link (block link under sections) */ +.dosage-info-link { margin: 12px 0; } +.dosage-info-link a { font-size: 0.88rem; color: #1a73c8; text-decoration: none; } +.dosage-info-link a:hover { text-decoration: underline; } + +/* Sidebar images box */ +.sidebar-images-row { display: flex; gap: 8px; flex-wrap: wrap; margin: 8px 0; } +.sidebar-pill-thumb { display: block; width: 56px; height: 28px; } +.sidebar-pill-thumb svg { width: 56px; height: 28px; } +.sidebar-images-link { font-size: 0.82rem; color: #1a73c8; text-decoration: none; } +.sidebar-images-link:hover { text-decoration: underline; } + +/* Sidebar write review link */ +.sidebar-write-review { display: block; margin-top: 6px; font-size: 0.82rem; color: #1a73c8; text-decoration: none; } +.sidebar-write-review:hover { text-decoration: underline; } + +/* Rating score in sidebar */ +.rating-score { font-size: 1.5rem; font-weight: 700; color: #1a3a6e; line-height: 1.1; } +.rating-score-denom { font-size: 0.9rem; font-weight: 400; color: #666; } + +/* More about section in main content */ +.more-about-section { margin: 24px 0; } +.more-about-section h2 { font-size: 1.2rem; margin-bottom: 12px; } +.more-about-section h3 { font-size: 0.95rem; font-weight: 700; color: #555; margin: 14px 0 6px; text-transform: uppercase; letter-spacing: 0.04em; font-size: 0.78rem; } +.more-about-links { margin: 0 0 8px; padding: 0; list-style: none; } +.more-about-links li { padding: 3px 0; } +.more-about-links a { color: #1a73c8; font-size: 0.9rem; text-decoration: none; } +.more-about-links a:hover { text-decoration: underline; } + +/* More FAQ list */ +.more-faq-list { list-style: none; padding: 0; margin: 8px 0 0; } +.more-faq-list li { padding: 2px 0; } +.more-faq-list a { color: #1a73c8; font-size: 0.9rem; } + +/* ── Side effects page: accordion severity groups ── */ +.se-applies-to { font-size: 0.9rem; color: #555; margin-bottom: 1rem; } +.se-precautions { margin: 1.2rem 0; } +.se-severity-group { border: 1px solid #e0e0e0; border-radius: 4px; margin-bottom: 0.6rem; } +.se-severity-group summary { padding: 0.65rem 1rem; font-size: 0.95rem; cursor: pointer; background: #f7f8fa; border-radius: 4px; } +.se-severity-group[open] summary { border-bottom: 1px solid #e0e0e0; border-radius: 4px 4px 0 0; } +.se-severity-group ul { padding: 0.6rem 1rem 0.6rem 2rem; margin: 0; } +.se-serious-list { list-style: disc; padding-left: 1.5rem; } +.se-serious-list li { margin-bottom: 0.3rem; font-size: 0.92rem; } +.se-block--other { margin-top: 1.6rem; } +.se-block--pro { background: #f0f4fb; border-radius: 6px; padding: 1.2rem 1.4rem; margin: 1.6rem 0; } +.se-block--pro h2 { font-size: 1.1rem; margin-bottom: 0.8rem; } +.se-block--pro h3 { font-size: 0.95rem; margin: 1rem 0 0.4rem; font-weight: 700; } +.se-block--medwatch { background: #fafafa; border: 1px solid #ddd; border-radius: 6px; padding: 1.2rem 1.4rem; margin: 1.6rem 0; } +.medwatch-card { background: #fff; border: 1px solid #ccc; border-radius: 4px; padding: 1rem 1.2rem; margin: 0.8rem 0; } +.medwatch-channels { list-style: none; padding: 0; margin: 0.5rem 0; } +.medwatch-channels li { padding: 0.25rem 0; font-size: 0.9rem; } +.medwatch-note { font-size: 0.82rem; color: #666; margin-top: 0.5rem; } +.se-report-sidebar { font-size: 0.9rem; } +.se-report-link { color: #1a73c8; text-decoration: none; font-weight: 600; } +.se-report-link:hover { text-decoration: underline; } + +/* ── Dosage page quick nav ── */ +.dosage-quick-nav { background: #f7f8fa; border: 1px solid #e0e0e0; border-radius: 4px; padding: 1rem 1.4rem; margin-bottom: 1.2rem; } +.dosage-quick-nav h3 { font-size: 0.88rem; text-transform: uppercase; letter-spacing: 0.04em; color: #555; margin: 0.8rem 0 0.3rem; } +.dosage-quick-nav h3:first-child { margin-top: 0; } +.dosage-quick-nav ul { list-style: disc; padding-left: 1.4rem; margin: 0 0 0.4rem; } +.dosage-quick-nav li { font-size: 0.9rem; } +.dosage-quick-nav a { color: #1a73c8; text-decoration: none; } +.dosage-quick-nav a:hover { text-decoration: underline; } +.dosage-faq-list { list-style: none; padding: 0; margin: 0; } +.dosage-faq-list li { padding: 0.35rem 0; border-bottom: 1px solid #f0f0f0; font-size: 0.9rem; } +.dosage-faq-list a { color: #1a73c8; text-decoration: none; } +.dosage-faq-list a:hover { text-decoration: underline; } +.max-dose-warning { background: #fff8f0; border-left: 4px solid #e67e22; border-radius: 0 4px 4px 0; padding: 1rem 1.2rem; margin: 1.2rem 0; } +.max-dose-warning h2 { font-size: 1rem; margin-bottom: 0.4rem; color: #c0392b; } + +/* ── Interactions page: frequently checked + inline checker ── */ +.ix-inline-checker { background: #f0f4fb; border: 1px solid #c5d8f0; border-radius: 6px; padding: 1rem 1.2rem; margin: 1rem 0 1.4rem; } +.ix-inline-checker p { margin: 0 0 0.5rem; font-size: 0.92rem; } +.ix-inline-form { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; } +.ix-inline-input { flex: 1; min-width: 180px; padding: 7px 10px; border: 1px solid #ccc; border-radius: 4px; font-size: 0.9rem; } +.ix-frequently-checked { margin: 1.2rem 0 1.6rem; } +.ix-freq-list { list-style: none; padding: 0; margin: 0.5rem 0; columns: 2; column-gap: 2rem; } +.ix-freq-list li { padding: 0.3rem 0; font-size: 0.9rem; display: flex; align-items: center; gap: 6px; break-inside: avoid; } +.ix-freq-list a { color: #1a73c8; text-decoration: none; } +.ix-freq-list a:hover { text-decoration: underline; } +.sev-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; } +.sev-dot--major { background: #c0392b; } +.sev-dot--moderate { background: #d97706; } +.sev-dot--minor { background: #2e7d32; } +.sev-dot--unknown { background: #888; } +.sev-label--major { color: #c0392b; font-size: 0.78rem; font-weight: 600; } +.sev-label--moderate { color: #d97706; font-size: 0.78rem; font-weight: 600; } +.sev-label--minor { color: #2e7d32; font-size: 0.78rem; font-weight: 600; } +.sev-label--unknown { color: #888; font-size: 0.78rem; font-weight: 600; } + +/* ── Footer mobile app banner ── */ +.footer-mobile-app { border-top: 1px solid #ddd; padding: 20px 0; } +.footer-app-banner { display: flex; align-items: center; gap: 24px; flex-wrap: wrap; } +.footer-app-text { flex: 1; } +.footer-app-text strong { display: block; font-size: 1rem; margin-bottom: 4px; } +.footer-app-text p { font-size: 0.85rem; color: #555; margin: 0; } +.footer-app-links { display: flex; gap: 10px; flex-wrap: wrap; } +.footer-app-badge { display: inline-block; border: 1px solid #ccc; border-radius: 6px; padding: 6px 12px; color: #333; font-size: 0.82rem; text-decoration: none; background: #f7f8fa; } +.footer-app-badge:hover { background: #eee; } + +/* ── Reviews page ── */ +.reviews-faq { margin: 1.6rem 0; } +.reviews-disclaimer { font-size: 0.82rem; margin: 0.8rem 0; } +.reviews-see-also { margin: 1.4rem 0; } diff --git a/sites/drugs_com/templates/_drug_sidebar.html b/sites/drugs_com/templates/_drug_sidebar.html new file mode 100644 index 0000000..3b0e5bf --- /dev/null +++ b/sites/drugs_com/templates/_drug_sidebar.html @@ -0,0 +1,115 @@ +{% macro drug_status_sidebar(drug) %} + +{% endmacro %} diff --git a/sites/drugs_com/templates/account.html b/sites/drugs_com/templates/account.html index bf3a271..545dc93 100644 --- a/sites/drugs_com/templates/account.html +++ b/sites/drugs_com/templates/account.html @@ -26,9 +26,9 @@

    My Drugs.com — {{ current_user.username }}< @@ -99,7 +99,7 @@

    for {{ r.condition_treated }} · {{ r.created_at.strftime('%B %d, %Y') }}

    {{ r.body }}

    diff --git a/sites/drugs_com/templates/account_settings.html b/sites/drugs_com/templates/account_settings.html index 35e7a0c..c8a534a 100644 --- a/sites/drugs_com/templates/account_settings.html +++ b/sites/drugs_com/templates/account_settings.html @@ -12,9 +12,9 @@

    Account Settings

    {% with messages = get_flashed_messages(with_categories=true) %} diff --git a/sites/drugs_com/templates/account_subscriptions.html b/sites/drugs_com/templates/account_subscriptions.html index 9bd0109..e96306e 100644 --- a/sites/drugs_com/templates/account_subscriptions.html +++ b/sites/drugs_com/templates/account_subscriptions.html @@ -12,9 +12,9 @@

    Email Subscriptions & Preferences

    {% with messages = get_flashed_messages(with_categories=true) %} @@ -55,7 +55,7 @@

    Current subscription tier

    You have access to the full consumer drug database, the interaction checker, the pill identifier, and your personal Med List.

    Drugs.com Pro Edition

    Unlock prescriber-grade monographs, advanced interaction reports, and clinical decision support designed for healthcare professionals.

    - Upgrade to Pro Edition + Upgrade to Pro Edition {% endblock %} diff --git a/sites/drugs_com/templates/apps.html b/sites/drugs_com/templates/apps.html new file mode 100644 index 0000000..29ea1ff --- /dev/null +++ b/sites/drugs_com/templates/apps.html @@ -0,0 +1,63 @@ +{% extends "base.html" %} +{% block title %}Drugs.com Apps | {{ site_name }}{% endblock %} +{% block content %} +
    + + +

    Drugs.com Mobile Apps

    +

    Access trusted drug information on-the-go with the official Drugs.com mobile apps.

    + +
    +
    +
    📱
    +

    Drugs.com Medication Guide

    +

    The most trusted drug information app. Look up any medication, check interactions, identify pills, and manage your med list.

    +
      +
    • Drug information for 24,000+ medications
    • +
    • Drug interaction checker
    • +
    • Pill identifier with images
    • +
    • My Med List & reminders
    • +
    • FDA drug alerts
    • +
    + +
    + +
    +
    💊
    +

    Pill Identifier & Drug Search

    +

    Quickly identify unknown pills by imprint, shape, and color. Search our comprehensive drug database.

    +
      +
    • Identify pills by imprint code
    • +
    • Filter by shape and color
    • +
    • View pill images
    • +
    • Link to full drug information
    • +
    + +
    +
    + +
    + Free to download. Available for iOS and Android devices. Over 10 million downloads worldwide. +
    +
    +{% endblock %} diff --git a/sites/drugs_com/templates/base.html b/sites/drugs_com/templates/base.html index 0287541..fad2e2c 100644 --- a/sites/drugs_com/templates/base.html +++ b/sites/drugs_com/templates/base.html @@ -12,11 +12,11 @@
    Subscribe @@ -74,7 +74,7 @@ {% for L in ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','0-9'] %} {{ L }} {% endfor %} - Advanced Search + Advanced Search
    @@ -86,7 +86,7 @@ Drug Interaction Checker Compare Drugs News - Pro Edition + Pro Edition
    @@ -199,7 +199,7 @@

    Popular drugs

    Company

    @@ -236,8 +236,8 @@

    News

    + {% if faq_items %}
    -

    Frequently asked questions

    +

    Popular FAQ

    {% for item in faq_items %}
    @@ -267,82 +345,45 @@

    Frequently asked questions

    {% endfor %}
    -

    FAQs authored by the {{ site_name }} editorial team. Last reviewed {{ 'Nov 5, 2025' }}.

    -
    - {% endif %} - - {% if related_drugs %} -
    -

    Similar medications

    - {% if drug.drug_class %} -

    Other drugs in the {{ drug.drug_class.name }} class.

    - {% endif %} - - {% if drug.drug_class %} - {% set _class_name_lc = drug.drug_class.name | lower %} - - {% endif %} +

    More FAQ

    +
    {% endif %} -
    -

    User reviews for {{ drug.generic_name }}

    - {% if drug.avg_rating and drug.review_count %} -
    - {{ "%.1f"|format(drug.avg_rating) }} - /10 - — {{ drug.review_count }} ratings -
    - {% endif %} - {% if reviews %} -
    - {% for r in reviews[:3] %} - {% set star5 = ((r.rating or 0) / 2)|round(0, 'floor')|int %} -
    -
    - {{ r.rating }}/10 - - {% for s in range(1, 6) %}{% endfor %} - - {{ r.title }} - — {{ r.user.username if r.user else 'anon' }}, {{ r.created_at.strftime('%B %d, %Y') if r.created_at else '' }} -
    - {% if r.condition_treated %}

    Condition: {{ r.condition_treated|replace('_',' ')|replace('-',' ')|title }}

    {% endif %} -

    {{ r.body }}

    -
    +
    +

    More about {{ drug.generic_name }}

    + +

    Patient resources

    + +

    Professional resources

    + + {% if drug_conditions %} +

    Related treatment guides

    +
    - {% else %} -

    No reviews yet. Be the first to review {{ drug.generic_name }}.

    + {% endif %} -

    Read all {{ drug.review_count or 0 }} user reviews →

    @@ -438,105 +479,56 @@

    DRUG STATUS

    {% endif %} - {% if drug_conditions %} - - {% endif %} - - - - {% if recently_viewed %} - - {% endif %} - - - - {% if related %} + {% if related_news %} {% endif %} - - - + View larger images - {% if related_news %} - {% endblock %} diff --git a/sites/drugs_com/templates/drug_faq_page.html b/sites/drugs_com/templates/drug_faq_page.html new file mode 100644 index 0000000..47d5fda --- /dev/null +++ b/sites/drugs_com/templates/drug_faq_page.html @@ -0,0 +1,82 @@ +{% extends "base.html" %} +{% from "_drug_sidebar.html" import drug_status_sidebar %} +{% block title %}{{ drug.generic_name|capitalize }} FAQ - {{ site_name }}{% endblock %} +{% block content %} + + + + +
    +
    +
    + +
    +

    {{ drug.generic_name|capitalize }} — Frequently Asked Questions

    +

    + Medically reviewed by {{ drug.reviewer_name }}, {{ drug.reviewer_credential }}. Last updated on {{ drug.last_updated.strftime('%b %d, %Y') }}. +

    +
    + + {% if faq_items %} +
    +
    + {% for item in faq_items %} +
    + {{ item.q }} +

    {{ item.a }}

    +
    + {% endfor %} +
    +
    + {% else %} +
    +

    Frequently asked questions for {{ drug.generic_name }} are not yet available.

    +
    + {% endif %} + +
    +

    More about {{ drug.generic_name|capitalize }}

    + +
    + +
    +

    Further information

    +

    Remember, keep this and all other medicines out of the reach of children, never share your medicines with others, and use {{ drug.generic_name|capitalize }} only for the indication prescribed.

    +

    Always consult your healthcare provider to ensure the information displayed on this page applies to your personal circumstances.

    +

    Copyright 1996-{{ drug.last_updated.strftime('%Y') }} Drugs.com. Version: {{ drug.last_updated.strftime('%m.%y') }}.

    +
    + +

    « Back to full {{ drug.generic_name|capitalize }} information

    +
    +
    + +{{ drug_status_sidebar(drug) }} +
    +{% endblock %} diff --git a/sites/drugs_com/templates/drug_images.html b/sites/drugs_com/templates/drug_images.html index cd04998..7920875 100644 --- a/sites/drugs_com/templates/drug_images.html +++ b/sites/drugs_com/templates/drug_images.html @@ -1,14 +1,22 @@ {% extends "base.html" %} +{% from "_drug_sidebar.html" import drug_status_sidebar %} {% from "_pill_svg.html" import render_pill %} {% block title %}{{ drug.generic_name|capitalize }} Pill Images | {{ site_name }}{% endblock %} {% block content %} + +
    @@ -79,31 +86,16 @@

    No pill images are available for {{ drug.generic_name|capitalize }}.

    {% endif %} -

    - ← Back to {{ drug.generic_name|capitalize }} drug information -

    +
    +

    Further information

    +

    Always consult your healthcare provider to ensure the information displayed on this page applies to your personal circumstances.

    +

    Copyright 1996-{{ drug.last_updated.strftime('%Y') }} Drugs.com.

    +
    + +

    « Back to full {{ drug.generic_name|capitalize }} information

    - +{{ drug_status_sidebar(drug) }}
    {% endblock %} diff --git a/sites/drugs_com/templates/drug_interactions_page.html b/sites/drugs_com/templates/drug_interactions_page.html index cae9f7c..add44fc 100644 --- a/sites/drugs_com/templates/drug_interactions_page.html +++ b/sites/drugs_com/templates/drug_interactions_page.html @@ -1,5 +1,6 @@ {% extends "base.html" %} -{% block title %}{{ drug.generic_name|title }} Drug Interactions - {{ site_name }}{% endblock %} +{% from "_drug_sidebar.html" import drug_status_sidebar %} +{% block title %}{{ drug.generic_name|title }} Interactions Checker - {{ site_name }}{% endblock %} {% block content %} + +
    -

    {{ drug.generic_name|title }} Drug Interactions

    -

    A list of known interactions between {{ drug.generic_name|title }} and other -medications in our database. This list is not exhaustive — always tell your healthcare -provider about every medication and supplement you take.

    +

    {{ drug.generic_name|title }} Interactions

    +

    There are {{ summary.total if summary else 0 }} disease interactions and {{ interactions|length if interactions else 0 }} drug interactions with {{ drug.generic_name|title }}. Use the interaction checker to see interactions with other medications.

    + +
    +

    Does {{ drug.generic_name|title }} interact with any of my other drugs?

    +
    + + + +
    +
    {{ summary.total }} interaction{{ '' if summary.total == 1 else 's' }} found @@ -42,6 +55,22 @@

    {{ drug.generic_name|title }} Drug Interactions

    {{ sev }} {%- endmacro %} +{% if interactions %} +
    +

    Most frequently checked interactions

    +

    View interaction reports for {{ drug.generic_name|title }} and the medicines listed below.

    + +
    +{% endif %} +

    Drug-drug interactions

    {% if interactions %} {% for sev in ['major', 'moderate', 'minor', 'unknown'] %} @@ -112,6 +141,16 @@

    Drug-alcohol interactions

    alcohol use with your clinician regardless.

    {% endif %} +

    {{ drug.generic_name|title }} disease interactions

    +

    There are {{ drug.drug_class.name if drug.drug_class else 'this drug class' }} disease interactions that affect {{ drug.generic_name|title }}, including:

    +
      +
    • Renal dysfunction
    • +
    • Hepatic dysfunction
    • +
    • Cardiovascular disease
    • +
    • Gastrointestinal disorders
    • +
    • Coagulation disorders
    • +
    +

    Check for more interactions

    Add other medications you take alongside {{ drug.generic_name|title }} to see how they @@ -119,22 +158,34 @@

    Check for more interactions

    Open the interaction checker with {{ drug.generic_name|title }} pre-filled

    +{% if related_drugs %} +
    +

    See also:

    + +
    +{% endif %} + +
    +

    Further information

    +

    Always consult your healthcare provider to ensure the information displayed on this page applies to your personal circumstances.

    +

    Copyright 1996-{{ drug.last_updated.strftime('%Y') }} Drugs.com. Version: {{ drug.last_updated.strftime('%m.%y') }}.

    +
    +

    ← Back to full {{ drug.generic_name|title }} information

    - +{{ drug_status_sidebar(drug) }}
    {% endblock %} diff --git a/sites/drugs_com/templates/drug_pregnancy.html b/sites/drugs_com/templates/drug_pregnancy.html index ca44975..b8b9049 100644 --- a/sites/drugs_com/templates/drug_pregnancy.html +++ b/sites/drugs_com/templates/drug_pregnancy.html @@ -1,4 +1,5 @@ {% extends "base.html" %} +{% from "_drug_sidebar.html" import drug_status_sidebar %} {% block title %}{{ drug.generic_name|title }} Pregnancy and Breastfeeding Warnings - {{ site_name }}{% endblock %} {% block content %} + +

    {{ drug.generic_name|title }} Pregnancy and Breastfeeding Warnings

    @@ -35,7 +41,7 @@

    {{ drug.generic_name|title }} Pregnancy and Breastfeeding Warnings

    -

    Risk Summary

    +

    {{ drug.generic_name|title }} Pregnancy Warnings

    {{ preg.risk_summary }}

    @@ -99,7 +105,7 @@

    Bottom line

    -

    Lactation / Breastfeeding

    +

    {{ drug.generic_name|title }} Breastfeeding Warnings

    {{ preg.breastfeeding }}

    Whether {{ drug.generic_name|title }} passes into human milk in clinically significant amounts depends on the specific drug's pharmacokinetic profile. Many medications are excreted @@ -125,22 +131,16 @@

    General recommendations

    Talk to a healthcare provider about {{ drug.generic_name|title }} ›

    +
    +

    Further information

    +

    Always consult your healthcare provider to ensure the information displayed on this page applies to your personal circumstances.

    +

    Copyright 1996-{{ drug.last_updated.strftime('%Y') }} Drugs.com.

    +
    +

    ← Back to {{ drug.generic_name|title }} drug information

    - +{{ drug_status_sidebar(drug) }}
    {% endblock %} diff --git a/sites/drugs_com/templates/drug_prices.html b/sites/drugs_com/templates/drug_prices.html index 44f0b8d..f0c9cc3 100644 --- a/sites/drugs_com/templates/drug_prices.html +++ b/sites/drugs_com/templates/drug_prices.html @@ -1,11 +1,34 @@ {% extends "base.html" %} +{% from "_drug_sidebar.html" import drug_status_sidebar %} {% block title %}{{ drug.generic_name }} Prices, Coupons & Savings Tips | {{ site_name }}{% endblock %} {% block content %} -
    -
    + - {% if related is defined and related %} - - {% endif %} - - +{{ drug_status_sidebar(drug) }}