Spaces:
Runtime error
Runtime error
| """Generate synthetic fintech UI screenshots containing dark-pattern copy. | |
| The screenshots are plain rendered UI with real text so Tesseract can read | |
| them and the classifier has something genuine to detect. Run once: | |
| python generate_samples.py | |
| Outputs PNGs into ./samples/ (served by Flask at /api/samples/<name>). | |
| """ | |
| from PIL import Image, ImageDraw, ImageFont | |
| import os | |
| OUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "samples") | |
| os.makedirs(OUT_DIR, exist_ok=True) | |
| W, H = 900, 640 | |
| BG = (248, 249, 252) | |
| CARD = (255, 255, 255) | |
| INK = (24, 28, 40) | |
| MUTED = (110, 118, 135) | |
| ACCENT = (79, 70, 229) | |
| DANGER = (220, 53, 69) | |
| WARN_BG = (255, 243, 205) | |
| WARN_INK = (133, 100, 4) | |
| GREEN = (25, 135, 84) | |
| def font(size, bold=False): | |
| names = ["arialbd.ttf"] if bold else ["arial.ttf"] | |
| for name in names + ["DejaVuSans-Bold.ttf" if bold else "DejaVuSans.ttf"]: | |
| try: | |
| return ImageFont.truetype(name, size) | |
| except OSError: | |
| continue | |
| return ImageFont.load_default() | |
| def card(d, x0, y0, x1, y1, fill=CARD, outline=(225, 228, 235)): | |
| d.rounded_rectangle([x0, y0, x1, y1], radius=12, fill=fill, outline=outline, width=1) | |
| def button(d, x0, y0, x1, y1, label, fill=ACCENT, ink=(255, 255, 255), size=20, bold=True): | |
| d.rounded_rectangle([x0, y0, x1, y1], radius=8, fill=fill) | |
| f = font(size, bold) | |
| tw = d.textlength(label, font=f) | |
| d.text(((x0 + x1 - tw) / 2, (y0 + y1) / 2 - size / 2 - 2), label, font=f, fill=ink) | |
| def make_checkout(): | |
| img = Image.new("RGB", (W, H), BG) | |
| d = ImageDraw.Draw(img) | |
| d.rectangle([0, 0, W, 64], fill=(30, 34, 50)) | |
| d.text((30, 20), "PayVault | Secure Checkout", font=font(22, True), fill=(255, 255, 255)) | |
| card(d, 30, 90, 560, 600) | |
| d.text((55, 110), "Premium Trading Plan", font=font(26, True), fill=INK) | |
| d.text((55, 150), "$29.99 / month", font=font(22), fill=MUTED) | |
| d.rounded_rectangle([55, 195, 535, 245], radius=8, fill=WARN_BG) | |
| d.text((70, 207), "Hurry! Offer expires in 04:59 minutes", font=font(20, True), fill=WARN_INK) | |
| d.rounded_rectangle([55, 260, 535, 310], radius=8, fill=(253, 232, 232)) | |
| d.text((70, 272), "Only 2 spots left at this price!", font=font(20, True), fill=DANGER) | |
| d.text((55, 335), "17 people are viewing this offer right now", font=font(18), fill=MUTED) | |
| d.text((55, 370), "1,358 traders upgraded this week", font=font(18), fill=MUTED) | |
| button(d, 55, 430, 535, 485, "CLAIM MY DISCOUNT NOW") | |
| d.text((150, 510), "No thanks, I prefer losing money", font=font(15), fill=(180, 184, 195)) | |
| card(d, 590, 90, 870, 600) | |
| d.text((615, 110), "Order Summary", font=font(20, True), fill=INK) | |
| d.text((615, 155), "Plan", font=font(16), fill=MUTED) | |
| d.text((800, 155), "$29.99", font=font(16), fill=INK) | |
| d.text((615, 185), "Priority support", font=font(16), fill=MUTED) | |
| d.text((800, 185), "$4.99", font=font(16), fill=INK) | |
| d.line([615, 225, 845, 225], fill=(225, 228, 235), width=1) | |
| d.text((615, 240), "Total today", font=font(18, True), fill=INK) | |
| d.text((790, 240), "$34.98", font=font(18, True), fill=INK) | |
| img.save(os.path.join(OUT_DIR, "checkout_urgency.png")) | |
| def make_cancellation(): | |
| img = Image.new("RGB", (W, H), BG) | |
| d = ImageDraw.Draw(img) | |
| d.rectangle([0, 0, W, 64], fill=(30, 34, 50)) | |
| d.text((30, 20), "CoinNest | Account Settings", font=font(22, True), fill=(255, 255, 255)) | |
| card(d, 150, 110, 750, 560) | |
| d.text((185, 140), "Wait! Before you go...", font=font(28, True), fill=INK) | |
| d.text((185, 195), "Are you sure you want to cancel? You will lose all", font=font(19), fill=INK) | |
| d.text((185, 225), "your rewards and your exclusive member benefits", font=font(19), fill=INK) | |
| d.text((185, 255), "will be gone forever.", font=font(19), fill=INK) | |
| d.text((185, 305), "93% of members regret cancelling within a month", font=font(17), fill=MUTED) | |
| button(d, 185, 360, 715, 415, "KEEP MY BENEFITS", fill=GREEN) | |
| button(d, 185, 430, 715, 480, "Remind me later", fill=(235, 237, 242), ink=MUTED, bold=False) | |
| d.text((340, 505), "continue to cancellation step 1 of 6", font=font(13), fill=(195, 198, 207)) | |
| img.save(os.path.join(OUT_DIR, "cancel_confirmshaming.png")) | |
| def make_signup(): | |
| img = Image.new("RGB", (W, H), BG) | |
| d = ImageDraw.Draw(img) | |
| d.rectangle([0, 0, W, 64], fill=(30, 34, 50)) | |
| d.text((30, 20), "LoanLeap | Create Account", font=font(22, True), fill=(255, 255, 255)) | |
| card(d, 120, 100, 780, 580) | |
| d.text((155, 125), "You're almost done!", font=font(26, True), fill=INK) | |
| def checkbox(y, label, sub, checked=True): | |
| d.rounded_rectangle([155, y, 179, y + 24], radius=5, | |
| fill=ACCENT if checked else CARD, | |
| outline=(200, 203, 212), width=1) | |
| if checked: | |
| d.line([160, y + 12, 166, y + 18], fill=(255, 255, 255), width=3) | |
| d.line([166, y + 18, 175, y + 6], fill=(255, 255, 255), width=3) | |
| d.text((195, y), label, font=font(18, True), fill=INK) | |
| d.text((195, y + 28), sub, font=font(15), fill=MUTED) | |
| checkbox(180, "Add Payment Protection Plus (+$9.99/mo)", | |
| "Automatically added to protect your transactions") | |
| checkbox(255, "Enroll in Premium Credit Monitoring (+$14.99/mo)", | |
| "Free trial converts to paid plan after 7 days") | |
| checkbox(330, "Share my data with trusted marketing partners", | |
| "By continuing you agree to receive personalised offers") | |
| d.text((155, 410), "By clicking Continue you accept the Terms, the Fee Schedule,", font=font(14), fill=MUTED) | |
| d.text((155, 432), "and authorise recurring charges to your saved card.", font=font(14), fill=MUTED) | |
| button(d, 155, 475, 745, 530, "CONTINUE") | |
| img.save(os.path.join(OUT_DIR, "signup_sneaking.png")) | |
| if __name__ == "__main__": | |
| make_checkout() | |
| make_cancellation() | |
| make_signup() | |
| print(f"Saved 3 sample screenshots to {OUT_DIR}") | |