fintechdarkpatterns / ai-backend /generate_samples.py
yujisium's picture
Fintech Dark Patterns NLP Detector - full project upload
5752a28 verified
Raw
History Blame Contribute Delete
5.99 kB
"""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}")