diff --git a/.gitignore b/.gitignore index c2efc04c..24ce1529 100644 --- a/.gitignore +++ b/.gitignore @@ -9,8 +9,10 @@ sites/*/static/external_cache/ # ============================================================= # Intermediate / volatile — never committed anywhere. # ============================================================= -sites/*/scraped_data/ # scrape pipeline intermediate; runtime data lives in instance_seed/*.db -sites/*/instance/ # rebuilt at every container boot from instance_seed/ +# scrape pipeline intermediate; runtime data lives in instance_seed/*.db +sites/*/scraped_data/ +# rebuilt at every container boot from instance_seed/ +sites/*/instance/ sites/*/venv/ # HF download metadata produced by `hf download`. diff --git a/Dockerfile b/Dockerfile index 991e5ab6..f653515e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,6 +33,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 c255253c..0acbc1f4 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', 'nvidia', ] BASE_PORT = 40000 WEBSYN_DIR = '/opt/WebSyn' diff --git a/sites/nvidia/_health.py b/sites/nvidia/_health.py new file mode 100644 index 00000000..83237fc3 --- /dev/null +++ b/sites/nvidia/_health.py @@ -0,0 +1,3 @@ +"""Per-site health probe (optional, called by control_server).""" +def health(): + return {"ok": True, "site": "nvidia"} diff --git a/sites/nvidia/app.py b/sites/nvidia/app.py new file mode 100644 index 00000000..ad43582c --- /dev/null +++ b/sites/nvidia/app.py @@ -0,0 +1,809 @@ +#!/usr/bin/env python3 +"""NVIDIA.com mirror — Flask app. + +Models a faithful slice of nvidia.com for the WebHarbor benchmark: a product +catalog of GPUs/hardware (GeForce gaming, Studio/Professional, Data Center, +Embedded, Consumer Devices) with full spec sheets, a spec-comparison tool, a +driver-download finder, a news/blog section, search, accounts, cart/checkout, +wishlist, and product reviews. +""" +import os +import re +from datetime import datetime, date + +from flask import (Flask, render_template, request, redirect, url_for, + flash, jsonify, abort) +from flask_sqlalchemy import SQLAlchemy +from flask_login import (LoginManager, UserMixin, login_user, logout_user, + login_required, current_user) +from flask_wtf import FlaskForm +from flask_wtf.csrf import CSRFProtect +from flask_bcrypt import Bcrypt +from wtforms import (StringField, PasswordField, TextAreaField, IntegerField, + SelectField, BooleanField) +from wtforms.validators import (DataRequired, Email, Length, EqualTo, Optional, + NumberRange) +from sqlalchemy import or_ + +from seed_data import (PRODUCTS, ARTICLES, DRIVERS, BENCHMARK_USERS, + NOTABLE_REVIEWS, BENCHMARK_ACTIVITY) + +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) + +app = Flask(__name__) +app.config['SECRET_KEY'] = 'nvidia-mirror-dev-secret-key' +app.config['SQLALCHEMY_DATABASE_URI'] = \ + f"sqlite:///{os.path.join(BASE_DIR, 'instance', 'nvidia.db')}" +app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False +app.config['WTF_CSRF_TIME_LIMIT'] = None + +os.makedirs(os.path.join(BASE_DIR, 'instance'), exist_ok=True) + +db = SQLAlchemy(app) +bcrypt = Bcrypt(app) +login_manager = LoginManager(app) +login_manager.login_view = 'login' +login_manager.login_message = 'Please log in to continue.' +login_manager.login_message_category = 'info' +csrf = CSRFProtect(app) + +CATEGORIES = [ + ("GeForce Gaming", "geforce-gaming"), + ("Studio / Professional", "studio-professional"), + ("Data Center", "data-center"), + ("Embedded", "embedded"), + ("Consumer Devices", "consumer-devices"), +] +CAT_BY_SLUG = {s: n for n, s in CATEGORIES} +CAT_SLUG = {n: s for n, s in CATEGORIES} + + +# -------------------------------------------------------------------------- +# Models +# -------------------------------------------------------------------------- +class User(db.Model, UserMixin): + __tablename__ = 'users' + id = db.Column(db.Integer, primary_key=True) + email = db.Column(db.String(120), unique=True, nullable=False, index=True) + password_hash = db.Column(db.String(255), nullable=False) + name = db.Column(db.String(120), nullable=False) + company = db.Column(db.String(120), default='') + country = db.Column(db.String(60), default='United States') + newsletter_opt_in = db.Column(db.Boolean, default=False) + created = db.Column(db.DateTime, default=datetime(2026, 1, 1)) + + cart_items = db.relationship('CartItem', backref='user', lazy=True, + cascade='all, delete-orphan') + orders = db.relationship('Order', backref='user', lazy=True, + cascade='all, delete-orphan') + wishlist = db.relationship('WishlistItem', backref='user', lazy=True, + cascade='all, delete-orphan') + reviews = db.relationship('Review', backref='user', lazy=True, + cascade='all, delete-orphan') + + def set_password(self, pw): + self.password_hash = bcrypt.generate_password_hash(pw).decode('utf-8') + + def check_password(self, pw): + return bcrypt.check_password_hash(self.password_hash, pw) + + +class Product(db.Model): + __tablename__ = 'products' + id = db.Column(db.Integer, primary_key=True) + slug = db.Column(db.String(120), unique=True, nullable=False, index=True) + name = db.Column(db.String(160), nullable=False) + category = db.Column(db.String(60), nullable=False, index=True) + series = db.Column(db.String(80), default='') + tagline = db.Column(db.String(240), default='') + description = db.Column(db.Text, default='') + price_usd = db.Column(db.Integer, nullable=True) # null => "Contact sales" + image = db.Column(db.String(240), default='') + release_year = db.Column(db.Integer, nullable=True) + in_stock = db.Column(db.Boolean, default=True) + is_featured = db.Column(db.Boolean, default=False) + # spec sheet + architecture = db.Column(db.String(60), default='') + cuda_cores = db.Column(db.Integer, nullable=True) + tensor_cores = db.Column(db.Integer, nullable=True) + rt_cores = db.Column(db.Integer, nullable=True) + memory_gb = db.Column(db.Integer, nullable=True) + memory_type = db.Column(db.String(40), default='') + memory_bandwidth = db.Column(db.String(40), default='') + boost_clock_ghz = db.Column(db.Float, nullable=True) + tdp_watts = db.Column(db.Integer, nullable=True) + interface = db.Column(db.String(40), default='') + recommended_psu_watts = db.Column(db.Integer, nullable=True) + + reviews = db.relationship('Review', backref='product', lazy=True, + cascade='all, delete-orphan') + + @property + def category_slug(self): + return CAT_SLUG.get(self.category, '') + + @property + def avg_rating(self): + rs = [r.rating for r in self.reviews] + return round(sum(rs) / len(rs), 1) if rs else None + + @property + def review_count(self): + return len(self.reviews) + + @property + def price_display(self): + return f"${self.price_usd:,}" if self.price_usd else "Contact Sales" + + +class Review(db.Model): + __tablename__ = 'reviews' + id = db.Column(db.Integer, primary_key=True) + product_id = db.Column(db.Integer, db.ForeignKey('products.id'), nullable=False) + user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + rating = db.Column(db.Integer, nullable=False) + title = db.Column(db.String(160), default='') + body = db.Column(db.Text, default='') + created = db.Column(db.DateTime, default=datetime(2026, 3, 1)) + + +class Article(db.Model): + __tablename__ = 'articles' + id = db.Column(db.Integer, primary_key=True) + slug = db.Column(db.String(160), unique=True, nullable=False, index=True) + title = db.Column(db.String(240), nullable=False) + category = db.Column(db.String(60), default='') + author = db.Column(db.String(120), default='NVIDIA Newsroom') + published = db.Column(db.Date, default=date(2026, 1, 1)) + excerpt = db.Column(db.Text, default='') + body = db.Column(db.Text, default='') + image = db.Column(db.String(240), default='') + read_minutes = db.Column(db.Integer, default=4) + + +class Driver(db.Model): + __tablename__ = 'drivers' + id = db.Column(db.Integer, primary_key=True) + product_series = db.Column(db.String(80), nullable=False, index=True) + branch = db.Column(db.String(40), default='Game Ready') # Game Ready / Studio + version = db.Column(db.String(40), nullable=False) + os = db.Column(db.String(60), default='Windows 11') + released = db.Column(db.Date, default=date(2026, 1, 1)) + size_mb = db.Column(db.Integer, default=600) + highlights = db.Column(db.Text, default='') + download_count = db.Column(db.Integer, default=0) + + +class CartItem(db.Model): + __tablename__ = 'cart_items' + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + product_id = db.Column(db.Integer, db.ForeignKey('products.id'), nullable=False) + quantity = db.Column(db.Integer, default=1) + product = db.relationship('Product') + + +class Order(db.Model): + __tablename__ = 'orders' + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + created = db.Column(db.DateTime, default=datetime(2026, 4, 1)) + status = db.Column(db.String(40), default='Processing') + total_usd = db.Column(db.Integer, default=0) + items = db.relationship('OrderItem', backref='order', lazy=True, + cascade='all, delete-orphan') + + +class OrderItem(db.Model): + __tablename__ = 'order_items' + id = db.Column(db.Integer, primary_key=True) + order_id = db.Column(db.Integer, db.ForeignKey('orders.id'), nullable=False) + product_id = db.Column(db.Integer, db.ForeignKey('products.id'), nullable=False) + name = db.Column(db.String(160)) + price_usd = db.Column(db.Integer) + quantity = db.Column(db.Integer, default=1) + product = db.relationship('Product') + + +class WishlistItem(db.Model): + __tablename__ = 'wishlist_items' + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + product_id = db.Column(db.Integer, db.ForeignKey('products.id'), nullable=False) + product = db.relationship('Product') + + +class NewsletterSubscriber(db.Model): + __tablename__ = 'newsletter' + id = db.Column(db.Integer, primary_key=True) + email = db.Column(db.String(120), unique=True, nullable=False) + topic = db.Column(db.String(60), default='GeForce') + + +@login_manager.user_loader +def load_user(uid): + return db.session.get(User, int(uid)) + + +# -------------------------------------------------------------------------- +# Forms +# -------------------------------------------------------------------------- +class LoginForm(FlaskForm): + email = StringField('Email', validators=[DataRequired(), Email()]) + password = PasswordField('Password', validators=[DataRequired()]) + + +class RegisterForm(FlaskForm): + name = StringField('Full name', validators=[DataRequired(), Length(max=120)]) + email = StringField('Email', validators=[DataRequired(), Email()]) + password = PasswordField('Password', validators=[DataRequired(), Length(min=6)]) + confirm = PasswordField('Confirm password', + validators=[DataRequired(), EqualTo('password')]) + newsletter_opt_in = BooleanField('Email me NVIDIA news and offers') + + +class AccountForm(FlaskForm): + name = StringField('Full name', validators=[DataRequired(), Length(max=120)]) + company = StringField('Company', validators=[Optional(), Length(max=120)]) + country = StringField('Country', validators=[Optional(), Length(max=60)]) + newsletter_opt_in = BooleanField('Subscribe to the NVIDIA newsletter') + + +class PasswordForm(FlaskForm): + current = PasswordField('Current password', validators=[DataRequired()]) + new = PasswordField('New password', validators=[DataRequired(), Length(min=6)]) + confirm = PasswordField('Confirm new password', + validators=[DataRequired(), EqualTo('new')]) + + +class ReviewForm(FlaskForm): + rating = SelectField('Rating', choices=[(str(i), f'{i} stars') for i in range(5, 0, -1)], + validators=[DataRequired()]) + title = StringField('Title', validators=[DataRequired(), Length(max=160)]) + body = TextAreaField('Your review', validators=[DataRequired(), Length(max=2000)]) + + +class CheckoutForm(FlaskForm): + full_name = StringField('Full name', validators=[DataRequired()]) + address = StringField('Shipping address', validators=[DataRequired()]) + city = StringField('City', validators=[DataRequired()]) + zip_code = StringField('ZIP / Postal code', validators=[DataRequired()]) + card = StringField('Card number', validators=[DataRequired(), Length(min=12, max=19)]) + + +class SimpleForm(FlaskForm): + """CSRF-only form for POST actions (add to cart, wishlist, download, etc.).""" + + +# -------------------------------------------------------------------------- +# Search scoring — token overlap (NOT strict AND) +# -------------------------------------------------------------------------- +def _tokenize(q): + return [t for t in re.split(r'[^a-z0-9]+', (q or '').lower()) if t] + + +def _score(haystack, tokens): + if not tokens: + return 0 + h = haystack.lower() + return sum(1 for t in tokens if t in h) + + +def _product_hay(p): + return ' '.join(str(x) for x in (p.name, p.category, p.series, p.tagline, + p.description, p.architecture, p.memory_type)) + + +def _article_hay(a): + return ' '.join(str(x) for x in (a.title, a.category, a.author, a.excerpt, a.body)) + + +# -------------------------------------------------------------------------- +# Context +# -------------------------------------------------------------------------- +@app.context_processor +def inject_globals(): + cart_count = 0 + if current_user.is_authenticated: + cart_count = sum(c.quantity for c in current_user.cart_items) + return {'CATEGORIES': CATEGORIES, 'cart_count': cart_count, + 'current_year': 2026, 'simple_form': SimpleForm()} + + +# -------------------------------------------------------------------------- +# Routes — browse +# -------------------------------------------------------------------------- +@app.route('/') +def index(): + featured = Product.query.filter_by(is_featured=True).order_by( + Product.price_usd.desc().nullslast()).limit(6).all() + if not featured: + featured = Product.query.limit(6).all() + latest_news = Article.query.order_by(Article.published.desc()).limit(3).all() + flagship = Product.query.filter_by(slug='geforce-rtx-5090').first() + return render_template('index.html', featured=featured, latest_news=latest_news, + flagship=flagship) + + +@app.route('/products') +def products(): + cat = request.args.get('category', '').strip() + series = request.args.get('series', '').strip() + sort = request.args.get('sort', 'featured') + q = request.args.get('q', '').strip() + + query = Product.query + cat_name = CAT_BY_SLUG.get(cat) + if cat_name: + query = query.filter(Product.category == cat_name) + if series: + query = query.filter(Product.series == series) + items = query.all() + + tokens = _tokenize(q) + if tokens: + scored = [(s, p) for p in items if (s := _score(_product_hay(p), tokens)) > 0] + scored.sort(key=lambda sp: (-sp[0], -(sp[1].price_usd or 0))) + items = [p for _, p in scored] + elif sort == 'price-low': + items.sort(key=lambda p: (p.price_usd or 10**9)) + elif sort == 'price-high': + items.sort(key=lambda p: -(p.price_usd or 0)) + elif sort == 'newest': + items.sort(key=lambda p: -(p.release_year or 0)) + else: # featured + items.sort(key=lambda p: (not p.is_featured, -(p.price_usd or 0))) + + series_list = sorted({p.series for p in Product.query.all() if p.series}) + return render_template('products.html', items=items, cat=cat, cat_name=cat_name, + series=series, series_list=series_list, sort=sort, q=q) + + +@app.route('/products/') +def product_detail(slug): + p = Product.query.filter_by(slug=slug).first_or_404() + related = Product.query.filter(Product.category == p.category, + Product.id != p.id).limit(4).all() + in_wishlist = False + if current_user.is_authenticated: + in_wishlist = WishlistItem.query.filter_by( + user_id=current_user.id, product_id=p.id).first() is not None + reviews = Review.query.filter_by(product_id=p.id).order_by(Review.created.desc()).all() + return render_template('product_detail.html', p=p, related=related, + in_wishlist=in_wishlist, reviews=reviews, + review_form=ReviewForm()) + + +@app.route('/compare') +def compare(): + ids = [s for s in request.args.get('ids', '').split(',') if s.strip()] + items = [] + for s in ids: + p = Product.query.filter_by(slug=s.strip()).first() + if p: + items.append(p) + all_products = Product.query.order_by(Product.name).all() + return render_template('compare.html', items=items, all_products=all_products) + + +@app.route('/drivers') +def drivers(): + series = request.args.get('series', '').strip() + branch = request.args.get('branch', '').strip() + os_filter = request.args.get('os', '').strip() + query = Driver.query + if series: + query = query.filter(Driver.product_series == series) + if branch: + query = query.filter(Driver.branch == branch) + if os_filter: + query = query.filter(Driver.os == os_filter) + searched = bool(series or branch or os_filter) + results = query.order_by(Driver.released.desc()).all() if searched else [] + series_list = sorted({d.product_series for d in Driver.query.all()}) + os_list = sorted({d.os for d in Driver.query.all()}) + return render_template('drivers.html', results=results, series=series, + branch=branch, os_filter=os_filter, + series_list=series_list, os_list=os_list, searched=searched) + + +@app.route('/drivers/') +def driver_detail(driver_id): + d = db.session.get(Driver, driver_id) or abort(404) + return render_template('driver_detail.html', d=d) + + +@app.route('/drivers//download', methods=['POST']) +def driver_download(driver_id): + d = db.session.get(Driver, driver_id) or abort(404) + d.download_count += 1 + db.session.commit() + flash(f'Download started: {d.product_series} driver {d.version} ({d.branch}).', 'success') + return redirect(url_for('driver_detail', driver_id=driver_id)) + + +@app.route('/news') +def news(): + cat = request.args.get('category', '').strip() + query = Article.query + if cat: + query = query.filter(Article.category == cat) + items = query.order_by(Article.published.desc()).all() + cats = sorted({a.category for a in Article.query.all() if a.category}) + return render_template('news.html', items=items, cats=cats, cat=cat) + + +@app.route('/news/') +def news_detail(slug): + a = Article.query.filter_by(slug=slug).first_or_404() + more = Article.query.filter(Article.id != a.id).order_by( + Article.published.desc()).limit(3).all() + return render_template('news_detail.html', a=a, more=more) + + +@app.route('/search') +def search(): + q = request.args.get('q', '').strip() + tokens = _tokenize(q) + products_found, articles_found = [], [] + if tokens: + ps = [(s, p) for p in Product.query.all() + if (s := _score(_product_hay(p), tokens)) > 0] + ps.sort(key=lambda sp: (-sp[0], -(sp[1].price_usd or 0))) + products_found = [p for _, p in ps] + asc = [(s, a) for a in Article.query.all() + if (s := _score(_article_hay(a), tokens)) > 0] + asc.sort(key=lambda sa: -sa[0]) + articles_found = [a for _, a in asc] + return render_template('search.html', q=q, products=products_found, + articles=articles_found) + + +# -------------------------------------------------------------------------- +# Auth +# -------------------------------------------------------------------------- +@app.route('/login', methods=['GET', 'POST']) +def login(): + if current_user.is_authenticated: + return redirect(url_for('account')) + form = LoginForm() + if form.validate_on_submit(): + user = User.query.filter_by(email=form.email.data.lower().strip()).first() + if user and user.check_password(form.password.data): + login_user(user) + nxt = request.args.get('next') + return redirect(nxt or url_for('account')) + flash('Invalid email or password.', 'error') + return render_template('login.html', form=form) + + +@app.route('/register', methods=['GET', 'POST']) +def register(): + if current_user.is_authenticated: + return redirect(url_for('account')) + form = RegisterForm() + if form.validate_on_submit(): + email = form.email.data.lower().strip() + if User.query.filter_by(email=email).first(): + flash('An account with that email already exists.', 'error') + else: + u = User(email=email, name=form.name.data.strip(), + newsletter_opt_in=form.newsletter_opt_in.data) + u.set_password(form.password.data) + db.session.add(u) + db.session.commit() + login_user(u) + flash('Welcome to NVIDIA. Your account has been created.', 'success') + return redirect(url_for('account')) + return render_template('register.html', form=form) + + +@app.route('/logout') +@login_required +def logout(): + logout_user() + flash('You have been signed out.', 'info') + return redirect(url_for('index')) + + +# -------------------------------------------------------------------------- +# Account +# -------------------------------------------------------------------------- +@app.route('/account') +@login_required +def account(): + orders = Order.query.filter_by(user_id=current_user.id).order_by( + Order.created.desc()).all() + return render_template('account.html', orders=orders) + + +@app.route('/account/edit', methods=['GET', 'POST']) +@login_required +def account_edit(): + form = AccountForm(obj=current_user) + if form.validate_on_submit(): + current_user.name = form.name.data.strip() + current_user.company = form.company.data.strip() + current_user.country = form.country.data.strip() + current_user.newsletter_opt_in = form.newsletter_opt_in.data + db.session.commit() + flash('Your profile has been updated.', 'success') + return redirect(url_for('account')) + return render_template('account_edit.html', form=form) + + +@app.route('/account/password', methods=['GET', 'POST']) +@login_required +def account_password(): + form = PasswordForm() + if form.validate_on_submit(): + if not current_user.check_password(form.current.data): + flash('Current password is incorrect.', 'error') + else: + current_user.set_password(form.new.data) + db.session.commit() + flash('Your password has been changed.', 'success') + return redirect(url_for('account')) + return render_template('account_password.html', form=form) + + +@app.route('/account/wishlist') +@login_required +def wishlist(): + items = WishlistItem.query.filter_by(user_id=current_user.id).all() + return render_template('wishlist.html', items=items) + + +# -------------------------------------------------------------------------- +# Cart + checkout +# -------------------------------------------------------------------------- +@app.route('/cart') +@login_required +def cart(): + items = CartItem.query.filter_by(user_id=current_user.id).all() + subtotal = sum((c.product.price_usd or 0) * c.quantity for c in items) + return render_template('cart.html', items=items, subtotal=subtotal) + + +@app.route('/cart/add/', methods=['POST']) +@login_required +def cart_add(product_id): + p = db.session.get(Product, product_id) or abort(404) + if p.price_usd is None: + flash(f'{p.name} is sold through NVIDIA sales — contact sales for a quote.', 'info') + return redirect(url_for('product_detail', slug=p.slug)) + item = CartItem.query.filter_by(user_id=current_user.id, product_id=p.id).first() + if item: + item.quantity += 1 + else: + db.session.add(CartItem(user_id=current_user.id, product_id=p.id, quantity=1)) + db.session.commit() + flash(f'Added {p.name} to your cart.', 'success') + return redirect(request.referrer or url_for('cart')) + + +@app.route('/cart/update/', methods=['POST']) +@login_required +def cart_update(item_id): + item = db.session.get(CartItem, item_id) or abort(404) + if item.user_id != current_user.id: + abort(403) + qty = request.form.get('quantity', type=int) or 1 + item.quantity = max(1, qty) + db.session.commit() + return redirect(url_for('cart')) + + +@app.route('/cart/remove/', methods=['POST']) +@login_required +def cart_remove(item_id): + item = db.session.get(CartItem, item_id) or abort(404) + if item.user_id != current_user.id: + abort(403) + db.session.delete(item) + db.session.commit() + flash('Item removed from cart.', 'info') + return redirect(url_for('cart')) + + +@app.route('/checkout', methods=['GET', 'POST']) +@login_required +def checkout(): + items = CartItem.query.filter_by(user_id=current_user.id).all() + if not items: + flash('Your cart is empty.', 'info') + return redirect(url_for('cart')) + subtotal = sum((c.product.price_usd or 0) * c.quantity for c in items) + tax = round(subtotal * 0.0875) + total = subtotal + tax + form = CheckoutForm() + if request.method == 'GET': + form.full_name.data = current_user.name + if form.validate_on_submit(): + order = Order(user_id=current_user.id, status='Processing', total_usd=total) + db.session.add(order) + db.session.flush() + for c in items: + db.session.add(OrderItem(order_id=order.id, product_id=c.product_id, + name=c.product.name, price_usd=c.product.price_usd, + quantity=c.quantity)) + db.session.delete(c) + db.session.commit() + flash('Order placed! A confirmation has been emailed to you.', 'success') + return redirect(url_for('order_detail', order_id=order.id)) + return render_template('checkout.html', items=items, subtotal=subtotal, + tax=tax, total=total, form=form) + + +@app.route('/order/') +@login_required +def order_detail(order_id): + order = db.session.get(Order, order_id) or abort(404) + if order.user_id != current_user.id: + abort(403) + return render_template('order_detail.html', order=order) + + +# -------------------------------------------------------------------------- +# Wishlist + reviews + newsletter (POST actions) +# -------------------------------------------------------------------------- +@app.route('/wishlist/toggle/', methods=['POST']) +@login_required +def wishlist_toggle(product_id): + p = db.session.get(Product, product_id) or abort(404) + item = WishlistItem.query.filter_by(user_id=current_user.id, product_id=p.id).first() + if item: + db.session.delete(item) + db.session.commit() + flash(f'Removed {p.name} from your wishlist.', 'info') + else: + db.session.add(WishlistItem(user_id=current_user.id, product_id=p.id)) + db.session.commit() + flash(f'Saved {p.name} to your wishlist.', 'success') + return redirect(request.referrer or url_for('product_detail', slug=p.slug)) + + +@app.route('/products//review', methods=['POST']) +@login_required +def add_review(slug): + p = Product.query.filter_by(slug=slug).first_or_404() + form = ReviewForm() + if form.validate_on_submit(): + existing = Review.query.filter_by(product_id=p.id, user_id=current_user.id).first() + if existing: + flash('You have already reviewed this product.', 'info') + else: + db.session.add(Review(product_id=p.id, user_id=current_user.id, + rating=int(form.rating.data), + title=form.title.data.strip(), + body=form.body.data.strip(), + created=datetime(2026, 6, 1))) + db.session.commit() + flash('Thank you — your review has been posted.', 'success') + else: + flash('Please complete all review fields.', 'error') + return redirect(url_for('product_detail', slug=slug)) + + +@app.route('/newsletter', methods=['POST']) +def newsletter(): + email = (request.form.get('email') or '').lower().strip() + topic = (request.form.get('topic') or 'GeForce').strip() + if not email or '@' not in email: + flash('Please enter a valid email address.', 'error') + elif NewsletterSubscriber.query.filter_by(email=email).first(): + flash('You are already subscribed.', 'info') + else: + db.session.add(NewsletterSubscriber(email=email, topic=topic)) + db.session.commit() + flash(f'Subscribed to {topic} updates. Welcome aboard!', 'success') + return redirect(request.referrer or url_for('index')) + + +@app.errorhandler(404) +def not_found(e): + return render_template('404.html'), 404 + + +# -------------------------------------------------------------------------- +# Seeding — each function gated as a whole for byte-identical reset +# -------------------------------------------------------------------------- +def seed_catalog(): + if Product.query.count() > 0: + return + for p in PRODUCTS: + db.session.add(Product(**p)) + db.session.commit() + + +def seed_articles(): + if Article.query.count() > 0: + return + for a in ARTICLES: + db.session.add(Article(**a)) + db.session.commit() + + +def seed_drivers(): + if Driver.query.count() > 0: + return + for d in DRIVERS: + db.session.add(Driver(**d)) + db.session.commit() + + +def seed_benchmark_users(): + if User.query.filter_by(email='alice.j@test.com').first(): + return + for u in BENCHMARK_USERS: + user = User(email=u['email'], name=u['name'], company=u.get('company', ''), + country=u.get('country', 'United States'), + newsletter_opt_in=u.get('newsletter_opt_in', False), + created=datetime(2026, 1, 1)) + user.set_password(u['password']) + db.session.add(user) + db.session.commit() + + +def seed_reviews(): + if Review.query.count() > 0: + return + for r in NOTABLE_REVIEWS: + user = User.query.filter_by(email=r['user_email']).first() + prod = Product.query.filter_by(slug=r['product_slug']).first() + if user and prod: + db.session.add(Review(product_id=prod.id, user_id=user.id, + rating=r['rating'], title=r['title'], + body=r['body'], created=datetime(2026, 3, 1))) + db.session.commit() + + +def seed_benchmark_activity(): + """Pre-populate benchmark users' wishlists / orders so their account pages + feel lived-in. Gated as a whole. Deliberately avoids tasks.jsonl targets.""" + if Order.query.count() > 0 or WishlistItem.query.count() > 0: + return + for act in BENCHMARK_ACTIVITY: + user = User.query.filter_by(email=act['user_email']).first() + if not user: + continue + for slug in act.get('wishlist', []): + p = Product.query.filter_by(slug=slug).first() + if p: + db.session.add(WishlistItem(user_id=user.id, product_id=p.id)) + for o in act.get('orders', []): + prods = [Product.query.filter_by(slug=s).first() for s in o['products']] + prods = [p for p in prods if p] + if not prods: + continue + total = sum(p.price_usd or 0 for p in prods) + order = Order(user_id=user.id, status=o.get('status', 'Delivered'), + created=datetime(2026, 4, 1), total_usd=total) + db.session.add(order) + db.session.flush() + for p in prods: + db.session.add(OrderItem(order_id=order.id, product_id=p.id, + name=p.name, price_usd=p.price_usd, quantity=1)) + db.session.commit() + + +with app.app_context(): + db.create_all() + seed_catalog() + seed_articles() + seed_drivers() + seed_benchmark_users() + seed_reviews() + seed_benchmark_activity() + + +@app.route('/_health') +def health(): + return {'ok': True, 'site': 'nvidia', + 'products': Product.query.count(), + 'articles': Article.query.count()} + + +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/nvidia/requirements.txt b/sites/nvidia/requirements.txt new file mode 100644 index 00000000..4d37a226 --- /dev/null +++ b/sites/nvidia/requirements.txt @@ -0,0 +1,10 @@ +# Runtime deps are pinned in the repo Dockerfile (the image is the source of +# truth). Listed here for local dev parity. +Flask==3.1.0 +Flask-SQLAlchemy==3.1.1 +Flask-Login==0.6.3 +Flask-WTF==1.2.2 +Flask-Bcrypt==1.0.1 +SQLAlchemy==2.0.36 +WTForms==3.2.1 +email-validator==2.2.0 diff --git a/sites/nvidia/seed_data.py b/sites/nvidia/seed_data.py new file mode 100644 index 00000000..851cc2e2 --- /dev/null +++ b/sites/nvidia/seed_data.py @@ -0,0 +1,379 @@ +#!/usr/bin/env python3 +"""Build-time seed data for the NVIDIA mirror. + +All consumed by the gated seed_*() functions in app.py. Specs reflect real +NVIDIA published figures. Image paths are relative to static/images/. +""" +from datetime import date + +# -------------------------------------------------------------------------- +# Products — GPUs / hardware across five categories +# -------------------------------------------------------------------------- +def _p(slug, name, category, series, price, tagline, description, image=None, + featured=False, year=2025, in_stock=True, **specs): + return dict( + slug=slug, name=name, category=category, series=series, price_usd=price, + tagline=tagline, description=description, + image=image or f"products/{slug}.png", is_featured=featured, + release_year=year, in_stock=in_stock, + architecture=specs.get('architecture', ''), + cuda_cores=specs.get('cuda_cores'), + tensor_cores=specs.get('tensor_cores'), + rt_cores=specs.get('rt_cores'), + memory_gb=specs.get('memory_gb'), + memory_type=specs.get('memory_type', ''), + memory_bandwidth=specs.get('memory_bandwidth', ''), + boost_clock_ghz=specs.get('boost_clock_ghz'), + tdp_watts=specs.get('tdp_watts'), + interface=specs.get('interface', ''), + recommended_psu_watts=specs.get('recommended_psu_watts'), + ) + + +PRODUCTS = [ + # ---- GeForce Gaming — RTX 50 Series (Blackwell) ---- + _p("geforce-rtx-5090", "GeForce RTX 5090", "GeForce Gaming", "RTX 50 Series", + 1999, "The most powerful GeForce GPU ever built.", + "Powered by the NVIDIA Blackwell architecture and DLSS 4, the GeForce RTX 5090 " + "delivers unprecedented gaming and creator performance with 32 GB of GDDR7 memory.", + featured=True, year=2025, architecture="Blackwell", cuda_cores=21760, + tensor_cores=680, rt_cores=170, memory_gb=32, memory_type="GDDR7", + memory_bandwidth="1792 GB/s", boost_clock_ghz=2.41, tdp_watts=575, + interface="PCIe 5.0", recommended_psu_watts=1000), + _p("geforce-rtx-5080", "GeForce RTX 5080", "GeForce Gaming", "RTX 50 Series", + 999, "Game-changing performance with DLSS 4.", + "The GeForce RTX 5080 brings Blackwell efficiency, GDDR7 memory, and full ray " + "tracing to high-refresh 4K gaming.", + featured=True, year=2025, architecture="Blackwell", cuda_cores=10752, + tensor_cores=336, rt_cores=84, memory_gb=16, memory_type="GDDR7", + memory_bandwidth="960 GB/s", boost_clock_ghz=2.62, tdp_watts=360, + interface="PCIe 5.0", recommended_psu_watts=850), + _p("geforce-rtx-5070-ti", "GeForce RTX 5070 Ti", "GeForce Gaming", "RTX 50 Series", + 749, "Elevated 1440p and 4K play.", + "16 GB of GDDR7 and Blackwell tensor cores make the RTX 5070 Ti a sweet spot for " + "high-frame-rate gaming with DLSS 4 Multi Frame Generation.", + year=2025, architecture="Blackwell", cuda_cores=8960, tensor_cores=280, + rt_cores=70, memory_gb=16, memory_type="GDDR7", memory_bandwidth="896 GB/s", + boost_clock_ghz=2.45, tdp_watts=300, interface="PCIe 5.0", + recommended_psu_watts=750), + _p("geforce-rtx-5070", "GeForce RTX 5070", "GeForce Gaming", "RTX 50 Series", + 549, "RTX 4090 performance at a fraction of the power.", + "The RTX 5070 pairs 12 GB of GDDR7 with DLSS 4 to deliver flagship-class frames " + "in today's most demanding titles.", + featured=True, year=2025, architecture="Blackwell", cuda_cores=6144, + tensor_cores=192, rt_cores=48, memory_gb=12, memory_type="GDDR7", + memory_bandwidth="672 GB/s", boost_clock_ghz=2.51, tdp_watts=250, + interface="PCIe 5.0", recommended_psu_watts=650), + _p("geforce-rtx-5060-ti", "GeForce RTX 5060 Ti", "GeForce Gaming", "RTX 50 Series", + 429, "Mainstream gaming, supercharged.", + "With 16 GB of GDDR7, the RTX 5060 Ti makes high-fidelity 1440p gaming and local " + "AI workloads accessible.", + year=2025, architecture="Blackwell", cuda_cores=4608, tensor_cores=144, + rt_cores=36, memory_gb=16, memory_type="GDDR7", memory_bandwidth="448 GB/s", + boost_clock_ghz=2.57, tdp_watts=180, interface="PCIe 5.0", + recommended_psu_watts=550), + _p("geforce-rtx-5060", "GeForce RTX 5060", "GeForce Gaming", "RTX 50 Series", + 299, "DLSS 4 for every gamer.", + "The RTX 5060 brings Blackwell and DLSS 4 to the most popular price point in PC gaming.", + year=2025, architecture="Blackwell", cuda_cores=3840, tensor_cores=120, + rt_cores=30, memory_gb=8, memory_type="GDDR7", memory_bandwidth="448 GB/s", + boost_clock_ghz=2.50, tdp_watts=145, interface="PCIe 5.0", + recommended_psu_watts=450), + # ---- GeForce Gaming — RTX 40 Series (Ada Lovelace) ---- + _p("geforce-rtx-4090", "GeForce RTX 4090", "GeForce Gaming", "RTX 40 Series", + 1599, "Beyond fast. The Ada Lovelace flagship.", + "24 GB of GDDR6X and 16,384 CUDA cores made the RTX 4090 the definitive 4K and " + "creator GPU of the Ada generation.", + year=2022, architecture="Ada Lovelace", cuda_cores=16384, tensor_cores=512, + rt_cores=128, memory_gb=24, memory_type="GDDR6X", memory_bandwidth="1008 GB/s", + boost_clock_ghz=2.52, tdp_watts=450, interface="PCIe 4.0", + recommended_psu_watts=850), + _p("geforce-rtx-4080-super", "GeForce RTX 4080 SUPER", "GeForce Gaming", "RTX 40 Series", + 999, "Enthusiast 4K with Ada efficiency.", + "The RTX 4080 SUPER delivers high-refresh 4K gaming and creator acceleration with " + "16 GB of fast GDDR6X.", + year=2024, architecture="Ada Lovelace", cuda_cores=10240, tensor_cores=320, + rt_cores=80, memory_gb=16, memory_type="GDDR6X", memory_bandwidth="736 GB/s", + boost_clock_ghz=2.55, tdp_watts=320, interface="PCIe 4.0", + recommended_psu_watts=750), + _p("geforce-rtx-4070-super", "GeForce RTX 4070 SUPER", "GeForce Gaming", "RTX 40 Series", + 599, "Seriously fast 1440p.", + "More cores than the original RTX 4070 plus DLSS 3 Frame Generation make the 4070 " + "SUPER an outstanding 1440p performer.", + year=2024, architecture="Ada Lovelace", cuda_cores=7168, tensor_cores=224, + rt_cores=56, memory_gb=12, memory_type="GDDR6X", memory_bandwidth="504 GB/s", + boost_clock_ghz=2.48, tdp_watts=220, interface="PCIe 4.0", + recommended_psu_watts=650), + _p("geforce-rtx-4060", "GeForce RTX 4060", "GeForce Gaming", "RTX 40 Series", + 299, "The most popular way into RTX.", + "Efficient Ada Lovelace gaming with DLSS 3 at 1080p, sipping just 115 W.", + year=2023, architecture="Ada Lovelace", cuda_cores=3072, tensor_cores=96, + rt_cores=24, memory_gb=8, memory_type="GDDR6", memory_bandwidth="272 GB/s", + boost_clock_ghz=2.46, tdp_watts=115, interface="PCIe 4.0", + recommended_psu_watts=550), + + # ---- Studio / Professional (RTX workstation) ---- + _p("rtx-pro-6000-blackwell", "RTX PRO 6000 Blackwell", "Studio / Professional", + "RTX PRO Blackwell", 8565, "The ultimate workstation GPU.", + "96 GB of GDDR7 and the Blackwell architecture power the most demanding AI, " + "rendering, and simulation workloads in professional workstations.", + featured=True, year=2025, architecture="Blackwell", cuda_cores=24064, + memory_gb=96, memory_type="GDDR7 ECC", memory_bandwidth="1792 GB/s", + tdp_watts=600, interface="PCIe 5.0"), + _p("rtx-6000-ada", "RTX 6000 Ada Generation", "Studio / Professional", + "RTX Ada", 6800, "Workstation performance for rendering and AI.", + "48 GB of ECC GDDR6 and Ada Lovelace cores deliver massive throughput for " + "visualization, simulation, and AI development.", + year=2022, architecture="Ada Lovelace", cuda_cores=18176, memory_gb=48, + memory_type="GDDR6 ECC", memory_bandwidth="960 GB/s", tdp_watts=300, + interface="PCIe 4.0"), + _p("rtx-5000-ada", "RTX 5000 Ada Generation", "Studio / Professional", + "RTX Ada", 4000, "Pro-grade power in a 250 W envelope.", + "32 GB of ECC memory for large scenes, multi-app workflows, and AI inference at " + "the desk.", + year=2023, architecture="Ada Lovelace", cuda_cores=12800, memory_gb=32, + memory_type="GDDR6 ECC", memory_bandwidth="576 GB/s", tdp_watts=250, + interface="PCIe 4.0"), + _p("rtx-4000-ada", "RTX 4000 Ada Generation", "Studio / Professional", + "RTX Ada", 1250, "Compact, single-slot pro acceleration.", + "20 GB of memory in a single-slot, 130 W card for space-constrained workstations.", + year=2023, architecture="Ada Lovelace", cuda_cores=6144, memory_gb=20, + memory_type="GDDR6 ECC", memory_bandwidth="360 GB/s", tdp_watts=130, + interface="PCIe 4.0"), + + # ---- Data Center / AI (price = contact sales) ---- + _p("dgx-b200", "NVIDIA Blackwell B200", "Data Center", "Blackwell", + None, "The engine of the AI factory.", + "The B200 Tensor Core GPU delivers a generational leap for trillion-parameter " + "training and inference with 192 GB of HBM3e.", + featured=True, year=2025, architecture="Blackwell", memory_gb=192, + memory_type="HBM3e", memory_bandwidth="8 TB/s", tdp_watts=1000, + interface="SXM"), + _p("h200-tensor-core", "NVIDIA H200 Tensor Core GPU", "Data Center", "Hopper", + None, "Supercharged generative AI and HPC.", + "The H200 is the first GPU with 141 GB of HBM3e, nearly doubling capacity and " + "boosting bandwidth to 4.8 TB/s for LLM inference.", + featured=True, year=2024, architecture="Hopper", memory_gb=141, + memory_type="HBM3e", memory_bandwidth="4.8 TB/s", tdp_watts=700, + interface="SXM"), + _p("h100-tensor-core", "NVIDIA H100 Tensor Core GPU", "Data Center", "Hopper", + None, "The proven workhorse of modern AI.", + "With the Transformer Engine and 80 GB of HBM3, the H100 accelerates LLM training " + "and inference at data-center scale.", + year=2022, architecture="Hopper", memory_gb=80, memory_type="HBM3", + memory_bandwidth="3.35 TB/s", tdp_watts=700, interface="SXM"), + _p("a100-tensor-core", "NVIDIA A100 Tensor Core GPU", "Data Center", "Ampere", + None, "The data-center GPU that defined AI scale.", + "80 GB of HBM2e and Multi-Instance GPU make the A100 a versatile platform for " + "training, inference, and HPC.", + year=2020, architecture="Ampere", memory_gb=80, memory_type="HBM2e", + memory_bandwidth="2039 GB/s", tdp_watts=400, interface="SXM"), + _p("l40s", "NVIDIA L40S", "Data Center", "Ada", + None, "Universal GPU for AI, graphics, and video.", + "48 GB of GDDR6 and Ada Lovelace cores make the L40S a versatile data-center GPU " + "for inference, fine-tuning, and rendering.", + year=2023, architecture="Ada Lovelace", cuda_cores=18176, memory_gb=48, + memory_type="GDDR6", memory_bandwidth="864 GB/s", tdp_watts=350, + interface="PCIe 4.0"), + _p("gh200-grace-hopper", "NVIDIA GH200 Grace Hopper Superchip", "Data Center", + "Grace Hopper", None, "CPU and GPU, coherently fused.", + "The GH200 connects a Grace CPU and Hopper GPU over NVLink-C2C for giant-model AI " + "and HPC with up to 624 GB of fast memory.", + year=2024, architecture="Grace Hopper", memory_gb=144, memory_type="HBM3e", + memory_bandwidth="4.9 TB/s", tdp_watts=1000, interface="NVLink-C2C"), + + # ---- Embedded / Edge (Jetson) ---- + _p("jetson-orin-nano-super", "Jetson Orin Nano Super Developer Kit", "Embedded", + "Jetson Orin", 249, "The world's most affordable generative AI computer.", + "67 TOPS of AI performance in a tiny module — the Jetson Orin Nano Super powers " + "edge robotics, vision, and on-device LLMs.", + featured=True, year=2024, architecture="Ampere", cuda_cores=1024, + memory_gb=8, memory_type="LPDDR5", memory_bandwidth="102 GB/s", tdp_watts=25, + interface="—"), + _p("jetson-agx-orin", "Jetson AGX Orin 64GB", "Embedded", "Jetson Orin", + 1999, "Server-class AI at the edge.", + "Up to 275 TOPS and 64 GB of memory bring advanced robotics and autonomous machine " + "workloads to a 60 W module.", + year=2022, architecture="Ampere", cuda_cores=2048, memory_gb=64, + memory_type="LPDDR5", memory_bandwidth="204 GB/s", tdp_watts=60, interface="—"), + _p("jetson-orin-nx", "Jetson Orin NX 16GB", "Embedded", "Jetson Orin", + 699, "Compact power for autonomous machines.", + "100 TOPS in a small module for drones, robots, and smart cameras.", + year=2023, architecture="Ampere", cuda_cores=1024, memory_gb=16, + memory_type="LPDDR5", memory_bandwidth="102 GB/s", tdp_watts=25, interface="—"), + + # ---- Consumer Devices (SHIELD) ---- + _p("shield-tv-pro", "SHIELD TV Pro", "Consumer Devices", "SHIELD", + 199, "The ultimate 4K HDR streaming media player.", + "AI-enhanced 4K upscaling, Dolby Vision and Atmos, and Google TV — powered by the " + "NVIDIA Tegra X1+ processor.", + featured=True, year=2019, architecture="Tegra X1+", memory_gb=3, + memory_type="LPDDR4", tdp_watts=40, interface="HDMI 2.0b"), + _p("shield-tv", "SHIELD TV", "Consumer Devices", "SHIELD", + 149, "Compact 4K HDR streaming, supercharged by AI.", + "A tube-shaped 4K HDR streamer with AI upscaling and the Tegra X1+ processor.", + year=2019, architecture="Tegra X1+", memory_gb=2, memory_type="LPDDR4", + tdp_watts=40, interface="HDMI 2.0b"), +] + +# -------------------------------------------------------------------------- +# News / blog articles +# -------------------------------------------------------------------------- +ARTICLES = [ + dict(slug="top500-green500-supercomputers-isc-2026", + title="NVIDIA Powers Over 400 of the World's 500 Fastest Supercomputers", + category="AI Infrastructure", author="NVIDIA Newsroom", published=date(2026, 6, 23), + image="news/top500-green500-supercomputers-isc-2026.png", read_minutes=5, + excerpt="NVIDIA technologies power more than 400 of the world's 500 fastest " + "supercomputers — 81% of the TOP500.", + body="At ISC 2026, the latest TOP500 list confirmed that NVIDIA technologies now " + "power more than 400 of the world's 500 fastest supercomputers — 81% of the " + "list. NVIDIA also dominates the Green500 ranking of the most energy-efficient " + "systems, underscoring how accelerated computing delivers both performance and " + "efficiency for the world's most demanding scientific workloads."), + dict(slug="jupiter-exascale-supercomputing-science", + title="At ISC, JUPITER Shows What Exascale Science Looks Like", + category="AI Infrastructure", author="NVIDIA Newsroom", published=date(2026, 6, 22), + image="news/jupiter-exascale-supercomputing-science.png", read_minutes=6, + excerpt="Europe's first exascale supercomputer — running on NVIDIA Grace Hopper " + "Superchips — is mapping the brain, modeling climate, and advancing 6G AI.", + body="JUPITER, Europe's first exascale supercomputer, is built on NVIDIA Grace " + "Hopper Superchips. Researchers are using it to map the human brain, model " + "climate systems, advance 6G AI research, and break scientific records — a " + "showcase of what exascale-class accelerated computing makes possible."), + dict(slug="ai-for-science-software-cuda", + title="New NVIDIA AI Software Unlocks Scientific Discoveries", + category="AI", author="NVIDIA Newsroom", published=date(2026, 6, 22), + image="news/ai-for-science-software-cuda.png", read_minutes=5, + excerpt="NVIDIA CUDA-X libraries, microservices and reference code accelerate AI " + "for science.", + body="From materials simulation to experimental astronomy, new NVIDIA CUDA-X " + "libraries, microservices, and reference workflows are accelerating AI for " + "science. The tools help researchers move from data to discovery faster across " + "physics, chemistry, biology, and astronomy."), + dict(slug="nvidia-vera-cpu-los-alamos-national-laboratory", + title="NVIDIA Vera CPU Opens the Way for Agentic Scientific AI at Los Alamos", + category="AI Infrastructure", author="NVIDIA Newsroom", published=date(2026, 6, 22), + image="news/nvidia-vera-cpu-los-alamos-national-laboratory.png", read_minutes=5, + excerpt="Mission, Vision and Veritas supercomputers with Vera CPUs to advance " + "materials simulation, scientific AI agents and molecular design.", + body="Los Alamos National Laboratory is deploying new Mission, Vision, and Veritas " + "supercomputers powered by the NVIDIA Vera CPU. The systems will advance " + "materials simulation, agentic scientific AI, and molecular design for national " + "research priorities."), + dict(slug="blackwell-mlperf-training-6-0", + title="Fastest, Largest, Strongest: NVIDIA Blackwell Sweeps MLPerf Training 6.0", + category="AI Infrastructure", author="NVIDIA Newsroom", published=date(2026, 6, 16), + image="news/blackwell-mlperf-training-6-0.png", read_minutes=6, + excerpt="NVIDIA delivers the performance, scale and reliability that frontier " + "training requires — in benchmarks and beyond.", + body="In the latest MLPerf Training 6.0 results, the NVIDIA Blackwell platform " + "swept every benchmark, delivering record performance at scale. The results " + "demonstrate the throughput and reliability that frontier model training " + "demands across the largest GPU clusters."), + dict(slug="rtx-ai-garage-local-gemma-diffusion", + title="NVIDIA Accelerates Google DeepMind's DiffusionGemma for Local AI", + category="AI", author="NVIDIA Newsroom", published=date(2026, 6, 10), + image="news/rtx-ai-garage-local-gemma-diffusion.png", read_minutes=5, + excerpt="The new DiffusionGemma open model generates text in parallel and is " + "optimized to run locally on the NVIDIA RTX platform.", + body="DiffusionGemma, a new open model from Google DeepMind, generates text in " + "parallel rather than one token at a time. NVIDIA has optimized it to run " + "locally on GeForce RTX and RTX PRO GPUs, bringing fast, private generative " + "AI to the desktop."), + dict(slug="eco-wave-power-ai-digital-twins", + title="Eco Wave Power Turns Waves Into Watts With NVIDIA AI and Digital Twins", + category="AI", author="NVIDIA Newsroom", published=date(2026, 6, 22), + image="news/eco-wave-power-ai-digital-twins.png", read_minutes=4, + excerpt="An NVIDIA Inception startup is developing wave-energy technology powered " + "by NVIDIA AI infrastructure and digital twins.", + body="Eco Wave Power, part of the NVIDIA Inception program's Sustainable Futures " + "initiative, is building wave-energy technology using NVIDIA AI infrastructure " + "and Omniverse digital twins to simulate and optimize clean power generation " + "from ocean waves."), + dict(slug="halos-os-robotaxi-safety", + title="For Robotaxis, Safety Must Be Built In, Not Bolted On", + category="Driving", author="NVIDIA Newsroom", published=date(2026, 6, 10), + image="news/halos-os-robotaxi-safety.png", read_minutes=5, + excerpt="NVIDIA Halos OS delivers safety-certified platform software, standardized " + "interfaces, AI guardrails and pre-deployment validation for L4 robotaxis.", + body="NVIDIA Halos OS provides safety-certified platform software, standardized " + "interfaces, AI guardrails, and pre-deployment validation for Level 4 robotaxi " + "fleets. The approach builds safety into the autonomous-driving stack from the " + "ground up rather than adding it after the fact."), +] + +# -------------------------------------------------------------------------- +# Driver downloads +# -------------------------------------------------------------------------- +DRIVERS = [ + dict(product_series="GeForce RTX 50 Series", branch="Game Ready", version="566.36", + os="Windows 11", released=date(2026, 6, 10), size_mb=712, + highlights="Day-0 support for the latest AAA titles and DLSS 4 updates."), + dict(product_series="GeForce RTX 50 Series", branch="Studio", version="566.14", + os="Windows 11", released=date(2026, 5, 28), size_mb=705, + highlights="Optimized and validated for creative applications."), + dict(product_series="GeForce RTX 50 Series", branch="Game Ready", version="566.36", + os="Windows 10", released=date(2026, 6, 10), size_mb=708, + highlights="Day-0 support for the latest AAA titles and DLSS 4 updates."), + dict(product_series="GeForce RTX 40 Series", branch="Game Ready", version="566.36", + os="Windows 11", released=date(2026, 6, 10), size_mb=690, + highlights="Performance optimizations for recent releases."), + dict(product_series="GeForce RTX 40 Series", branch="Studio", version="566.14", + os="Windows 11", released=date(2026, 5, 28), size_mb=688, + highlights="Validated for content-creation workflows."), + dict(product_series="GeForce RTX 40 Series", branch="Game Ready", version="565.90", + os="Linux", released=date(2026, 4, 30), size_mb=395, + highlights="Production branch for Linux gaming and Vulkan."), + dict(product_series="GeForce RTX 30 Series", branch="Game Ready", version="566.36", + os="Windows 11", released=date(2026, 6, 10), size_mb=672, + highlights="Continued support for Ampere GeForce GPUs."), + dict(product_series="RTX PRO / Workstation", branch="Studio", version="553.62", + os="Windows 11", released=date(2026, 5, 15), size_mb=820, + highlights="Enterprise-validated production branch (NVIDIA RTX Enterprise)."), + dict(product_series="RTX PRO / Workstation", branch="Studio", version="553.24", + os="Linux", released=date(2026, 3, 20), size_mb=430, + highlights="Long-term support branch for professional Linux workstations."), +] + +# -------------------------------------------------------------------------- +# Benchmark users — alice.j@test.com et al. (password: TestPass123!) +# -------------------------------------------------------------------------- +BENCHMARK_USERS = [ + dict(email="alice.j@test.com", name="Alice Johnson", password="TestPass123!", + company="Pixel Forge Studios", country="United States", newsletter_opt_in=True), + dict(email="bob.c@test.com", name="Bob Chen", password="TestPass123!", + company="", country="United States", newsletter_opt_in=False), + dict(email="carol.d@test.com", name="Carol Davis", password="TestPass123!", + company="Helix Robotics", country="Canada", newsletter_opt_in=True), + dict(email="david.k@test.com", name="David Kim", password="TestPass123!", + company="", country="United Kingdom", newsletter_opt_in=False), +] + +# -------------------------------------------------------------------------- +# Notable reviews — placed on NON-task-target products to avoid answer leaks +# -------------------------------------------------------------------------- +NOTABLE_REVIEWS = [ + dict(user_email="bob.c@test.com", product_slug="geforce-rtx-4090", rating=5, + title="Still a monster", body="Two years on and the 4090 handles everything at 4K."), + dict(user_email="carol.d@test.com", product_slug="jetson-agx-orin", rating=5, + title="Perfect for our robots", body="64 GB lets us run large perception models at the edge."), + dict(user_email="david.k@test.com", product_slug="geforce-rtx-4070-super", rating=4, + title="Great 1440p value", body="Runs quiet and cool; DLSS 3 is excellent."), + dict(user_email="bob.c@test.com", product_slug="shield-tv-pro", rating=5, + title="Best streamer", body="The AI upscaling genuinely makes 1080p content look great."), + dict(user_email="carol.d@test.com", product_slug="rtx-6000-ada", rating=5, + title="Workstation beast", body="48 GB of ECC memory handles our largest scenes."), +] + +# -------------------------------------------------------------------------- +# Benchmark activity — pre-existing wishlists / orders (avoid task targets) +# -------------------------------------------------------------------------- +BENCHMARK_ACTIVITY = [ + dict(user_email="alice.j@test.com", + wishlist=["rtx-pro-6000-blackwell", "geforce-rtx-4080-super"], + orders=[dict(products=["geforce-rtx-4090"], status="Delivered")]), + dict(user_email="carol.d@test.com", + wishlist=["jetson-orin-nx"], + orders=[dict(products=["jetson-agx-orin"], status="Delivered")]), +] diff --git a/sites/nvidia/static/css/.gitkeep b/sites/nvidia/static/css/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/sites/nvidia/static/css/main.css b/sites/nvidia/static/css/main.css new file mode 100644 index 00000000..7eed328f --- /dev/null +++ b/sites/nvidia/static/css/main.css @@ -0,0 +1,160 @@ +/* NVIDIA mirror — design system (black + NVIDIA green #76B900) */ +:root{ + --nv-green:#76b900; --nv-green-d:#5e9400; --ink:#1a1a1a; --muted:#6b6b6b; + --line:#e3e3e3; --bg:#ffffff; --bg-soft:#f7f7f7; --black:#000; --dark:#1a1a1a; + --radius:6px; --maxw:1280px; +} +*{box-sizing:border-box} +html,body{margin:0;padding:0} +body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif; + color:var(--ink);background:var(--bg);line-height:1.5;font-size:15px} +a{color:inherit;text-decoration:none} +a:hover{color:var(--nv-green-d)} +img{max-width:100%;display:block} +.container{max-width:var(--maxw);margin:0 auto;padding:0 24px} + +/* header */ +.topbar{background:var(--black);color:#fff;font-size:13px} +.topbar .container{display:flex;align-items:center;gap:22px;height:56px} +.logo{display:flex;align-items:center;gap:8px;font-weight:800;letter-spacing:.5px;font-size:20px} +.logo .eye{color:var(--nv-green);font-size:24px;line-height:1} +.logo b{color:#fff} +.nav{display:flex;gap:20px;flex:1} +.nav a{color:#eaeaea;font-weight:500} +.nav a:hover{color:var(--nv-green)} +.topbar .actions{display:flex;align-items:center;gap:16px} +.topbar .actions a{color:#eaeaea} +.topbar .actions a:hover{color:var(--nv-green)} +.searchbox{display:flex;align-items:center;background:#222;border-radius:20px;padding:5px 12px} +.searchbox input{background:transparent;border:0;color:#fff;outline:none;width:150px} +.searchbox input::placeholder{color:#999} +.cart-badge{background:var(--nv-green);color:#000;border-radius:10px;padding:0 7px; + font-weight:700;font-size:11px;margin-left:3px} + +/* buttons */ +.btn{display:inline-block;background:var(--nv-green);color:#000;font-weight:700; + padding:10px 22px;border-radius:var(--radius);border:0;cursor:pointer;font-size:14px} +.btn:hover{background:var(--nv-green-d);color:#000} +.btn-ghost{background:transparent;border:1px solid #555;color:#fff} +.btn-ghost:hover{border-color:var(--nv-green);color:var(--nv-green)} +.btn-dark{background:var(--dark);color:#fff} +.btn-dark:hover{background:#000;color:var(--nv-green)} +.btn-block{display:block;width:100%;text-align:center} +.btn:disabled{opacity:.5;cursor:not-allowed} + +/* hero */ +.hero{background:var(--black);color:#fff;position:relative;overflow:hidden} +.hero .container{display:flex;align-items:center;gap:40px;padding:64px 24px;min-height:420px} +.hero-copy{flex:1;z-index:2} +.hero-copy .eyebrow{color:var(--nv-green);font-weight:700;letter-spacing:1px;text-transform:uppercase;font-size:13px} +.hero-copy h1{font-size:52px;line-height:1.05;margin:12px 0 16px;font-weight:800} +.hero-copy p{font-size:18px;color:#cfcfcf;max-width:540px} +.hero-media{flex:1;text-align:center} +.hero-media img{margin:0 auto;filter:drop-shadow(0 20px 40px rgba(118,185,0,.25))} + +/* sections */ +.section{padding:56px 0} +.section h2{font-size:30px;font-weight:800;margin:0 0 6px} +.section .sub{color:var(--muted);margin:0 0 28px} +.section.alt{background:var(--bg-soft)} + +/* category pills */ +.pills{display:flex;flex-wrap:wrap;gap:10px;margin:18px 0} +.pill{border:1px solid var(--line);border-radius:20px;padding:7px 16px;background:#fff;font-weight:600;font-size:13px} +.pill.active,.pill:hover{border-color:var(--nv-green);color:var(--nv-green-d)} + +/* product grid */ +.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:22px} +.card{border:1px solid var(--line);border-radius:8px;background:#fff;overflow:hidden; + display:flex;flex-direction:column;transition:box-shadow .15s,transform .15s} +.card:hover{box-shadow:0 8px 24px rgba(0,0,0,.10);transform:translateY(-2px)} +.card .thumb{background:#0b0b0b;aspect-ratio:4/3;display:flex;align-items:center;justify-content:center;padding:14px} +.card .thumb img{max-height:100%;object-fit:contain} +.card .body{padding:14px 16px 18px;display:flex;flex-direction:column;gap:6px;flex:1} +.card .series{color:var(--nv-green-d);font-weight:700;font-size:12px;text-transform:uppercase} +.card .name{font-weight:700;font-size:16px} +.card .tagline{color:var(--muted);font-size:13px;flex:1} +.card .price{font-weight:800;font-size:17px;margin-top:6px} +.card .stars{color:#f5a623;font-size:13px} +.muted{color:var(--muted)} +.green{color:var(--nv-green-d)} + +/* spec table */ +.specs{width:100%;border-collapse:collapse;margin:18px 0} +.specs th,.specs td{text-align:left;padding:11px 14px;border-bottom:1px solid var(--line);font-size:14px} +.specs th{width:240px;color:var(--muted);font-weight:600} +.compare-tbl{width:100%;border-collapse:collapse;overflow-x:auto} +.compare-tbl th,.compare-tbl td{border:1px solid var(--line);padding:12px 14px;text-align:center;font-size:14px} +.compare-tbl th:first-child,.compare-tbl td:first-child{text-align:left;color:var(--muted);font-weight:600;background:var(--bg-soft)} +.compare-tbl thead th{background:#000;color:#fff} +.scroll-x{overflow-x:auto} + +/* detail layout */ +.detail{display:grid;grid-template-columns:1fr 1fr;gap:40px;padding:40px 0} +.detail .media{background:#0b0b0b;border-radius:10px;padding:30px;display:flex;align-items:center;justify-content:center} +.detail h1{font-size:34px;margin:6px 0 10px;font-weight:800} +.detail .price{font-size:26px;font-weight:800;margin:14px 0} +.row{display:flex;gap:12px;align-items:center;flex-wrap:wrap} + +/* forms */ +.form{max-width:440px;margin:0 auto} +.form.wide{max-width:720px} +.field{margin-bottom:16px} +.field label{display:block;font-weight:600;margin-bottom:6px;font-size:14px} +.field input,.field select,.field textarea{width:100%;padding:10px 12px;border:1px solid var(--line); + border-radius:var(--radius);font-size:14px;font-family:inherit} +.field input:focus,.field select:focus,.field textarea:focus{outline:none;border-color:var(--nv-green)} +.err{color:#c0392b;font-size:13px;margin-top:4px} +.check{display:flex;align-items:center;gap:8px} +.check input{width:auto} + +/* flash */ +.flashes{margin:16px 0} +.flash{padding:12px 16px;border-radius:var(--radius);margin-bottom:10px;font-size:14px} +.flash.success{background:#eef7df;color:#3c6300;border:1px solid #cce6a0} +.flash.error{background:#fdecea;color:#a83228;border:1px solid #f5c6c2} +.flash.info{background:#eef4fb;color:#27496b;border:1px solid #c9ddf0} + +/* misc */ +.page-head{background:var(--black);color:#fff;padding:40px 0} +.page-head h1{font-size:34px;margin:0;font-weight:800} +.page-head p{color:#bbb;margin:8px 0 0} +.breadcrumb{font-size:13px;color:var(--muted);margin:18px 0} +.breadcrumb a:hover{color:var(--nv-green-d)} +.toolbar{display:flex;justify-content:space-between;align-items:center;gap:16px;flex-wrap:wrap;margin:18px 0} +.toolbar select{padding:8px 10px;border:1px solid var(--line);border-radius:var(--radius)} +.tag{display:inline-block;background:var(--bg-soft);border:1px solid var(--line);border-radius:4px; + padding:2px 8px;font-size:12px;color:var(--muted);margin-right:6px} +.badge-stock{color:var(--nv-green-d);font-weight:700;font-size:13px} +.badge-out{color:#c0392b;font-weight:700;font-size:13px} + +/* news */ +.news-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:24px} +.news-card{border:1px solid var(--line);border-radius:8px;overflow:hidden;background:#fff} +.news-card .thumb{aspect-ratio:16/9;background:#0b0b0b;overflow:hidden} +.news-card .thumb img{width:100%;height:100%;object-fit:cover} +.news-card .body{padding:16px} +.news-card .cat{color:var(--nv-green-d);font-weight:700;font-size:12px;text-transform:uppercase} +.news-card h3{margin:6px 0;font-size:17px} +.article{max-width:760px;margin:0 auto;padding:40px 0} +.article h1{font-size:36px;font-weight:800;line-height:1.1} +.article .meta{color:var(--muted);margin:12px 0 24px;font-size:14px} +.article img{border-radius:8px;margin:20px 0} +.article p{font-size:17px;margin:18px 0} + +/* footer */ +.footer{background:#000;color:#bbb;padding:40px 0;font-size:13px;margin-top:40px} +.footer .cols{display:flex;flex-wrap:wrap;gap:40px;justify-content:space-between} +.footer h4{color:#fff;font-size:13px;text-transform:uppercase;letter-spacing:.5px;margin:0 0 12px} +.footer a{display:block;color:#bbb;margin-bottom:7px} +.footer a:hover{color:var(--nv-green)} +.footer .news-signup input{padding:9px 12px;border-radius:4px 0 0 4px;border:0;width:200px} +.footer .news-signup button{border-radius:0 4px 4px 0} +.footer .copyright{border-top:1px solid #222;margin-top:28px;padding-top:18px;color:#777} + +@media(max-width:880px){ + .hero .container{flex-direction:column;padding:40px 24px} + .hero-copy h1{font-size:36px} + .detail{grid-template-columns:1fr} + .nav{display:none} +} diff --git a/sites/nvidia/static/icons/.gitkeep b/sites/nvidia/static/icons/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/sites/nvidia/static/icons/favicon.svg b/sites/nvidia/static/icons/favicon.svg new file mode 100644 index 00000000..e8c141fd --- /dev/null +++ b/sites/nvidia/static/icons/favicon.svg @@ -0,0 +1 @@ + diff --git a/sites/nvidia/static/icons/nvidia-logo-black.svg b/sites/nvidia/static/icons/nvidia-logo-black.svg new file mode 100644 index 00000000..0d7efb3f --- /dev/null +++ b/sites/nvidia/static/icons/nvidia-logo-black.svg @@ -0,0 +1,26 @@ + + + + +NVIDIA-Logo + + + + diff --git a/sites/nvidia/static/icons/nvidia-logo-white.svg b/sites/nvidia/static/icons/nvidia-logo-white.svg new file mode 100644 index 00000000..f4f17895 --- /dev/null +++ b/sites/nvidia/static/icons/nvidia-logo-white.svg @@ -0,0 +1 @@ +NVIDIA_logo \ No newline at end of file diff --git a/sites/nvidia/static/js/.gitkeep b/sites/nvidia/static/js/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/sites/nvidia/static/js/main.js b/sites/nvidia/static/js/main.js new file mode 100644 index 00000000..2aa215c7 --- /dev/null +++ b/sites/nvidia/static/js/main.js @@ -0,0 +1,7 @@ +// NVIDIA mirror — minimal progressive enhancement +document.addEventListener('DOMContentLoaded', function () { + // auto-dismiss flash messages after a few seconds + document.querySelectorAll('.flash').forEach(function (el) { + setTimeout(function () { el.style.transition = 'opacity .4s'; el.style.opacity = '0'; }, 6000); + }); +}); diff --git a/sites/nvidia/tasks.jsonl b/sites/nvidia/tasks.jsonl new file mode 100644 index 00000000..3b66184c --- /dev/null +++ b/sites/nvidia/tasks.jsonl @@ -0,0 +1,20 @@ +{"web_name": "NVIDIA", "id": "NVIDIA--0", "ques": "Find the GeForce RTX 5090 graphics card and report how much memory it has and what type.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} +{"web_name": "NVIDIA", "id": "NVIDIA--1", "ques": "How many CUDA cores does the GeForce RTX 5080 have?", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} +{"web_name": "NVIDIA", "id": "NVIDIA--2", "ques": "What is the graphics card power (TDP, in watts) of the GeForce RTX 4090?", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} +{"web_name": "NVIDIA", "id": "NVIDIA--3", "ques": "Among the GeForce RTX 50 Series graphics cards, which one is the least expensive, and what is its price?", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} +{"web_name": "NVIDIA", "id": "NVIDIA--4", "ques": "Browse the Data Center products and find which GPU has 141 GB of memory.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} +{"web_name": "NVIDIA", "id": "NVIDIA--5", "ques": "In the Embedded / robotics products, what is the price of the Jetson Orin Nano Super Developer Kit?", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} +{"web_name": "NVIDIA", "id": "NVIDIA--6", "ques": "Using the GPU comparison tool, compare the GeForce RTX 5090 and the GeForce RTX 4090. Which has more CUDA cores, and how many more?", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} +{"web_name": "NVIDIA", "id": "NVIDIA--7", "ques": "Compare the GeForce RTX 5080 and the GeForce RTX 4080 SUPER. Which one has the higher memory bandwidth?", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} +{"web_name": "NVIDIA", "id": "NVIDIA--8", "ques": "Of all the Studio / Professional GPUs, which one has the most memory, and how much?", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} +{"web_name": "NVIDIA", "id": "NVIDIA--9", "ques": "Go to the Drivers section and find the latest Game Ready driver version for the GeForce RTX 50 Series on Windows 11.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} +{"web_name": "NVIDIA", "id": "NVIDIA--10", "ques": "Find the Studio branch driver for the GeForce RTX 40 Series on Windows 11 and report its version number.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} +{"web_name": "NVIDIA", "id": "NVIDIA--11", "ques": "Sign in with email alice.j@test.com and password TestPass123!, then add the GeForce RTX 5080 to your cart and proceed through checkout to place the order.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} +{"web_name": "NVIDIA", "id": "NVIDIA--12", "ques": "Sign in with email alice.j@test.com and password TestPass123!, then add the least expensive GeForce RTX 40 Series graphics card to your cart.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} +{"web_name": "NVIDIA", "id": "NVIDIA--13", "ques": "Sign in with email alice.j@test.com and password TestPass123!, then save the GeForce RTX 5070 Ti to your wishlist.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} +{"web_name": "NVIDIA", "id": "NVIDIA--14", "ques": "Sign in with email alice.j@test.com and password TestPass123!, then change the country on your profile to Germany.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} +{"web_name": "NVIDIA", "id": "NVIDIA--15", "ques": "Sign in with email alice.j@test.com and password TestPass123!, then write a 5-star review titled 'Incredible' for the Jetson Orin Nano Super Developer Kit.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} +{"web_name": "NVIDIA", "id": "NVIDIA--16", "ques": "Sign in with email alice.j@test.com and password TestPass123!. Your wishlist already has more than one item — remove the workstation GPU (the RTX PRO 6000 Blackwell) from it.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} +{"web_name": "NVIDIA", "id": "NVIDIA--17", "ques": "Among all GeForce Gaming graphics cards with at least 16 GB of memory, find the least expensive one and add it to your cart (sign in as alice.j@test.com / TestPass123!).", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} +{"web_name": "NVIDIA", "id": "NVIDIA--18", "ques": "In the NVIDIA Newsroom, find the article about Blackwell sweeping MLPerf Training 6.0 and report its publication date.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} +{"web_name": "NVIDIA", "id": "NVIDIA--19", "ques": "Subscribe the email address gamer42@example.com to the NVIDIA newsletter.", "web": "http://localhost:40015/", "upstream_url": "https://www.nvidia.com/"} diff --git a/sites/nvidia/templates/.gitkeep b/sites/nvidia/templates/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/sites/nvidia/templates/404.html b/sites/nvidia/templates/404.html new file mode 100644 index 00000000..9d9c3aa1 --- /dev/null +++ b/sites/nvidia/templates/404.html @@ -0,0 +1,14 @@ +{% extends "base.html" %} +{% block title %}Page Not Found — NVIDIA{% endblock %} +{% block content %} + +
+
+
Error 404
+

Page Not Found

+

The page you're looking for doesn't exist or has been moved.

+ Back to Home +
+
+ +{% endblock %} diff --git a/sites/nvidia/templates/_product_card.html b/sites/nvidia/templates/_product_card.html new file mode 100644 index 00000000..0e711ff4 --- /dev/null +++ b/sites/nvidia/templates/_product_card.html @@ -0,0 +1,16 @@ +{# expects: p (Product) #} + +
+ {% if p.image %} + {{ p.name }} + {% endif %} +
+
+ {% if p.series %}
{{ p.series }}
{% endif %} +
{{ p.name }}
+ {% if p.tagline %}
{{ p.tagline }}
{% endif %} + {% if p.avg_rating %}
★ {{ p.avg_rating }} ({{ p.review_count }})
{% endif %} +
{{ p.price_display }}
+
+
diff --git a/sites/nvidia/templates/account.html b/sites/nvidia/templates/account.html new file mode 100644 index 00000000..26e519ec --- /dev/null +++ b/sites/nvidia/templates/account.html @@ -0,0 +1,45 @@ +{% extends "base.html" %} +{% block title %}My Account — NVIDIA{% endblock %} +{% block content %} + +
+
+

Welcome, {{ current_user.name }}

+

{{ current_user.email }}{% if current_user.company %} · {{ current_user.company }}{% endif %}{% if current_user.country %} · {{ current_user.country }}{% endif %}

+
+
+ +
+
+ + +

Order History

+ {% if orders %} + {% for o in orders %} +
+
+
Order #{{ o.id }} · {{ o.created.strftime('%b %d, %Y') }}
+
{{ o.status }} ${{ '{:,}'.format(o.total_usd) }}
+
+ + {% for it in o.items %} + + + + + {% endfor %} +
{{ it.name }}Qty {{ it.quantity }} · ${{ '{:,}'.format(it.price_usd or 0) }}
+ View order +
+ {% endfor %} + {% else %} +

You have no orders yet. Start shopping.

+ {% endif %} +
+
+ +{% endblock %} diff --git a/sites/nvidia/templates/account_edit.html b/sites/nvidia/templates/account_edit.html new file mode 100644 index 00000000..f2ea26e2 --- /dev/null +++ b/sites/nvidia/templates/account_edit.html @@ -0,0 +1,37 @@ +{% extends "base.html" %} +{% block title %}Edit Profile — NVIDIA{% endblock %} +{% block content %} + +
+
+
+

Edit Profile

+
+ {{ form.hidden_tag() }} +
+ {{ form.name.label }} + {{ form.name() }} + {% for e in form.name.errors %}
{{ e }}
{% endfor %} +
+
+ {{ form.company.label }} + {{ form.company() }} + {% for e in form.company.errors %}
{{ e }}
{% endfor %} +
+
+ {{ form.country.label }} + {{ form.country() }} + {% for e in form.country.errors %}
{{ e }}
{% endfor %} +
+
+ {{ form.newsletter_opt_in() }} + {{ form.newsletter_opt_in.label }} +
+ +
+

Back to account

+
+
+
+ +{% endblock %} diff --git a/sites/nvidia/templates/account_password.html b/sites/nvidia/templates/account_password.html new file mode 100644 index 00000000..a30e9357 --- /dev/null +++ b/sites/nvidia/templates/account_password.html @@ -0,0 +1,33 @@ +{% extends "base.html" %} +{% block title %}Change Password — NVIDIA{% endblock %} +{% block content %} + +
+
+
+

Change Password

+
+ {{ form.hidden_tag() }} +
+ {{ form.current.label }} + {{ form.current() }} + {% for e in form.current.errors %}
{{ e }}
{% endfor %} +
+
+ {{ form.new.label }} + {{ form.new() }} + {% for e in form.new.errors %}
{{ e }}
{% endfor %} +
+
+ {{ form.confirm.label }} + {{ form.confirm() }} + {% for e in form.confirm.errors %}
{{ e }}
{% endfor %} +
+ +
+

Back to account

+
+
+
+ +{% endblock %} diff --git a/sites/nvidia/templates/base.html b/sites/nvidia/templates/base.html new file mode 100644 index 00000000..68eecc9d --- /dev/null +++ b/sites/nvidia/templates/base.html @@ -0,0 +1,84 @@ + + + + + + {% block title %}NVIDIA{% endblock %} + + + + +
+
+ + +
+ + {% if current_user.is_authenticated %} + {{ current_user.name.split()[0] }} + Sign Out + {% else %} + Login + {% endif %} + Cart{% if cart_count %}{{ cart_count }}{% endif %} +
+
+
+ +{% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} +
+ {% for cat, msg in messages %}
{{ msg }}
{% endfor %} +
+ {% endif %} +{% endwith %} + +
{% block content %}{% endblock %}
+ + + + + diff --git a/sites/nvidia/templates/cart.html b/sites/nvidia/templates/cart.html new file mode 100644 index 00000000..45784fe0 --- /dev/null +++ b/sites/nvidia/templates/cart.html @@ -0,0 +1,64 @@ +{% extends "base.html" %} +{% block title %}Shopping Cart — NVIDIA{% endblock %} +{% block content %} + +
+

Shopping Cart

+
+ +
+
+ {% if items %} +
+ + + + + + + + + + {% for item in items %} + {% set p = item.product %} + + + + + + + + + {% endfor %} + +
ProductPriceQuantityTotal
+ {% if p.image %} + {{ p.name }} + {% endif %} + {{ p.name }}{{ p.price_display }} +
+ + + +
+
${{ '{:,}'.format((p.price_usd or 0) * item.quantity) }} +
+ + +
+
+
+ +
+
Subtotal: ${{ '{:,}'.format(subtotal) }}
+ Proceed to Checkout +
+ {% else %} +

Your cart is empty. Browse products.

+ {% endif %} +
+
+ +{% endblock %} diff --git a/sites/nvidia/templates/checkout.html b/sites/nvidia/templates/checkout.html new file mode 100644 index 00000000..2b5c275e --- /dev/null +++ b/sites/nvidia/templates/checkout.html @@ -0,0 +1,65 @@ +{% extends "base.html" %} +{% block title %}Checkout — NVIDIA{% endblock %} +{% block content %} + +
+

Checkout

+
+ +
+
+
+
+

Shipping & Payment

+
+ {{ form.hidden_tag() }} +
+ {{ form.full_name.label }} + {{ form.full_name() }} + {% for e in form.full_name.errors %}
{{ e }}
{% endfor %} +
+
+ {{ form.address.label }} + {{ form.address() }} + {% for e in form.address.errors %}
{{ e }}
{% endfor %} +
+
+
+ {{ form.city.label }} + {{ form.city() }} + {% for e in form.city.errors %}
{{ e }}
{% endfor %} +
+
+ {{ form.zip_code.label }} + {{ form.zip_code() }} + {% for e in form.zip_code.errors %}
{{ e }}
{% endfor %} +
+
+
+ {{ form.card.label }} + {{ form.card(placeholder="Card number") }} + {% for e in form.card.errors %}
{{ e }}
{% endfor %} +
+ +
+
+ +
+

Order Summary

+ + {% for item in items %} + + + + + {% endfor %} + + + +
{{ item.product.name }} × {{ item.quantity }}${{ '{:,}'.format((item.product.price_usd or 0) * item.quantity) }}
Subtotal${{ '{:,}'.format(subtotal) }}
Tax${{ '{:,}'.format(tax) }}
Total${{ '{:,}'.format(total) }}
+
+
+
+
+ +{% endblock %} diff --git a/sites/nvidia/templates/compare.html b/sites/nvidia/templates/compare.html new file mode 100644 index 00000000..d48e01b5 --- /dev/null +++ b/sites/nvidia/templates/compare.html @@ -0,0 +1,76 @@ +{% extends "base.html" %} +{% block title %}Compare GPUs — NVIDIA{% endblock %} +{% block content %} + +
+

Compare GPUs

Select products to compare side by side.

+
+ +
+
+
+
+ {% for ap in all_products %} + + {% endfor %} +
+ +
+ + {% if items %} +
+ + + + + {% for p in items %} + + {% endfor %} + + + + {% for p in items %}{% endfor %} + {% for p in items %}{% endfor %} + {% for p in items %}{% endfor %} + {% for p in items %}{% endfor %} + {% for p in items %}{% endfor %} + {% for p in items %}{% endfor %} + {% for p in items %}{% endfor %} + {% for p in items %}{% endfor %} + {% for p in items %}{% endfor %} + {% for p in items %}{% endfor %} + {% for p in items %}{% endfor %} + {% for p in items %}{% endfor %} + +
Specification + + {% if p.image %} + {{ p.name }} + {% endif %} + {{ p.name }} + +
Price{{ p.price_display }}
Series{{ p.series or '—' }}
Architecture{{ p.architecture or '—' }}
CUDA Cores{{ '{:,}'.format(p.cuda_cores) if p.cuda_cores else '—' }}
Tensor Cores{{ '{:,}'.format(p.tensor_cores) if p.tensor_cores else '—' }}
RT Cores{{ '{:,}'.format(p.rt_cores) if p.rt_cores else '—' }}
Memory{% if p.memory_gb %}{{ p.memory_gb }} GB {{ p.memory_type }}{% else %}—{% endif %}
Memory Bandwidth{{ p.memory_bandwidth or '—' }}
Boost Clock{{ p.boost_clock_ghz ~ ' GHz' if p.boost_clock_ghz else '—' }}
TDP{{ p.tdp_watts ~ ' W' if p.tdp_watts else '—' }}
Interface{{ p.interface or '—' }}
Recommended PSU{{ p.recommended_psu_watts ~ ' W' if p.recommended_psu_watts else '—' }}
+
+ {% else %} +

Select two or more products above and click "Compare Selected" to see a side-by-side spec sheet.

+ {% endif %} +
+
+ + + +{% endblock %} diff --git a/sites/nvidia/templates/driver_detail.html b/sites/nvidia/templates/driver_detail.html new file mode 100644 index 00000000..59fec422 --- /dev/null +++ b/sites/nvidia/templates/driver_detail.html @@ -0,0 +1,38 @@ +{% extends "base.html" %} +{% block title %}{{ d.product_series }} Driver {{ d.version }} — NVIDIA{% endblock %} +{% block content %} + +
+ + +
+
{{ d.branch }} Driver
+

{{ d.product_series }} — Version {{ d.version }}

+ + + + + + + + + +
Version{{ d.version }}
Branch{{ d.branch }}
Product Series{{ d.product_series }}
Operating System{{ d.os }}
Release Date{{ d.released.strftime('%b %d, %Y') }}
File Size{{ d.size_mb }} MB
Downloads{{ '{:,}'.format(d.download_count) }}
+ + {% if d.highlights %} +

Release Highlights

+

{{ d.highlights }}

+ {% endif %} + +
+ + +
+
+
+ +{% endblock %} diff --git a/sites/nvidia/templates/drivers.html b/sites/nvidia/templates/drivers.html new file mode 100644 index 00000000..9b82e3fd --- /dev/null +++ b/sites/nvidia/templates/drivers.html @@ -0,0 +1,76 @@ +{% extends "base.html" %} +{% block title %}Download Drivers — NVIDIA{% endblock %} +{% block content %} + +
+

NVIDIA Drivers

Find the latest Game Ready and Studio drivers for your GPU.

+
+ +
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + {% if searched %} + {% if results %} + + + + + + + + + + {% for d in results %} + + + + + + + + + + {% endfor %} + +
Product SeriesBranchVersionOSReleasedSize
{{ d.product_series }}{{ d.branch }}{{ d.version }}{{ d.os }}{{ d.released.strftime('%b %d, %Y') }}{{ d.size_mb }} MBDetails
+ {% else %} +

No drivers match your selection.

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

Pick a product series above to find available drivers.

+ {% endif %} +
+
+ +{% endblock %} diff --git a/sites/nvidia/templates/index.html b/sites/nvidia/templates/index.html new file mode 100644 index 00000000..7296e2a0 --- /dev/null +++ b/sites/nvidia/templates/index.html @@ -0,0 +1,77 @@ +{% extends "base.html" %} +{% block title %}NVIDIA — Accelerated Computing{% endblock %} +{% block content %} + +{% if flagship %} +
+
+
+
{{ flagship.series }}
+

{{ flagship.name }}

+

{{ flagship.tagline }}

+ +
+
+ {% if flagship.image %} + {{ flagship.name }} + {% endif %} +
+
+
+{% endif %} + +
+
+

Explore Products

+

From gaming GPUs to data-center accelerators.

+
+ {% for name, slug in CATEGORIES %} + {{ name }} + {% endfor %} +
+
+
+ +
+
+

Featured Products

+

Our most powerful hardware, hand-picked.

+
+ {% for p in featured %} + {% include "_product_card.html" %} + {% endfor %} +
+
+
+ +{% if latest_news %} +
+ +
+{% endif %} + +{% endblock %} diff --git a/sites/nvidia/templates/login.html b/sites/nvidia/templates/login.html new file mode 100644 index 00000000..b68393b8 --- /dev/null +++ b/sites/nvidia/templates/login.html @@ -0,0 +1,30 @@ +{% extends "base.html" %} +{% block title %}Login — NVIDIA{% endblock %} +{% block content %} + +
+
+
+

Sign In

+
+ {{ form.hidden_tag() }} +
+ {{ form.email.label }} + {{ form.email(type="email", placeholder="you@example.com") }} + {% for e in form.email.errors %}
{{ e }}
{% endfor %} +
+
+ {{ form.password.label }} + {{ form.password(placeholder="Password") }} + {% for e in form.password.errors %}
{{ e }}
{% endfor %} +
+ +
+

+ New to NVIDIA? Create an account +

+
+
+
+ +{% endblock %} diff --git a/sites/nvidia/templates/news.html b/sites/nvidia/templates/news.html new file mode 100644 index 00000000..35356e1c --- /dev/null +++ b/sites/nvidia/templates/news.html @@ -0,0 +1,43 @@ +{% extends "base.html" %} +{% block title %}Newsroom — NVIDIA{% endblock %} +{% block content %} + +
+

NVIDIA Newsroom

The latest news, announcements, and stories.

+
+ +
+
+
+ All + {% for c in cats %} + {{ c }} + {% endfor %} +
+ + {% if items %} + + {% else %} +

No articles found.

+ {% endif %} +
+
+ +{% endblock %} diff --git a/sites/nvidia/templates/news_detail.html b/sites/nvidia/templates/news_detail.html new file mode 100644 index 00000000..4085f11f --- /dev/null +++ b/sites/nvidia/templates/news_detail.html @@ -0,0 +1,45 @@ +{% extends "base.html" %} +{% block title %}{{ a.title }} — NVIDIA{% endblock %} +{% block content %} + +
+
+
{{ a.category }}
+

{{ a.title }}

+
{{ a.author }} · {{ a.published.strftime('%b %d, %Y') }} · {{ a.read_minutes }} min read
+ {% if a.image %} + {{ a.title }} + {% endif %} + {% for para in a.body.split('\n\n') %} + {% if para.strip() %}

{{ para.strip() }}

{% endif %} + {% endfor %} +
+
+ +{% if more %} +
+ +
+{% endif %} + +{% endblock %} diff --git a/sites/nvidia/templates/order_detail.html b/sites/nvidia/templates/order_detail.html new file mode 100644 index 00000000..0a5fee8d --- /dev/null +++ b/sites/nvidia/templates/order_detail.html @@ -0,0 +1,35 @@ +{% extends "base.html" %} +{% block title %}Order #{{ order.id }} — NVIDIA{% endblock %} +{% block content %} + +
+
+
+ Thank you for your order! A confirmation has been emailed to you. +
+ +
+
+

Order #{{ order.id }}

+
{{ order.status }} {{ order.created.strftime('%b %d, %Y') }}
+
+ + + {% for it in order.items %} + + + + + {% endfor %} + + + + +
{{ it.name }} × {{ it.quantity }}${{ '{:,}'.format(it.price_usd or 0) }}
Total${{ '{:,}'.format(order.total_usd) }}
+ + Back to Account +
+
+
+ +{% endblock %} diff --git a/sites/nvidia/templates/product_detail.html b/sites/nvidia/templates/product_detail.html new file mode 100644 index 00000000..50aa0e8f --- /dev/null +++ b/sites/nvidia/templates/product_detail.html @@ -0,0 +1,130 @@ +{% extends "base.html" %} +{% block title %}{{ p.name }} — NVIDIA{% endblock %} +{% block content %} + +
+ + +
+
+ {% if p.image %} + {{ p.name }} + {% endif %} +
+
+ {% if p.series %}
{{ p.series }}
{% endif %} +

{{ p.name }}

+

{{ p.tagline }}

+
{{ p.price_display }}
+

+ {% if p.in_stock %}In Stock + {% else %}Out of Stock{% endif %} + {% if p.avg_rating %}★ {{ p.avg_rating }} ({{ p.review_count }}){% endif %} +

+ +
+ {% if p.price_usd is not none %} +
+ + +
+ {% else %} + Contact Sales + {% endif %} + +
+ + +
+ + Compare +
+ + {% if p.description %}

{{ p.description }}

{% endif %} +
+
+ + {# Tech specs — only non-empty fields #} + {% set has_specs = p.architecture or p.cuda_cores or p.tensor_cores or p.rt_cores + or p.memory_gb or p.memory_bandwidth or p.boost_clock_ghz or p.tdp_watts + or p.interface or p.recommended_psu_watts %} + {% if has_specs %} +

Tech Specs

+ + {% if p.architecture %}{% endif %} + {% if p.cuda_cores %}{% endif %} + {% if p.tensor_cores %}{% endif %} + {% if p.rt_cores %}{% endif %} + {% if p.memory_gb %}{% endif %} + {% if p.memory_bandwidth %}{% endif %} + {% if p.boost_clock_ghz %}{% endif %} + {% if p.tdp_watts %}{% endif %} + {% if p.interface %}{% endif %} + {% if p.recommended_psu_watts %}{% endif %} +
Architecture{{ p.architecture }}
CUDA Cores{{ '{:,}'.format(p.cuda_cores) }}
Tensor Cores{{ '{:,}'.format(p.tensor_cores) }}
RT Cores{{ '{:,}'.format(p.rt_cores) }}
Memory{{ p.memory_gb }} GB {{ p.memory_type }}
Memory Bandwidth{{ p.memory_bandwidth }}
Boost Clock{{ p.boost_clock_ghz }} GHz
TDP{{ p.tdp_watts }} W
Interface{{ p.interface }}
Recommended PSU{{ p.recommended_psu_watts }} W
+ {% endif %} + + {# Reviews #} +
+

Reviews

+ {% if p.avg_rating %} +

★ {{ p.avg_rating }} · {{ p.review_count }} review{{ '' if p.review_count == 1 else 's' }}

+ {% else %} +

No reviews yet.

+ {% endif %} + + {% for r in reviews %} +
+
{% for _ in range(r.rating) %}★{% endfor %}{% for _ in range(5 - r.rating) %}☆{% endfor %}
+ {{ r.title }} +

{{ r.body }}

+

— {{ r.user.name }} · {{ r.created.strftime('%b %d, %Y') }}

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

Write a Review

+
+ {{ review_form.hidden_tag() }} +
+ {{ review_form.rating.label }} + {{ review_form.rating() }} + {% for e in review_form.rating.errors %}
{{ e }}
{% endfor %} +
+
+ {{ review_form.title.label }} + {{ review_form.title(class_="") }} + {% for e in review_form.title.errors %}
{{ e }}
{% endfor %} +
+
+ {{ review_form.body.label }} + {{ review_form.body(rows=4) }} + {% for e in review_form.body.errors %}
{{ e }}
{% endfor %} +
+ +
+
+ {% else %} +

Log in to write a review.

+ {% endif %} +
+ + {% if related %} +
+

Related Products

+
+ {% for p in related %} + {% include "_product_card.html" %} + {% endfor %} +
+
+ {% endif %} +
+ +{% endblock %} diff --git a/sites/nvidia/templates/products.html b/sites/nvidia/templates/products.html new file mode 100644 index 00000000..960b4975 --- /dev/null +++ b/sites/nvidia/templates/products.html @@ -0,0 +1,51 @@ +{% extends "base.html" %} +{% block title %}{{ cat_name or "Products" }} — NVIDIA{% endblock %} +{% block content %} + +
+
+

{{ cat_name or "Products" }}

+ {% if q %}

Results for "{{ q }}"

{% endif %} +
+
+ +
+
+
+ All + {% for name, slug in CATEGORIES %} + {{ name }} + {% endfor %} +
+ +
+
{{ items|length }} product{{ '' if items|length == 1 else 's' }}
+
+ {% if cat %}{% endif %} + {% if series %}{% endif %} + {% if q %}{% endif %} + + + +
+
+ + {% if items %} +
+ {% for p in items %} + {% include "_product_card.html" %} + {% endfor %} +
+ {% else %} +

No products found. Try a different category or search term.

+ {% endif %} +
+
+ +{% endblock %} diff --git a/sites/nvidia/templates/register.html b/sites/nvidia/templates/register.html new file mode 100644 index 00000000..d49891cc --- /dev/null +++ b/sites/nvidia/templates/register.html @@ -0,0 +1,44 @@ +{% extends "base.html" %} +{% block title %}Create Account — NVIDIA{% endblock %} +{% block content %} + +
+
+
+

Create Your NVIDIA Account

+
+ {{ form.hidden_tag() }} +
+ {{ form.name.label }} + {{ form.name(placeholder="Your full name") }} + {% for e in form.name.errors %}
{{ e }}
{% endfor %} +
+
+ {{ form.email.label }} + {{ form.email(type="email", placeholder="you@example.com") }} + {% for e in form.email.errors %}
{{ e }}
{% endfor %} +
+
+ {{ form.password.label }} + {{ form.password() }} + {% for e in form.password.errors %}
{{ e }}
{% endfor %} +
+
+ {{ form.confirm.label }} + {{ form.confirm() }} + {% for e in form.confirm.errors %}
{{ e }}
{% endfor %} +
+
+ {{ form.newsletter_opt_in() }} + {{ form.newsletter_opt_in.label }} +
+ +
+

+ Already have an account? Sign in +

+
+
+
+ +{% endblock %} diff --git a/sites/nvidia/templates/search.html b/sites/nvidia/templates/search.html new file mode 100644 index 00000000..83da840a --- /dev/null +++ b/sites/nvidia/templates/search.html @@ -0,0 +1,52 @@ +{% extends "base.html" %} +{% block title %}Search — NVIDIA{% endblock %} +{% block content %} + +
+
+

Search

+ {% if q %}

Results for "{{ q }}"

{% endif %} +
+
+ +
+
+ {% if not q %} +

Type a query in the search box above to find products and news.

+ {% elif not products and not articles %} +

No results for "{{ q }}". Try different keywords.

+ {% else %} + {% if products %} +

Products

+
+ {% for p in products %} + {% include "_product_card.html" %} + {% endfor %} +
+ {% endif %} + + {% if articles %} +

News

+ + {% endif %} + {% endif %} +
+
+ +{% endblock %} diff --git a/sites/nvidia/templates/wishlist.html b/sites/nvidia/templates/wishlist.html new file mode 100644 index 00000000..19dc50b6 --- /dev/null +++ b/sites/nvidia/templates/wishlist.html @@ -0,0 +1,40 @@ +{% extends "base.html" %} +{% block title %}My Wishlist — NVIDIA{% endblock %} +{% block content %} + +
+

My Wishlist

+
+ +
+
+ {% if items %} +
+ {% for item in items %} + {% set p = item.product %} +
+ + {% if p.image %} + {{ p.name }} + {% endif %} + +
+ {% if p.series %}
{{ p.series }}
{% endif %} + {{ p.name }} +
{{ p.price_display }}
+
+ + +
+
+
+ {% endfor %} +
+ {% else %} +

Your wishlist is empty. Browse products.

+ {% endif %} +
+
+ +{% endblock %} diff --git a/websyn_start.sh b/websyn_start.sh index 72defad8..4863bca1 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 nvidia) BASE_PORT=40000 PID_DIR=/tmp/websyn_pids mkdir -p "$PID_DIR"