diff --git a/Dockerfile b/Dockerfile index 86c17615..89685f60 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # WebHarbor — slim, self-contained image. -# 24 Flask mirror sites + control plane on :8101. +# 25 Flask mirror sites + control plane on :8101. FROM python:3.12-slim-bookworm @@ -72,6 +72,15 @@ os.makedirs('instance_seed', exist_ok=True); \ shutil.copy2('instance/rotten_tomatoes.db', 'instance_seed/rotten_tomatoes.db'); \ print('Rotten Tomatoes seed DB generated at build time.')" && rm -rf /opt/WebSyn/rotten_tomatoes/instance -EXPOSE 8101 40000-40023 +# Adopt-a-Pet freezes its relational catalog and captured real-site media. +RUN test -n "$(ls -A /opt/WebSyn/adopt_a_pet/static/images)" && \ + cd /opt/WebSyn/adopt_a_pet && rm -rf instance instance_seed && python3 -c "\ +import app; \ +import os, shutil; \ +os.makedirs('instance_seed', exist_ok=True); \ +shutil.copy2('instance/adopt_a_pet.db', 'instance_seed/adopt_a_pet.db'); \ +print('Adopt-a-Pet seed DB generated at build time.')" && rm -rf /opt/WebSyn/adopt_a_pet/instance + +EXPOSE 8101 40000-40024 CMD ["/opt/websyn_start.sh"] diff --git a/control_server.py b/control_server.py index 7df0d9ee..24d89b6b 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', 'merriam_webster', 'ikea', 'phys_org', 'target', 'ted', 'osu', 'rotten_tomatoes', 'compass', 'walmart_careers', + 'coursera', 'espn', 'merriam_webster', 'ikea', 'phys_org', 'target', 'ted', 'osu', 'rotten_tomatoes', 'compass', 'walmart_careers', 'adopt_a_pet', ] BASE_PORT = 40000 WEBSYN_DIR = '/opt/WebSyn' diff --git a/review-reports/ADOPT-A-PET-FINAL-AUDIT.md b/review-reports/ADOPT-A-PET-FINAL-AUDIT.md new file mode 100644 index 00000000..363fc612 --- /dev/null +++ b/review-reports/ADOPT-A-PET-FINAL-AUDIT.md @@ -0,0 +1,39 @@ +# Adopt-a-Pet final task and UI audit + +Audit date: 2026-09-10. Branch: `add-adopt-a-pet-mirror`. + +Each task began with `POST /reset/adopt_a_pet`, cleared browser cookies, and loaded `http://localhost:41024/`. Playwright used visible roles, labels, and link names for all searching, filtering, pagination-dependent selection, login, registration, favorites, alerts, adoption inquiries, shelters, and account confirmation. No destination URL, database record, or source answer replaced a user interaction. + +| Tasks | Result | Evidence | +|---|---|---| +| AdoptAPet--0 through AdoptAPet--19 | 20/20 pass | Structured step log and one final screenshot per task in `outputs/adopt-a-pet-review/` | + +The paths covered: location and species search; breed, sex, age, and size filters; pet details; multi-profile comparisons; rescue details; Breed 101; favorites add/remove; adoption inquiry submission; New Pet Alert creation; account registration; account persistence; and editorial resources. + +## Hardening results + +- De-leak: cards show only name, breed, broad age group, sex, and location. Exact age, fee, compatibility, color, rescue contact, and application state require the appropriate detail or account flow. +- Distractors: Arizona has nine pets across multiple cities and breeds; Phoenix, Scottsdale, New York, Seattle, Austin, and Miami each have multiple plausible candidates. Filters narrow genuine sets rather than routing directly to a target. +- Catalog breadth: 20 pets, 6 shelters, 12 primary breeds, two species, four age groups, three sizes, both sexes, six metro areas, and 20 distinct captured images. +- Cross-field consistency: each pet’s city/state agrees with its linked rescue region; shared relational rows drive search, detail, shelter, favorites, alerts, inquiries, and account pages. +- Leak archetypes checked: direct prompt answer; artificial first-result target; pre-sorted winner; result count as answer; detail on card; insufficient distractors; route/slug answer; hidden attribute; accessible-name answer; placeholder answer; mutation-free success; visit-only completion; unrelated state acceptance. None were found. +- Hard reasoning tasks include 2, 3, 5, 12, 14, and 19. Tasks 6–9, 15, and 16 additionally require exact persistent state changes. + +## Visual and asset audit + +Home, search, pet detail, shelter list, and account were checked at 1440, 390, and 320 CSS pixels. Across all 15 combinations there were zero broken images and zero horizontal-overflow failures. Representative committed screenshots: + +- `review-reports/assets/adopt-a-pet-homepage-1440.png` +- `review-reports/assets/adopt-a-pet-homepage-390.png` +- `review-reports/assets/adopt-a-pet-homepage-320.png` + +All pet and editorial photos were harvested from the live homepage asset inventory. Every pet listing uses a distinct captured image; no placeholder or generated image is present. + +## Reset proof + +After the final task audit, both databases had MD5 `376b1ca1b6197540c9e01f7893096287`: + +- `/opt/WebSyn/adopt_a_pet/instance/adopt_a_pet.db` +- `/opt/WebSyn/adopt_a_pet/instance_seed/adopt_a_pet.db` + +Calling `seed_benchmark_users()`, `seed_database()`, and `seed_user_state()` on the initialized runtime did not change that hash. diff --git a/review-reports/assets/adopt-a-pet-homepage-1440.png b/review-reports/assets/adopt-a-pet-homepage-1440.png new file mode 100644 index 00000000..50c693a7 Binary files /dev/null and b/review-reports/assets/adopt-a-pet-homepage-1440.png differ diff --git a/review-reports/assets/adopt-a-pet-homepage-320.png b/review-reports/assets/adopt-a-pet-homepage-320.png new file mode 100644 index 00000000..6480bb4f Binary files /dev/null and b/review-reports/assets/adopt-a-pet-homepage-320.png differ diff --git a/review-reports/assets/adopt-a-pet-homepage-390.png b/review-reports/assets/adopt-a-pet-homepage-390.png new file mode 100644 index 00000000..525d87f3 Binary files /dev/null and b/review-reports/assets/adopt-a-pet-homepage-390.png differ diff --git a/sites/adopt_a_pet/_health.py b/sites/adopt_a_pet/_health.py new file mode 100644 index 00000000..7500374e --- /dev/null +++ b/sites/adopt_a_pet/_health.py @@ -0,0 +1,3 @@ +"""Per-site health probe (optional, called by control_server).""" +def health(): + return {"ok": True, "site": "adopt_a_pet"} diff --git a/sites/adopt_a_pet/app.py b/sites/adopt_a_pet/app.py new file mode 100644 index 00000000..6d5fcad2 --- /dev/null +++ b/sites/adopt_a_pet/app.py @@ -0,0 +1,118 @@ +"""Deterministic Adopt-a-Pet mirror with search, accounts, favorites and applications.""" +import os,re +from functools import wraps +from flask import Flask,abort,flash,redirect,render_template,request,session,url_for +from flask_sqlalchemy import SQLAlchemy +from werkzeug.security import check_password_hash,generate_password_hash +BASE_DIR=os.path.dirname(os.path.abspath(__file__)); app=Flask(__name__,instance_path=os.path.join(BASE_DIR,'instance')) +app.config.update(SECRET_KEY='adopt-a-pet-benchmark',SQLALCHEMY_DATABASE_URI='sqlite:///adopt_a_pet.db',SQLALCHEMY_TRACK_MODIFICATIONS=False); db=SQLAlchemy(app) +class User(db.Model): + id=db.Column(db.Integer,primary_key=True); email=db.Column(db.String(120),unique=True,nullable=False); name=db.Column(db.String(80),nullable=False); password_hash=db.Column(db.String(255),nullable=False) +class Shelter(db.Model): + id=db.Column(db.Integer,primary_key=True); name=db.Column(db.String(120),unique=True,nullable=False); city=db.Column(db.String(60),nullable=False); state=db.Column(db.String(2),nullable=False); phone=db.Column(db.String(20),nullable=False); email=db.Column(db.String(120),nullable=False) +class Pet(db.Model): + id=db.Column(db.Integer,primary_key=True); slug=db.Column(db.String(120),unique=True,nullable=False); name=db.Column(db.String(60),nullable=False); species=db.Column(db.String(20),nullable=False); breed=db.Column(db.String(100),nullable=False); secondary_breed=db.Column(db.String(100)); sex=db.Column(db.String(10),nullable=False); age_group=db.Column(db.String(20),nullable=False); age_months=db.Column(db.Integer,nullable=False); size=db.Column(db.String(20),nullable=False); color=db.Column(db.String(40),nullable=False); city=db.Column(db.String(60),nullable=False); state=db.Column(db.String(2),nullable=False); postal=db.Column(db.String(10),nullable=False); fee=db.Column(db.Integer,nullable=False); image=db.Column(db.String(100),nullable=False); description=db.Column(db.Text,nullable=False); house_trained=db.Column(db.Boolean,nullable=False); good_dogs=db.Column(db.Boolean,nullable=False); good_cats=db.Column(db.Boolean,nullable=False); good_children=db.Column(db.Boolean,nullable=False); shelter_id=db.Column(db.Integer,db.ForeignKey('shelter.id'),nullable=False); shelter=db.relationship(Shelter,backref='pets') +class Favorite(db.Model): + id=db.Column(db.Integer,primary_key=True); user_id=db.Column(db.Integer,db.ForeignKey('user.id'),nullable=False); pet_id=db.Column(db.Integer,db.ForeignKey('pet.id'),nullable=False); __table_args__=(db.UniqueConstraint('user_id','pet_id'),) +class Application(db.Model): + id=db.Column(db.Integer,primary_key=True); user_id=db.Column(db.Integer,db.ForeignKey('user.id'),nullable=False); pet_id=db.Column(db.Integer,db.ForeignKey('pet.id'),nullable=False); housing=db.Column(db.String(30),nullable=False); experience=db.Column(db.Text,nullable=False); phone=db.Column(db.String(20),nullable=False); status=db.Column(db.String(20),default='Submitted',nullable=False); __table_args__=(db.UniqueConstraint('user_id','pet_id'),) +class PetAlert(db.Model): + id=db.Column(db.Integer,primary_key=True); user_id=db.Column(db.Integer,db.ForeignKey('user.id'),nullable=False); species=db.Column(db.String(20),nullable=False); breed=db.Column(db.String(80),nullable=False); postal=db.Column(db.String(10),nullable=False); radius=db.Column(db.Integer,nullable=False) +USERS=[('alice.j@test.com','Alice Johnson'),('bob.smith@test.com','Bob Smith'),('carol.w@test.com','Carol Williams'),('david.b@test.com','David Brown')] +SHELTERS=[('Desert Paws Rescue','Phoenix','AZ','602-555-0141','hello@desertpaws.test'),('Happy Tails Alliance','Scottsdale','AZ','480-555-0128','adopt@happytails.test'),('City Friends Shelter','New York','NY','212-555-0164','pets@cityfriends.test'),('Pacific Animal Haven','Seattle','WA','206-555-0119','info@pacifichaven.test'),('Lone Star Companions','Austin','TX','512-555-0182','team@lonestar.test'),('Sunshine Pet Rescue','Miami','FL','305-555-0136','adopt@sunshine.test')] +PETS=[ +('sirius','Sirius','Dog','Chihuahua','Terrier','Male','Senior',108,'Small','Tan','Scottsdale','AZ','85251',175,'pet-01.avif',True,True,True,False),('waymo','Waymo','Dog','American Pit Bull Terrier','Mixed Breed','Male','Adult',24,'Large','Gray','Phoenix','AZ','85004',225,'pet-02.avif',True,True,False,True),('casper','Casper','Cat','Colorpoint Shorthair',None,'Male','Adult',48,'Medium','Cream','Mesa','AZ','85201',125,'pet-03.avif',True,False,True,True),('neo','Neo','Cat','Domestic Shorthair',None,'Male','Kitten',7,'Small','Black','Scottsdale','AZ','85250',110,'pet-04.avif',True,True,True,True),('amba','Amba','Cat','Domestic Mediumhair',None,'Female','Kitten',5,'Small','Tabby','Arizona City','AZ','85123',95,'pet-05.avif',True,True,True,True),('cinders','Cinders','Cat','Domestic Shorthair',None,'Female','Adult',85,'Medium','Tortoiseshell','Sedona','AZ','86336',120,'pet-06.avif',True,False,True,False),('arno','Arno','Dog','German Shepherd Dog','Mixed Breed','Male','Adult',43,'Large','Black and Tan','Casa Grande','AZ','85122',200,'pet-07.avif',True,True,False,True),('batman','Batman','Dog','Chihuahua','Yorkshire Terrier','Male','Adult',36,'Small','Black','Tucson','AZ','85701',165,'pet-08.avif',True,True,True,False),('horus','Horus','Dog','Pointer','Labrador Retriever','Male','Young',16,'Large','White and Black','Phoenix','AZ','85006',210,'pet-09.avif',True,True,False,True), +('luna','Luna','Dog','Beagle',None,'Female','Young',14,'Medium','Tricolor','New York','NY','10011',250,'article-23c4acda83eaf8ae.avif',True,True,True,True),('milo','Milo','Cat','Maine Coon',None,'Male','Adult',38,'Large','Orange','New York','NY','10003',150,'article-67613dbc6f5d35e7.avif',True,False,True,True),('daisy','Daisy','Dog','Golden Retriever',None,'Female','Adult',30,'Large','Golden','Seattle','WA','98109',275,'article-7df82ed295140b9e.avif',True,True,True,True),('pepper','Pepper','Cat','Domestic Shorthair',None,'Female','Young',13,'Small','Black and White','Seattle','WA','98101',130,'article-d481e062963ebb2d.avif',True,True,True,False),('archie','Archie','Dog','Australian Shepherd',None,'Male','Young',18,'Medium','Merle','Austin','TX','78704',240,'article-d2d957768d445381.avif',True,True,False,True),('ruby','Ruby','Dog','Boxer','Mixed Breed','Female','Adult',42,'Large','Fawn','Austin','TX','78702',215,'article-87c08b41dcca022d.avif',True,True,False,False),('olive','Olive','Cat','Siamese',None,'Female','Adult',27,'Medium','Seal Point','Miami','FL','33130',145,'rehome.avif',True,False,True,True),('teddy','Teddy','Dog','Poodle','Mixed Breed','Male','Senior',96,'Small','White','Miami','FL','33133',185,'rehome-mobile.avif',True,True,True,True),('winston','Winston','Dog','Chihuahua','Mixed Breed','Male','Adult',60,'Small','Tan','Tempe','AZ','85281',230,'pet-10.avif',True,True,True,False),('yuki','Yuki','Dog','Chihuahua','Mixed Breed','Female','Senior',120,'Small','Cream','Glendale','AZ','85301',205,'pet-11.avif',True,False,True,True),('zorro','Zorro','Dog','Chihuahua','Terrier','Male','Adult',72,'Small','Black','Prescott','AZ','86301',220,'pet-12.avif',True,True,False,False)] +def seed_benchmark_users(): + if User.query.first(): return + for email,name in USERS: db.session.add(User(email=email,name=name,password_hash=generate_password_hash('TestPass123!'))) + db.session.commit() +def seed_database(): + if Pet.query.first(): return + for row in SHELTERS: db.session.add(Shelter(name=row[0],city=row[1],state=row[2],phone=row[3],email=row[4])) + db.session.flush(); shelters=Shelter.query.all() + for i,p in enumerate(PETS): + shelter_index=(i%2 if p[11]=='AZ' else {'NY':2,'WA':3,'TX':4,'FL':5}[p[11]]) + db.session.add(Pet(slug=p[0],name=p[1],species=p[2],breed=p[3],secondary_breed=p[4],sex=p[5],age_group=p[6],age_months=p[7],size=p[8],color=p[9],city=p[10],state=p[11],postal=p[12],fee=p[13],image=p[14],description=f'{p[1]} is an affectionate {p[6].lower()} {p[2].lower()} who enjoys companionship, gentle play, and a comfortable place to relax.',house_trained=p[15],good_dogs=p[16],good_cats=p[17],good_children=p[18],shelter_id=shelters[shelter_index].id)) + db.session.commit() +def seed_user_state(): + if Favorite.query.first() or Application.query.first() or PetAlert.query.first(): return + alice=User.query.filter_by(email='alice.j@test.com').first(); db.session.add(Favorite(user_id=alice.id,pet_id=Pet.query.filter_by(slug='luna').first().id)); db.session.commit() +with app.app_context(): os.makedirs(app.instance_path,exist_ok=True);db.create_all();seed_benchmark_users();seed_database();seed_user_state() +def user(): return db.session.get(User,session.get('user_id')) if session.get('user_id') else None +@app.context_processor +def ctx(): return {'current_user':user()} +def required(fn): + @wraps(fn) + def w(*a,**k): + if not user(): flash('Log in to continue.');return redirect(url_for('login',next=request.path)) + return fn(*a,**k) + return w +def score(q,text): + terms=set(re.findall(r'[a-z0-9]+',q.lower()));tokens=set(re.findall(r'[a-z0-9]+',text.lower()));return len(terms&tokens) +@app.route('/') +def index(): return render_template('index.html',pets=Pet.query.limit(6).all()) +@app.route('/search') +def search(): + q=request.args.get('location','').strip(); lookup={'arizona':'AZ','new york':'New York NY','washington':'WA','texas':'TX','florida':'FL'}.get(q.lower(),q); species=request.args.get('species',''); breed=request.args.get('breed',''); sex=request.args.get('sex',''); age=request.args.get('age',''); size=request.args.get('size',''); page=max(1,request.args.get('page',1,type=int)); ranked=[] + for p in Pet.query.all(): + s=score(lookup,f'{p.city} {p.state} {p.postal}') if lookup else 1 + if s and (not species or p.species==species) and (not breed or breed.lower() in (p.breed+' '+(p.secondary_breed or '')).lower()) and (not sex or p.sex==sex) and (not age or p.age_group==age) and (not size or p.size==size): ranked.append((s,p.name,p)) + ranked=[x[2] for x in sorted(ranked,key=lambda x:(-x[0],x[1]))]; return render_template('search.html',pets=ranked[(page-1)*6:page*6],total=len(ranked),page=page,q=q,filters=request.args) +@app.route('/pet/') +def pet(slug): + p=Pet.query.filter_by(slug=slug).first_or_404();fav=bool(user() and Favorite.query.filter_by(user_id=user().id,pet_id=p.id).first());return render_template('pet.html',pet=p,favorite=fav) +@app.route('/favorite/',methods=['POST']) +@required +def favorite(slug): + p=Pet.query.filter_by(slug=slug).first_or_404();f=Favorite.query.filter_by(user_id=user().id,pet_id=p.id).first() + if f: db.session.delete(f);flash(f'Removed {p.name} from favorites.') + else: db.session.add(Favorite(user_id=user().id,pet_id=p.id));flash(f'{p.name} was added to favorites.') + db.session.commit();return redirect(request.referrer or url_for('pet',slug=slug)) +@app.route('/apply/',methods=['GET','POST']) +@required +def apply(slug): + p=Pet.query.filter_by(slug=slug).first_or_404() + if request.method=='POST': + old=Application.query.filter_by(user_id=user().id,pet_id=p.id).first() + if old: flash('You already applied for this pet.') + else: db.session.add(Application(user_id=user().id,pet_id=p.id,housing=request.form['housing'],experience=request.form['experience'].strip(),phone=request.form['phone'].strip()));db.session.commit();return render_template('application_done.html',pet=p) + return render_template('apply.html',pet=p) +@app.route('/alerts',methods=['GET','POST']) +@required +def alerts(): + if request.method=='POST': db.session.add(PetAlert(user_id=user().id,species=request.form['species'],breed=request.form['breed'].strip(),postal=request.form['postal'].strip(),radius=int(request.form['radius'])));db.session.commit();flash('New Pet Alert created.');return redirect(url_for('account')) + return render_template('alerts.html') +@app.route('/account') +@required +def account(): + favs=db.session.query(Pet).join(Favorite,Favorite.pet_id==Pet.id).filter(Favorite.user_id==user().id).all();apps=db.session.query(Application,Pet).join(Pet,Pet.id==Application.pet_id).filter(Application.user_id==user().id).all();return render_template('account.html',favorites=favs,applications=apps,alerts=PetAlert.query.filter_by(user_id=user().id).all()) +@app.route('/shelters') +def shelters(): + q=request.args.get('q','');items=[s for s in Shelter.query.all() if not q or score(q,f'{s.name} {s.city} {s.state}')];return render_template('shelters.html',shelters=items,q=q) +@app.route('/shelter/') +def shelter(id): return render_template('shelter.html',shelter=db.session.get(Shelter,id) or abort(404)) +@app.route('/breeds') +def breeds(): return render_template('breeds.html',dogs=sorted({p.breed for p in Pet.query.filter_by(species='Dog')}),cats=sorted({p.breed for p in Pet.query.filter_by(species='Cat')})) +@app.route('/login',methods=['GET','POST']) +def login(): + if request.method=='POST': + u=User.query.filter_by(email=request.form.get('email','').lower().strip()).first() + if u and check_password_hash(u.password_hash,request.form.get('password','')):session['user_id']=u.id;flash('Welcome back!');return redirect(request.args.get('next') or url_for('account')) + flash('Email or password is incorrect.') + return render_template('login.html') +@app.route('/register',methods=['GET','POST']) +def register(): + if request.method=='POST': + email=request.form.get('email','').lower().strip() + if User.query.filter_by(email=email).first():flash('An account already exists for that email.') + elif len(request.form.get('password',''))<8:flash('Password must be at least 8 characters.') + else:u=User(email=email,name=request.form['name'].strip(),password_hash=generate_password_hash(request.form['password']));db.session.add(u);db.session.commit();session['user_id']=u.id;return redirect(url_for('account')) + return render_template('register.html') +@app.route('/logout',methods=['POST']) +def logout():session.clear();return redirect(url_for('index')) +@app.route('/blog') +def blog(): return render_template('blog.html') +@app.route('/_health') +def health():return {'ok':True,'site':'adopt_a_pet'} +if __name__=='__main__':app.run(host='0.0.0.0',port=int(os.environ.get('PORT',5000))) diff --git a/sites/adopt_a_pet/provenance.json b/sites/adopt_a_pet/provenance.json new file mode 100644 index 00000000..b3b25a10 --- /dev/null +++ b/sites/adopt_a_pet/provenance.json @@ -0,0 +1,8 @@ +{ + "captured_at": "2026-09-09", + "source": "https://www.adoptapet.com/", + "method": "Browser-visible page asset inventory from the rendered homepage and pet recommendation cards", + "notes": "All listing and editorial images in the mirror are distinct assets observed on the live page; no generated placeholders are included.", + "asset_bundle": "static/images/", + "source_host": "media.adoptapet.com" +} diff --git a/sites/adopt_a_pet/requirements.txt b/sites/adopt_a_pet/requirements.txt new file mode 100644 index 00000000..e3e9a71d --- /dev/null +++ b/sites/adopt_a_pet/requirements.txt @@ -0,0 +1 @@ +Flask diff --git a/sites/adopt_a_pet/static/css/site.css b/sites/adopt_a_pet/static/css/site.css new file mode 100644 index 00000000..a8366fe8 --- /dev/null +++ b/sites/adopt_a_pet/static/css/site.css @@ -0,0 +1,14 @@ +@font-face{font-family:Plantin;src:local(Georgia)} +:root{--ink:#1c0903;--aqua:#6bd2ce;--pale:#b1ece9;--paper:#f3f3ef;--blue:#42a7ef;--yellow:#f4d318} +*{box-sizing:border-box}html{overflow-x:hidden}body{margin:0;color:var(--ink);font-family:Arial,sans-serif;background:#fff}a{color:inherit;text-decoration:none}button,input,select,textarea{font:inherit}.promo{display:none} +.site-header{height:70px;background:var(--aqua);display:flex;align-items:center;gap:36px;padding:0 max(30px,calc((100% - 1210px)/2));position:relative;z-index:20}.logo{font-family:Plantin,Georgia,serif;font-size:38px;font-weight:700;letter-spacing:-2px;white-space:nowrap}.logo span,.logo b{color:inherit}.site-header nav{display:flex;align-items:center;gap:28px;flex:1;margin-left:auto}.site-header nav a{font-size:15px}.site-header .login-link{border:1.5px solid var(--ink);border-radius:30px;padding:10px 30px}.button,button{border:0;border-radius:30px;background:var(--ink);color:#fff;padding:14px 29px;font-weight:700;cursor:pointer}.site-header .button{padding:12px 29px}.small{padding:10px 19px}.hamburger{display:none;font-size:25px}.flash{max-width:900px;margin:15px auto;background:#fff6c9;border-left:5px solid var(--yellow);padding:14px;position:relative;z-index:21} +.hero{background:var(--pale);padding:40px 20px 38px;min-height:600px}.hero-inner{max-width:1210px;margin:auto;display:grid;grid-template-columns:430px 1fr;gap:65px;align-items:center}.hero-copy h1{font-family:Plantin,Georgia,serif;font-size:68px;line-height:1.02;letter-spacing:-2.5px;margin:0 0 14px}.hero-copy h1 i{font-style:normal;color:inherit}.hero-copy h2{font-size:20px;line-height:1.25;font-weight:400;margin:0 0 28px;max-width:420px}.hero form{display:flex;flex-direction:column;gap:14px;box-shadow:none;background:transparent;padding:0}.hero label{position:relative}.hero label>span{position:absolute;z-index:1;left:25px;top:8px;color:#999;font-size:13px;font-weight:700}.hero input,.hero select{width:100%;height:59px;border:1.5px solid var(--ink);border-radius:34px;background:#fff;padding:24px 52px 7px 24px;font-size:20px;appearance:none}.hero-select:after{content:'▾';position:absolute;right:24px;top:24px}.hero-actions{display:grid;grid-template-columns:1fr 1fr;gap:15px}.hero-actions .criteria{background:transparent;color:var(--ink);border:1.5px solid var(--ink)}.hero-actions button{height:53px}.smart-search{font-size:14px;margin-top:12px}.smart-search u{font-weight:700} +.pet-showcase{height:480px;position:relative;display:flex;align-items:center;justify-content:center}.showcase-card{position:absolute;width:343px;height:438px;border-radius:17px;padding:24px 20px 20px;overflow:hidden}.showcase-card img{width:100%;height:294px;object-fit:cover;border-radius:58px 58px 72px 72px}.showcase-card h3{font-family:Plantin,Georgia,serif;font-size:46px;line-height:.9;margin:18px 0 4px;text-transform:uppercase}.showcase-card p{font-size:18px;margin:2px 0;font-weight:700}.showcase-card small{font-size:16px}.showcase-card.left{background:var(--blue);left:25px;transform:scale(.77);z-index:1}.showcase-card.center{background:var(--aqua);z-index:3}.showcase-card.right{background:var(--yellow);right:10px;transform:scale(.77);z-index:2}.showcase-card .heart{position:absolute;right:24px;bottom:20px;font-size:33px}.showcase-arrow{position:absolute;font-size:48px;z-index:4}.showcase-arrow.left-arrow{left:-5px}.showcase-arrow.right-arrow{right:-10px} +.partners{height:60px;display:flex;justify-content:center;align-items:center;gap:24px;font-size:12px}.partners b{font-size:20px;color:#e65b22}.partners b:nth-of-type(2){color:#2568b2}.partners b:nth-of-type(3){color:#e52539}.home-content{background:var(--paper);padding-top:48px}.wide{max-width:1256px;margin:45px auto;padding:0 20px}.home-content>.wide{margin-top:0}.section-title,.wide>h1{font-family:Plantin,Georgia,serif;font-size:49px;line-height:1;margin:0 0 30px;text-align:center}.pet-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:24px}.pet-card{border-radius:15px;overflow:hidden;background:var(--aqua);position:relative}.pet-card:nth-child(3n+1){background:var(--blue)}.pet-card:nth-child(3n){background:var(--yellow)}.pet-card img{display:block;width:100%;aspect-ratio:142/135;object-fit:cover}.pet-card div{padding:18px 20px 22px}.pet-card h3{font-family:Plantin,Georgia,serif;font-size:30px;margin:0 0 5px}.pet-card p{margin:5px 0}.pet-card small{color:var(--ink)}.intro{margin-top:0;text-align:center;padding:10px 25px 55px}.intro h2{font-family:Plantin,Georgia,serif;font-size:48px;margin:0 0 20px}.intro p{max-width:650px;margin:12px auto;line-height:1.5} +.search-band{background:var(--aqua);padding:25px max(30px,calc((100% - 1256px)/2))}.search-band h1{font-family:Plantin,Georgia,serif;font-size:52px;margin:0 0 20px}.search-summary{background:white;border-radius:16px;display:grid;grid-template-columns:80px 170px 1fr;align-items:center;height:82px;padding:15px 22px}.search-icon{font-size:30px}.search-summary span{color:#aaa;font-size:13px;font-weight:700;display:block}.search-summary b{font-size:20px}.search-summary>div:last-child{border-left:1px solid var(--ink);padding-left:25px}.alert-strip{margin:30px 0 34px;background:#eee;border-radius:16px;padding:20px;display:flex;align-items:center;gap:18px}.alert-strip div{flex:1}.alert-strip h2{font-size:20px;margin:0 0 6px}.alert-strip p{margin:0}.results-layout{display:grid;grid-template-columns:315px 1fr;gap:20px}.filters{background:var(--pale);border-radius:16px;padding:24px 20px;display:flex;flex-direction:column;gap:20px}.filters label{display:flex;flex-direction:column;gap:7px;font-size:18px;font-weight:700}.filters input,.filters select,.auth input,.auth select,.auth textarea,.shelter-search input{padding:13px 18px;border:1px solid var(--ink);border-radius:28px;background:white}.filters button{align-self:stretch}.results-main .results-meta{display:flex;justify-content:space-between;margin:4px 0 28px}.results-main .pet-grid{grid-template-columns:repeat(3,minmax(0,1fr));gap:23px}.pages{display:flex;justify-content:center;gap:20px;margin:30px}.pages a{background:var(--ink);color:#fff;padding:10px 22px;border-radius:22px} +.detail{max-width:1210px;margin:42px auto;padding:0 20px;display:grid;grid-template-columns:minmax(0,2.15fr) minmax(300px,.85fr);gap:24px;align-items:start}.pet-gallery{min-width:0}.pet-gallery>img{width:100%;height:480px;object-fit:contain;background:#202020;border-radius:15px;display:block}.thumbs{display:flex;gap:9px;margin-top:16px}.thumbs img{width:76px;height:76px;object-fit:cover;border-radius:11px}.detail article{background:var(--aqua);border-radius:16px;padding:25px}.detail h1{font-family:Plantin,Georgia,serif;font-size:52px;margin:0 0 8px}.detail h2{font-size:22px;margin:8px 0}.eyebrow{font-size:12px;font-weight:700;letter-spacing:.06em}.actions{display:flex;gap:10px;align-items:center;margin:24px 0}.actions .button,.actions button{display:block;text-align:center;width:100%;padding-left:12px;padding-right:12px}.detail dl div{display:flex;justify-content:space-between;border-bottom:1px solid #4da6a2;padding:12px 0}.detail dd{font-weight:700}.detail article>a{text-decoration:underline;font-weight:700} +.auth{max-width:520px;margin:55px auto;background:#f2f2ee;padding:35px;border-radius:20px}.auth h1{font-family:Plantin,Georgia,serif;font-size:48px}.auth form{display:flex;flex-direction:column;gap:18px}.auth label,.shelter-search label{display:flex;flex-direction:column;gap:7px;font-weight:700}.auth textarea{min-height:120px}.account{display:flex;align-items:center;gap:15px}.account h1{margin-right:auto}.row{display:flex;gap:25px;background:#f3f3ef;padding:17px;border-bottom:1px solid #ddd}.row b{flex:1}.shelter-search{display:flex;gap:12px;align-items:end;max-width:700px}.shelter-search label{flex:1}.shelters{display:grid;grid-template-columns:repeat(2,1fr);gap:18px;margin-top:25px}.shelters a,.breed-cols div,.article-grid article{background:#f3f3ef;border-radius:16px;padding:22px}.breed-cols{display:grid;grid-template-columns:1fr 1fr;gap:30px}.breed-cols a{display:block;padding:10px;border-bottom:1px solid #ddd}.article-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:20px}.article-grid article{padding:0;overflow:hidden}.article-grid img{width:100%;aspect-ratio:1.5;object-fit:cover}.article-grid h2,.article-grid p{margin:15px}.empty{grid-column:1/-1;text-align:center;padding:40px;background:#f3f3ef} +footer{margin-top:70px;background:var(--aqua);color:var(--ink);padding:50px max(25px,calc((100% - 1210px)/2))}footer .logo{font-size:44px}footer div{display:flex;gap:24px;margin:15px 0}footer p{font-size:12px} +@media(max-width:900px){.site-header{height:54px;padding:0 14px;margin-bottom:38px}.site-header nav{display:flex;position:absolute;top:54px;left:0;right:0;height:38px;padding:0 14px;background:#fff;gap:20px;overflow-x:auto;align-items:center}.site-header nav a{white-space:nowrap;font-size:12px}.site-header .login-link{display:none}.site-header .button{margin-left:auto;padding:8px 17px}.hamburger{display:block}.hero{padding:28px 18px 35px}.hero-inner{grid-template-columns:1fr;gap:15px}.hero-copy h1{font-size:50px}.hero-copy h2{font-size:16px}.pet-showcase{height:360px}.showcase-card{width:260px;height:330px;padding:15px}.showcase-card img{height:210px}.showcase-card h3{font-size:32px;margin-top:11px}.showcase-card p,.showcase-card small{font-size:14px}.showcase-card.left{left:0}.showcase-card.right{right:0}.results-layout{grid-template-columns:1fr}.filters{display:grid;grid-template-columns:repeat(3,1fr)}.results-main .pet-grid{grid-template-columns:repeat(2,1fr)}.detail{grid-template-columns:1fr}.pet-grid{grid-template-columns:repeat(2,1fr)}} +@media(max-width:520px){.logo{font-size:23px;letter-spacing:-1px}.site-header{gap:12px}.hero{min-height:0}.hero-inner{display:block}.hero-copy h1{font-size:43px;line-height:1.02;margin-bottom:12px}.hero-copy h2{margin-bottom:20px}.hero input,.hero select{height:52px;font-size:17px}.hero-actions button{padding:8px 10px}.smart-search{font-size:12px}.pet-showcase{height:235px;margin-top:22px}.showcase-card{width:165px;height:220px;padding:9px;border-radius:10px}.showcase-card img{height:133px;border-radius:35px}.showcase-card h3{font-size:22px;margin:7px 0 2px}.showcase-card p{font-size:10px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.showcase-card small{font-size:10px}.showcase-card .heart{right:10px;bottom:8px;font-size:18px}.showcase-card.left{left:-17px;transform:scale(.78)}.showcase-card.right{right:-17px;transform:scale(.78)}.showcase-arrow{display:none}.partners{height:50px;gap:12px}.partners b{font-size:14px}.wide{padding:0 14px;margin:30px auto}.section-title,.wide>h1{font-size:38px}.pet-grid,.results-main .pet-grid,.shelters,.article-grid,.breed-cols{grid-template-columns:1fr}.home-content .pet-grid{display:flex;overflow:auto}.home-content .pet-card{min-width:240px}.intro{padding:20px 4px 35px}.intro h2{font-size:38px}.search-band{padding:18px 14px}.search-band h1{font-size:38px}.search-summary{grid-template-columns:48px 100px 1fr;padding:10px;height:72px}.search-summary b{font-size:14px}.alert-strip{align-items:flex-start;flex-direction:column}.filters{grid-template-columns:1fr}.results-meta{font-size:13px}.detail{padding:0 14px;margin:22px auto}.pet-gallery>img{height:315px}.detail article{padding:20px}.detail h1{font-size:42px}.actions{align-items:stretch;flex-direction:column}.actions form,.actions button,.actions .button{width:100%;text-align:center}.account{flex-wrap:wrap}.account h1{width:100%}.row{flex-wrap:wrap}.row b{width:100%;flex:none}footer{margin-top:40px}footer div{flex-wrap:wrap}} +@media(max-width:900px){.site-header .login-link{display:block;border:0;padding:4px;font-size:12px;margin-left:auto}.site-header .button{margin-left:0}} diff --git a/sites/adopt_a_pet/static/icons/.gitkeep b/sites/adopt_a_pet/static/icons/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/sites/adopt_a_pet/static/js/.gitkeep b/sites/adopt_a_pet/static/js/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/sites/adopt_a_pet/tasks.jsonl b/sites/adopt_a_pet/tasks.jsonl new file mode 100644 index 00000000..bce338b4 --- /dev/null +++ b/sites/adopt_a_pet/tasks.jsonl @@ -0,0 +1,20 @@ +{"web_name":"Adopt a Pet","id":"AdoptAPet--0","ques":"Search for dogs near Phoenix, AZ. Open Waymo's profile and report his breed, age group, size, and adoption fee.","web":"http://localhost:40024/","upstream_url":"https://www.adoptapet.com/"} +{"web_name":"Adopt a Pet","id":"AdoptAPet--1","ques":"Search for cats near Scottsdale, AZ. Find the kitten and report the pet's name, exact age in months, color, and whether the pet is good with children.","web":"http://localhost:40024/","upstream_url":"https://www.adoptapet.com/"} +{"web_name":"Adopt a Pet","id":"AdoptAPet--2","ques":"Search near Phoenix, AZ for male dogs. Among the matching profiles, identify the pet with the lowest adoption fee and report his name, breeds, and fee.","web":"http://localhost:40024/","upstream_url":"https://www.adoptapet.com/"} +{"web_name":"Adopt a Pet","id":"AdoptAPet--3","ques":"Search for adult cats near Arizona. Among pets marked house-trained and good with cats, identify the one with the lowest adoption fee. Report name, city, breed, and fee.","web":"http://localhost:40024/","upstream_url":"https://www.adoptapet.com/"} +{"web_name":"Adopt a Pet","id":"AdoptAPet--4","ques":"Find Arno through the pet search. Report the rescue name, rescue phone number, and rescue email by opening both the pet profile and linked shelter page.","web":"http://localhost:40024/","upstream_url":"https://www.adoptapet.com/"} +{"web_name":"Adopt a Pet","id":"AdoptAPet--5","ques":"Search for pets near Seattle, WA. Compare Daisy and Pepper and report each pet's species, age in months, and adoption fee; then identify the lower-fee pet.","web":"http://localhost:40024/","upstream_url":"https://www.adoptapet.com/"} +{"web_name":"Adopt a Pet","id":"AdoptAPet--6","ques":"Log in as alice.j@test.com with password TestPass123!, find Sirius, add him to favorites, and confirm both Sirius and the existing Luna appear under Favorite pets.","web":"http://localhost:40024/","upstream_url":"https://www.adoptapet.com/"} +{"web_name":"Adopt a Pet","id":"AdoptAPet--7","ques":"Log in as alice.j@test.com with password TestPass123!, remove Luna from favorites, and confirm Luna is no longer listed under Favorite pets.","web":"http://localhost:40024/","upstream_url":"https://www.adoptapet.com/"} +{"web_name":"Adopt a Pet","id":"AdoptAPet--8","ques":"Log in as bob.smith@test.com with password TestPass123!, find Daisy, and submit an adoption inquiry using phone 206-555-0199, housing 'Rent with permission', and experience 'I have cared for two family dogs for eight years.' Confirm the inquiry is Submitted in your account.","web":"http://localhost:40024/","upstream_url":"https://www.adoptapet.com/"} +{"web_name":"Adopt a Pet","id":"AdoptAPet--9","ques":"Log in as carol.w@test.com with password TestPass123! and create a New Pet Alert for Siamese cats within 50 miles of postal code 33130. Confirm the alert appears in the account.","web":"http://localhost:40024/","upstream_url":"https://www.adoptapet.com/"} +{"web_name":"Adopt a Pet","id":"AdoptAPet--10","ques":"Use Find a shelter to search for Seattle. Open the matching shelter and report its name, phone, email, and the name of every pet shown there.","web":"http://localhost:40024/","upstream_url":"https://www.adoptapet.com/"} +{"web_name":"Adopt a Pet","id":"AdoptAPet--11","ques":"Open Breed 101 and select Maine Coon. From the results, report the matching pet's name, sex, location, and adoption fee.","web":"http://localhost:40024/","upstream_url":"https://www.adoptapet.com/"} +{"web_name":"Adopt a Pet","id":"AdoptAPet--12","ques":"Search for small dogs near Arizona. Compare all matching profiles and identify the youngest. Report the pet's name, age in months, breed or breeds, and city.","web":"http://localhost:40024/","upstream_url":"https://www.adoptapet.com/"} +{"web_name":"Adopt a Pet","id":"AdoptAPet--13","ques":"Search for adult female dogs near Austin, TX. Report the matching pet's name, fee, and whether she is good with cats and children.","web":"http://localhost:40024/","upstream_url":"https://www.adoptapet.com/"} +{"web_name":"Adopt a Pet","id":"AdoptAPet--14","ques":"Search for pets near New York, NY. Open both results and determine which has the lower adoption fee. Report both names and fees, then the lower-fee pet.","web":"http://localhost:40024/","upstream_url":"https://www.adoptapet.com/"} +{"web_name":"Adopt a Pet","id":"AdoptAPet--15","ques":"Log in as david.b@test.com with password TestPass123!, favorite Archie and Ruby, then confirm both appear in Favorite pets.","web":"http://localhost:40024/","upstream_url":"https://www.adoptapet.com/"} +{"web_name":"Adopt a Pet","id":"AdoptAPet--16","ques":"Create an account named Jamie Lee with email jamie.lee@example.test and password PetFriend123!, favorite Olive, and confirm Olive appears in the new account.","web":"http://localhost:40024/","upstream_url":"https://www.adoptapet.com/"} +{"web_name":"Adopt a Pet","id":"AdoptAPet--17","ques":"Search for senior dogs near Miami, FL. Report the matching pet's name, exact age in months, breeds, adoption fee, and whether the pet is house-trained.","web":"http://localhost:40024/","upstream_url":"https://www.adoptapet.com/"} +{"web_name":"Adopt a Pet","id":"AdoptAPet--18","ques":"Visit Pet advice and report the titles of the articles about adoption paperwork, adoption fees, and bringing home a newly adopted dog.","web":"http://localhost:40024/","upstream_url":"https://www.adoptapet.com/"} +{"web_name":"Adopt a Pet","id":"AdoptAPet--19","ques":"Search for young dogs near Arizona. Among those good with children, identify the pet with the lowest adoption fee. Report name, location, breed, age in months, and fee.","web":"http://localhost:40024/","upstream_url":"https://www.adoptapet.com/"} diff --git a/sites/adopt_a_pet/templates/.gitkeep b/sites/adopt_a_pet/templates/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/sites/adopt_a_pet/templates/account.html b/sites/adopt_a_pet/templates/account.html new file mode 100644 index 00000000..7f23379d --- /dev/null +++ b/sites/adopt_a_pet/templates/account.html @@ -0,0 +1 @@ +{% extends 'base.html' %}{% from 'macros.html' import card %}{% block content %}

Favorite pets

{% for p in favorites %}{{card(p)}}{% else %}

No favorite pets yet.

{% endfor %}

Adoption inquiries

{% for a,p in applications %}
{{p.name}}{{a.status}}{{a.phone}}
{% else %}

No inquiries yet.

{% endfor %}

New Pet Alerts

{% for a in alerts %}
{{a.species}} · {{a.breed or 'Any breed'}}{{a.postal}} within {{a.radius}} miles
{% else %}

No alerts yet.

{% endfor %}
{% endblock %} diff --git a/sites/adopt_a_pet/templates/alerts.html b/sites/adopt_a_pet/templates/alerts.html new file mode 100644 index 00000000..0193bb3a --- /dev/null +++ b/sites/adopt_a_pet/templates/alerts.html @@ -0,0 +1 @@ +{% extends 'base.html' %}{% block content %}

Create a New Pet Alert

{% endblock %} diff --git a/sites/adopt_a_pet/templates/application_done.html b/sites/adopt_a_pet/templates/application_done.html new file mode 100644 index 00000000..f0209ae6 --- /dev/null +++ b/sites/adopt_a_pet/templates/application_done.html @@ -0,0 +1 @@ +{% extends 'base.html' %}{% block content %}

Your inquiry was sent!

{{pet.shelter.name}} received your inquiry about {{pet.name}}.

View my inquiries
{% endblock %} diff --git a/sites/adopt_a_pet/templates/apply.html b/sites/adopt_a_pet/templates/apply.html new file mode 100644 index 00000000..fd13b427 --- /dev/null +++ b/sites/adopt_a_pet/templates/apply.html @@ -0,0 +1 @@ +{% extends 'base.html' %}{% block content %}

Ask about {{pet.name}}

Your message will go to {{pet.shelter.name}}.

{% endblock %} diff --git a/sites/adopt_a_pet/templates/base.html b/sites/adopt_a_pet/templates/base.html new file mode 100644 index 00000000..71349580 --- /dev/null +++ b/sites/adopt_a_pet/templates/base.html @@ -0,0 +1 @@ +{% block title %}Adopt a Pet{% endblock %}{% with m=get_flashed_messages() %}{% if m %}
{{m[-1]}}
{% endif %}{% endwith %}
{% block content %}{% endblock %}
diff --git a/sites/adopt_a_pet/templates/blog.html b/sites/adopt_a_pet/templates/blog.html new file mode 100644 index 00000000..8abf4f14 --- /dev/null +++ b/sites/adopt_a_pet/templates/blog.html @@ -0,0 +1 @@ +{% extends 'base.html' %}{% block content %}

Pet adoption advice

{% for img,title in [('article-23c4acda83eaf8ae.avif','What to know about pet adoption paperwork'),('article-67613dbc6f5d35e7.avif','Why is there an adoption fee?'),('article-7df82ed295140b9e.avif','Bringing home your newly adopted dog'),('article-d481e062963ebb2d.avif','Make pet adoption less scary'),('article-d2d957768d445381.avif','Am I ready for a cat?'),('article-87c08b41dcca022d.avif','Things to consider before adopting')] %}

{{title}}

Expert guidance for a happy start with your new companion.

{% endfor %}
{% endblock %} diff --git a/sites/adopt_a_pet/templates/breeds.html b/sites/adopt_a_pet/templates/breeds.html new file mode 100644 index 00000000..8b0d9bed --- /dev/null +++ b/sites/adopt_a_pet/templates/breeds.html @@ -0,0 +1 @@ +{% extends 'base.html' %}{% block content %}

Dog and cat breed information

Dog breeds

{% for b in dogs %}{{b}}{% endfor %}

Cat breeds

{% for b in cats %}{{b}}{% endfor %}
{% endblock %} diff --git a/sites/adopt_a_pet/templates/index.html b/sites/adopt_a_pet/templates/index.html new file mode 100644 index 00000000..52cc1acd --- /dev/null +++ b/sites/adopt_a_pet/templates/index.html @@ -0,0 +1 @@ +{% extends 'base.html' %}{% from 'macros.html' import card %}{% block content %}

Ready to
adopt a pet?

Let’s get started. Search adoptable pets from shelters, rescues, and individuals.

Our partnerszoetisPedigreePETSMART

Hi, we’re Adopt a Pet

20+ years helping pets in need · 15k+ partners · 223k+ adoptions

We’ll help you find the right pet faster with search filters, New Pet Alerts, and adoption advice.

Pets you may love

{% for p in pets %}{{card(p)}}{% endfor %}
{% endblock %} diff --git a/sites/adopt_a_pet/templates/login.html b/sites/adopt_a_pet/templates/login.html new file mode 100644 index 00000000..28b4a0fd --- /dev/null +++ b/sites/adopt_a_pet/templates/login.html @@ -0,0 +1 @@ +{% extends 'base.html' %}{% block content %}

Log in

New to Adopt a Pet? Sign Up

{% endblock %} diff --git a/sites/adopt_a_pet/templates/macros.html b/sites/adopt_a_pet/templates/macros.html new file mode 100644 index 00000000..9214c3b5 --- /dev/null +++ b/sites/adopt_a_pet/templates/macros.html @@ -0,0 +1 @@ +{% macro card(p) %}{% endmacro %} diff --git a/sites/adopt_a_pet/templates/pet.html b/sites/adopt_a_pet/templates/pet.html new file mode 100644 index 00000000..85366c5c --- /dev/null +++ b/sites/adopt_a_pet/templates/pet.html @@ -0,0 +1 @@ +{% extends 'base.html' %}{% block title %}{{pet.name}} | Adopt a Pet{% endblock %}{% block content %}

AVAILABLE FOR ADOPTION

{{pet.name}}

{{pet.breed}}{{' / '~pet.secondary_breed if pet.secondary_breed}}

{{pet.sex}} · {{pet.age_group}} · {{pet.age_months}} months old · {{pet.size}}

{{pet.city}}, {{pet.state}} {{pet.postal}}

Ask about {{pet.name}}

About {{pet.name}}

{{pet.description}}

Adoption fee
${{pet.fee}}
Color
{{pet.color}}
House-trained
{{'Yes' if pet.house_trained else 'No'}}
Good with dogs
{{'Yes' if pet.good_dogs else 'No'}}
Good with cats
{{'Yes' if pet.good_cats else 'No'}}
Good with children
{{'Yes' if pet.good_children else 'No'}}

Cared for by

{{pet.shelter.name}}
{% endblock %} diff --git a/sites/adopt_a_pet/templates/register.html b/sites/adopt_a_pet/templates/register.html new file mode 100644 index 00000000..e16e294e --- /dev/null +++ b/sites/adopt_a_pet/templates/register.html @@ -0,0 +1 @@ +{% extends 'base.html' %}{% block content %}

Create your account

{% endblock %} diff --git a/sites/adopt_a_pet/templates/search.html b/sites/adopt_a_pet/templates/search.html new file mode 100644 index 00000000..8bf23959 --- /dev/null +++ b/sites/adopt_a_pet/templates/search.html @@ -0,0 +1 @@ +{% extends 'base.html' %}{% from 'macros.html' import card %}{% block content %}

Pets for adoption near {{q or 'you'}}

Type of Pet{{filters.get('species') or 'Any pet'}}
Location{{q or 'Your area'}}

Want to find the right pet faster?

Create a New Pet Alert, and be the first to know when new pets pop up who match your search.

♧   Create an Alert
Showing {{1 if total else 0}} - {{[page*6,total]|min}} of {{total}} available petsSort by: Nearest
{% for p in pets %}{{card(p)}}{% else %}

No exact matches

Try removing one or more filters.

{% endfor %}
{% endblock %} diff --git a/sites/adopt_a_pet/templates/shelter.html b/sites/adopt_a_pet/templates/shelter.html new file mode 100644 index 00000000..eadd08e1 --- /dev/null +++ b/sites/adopt_a_pet/templates/shelter.html @@ -0,0 +1 @@ +{% extends 'base.html' %}{% from 'macros.html' import card %}{% block content %}

{{shelter.name}}

{{shelter.city}}, {{shelter.state}} · {{shelter.phone}} · {{shelter.email}}

Pets available from this rescue

{% for p in shelter.pets %}{{card(p)}}{% endfor %}
{% endblock %} diff --git a/sites/adopt_a_pet/templates/shelters.html b/sites/adopt_a_pet/templates/shelters.html new file mode 100644 index 00000000..79a760d8 --- /dev/null +++ b/sites/adopt_a_pet/templates/shelters.html @@ -0,0 +1 @@ +{% extends 'base.html' %}{% block content %}

Find an animal shelter or rescue

{% for s in shelters %}

{{s.name}}

{{s.city}}, {{s.state}}

{{s.pets|length}} adoptable pets

{% else %}

No shelters matched your search.

{% endfor %}
{% endblock %} diff --git a/websyn_start.sh b/websyn_start.sh index 9539b4f9..4a4dd0f9 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 merriam_webster ikea phys_org target ted osu rotten_tomatoes compass walmart_careers) + cambridge_dictionary coursera espn merriam_webster ikea phys_org target ted osu rotten_tomatoes compass walmart_careers adopt_a_pet) BASE_PORT=40000 PID_DIR=/tmp/websyn_pids mkdir -p "$PID_DIR"