diff --git a/.replit b/.replit index 36cd6ae..f2319f5 100644 --- a/.replit +++ b/.replit @@ -1,4 +1,4 @@ -modules = ["web", "python-3.12"] +modules = ["web", "python-3.12", "postgresql-16"] run = "python3 app.py" [nix] @@ -10,5 +10,35 @@ deploymentTarget = "cloudrun" [[ports]] localPort = 5000 +externalPort = 80 + +[[ports]] +localPort = 44245 externalPort = 3000 -exposeLocalhost = true + +[agent] +expertMode = true + +[workflows] +runButton = "Project" + +[[workflows.workflow]] +name = "Project" +mode = "parallel" +author = "agent" + +[[workflows.workflow.tasks]] +task = "workflow.run" +args = "Flask Server" + +[[workflows.workflow]] +name = "Flask Server" +author = "agent" + +[[workflows.workflow.tasks]] +task = "shell.exec" +args = "python app.py" +waitForPort = 5000 + +[workflows.workflow.metadata] +outputType = "webview" diff --git a/app.py b/app.py index a17099e..55d3b4d 100644 --- a/app.py +++ b/app.py @@ -1,20 +1,43 @@ -# TODO -# Fix all text sizes across all pages except competition log -# Fix all top margins (do 20ish vh instead) - - -from flask import Flask, render_template +import os +import psycopg2 +from psycopg2.extras import RealDictCursor +from flask import Flask, render_template, request, redirect, url_for, session, flash, jsonify +from werkzeug.utils import secure_filename +from datetime import datetime app = Flask(__name__) +app.secret_key = os.environ.get('SECRET_KEY', 'techturb-secret-key-change-in-production') +app.config['UPLOAD_FOLDER'] = 'static/images' +app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 + +ADMIN_PASSWORD = 'techturb123' + +def get_db_connection(): + conn = psycopg2.connect(os.environ['DATABASE_URL']) + return conn @app.route('/') @app.route('/home') def home(): - return render_template('home.html') + conn = get_db_connection() + cur = conn.cursor(cursor_factory=RealDictCursor) + cur.execute('SELECT * FROM stats ORDER BY key') + stats = cur.fetchall() + cur.close() + conn.close() + return render_template('home.html', stats=stats) @app.route('/contributors') def contributors(): - return render_template('contributors.html') + conn = get_db_connection() + cur = conn.cursor(cursor_factory=RealDictCursor) + cur.execute('SELECT * FROM members ORDER BY display_order') + members = cur.fetchall() + cur.execute('SELECT * FROM mentors ORDER BY display_order') + mentors = cur.fetchall() + cur.close() + conn.close() + return render_template('contributors.html', members=members, mentors=mentors) @app.route('/robot') def robot(): @@ -22,11 +45,21 @@ def robot(): @app.route('/competitions') def competitions(): - return render_template('competitions.html') - -# @app.route('/awards') -# def awards(): -# return render_template('awards.html') + conn = get_db_connection() + cur = conn.cursor(cursor_factory=RealDictCursor) + cur.execute('SELECT * FROM competitions ORDER BY display_order') + competitions = cur.fetchall() + cur.close() + conn.close() + + competitions_by_season = {} + for comp in competitions: + season = comp['season'] + if season not in competitions_by_season: + competitions_by_season[season] = [] + competitions_by_season[season].append(comp) + + return render_template('competitions.html', competitions_by_season=competitions_by_season) @app.route('/contact') def contact(): @@ -34,7 +67,281 @@ def contact(): @app.route('/sponsors') def sponsors(): - return render_template('sponsors.html') + conn = get_db_connection() + cur = conn.cursor(cursor_factory=RealDictCursor) + cur.execute('SELECT * FROM sponsors ORDER BY display_order') + sponsors = cur.fetchall() + cur.close() + conn.close() + return render_template('sponsors.html', sponsors=sponsors) + +@app.route('/robots') +@app.route('/robots/') +def robots(type=None): + if type is None: + return render_template('robots.html') + else: + return render_template(f'robots-{type}.html') + +@app.route('/admin/login', methods=['GET', 'POST']) +def admin_login(): + if request.method == 'POST': + password = request.form.get('password') + if password == ADMIN_PASSWORD: + session['admin_logged_in'] = True + return redirect(url_for('admin_stats')) + else: + flash('Incorrect password', 'error') + return render_template('admin/login.html') + +@app.route('/admin/logout') +def admin_logout(): + session.pop('admin_logged_in', None) + return redirect(url_for('home')) + +def admin_required(f): + def decorated_function(*args, **kwargs): + if not session.get('admin_logged_in'): + return redirect(url_for('admin_login')) + return f(*args, **kwargs) + decorated_function.__name__ = f.__name__ + return decorated_function + +@app.route('/admin/stats') +@admin_required +def admin_stats(): + conn = get_db_connection() + cur = conn.cursor(cursor_factory=RealDictCursor) + cur.execute('SELECT * FROM stats ORDER BY key') + stats = cur.fetchall() + cur.close() + conn.close() + return render_template('admin/stats.html', stats=stats) + +@app.route('/admin/stats/update', methods=['POST']) +@admin_required +def update_stat(): + stat_id = request.form.get('id') + value = request.form.get('value') + label = request.form.get('label') + + conn = get_db_connection() + cur = conn.cursor() + cur.execute('UPDATE stats SET value = %s, label = %s WHERE id = %s', (value, label, stat_id)) + conn.commit() + cur.close() + conn.close() + + flash('Stat updated successfully', 'success') + return redirect(url_for('admin_stats')) + +@app.route('/admin/members') +@admin_required +def admin_members(): + conn = get_db_connection() + cur = conn.cursor(cursor_factory=RealDictCursor) + cur.execute('SELECT * FROM members ORDER BY display_order') + members = cur.fetchall() + cur.execute('SELECT * FROM mentors ORDER BY display_order') + mentors = cur.fetchall() + cur.close() + conn.close() + return render_template('admin/members.html', members=members, mentors=mentors) + +@app.route('/admin/member/add', methods=['POST']) +@admin_required +def add_member(): + name = request.form.get('name') + role = request.form.get('role') + member_type = request.form.get('type') + + image_path = 'images/default.jpg' + if 'image' in request.files: + file = request.files['image'] + if file and file.filename: + filename = secure_filename(file.filename) + file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) + image_path = f'images/{filename}' + + conn = get_db_connection() + cur = conn.cursor() + + if member_type == 'mentor': + cur.execute('INSERT INTO mentors (name, role, image_path) VALUES (%s, %s, %s)', (name, role, image_path)) + else: + cur.execute('INSERT INTO members (name, role, image_path) VALUES (%s, %s, %s)', (name, role, image_path)) + + conn.commit() + cur.close() + conn.close() + + flash(f'{"Mentor" if member_type == "mentor" else "Member"} added successfully', 'success') + return redirect(url_for('admin_members')) + +@app.route('/admin/member/update', methods=['POST']) +@admin_required +def update_member(): + member_id = request.form.get('id') + name = request.form.get('name') + role = request.form.get('role') + member_type = request.form.get('type') + + conn = get_db_connection() + cur = conn.cursor() + + image_path = None + if 'image' in request.files: + file = request.files['image'] + if file and file.filename: + filename = secure_filename(file.filename) + file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) + image_path = f'images/{filename}' + + if member_type == 'mentor': + if image_path: + cur.execute('UPDATE mentors SET name = %s, role = %s, image_path = %s WHERE id = %s', (name, role, image_path, member_id)) + else: + cur.execute('UPDATE mentors SET name = %s, role = %s WHERE id = %s', (name, role, member_id)) + else: + if image_path: + cur.execute('UPDATE members SET name = %s, role = %s, image_path = %s WHERE id = %s', (name, role, image_path, member_id)) + else: + cur.execute('UPDATE members SET name = %s, role = %s WHERE id = %s', (name, role, member_id)) + + conn.commit() + cur.close() + conn.close() + + flash(f'{"Mentor" if member_type == "mentor" else "Member"} updated successfully', 'success') + return redirect(url_for('admin_members')) + +@app.route('/admin/member/delete', methods=['POST']) +@admin_required +def delete_member(): + member_id = request.form.get('id') + member_type = request.form.get('type') + + conn = get_db_connection() + cur = conn.cursor() + + if member_type == 'mentor': + cur.execute('DELETE FROM mentors WHERE id = %s', (member_id,)) + else: + cur.execute('DELETE FROM members WHERE id = %s', (member_id,)) + + conn.commit() + cur.close() + conn.close() + + flash(f'{"Mentor" if member_type == "mentor" else "Member"} deleted successfully', 'success') + return redirect(url_for('admin_members')) + +@app.route('/admin/competitions') +@admin_required +def admin_competitions(): + conn = get_db_connection() + cur = conn.cursor(cursor_factory=RealDictCursor) + cur.execute('SELECT * FROM competitions ORDER BY display_order') + competitions = cur.fetchall() + cur.execute('SELECT DISTINCT season FROM competitions ORDER BY season DESC') + seasons = [row['season'] for row in cur.fetchall()] + cur.close() + conn.close() + return render_template('admin/competitions.html', competitions=competitions, seasons=seasons) + +@app.route('/admin/competition/add', methods=['POST']) +@admin_required +def add_competition(): + season = request.form.get('season') + event_name = request.form.get('event_name') + date = request.form.get('date') + description = request.form.get('description') + awards_raw = request.form.get('awards') + awards = '|'.join([line.strip() for line in awards_raw.split('\n') if line.strip()]) + + image_path = None + if 'image' in request.files: + file = request.files['image'] + if file and file.filename: + filename = secure_filename(file.filename) + file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) + image_path = f'images/{filename}' + + conn = get_db_connection() + cur = conn.cursor() + cur.execute('INSERT INTO competitions (season, event_name, date, description, awards, image_path) VALUES (%s, %s, %s, %s, %s, %s)', + (season, event_name, date, description, awards, image_path)) + conn.commit() + cur.close() + conn.close() + + flash('Competition added successfully', 'success') + return redirect(url_for('admin_competitions')) + +@app.route('/admin/competition/delete', methods=['POST']) +@admin_required +def delete_competition(): + competition_id = request.form.get('id') + + conn = get_db_connection() + cur = conn.cursor() + cur.execute('DELETE FROM competitions WHERE id = %s', (competition_id,)) + conn.commit() + cur.close() + conn.close() + + flash('Competition deleted successfully', 'success') + return redirect(url_for('admin_competitions')) + +@app.route('/admin/sponsors') +@admin_required +def admin_sponsors(): + conn = get_db_connection() + cur = conn.cursor(cursor_factory=RealDictCursor) + cur.execute('SELECT * FROM sponsors ORDER BY display_order') + sponsors = cur.fetchall() + cur.close() + conn.close() + return render_template('admin/sponsors.html', sponsors=sponsors) + +@app.route('/admin/sponsor/add', methods=['POST']) +@admin_required +def add_sponsor(): + name = request.form.get('name') + website_url = request.form.get('website_url') + + logo_path = None + if 'logo' in request.files: + file = request.files['logo'] + if file and file.filename: + filename = secure_filename(file.filename) + file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) + logo_path = f'images/{filename}' + + conn = get_db_connection() + cur = conn.cursor() + cur.execute('INSERT INTO sponsors (name, logo_path, website_url) VALUES (%s, %s, %s)', (name, logo_path, website_url)) + conn.commit() + cur.close() + conn.close() + + flash('Sponsor added successfully', 'success') + return redirect(url_for('admin_sponsors')) + +@app.route('/admin/sponsor/delete', methods=['POST']) +@admin_required +def delete_sponsor(): + sponsor_id = request.form.get('id') + + conn = get_db_connection() + cur = conn.cursor() + cur.execute('DELETE FROM sponsors WHERE id = %s', (sponsor_id,)) + conn.commit() + cur.close() + conn.close() + + flash('Sponsor deleted successfully', 'success') + return redirect(url_for('admin_sponsors')) if __name__ == '__main__': - app.run(debug=True) \ No newline at end of file + app.run(debug=True, host='0.0.0.0', port=5000) diff --git a/attached_assets/image_1757182125664.png b/attached_assets/image_1757182125664.png new file mode 100644 index 0000000..389f3a2 Binary files /dev/null and b/attached_assets/image_1757182125664.png differ diff --git a/attached_assets/image_1759456988487.png b/attached_assets/image_1759456988487.png new file mode 100644 index 0000000..63e59dd Binary files /dev/null and b/attached_assets/image_1759456988487.png differ diff --git a/attached_assets/image_1759457109169.png b/attached_assets/image_1759457109169.png new file mode 100644 index 0000000..d7d5371 Binary files /dev/null and b/attached_assets/image_1759457109169.png differ diff --git a/attached_assets/image_1759465591069.png b/attached_assets/image_1759465591069.png new file mode 100644 index 0000000..156b231 Binary files /dev/null and b/attached_assets/image_1759465591069.png differ diff --git a/attached_assets/image_1759465825189.png b/attached_assets/image_1759465825189.png new file mode 100644 index 0000000..df64676 Binary files /dev/null and b/attached_assets/image_1759465825189.png differ diff --git a/attached_assets/image_1759465917104.png b/attached_assets/image_1759465917104.png new file mode 100644 index 0000000..e2d35cb Binary files /dev/null and b/attached_assets/image_1759465917104.png differ diff --git a/attached_assets/image_1759467028622.png b/attached_assets/image_1759467028622.png new file mode 100644 index 0000000..260e1ee Binary files /dev/null and b/attached_assets/image_1759467028622.png differ diff --git a/attached_assets/image_1759467055284.png b/attached_assets/image_1759467055284.png new file mode 100644 index 0000000..42730dd Binary files /dev/null and b/attached_assets/image_1759467055284.png differ diff --git a/attached_assets/image_1759467229465.png b/attached_assets/image_1759467229465.png new file mode 100644 index 0000000..f9554f8 Binary files /dev/null and b/attached_assets/image_1759467229465.png differ diff --git a/attached_assets/image_1759467381587.png b/attached_assets/image_1759467381587.png new file mode 100644 index 0000000..8907d16 Binary files /dev/null and b/attached_assets/image_1759467381587.png differ diff --git a/attached_assets/image_1759467488103.png b/attached_assets/image_1759467488103.png new file mode 100644 index 0000000..f3dcabe Binary files /dev/null and b/attached_assets/image_1759467488103.png differ diff --git a/attached_assets/image_1759467714189.png b/attached_assets/image_1759467714189.png new file mode 100644 index 0000000..5e44ea5 Binary files /dev/null and b/attached_assets/image_1759467714189.png differ diff --git a/attached_assets/image_1759467825268.png b/attached_assets/image_1759467825268.png new file mode 100644 index 0000000..82f57ad Binary files /dev/null and b/attached_assets/image_1759467825268.png differ diff --git a/attached_assets/image_1759468904010.png b/attached_assets/image_1759468904010.png new file mode 100644 index 0000000..b5aca95 Binary files /dev/null and b/attached_assets/image_1759468904010.png differ diff --git a/attached_assets/image_1759468987127.png b/attached_assets/image_1759468987127.png new file mode 100644 index 0000000..63a09c3 Binary files /dev/null and b/attached_assets/image_1759468987127.png differ diff --git a/attached_assets/image_1759469180396.png b/attached_assets/image_1759469180396.png new file mode 100644 index 0000000..f849cf1 Binary files /dev/null and b/attached_assets/image_1759469180396.png differ diff --git a/attached_assets/image_1759501420568.png b/attached_assets/image_1759501420568.png new file mode 100644 index 0000000..48da5dd Binary files /dev/null and b/attached_assets/image_1759501420568.png differ diff --git a/attached_assets/image_1759536015888.png b/attached_assets/image_1759536015888.png new file mode 100644 index 0000000..e556b1b Binary files /dev/null and b/attached_assets/image_1759536015888.png differ diff --git a/attached_assets/image_1759536296815.png b/attached_assets/image_1759536296815.png new file mode 100644 index 0000000..77a7f79 Binary files /dev/null and b/attached_assets/image_1759536296815.png differ diff --git a/attached_assets/image_1759536393787.png b/attached_assets/image_1759536393787.png new file mode 100644 index 0000000..ae93573 Binary files /dev/null and b/attached_assets/image_1759536393787.png differ diff --git a/attached_assets/image_1759604797997.png b/attached_assets/image_1759604797997.png new file mode 100644 index 0000000..67e8f56 Binary files /dev/null and b/attached_assets/image_1759604797997.png differ diff --git a/attached_assets/image_1759604912005.png b/attached_assets/image_1759604912005.png new file mode 100644 index 0000000..482dd61 Binary files /dev/null and b/attached_assets/image_1759604912005.png differ diff --git a/attached_assets/image_1759605102403.png b/attached_assets/image_1759605102403.png new file mode 100644 index 0000000..71c7d90 Binary files /dev/null and b/attached_assets/image_1759605102403.png differ diff --git a/attached_assets/image_1759605344760.png b/attached_assets/image_1759605344760.png new file mode 100644 index 0000000..e8e491e Binary files /dev/null and b/attached_assets/image_1759605344760.png differ diff --git a/attached_assets/image_1759605424150.png b/attached_assets/image_1759605424150.png new file mode 100644 index 0000000..07ad72a Binary files /dev/null and b/attached_assets/image_1759605424150.png differ diff --git a/attached_assets/image_1759605526872.png b/attached_assets/image_1759605526872.png new file mode 100644 index 0000000..08785b8 Binary files /dev/null and b/attached_assets/image_1759605526872.png differ diff --git a/attached_assets/image_1759605583069.png b/attached_assets/image_1759605583069.png new file mode 100644 index 0000000..a59755d Binary files /dev/null and b/attached_assets/image_1759605583069.png differ diff --git a/attached_assets/image_1759605759890.png b/attached_assets/image_1759605759890.png new file mode 100644 index 0000000..5d80340 Binary files /dev/null and b/attached_assets/image_1759605759890.png differ diff --git a/attached_assets/image_1759605767045.png b/attached_assets/image_1759605767045.png new file mode 100644 index 0000000..5d80340 Binary files /dev/null and b/attached_assets/image_1759605767045.png differ diff --git a/poetry.lock b/poetry.lock index a8843f2..c4e20b3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.5.4 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.6 and should not be changed by hand. [[package]] name = "blinker" @@ -11,6 +11,17 @@ files = [ {file = "blinker-1.8.2.tar.gz", hash = "sha256:8f77b09d3bf7c795e969e9486f39c2c5e9c39d4ee07424be2bc594ece9642d83"}, ] +[[package]] +name = "cachelib" +version = "0.13.0" +description = "A collection of cache libraries in the same API interface." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cachelib-0.13.0-py3-none-any.whl", hash = "sha256:8c8019e53b6302967d4e8329a504acf75e7bc46130291d30188a6e4e58162516"}, + {file = "cachelib-0.13.0.tar.gz", hash = "sha256:209d8996e3c57595bee274ff97116d1d73c4980b2fd9a34c7846cd07fd2e1a48"}, +] + [[package]] name = "click" version = "8.1.7" @@ -58,6 +69,30 @@ Werkzeug = ">=3.0.0" async = ["asgiref (>=3.2)"] dotenv = ["python-dotenv"] +[[package]] +name = "flask-session" +version = "0.8.0" +description = "Server-side session support for Flask" +optional = false +python-versions = ">=3.8" +files = [ + {file = "flask_session-0.8.0-py3-none-any.whl", hash = "sha256:5dae6e9ddab334f8dc4dea4305af37851f4e7dc0f484caf3351184001195e3b7"}, + {file = "flask_session-0.8.0.tar.gz", hash = "sha256:20e045eb01103694e70be4a49f3a80dbb1b57296a22dc6f44bbf3f83ef0742ff"}, +] + +[package.dependencies] +cachelib = "*" +flask = ">=2.2" +msgspec = ">=0.18.6" + +[package.extras] +all = ["Flask-Session[cachelib,memcached,mongodb,redis,sqlalchemy]"] +cachelib = ["cachelib (>=0.10.2)"] +memcached = ["pymemcache"] +mongodb = ["pymongo (>=4.6.2)"] +redis = ["redis (>=5.0.3)"] +sqlalchemy = ["flask-sqlalchemy (>=3.0.5)"] + [[package]] name = "itsdangerous" version = "2.2.0" @@ -156,14 +191,143 @@ files = [ ] [[package]] -name = "werkzeug" -version = "3.0.3" -description = "The comprehensive WSGI web application library." +name = "msgspec" +version = "0.19.0" +description = "A fast serialization and validation library, with builtin support for JSON, MessagePack, YAML, and TOML." +optional = false +python-versions = ">=3.9" +files = [ + {file = "msgspec-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d8dd848ee7ca7c8153462557655570156c2be94e79acec3561cf379581343259"}, + {file = "msgspec-0.19.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0553bbc77662e5708fe66aa75e7bd3e4b0f209709c48b299afd791d711a93c36"}, + {file = "msgspec-0.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe2c4bf29bf4e89790b3117470dea2c20b59932772483082c468b990d45fb947"}, + {file = "msgspec-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e87ecfa9795ee5214861eab8326b0e75475c2e68a384002aa135ea2a27d909"}, + {file = "msgspec-0.19.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3c4ec642689da44618f68c90855a10edbc6ac3ff7c1d94395446c65a776e712a"}, + {file = "msgspec-0.19.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2719647625320b60e2d8af06b35f5b12d4f4d281db30a15a1df22adb2295f633"}, + {file = "msgspec-0.19.0-cp310-cp310-win_amd64.whl", hash = "sha256:695b832d0091edd86eeb535cd39e45f3919f48d997685f7ac31acb15e0a2ed90"}, + {file = "msgspec-0.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa77046904db764b0462036bc63ef71f02b75b8f72e9c9dd4c447d6da1ed8f8e"}, + {file = "msgspec-0.19.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:047cfa8675eb3bad68722cfe95c60e7afabf84d1bd8938979dd2b92e9e4a9551"}, + {file = "msgspec-0.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e78f46ff39a427e10b4a61614a2777ad69559cc8d603a7c05681f5a595ea98f7"}, + {file = "msgspec-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c7adf191e4bd3be0e9231c3b6dc20cf1199ada2af523885efc2ed218eafd011"}, + {file = "msgspec-0.19.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f04cad4385e20be7c7176bb8ae3dca54a08e9756cfc97bcdb4f18560c3042063"}, + {file = "msgspec-0.19.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:45c8fb410670b3b7eb884d44a75589377c341ec1392b778311acdbfa55187716"}, + {file = "msgspec-0.19.0-cp311-cp311-win_amd64.whl", hash = "sha256:70eaef4934b87193a27d802534dc466778ad8d536e296ae2f9334e182ac27b6c"}, + {file = "msgspec-0.19.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f98bd8962ad549c27d63845b50af3f53ec468b6318400c9f1adfe8b092d7b62f"}, + {file = "msgspec-0.19.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:43bbb237feab761b815ed9df43b266114203f53596f9b6e6f00ebd79d178cdf2"}, + {file = "msgspec-0.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cfc033c02c3e0aec52b71710d7f84cb3ca5eb407ab2ad23d75631153fdb1f12"}, + {file = "msgspec-0.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d911c442571605e17658ca2b416fd8579c5050ac9adc5e00c2cb3126c97f73bc"}, + {file = "msgspec-0.19.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:757b501fa57e24896cf40a831442b19a864f56d253679f34f260dcb002524a6c"}, + {file = "msgspec-0.19.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5f0f65f29b45e2816d8bded36e6b837a4bf5fb60ec4bc3c625fa2c6da4124537"}, + {file = "msgspec-0.19.0-cp312-cp312-win_amd64.whl", hash = "sha256:067f0de1c33cfa0b6a8206562efdf6be5985b988b53dd244a8e06f993f27c8c0"}, + {file = "msgspec-0.19.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f12d30dd6266557aaaf0aa0f9580a9a8fbeadfa83699c487713e355ec5f0bd86"}, + {file = "msgspec-0.19.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82b2c42c1b9ebc89e822e7e13bbe9d17ede0c23c187469fdd9505afd5a481314"}, + {file = "msgspec-0.19.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19746b50be214a54239aab822964f2ac81e38b0055cca94808359d779338c10e"}, + {file = "msgspec-0.19.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60ef4bdb0ec8e4ad62e5a1f95230c08efb1f64f32e6e8dd2ced685bcc73858b5"}, + {file = "msgspec-0.19.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac7f7c377c122b649f7545810c6cd1b47586e3aa3059126ce3516ac7ccc6a6a9"}, + {file = "msgspec-0.19.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5bc1472223a643f5ffb5bf46ccdede7f9795078194f14edd69e3aab7020d327"}, + {file = "msgspec-0.19.0-cp313-cp313-win_amd64.whl", hash = "sha256:317050bc0f7739cb30d257ff09152ca309bf5a369854bbf1e57dffc310c1f20f"}, + {file = "msgspec-0.19.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15c1e86fff77184c20a2932cd9742bf33fe23125fa3fcf332df9ad2f7d483044"}, + {file = "msgspec-0.19.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3b5541b2b3294e5ffabe31a09d604e23a88533ace36ac288fa32a420aa38d229"}, + {file = "msgspec-0.19.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f5c043ace7962ef188746e83b99faaa9e3e699ab857ca3f367b309c8e2c6b12"}, + {file = "msgspec-0.19.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca06aa08e39bf57e39a258e1996474f84d0dd8130d486c00bec26d797b8c5446"}, + {file = "msgspec-0.19.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e695dad6897896e9384cf5e2687d9ae9feaef50e802f93602d35458e20d1fb19"}, + {file = "msgspec-0.19.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3be5c02e1fee57b54130316a08fe40cca53af92999a302a6054cd451700ea7db"}, + {file = "msgspec-0.19.0-cp39-cp39-win_amd64.whl", hash = "sha256:0684573a821be3c749912acf5848cce78af4298345cb2d7a8b8948a0a5a27cfe"}, + {file = "msgspec-0.19.0.tar.gz", hash = "sha256:604037e7cd475345848116e89c553aa9a233259733ab51986ac924ab1b976f8e"}, +] + +[package.extras] +dev = ["attrs", "coverage", "eval-type-backport", "furo", "ipython", "msgpack", "mypy", "pre-commit", "pyright", "pytest", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "tomli", "tomli_w"] +doc = ["furo", "ipython", "sphinx", "sphinx-copybutton", "sphinx-design"] +test = ["attrs", "eval-type-backport", "msgpack", "pytest", "pyyaml", "tomli", "tomli_w"] +toml = ["tomli", "tomli_w"] +yaml = ["pyyaml"] + +[[package]] +name = "psycopg2-binary" +version = "2.9.10" +description = "psycopg2 - Python-PostgreSQL Database Adapter" optional = false python-versions = ">=3.8" files = [ - {file = "werkzeug-3.0.3-py3-none-any.whl", hash = "sha256:fc9645dc43e03e4d630d23143a04a7f947a9a3b5727cd535fdfe155a17cc48c8"}, - {file = "werkzeug-3.0.3.tar.gz", hash = "sha256:097e5bfda9f0aba8da6b8545146def481d06aa7d3266e7448e2cccf67dd8bd18"}, + {file = "psycopg2-binary-2.9.10.tar.gz", hash = "sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:0ea8e3d0ae83564f2fc554955d327fa081d065c8ca5cc6d2abb643e2c9c1200f"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:3e9c76f0ac6f92ecfc79516a8034a544926430f7b080ec5a0537bca389ee0906"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ad26b467a405c798aaa1458ba09d7e2b6e5f96b1ce0ac15d82fd9f95dc38a92"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:270934a475a0e4b6925b5f804e3809dd5f90f8613621d062848dd82f9cd62007"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:48b338f08d93e7be4ab2b5f1dbe69dc5e9ef07170fe1f86514422076d9c010d0"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f4152f8f76d2023aac16285576a9ecd2b11a9895373a1f10fd9db54b3ff06b4"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:32581b3020c72d7a421009ee1c6bf4a131ef5f0a968fab2e2de0c9d2bb4577f1"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:2ce3e21dc3437b1d960521eca599d57408a695a0d3c26797ea0f72e834c7ffe5"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e984839e75e0b60cfe75e351db53d6db750b00de45644c5d1f7ee5d1f34a1ce5"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c4745a90b78e51d9ba06e2088a2fe0c693ae19cc8cb051ccda44e8df8a6eb53"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-win32.whl", hash = "sha256:e5720a5d25e3b99cd0dc5c8a440570469ff82659bb09431c1439b92caf184d3b"}, + {file = "psycopg2_binary-2.9.10-cp310-cp310-win_amd64.whl", hash = "sha256:3c18f74eb4386bf35e92ab2354a12c17e5eb4d9798e4c0ad3a00783eae7cd9f1"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:04392983d0bb89a8717772a193cfaac58871321e3ec69514e1c4e0d4957b5aff"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:1a6784f0ce3fec4edc64e985865c17778514325074adf5ad8f80636cd029ef7c"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5f86c56eeb91dc3135b3fd8a95dc7ae14c538a2f3ad77a19645cf55bab1799c"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b3d2491d4d78b6b14f76881905c7a8a8abcf974aad4a8a0b065273a0ed7a2cb"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2286791ececda3a723d1910441c793be44625d86d1a4e79942751197f4d30341"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:512d29bb12608891e349af6a0cccedce51677725a921c07dba6342beaf576f9a"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5a507320c58903967ef7384355a4da7ff3f28132d679aeb23572753cbf2ec10b"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6d4fa1079cab9018f4d0bd2db307beaa612b0d13ba73b5c6304b9fe2fb441ff7"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:851485a42dbb0bdc1edcdabdb8557c09c9655dfa2ca0460ff210522e073e319e"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:35958ec9e46432d9076286dda67942ed6d968b9c3a6a2fd62b48939d1d78bf68"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-win32.whl", hash = "sha256:ecced182e935529727401b24d76634a357c71c9275b356efafd8a2a91ec07392"}, + {file = "psycopg2_binary-2.9.10-cp311-cp311-win_amd64.whl", hash = "sha256:ee0e8c683a7ff25d23b55b11161c2663d4b099770f6085ff0a20d4505778d6b4"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:880845dfe1f85d9d5f7c412efea7a08946a46894537e4e5d091732eb1d34d9a0"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9440fa522a79356aaa482aa4ba500b65f28e5d0e63b801abf6aa152a29bd842a"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3923c1d9870c49a2d44f795df0c889a22380d36ef92440ff618ec315757e539"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b2c956c028ea5de47ff3a8d6b3cc3330ab45cf0b7c3da35a2d6ff8420896526"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f758ed67cab30b9a8d2833609513ce4d3bd027641673d4ebc9c067e4d208eec1"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cd9b4f2cfab88ed4a9106192de509464b75a906462fb846b936eabe45c2063e"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dc08420625b5a20b53551c50deae6e231e6371194fa0651dbe0fb206452ae1f"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d7cd730dfa7c36dbe8724426bf5612798734bff2d3c3857f36f2733f5bfc7c00"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:155e69561d54d02b3c3209545fb08938e27889ff5a10c19de8d23eb5a41be8a5"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3cc28a6fd5a4a26224007712e79b81dbaee2ffb90ff406256158ec4d7b52b47"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-win32.whl", hash = "sha256:ec8a77f521a17506a24a5f626cb2aee7850f9b69a0afe704586f63a464f3cd64"}, + {file = "psycopg2_binary-2.9.10-cp312-cp312-win_amd64.whl", hash = "sha256:18c5ee682b9c6dd3696dad6e54cc7ff3a1a9020df6a5c0f861ef8bfd338c3ca0"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:26540d4a9a4e2b096f1ff9cce51253d0504dca5a85872c7f7be23be5a53eb18d"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e217ce4d37667df0bc1c397fdcd8de5e81018ef305aed9415c3b093faaeb10fb"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:245159e7ab20a71d989da00f280ca57da7641fa2cdcf71749c193cea540a74f7"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c4ded1a24b20021ebe677b7b08ad10bf09aac197d6943bfe6fec70ac4e4690d"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3abb691ff9e57d4a93355f60d4f4c1dd2d68326c968e7db17ea96df3c023ef73"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8608c078134f0b3cbd9f89b34bd60a943b23fd33cc5f065e8d5f840061bd0673"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:230eeae2d71594103cd5b93fd29d1ace6420d0b86f4778739cb1a5a32f607d1f"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567"}, + {file = "psycopg2_binary-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:27422aa5f11fbcd9b18da48373eb67081243662f9b46e6fd07c3eb46e4535142"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-macosx_12_0_x86_64.whl", hash = "sha256:eb09aa7f9cecb45027683bb55aebaaf45a0df8bf6de68801a6afdc7947bb09d4"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b73d6d7f0ccdad7bc43e6d34273f70d587ef62f824d7261c4ae9b8b1b6af90e8"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce5ab4bf46a211a8e924d307c1b1fcda82368586a19d0a24f8ae166f5c784864"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:056470c3dc57904bbf63d6f534988bafc4e970ffd50f6271fc4ee7daad9498a5"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73aa0e31fa4bb82578f3a6c74a73c273367727de397a7a0f07bd83cbea696baa"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8de718c0e1c4b982a54b41779667242bc630b2197948405b7bd8ce16bcecac92"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:5c370b1e4975df846b0277b4deba86419ca77dbc25047f535b0bb03d1a544d44"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:ffe8ed017e4ed70f68b7b371d84b7d4a790368db9203dfc2d222febd3a9c8863"}, + {file = "psycopg2_binary-2.9.10-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:8aecc5e80c63f7459a1a2ab2c64df952051df196294d9f739933a9f6687e86b3"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:7a813c8bdbaaaab1f078014b9b0b13f5de757e2b5d9be6403639b298a04d218b"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d00924255d7fc916ef66e4bf22f354a940c67179ad3fd7067d7a0a9c84d2fbfc"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7559bce4b505762d737172556a4e6ea8a9998ecac1e39b5233465093e8cee697"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8b58f0a96e7a1e341fc894f62c1177a7c83febebb5ff9123b579418fdc8a481"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b269105e59ac96aba877c1707c600ae55711d9dcd3fc4b5012e4af68e30c648"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:79625966e176dc97ddabc142351e0409e28acf4660b88d1cf6adb876d20c490d"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:8aabf1c1a04584c168984ac678a668094d831f152859d06e055288fa515e4d30"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:19721ac03892001ee8fdd11507e6a2e01f4e37014def96379411ca99d78aeb2c"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7f5d859928e635fa3ce3477704acee0f667b3a3d3e4bb109f2b18d4005f38287"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-win32.whl", hash = "sha256:3216ccf953b3f267691c90c6fe742e45d890d8272326b4a8b20850a03d05b7b8"}, + {file = "psycopg2_binary-2.9.10-cp39-cp39-win_amd64.whl", hash = "sha256:30e34c4e97964805f715206c7b789d54a78b70f3ff19fbe590104b71c45600e5"}, +] + +[[package]] +name = "werkzeug" +version = "3.1.3" +description = "The comprehensive WSGI web application library." +optional = false +python-versions = ">=3.9" +files = [ + {file = "werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e"}, + {file = "werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746"}, ] [package.dependencies] @@ -175,4 +339,4 @@ watchdog = ["watchdog (>=2.3)"] [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "f8a7cfa653e10af88cc9a8e65f4048a61de85d9041112d08ec18adef5458fc26" +content-hash = "466c14d77e1b8cc59742d5929922403a3b91b09caf2287b1e00e03b3973ab5bf" diff --git a/pyproject.toml b/pyproject.toml index c160a9f..422d643 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,6 +9,9 @@ packages = [{include = "repl_nix_ftc23344"}] [tool.poetry.dependencies] python = "^3.12" flask = "^3.0.3" +werkzeug = "^3.1.3" +psycopg2-binary = "^2.9.10" +flask-session = "^0.8.0" [build-system] diff --git a/replit.md b/replit.md new file mode 100644 index 0000000..c2c5459 --- /dev/null +++ b/replit.md @@ -0,0 +1,95 @@ +# FTC Team 23344 "Technical Turbulence" Website + +## Overview +Flask-based website for FTC Team 23344 with a modern dark theme (#000000 pure black background), comprehensive content management system, and PostgreSQL database integration. + +## Recent Changes (October 3, 2025) + +### Admin Panel System +Created a complete admin panel with password-protected access for managing all website content: + +**Access:** `/admin/login` (Password: `techturb123`) + +**Features:** +- πŸ“Š **Stats Management**: Edit homepage statistics (seasons, members, awards, competitions) +- πŸ‘₯ **Members/Mentors Management**: Add, edit, remove team members and mentors with profile picture uploads +- πŸ† **Competitions Management**: Add competitions by season with optional images, awards (bullet points), descriptions, event names, and dates +- πŸ’Ό **Sponsors Management**: Add/remove sponsors with logo uploads and website URLs + +### Database Integration +- Migrated all content from hardcoded templates to PostgreSQL database +- All public pages now pull data dynamically from database +- Data persists across server restarts +- Initial data seeded from existing website content + +**Database Schema:** +- `stats` - Homepage statistics +- `members` - Team members with images, roles, names +- `mentors` - Mentors/coaches with images, roles, names +- `competitions` - Competition entries organized by season +- `sponsors` - Sponsor cards with logos and URLs + +### Design Improvements +- Pure black (#000000) background throughout +- Cinematic hero section (700px tall) with darkened team image and text overlay +- Light gray text (gray-300) for paragraph content +- Reduced top margins on headers (40px instead of 64px) +- Mobile-responsive with optimized padding (20px/16px) +- Modern cards with 20px border radius and blue accent hover effects + +## Project Structure +``` +β”œβ”€β”€ app.py # Flask routes, database connections, admin system +β”œβ”€β”€ templates/ +β”‚ β”œβ”€β”€ admin/ # Admin panel templates +β”‚ β”‚ β”œβ”€β”€ login.html +β”‚ β”‚ β”œβ”€β”€ base.html # Admin layout with side navigation +β”‚ β”‚ β”œβ”€β”€ stats.html +β”‚ β”‚ β”œβ”€β”€ members.html +β”‚ β”‚ β”œβ”€β”€ competitions.html +β”‚ β”‚ └── sponsors.html +β”‚ β”œβ”€β”€ base.html # Public site layout +β”‚ β”œβ”€β”€ home.html # Homepage with stats from DB +β”‚ β”œβ”€β”€ contributors.html # Team members from DB +β”‚ β”œβ”€β”€ competitions.html # Competitions from DB +β”‚ └── sponsors.html # Sponsors from DB +β”œβ”€β”€ static/ +β”‚ β”œβ”€β”€ css/styles.css # All styling +β”‚ β”œβ”€β”€ js/scripts.js +β”‚ └── images/ # Team photos, logos, uploads +└── replit.md # This file + +``` + +## Technology Stack +- **Backend**: Flask (Python) +- **Database**: PostgreSQL (Neon-backed via Replit) +- **Session Management**: Flask-Session +- **File Uploads**: Werkzeug secure_filename +- **Frontend**: HTML5, CSS3, JavaScript + +## User Preferences +- Pure black (#000000) background theme +- Gray-300 text color for paragraphs (light grayish, not pure white) +- Preserve hero section layered image design (michiana.png + techturb.gif) +- Minimal navbar with blur effect +- Dramatic spacing reduction throughout site +- Mobile-first responsive design + +## Database Connection +Environment variables automatically configured: +- `DATABASE_URL` +- `PGHOST`, `PGPORT`, `PGUSER`, `PGPASSWORD`, `PGDATABASE` + +## Security Notes +- Admin password currently hardcoded (techturb123) - consider moving to environment variable for production +- Session secret key uses environment variable with fallback +- File uploads use secure_filename to prevent path traversal +- Admin routes protected with session-based authentication + +## Future Considerations +- Add admin password management +- Implement image optimization for uploads +- Add bulk operations for members/competitions +- Consider adding revision history for content changes +- Add export/backup functionality for database content diff --git a/static/css/styles.css b/static/css/styles.css index 7334e93..8428e06 100644 --- a/static/css/styles.css +++ b/static/css/styles.css @@ -1,493 +1,196 @@ @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400..900&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +:root { + --black: #000000; + --gray-900: #0f0f0f; + --gray-800: #1a1a1a; + --gray-700: #2a2a2a; + --gray-600: #3a3a3a; + --gray-400: #666666; + --gray-300: #999999; + --white: #ffffff; + --accent: #3b82f6; + --accent-hover: #2563eb; + + --font-display: "Orbitron", monospace; + --font-body: "Inter", sans-serif; +} body { margin: 0; - margin-top: 0; - /* background-color: #2B2A2B; */ - background-color: black; - cursor: default; - user-select: none; - color: white; + padding: 0; + background: var(--black); + color: var(--white); + font-family: var(--font-body); + line-height: 1.6; overflow-x: hidden; } +nav { + position: fixed; + top: 20px; + left: 50%; + transform: translateX(-50%); + z-index: 1000; + width: calc(100% - 40px); + max-width: 1200px; +} + +.nav-container { + display: flex; + justify-content: space-between; + align-items: center; + list-style: none; + gap: 8px; + padding: 10px 20px; + background: rgba(0, 0, 0, 0.4); + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.05); + border-radius: 16px; + box-shadow: none; +} + +.nav-container li { + list-style: none; +} + +.nav-container li a { + color: var(--gray-300); + text-decoration: none; + font-family: var(--font-body); + font-weight: 500; + font-size: 14px; + padding: 8px 16px; + border-radius: 8px; + transition: all 0.2s ease; + display: block; +} + +.nav-item-center:hover a, +.nav-item-left:hover a { + color: var(--white); + background: var(--gray-700); +} + +.nav-item-center.active a, +.nav-item-left.active a { + color: #3b82f6; +} + +#logo { + height: 36px; + padding-right: 16px; + border-right: 1px solid var(--gray-700); + margin-right: 8px; +} + .menu-button { display: none; - /* border: 0.25vh solid white; */ - background: none; - color: white; - font-size: 1.5rem; - width: 13vw; - /* Equal width and height */ - height: 13vw; + background: var(--gray-800); + color: var(--white); + font-size: 24px; + width: 48px; + height: 48px; text-align: center; padding: 0; - /* Remove padding to keep it a perfect circle */ - border-radius: 50%; - /* Makes it circular */ + border-radius: 12px; + border: 1px solid var(--gray-700); cursor: pointer; position: fixed; - z-index: 1000000000000000000; - top: 4%; - left: 50%; - transform: translateX(-50%); - justify-content: center; - align-items: center; - transition: .5s; - background: rgba(0, 0, 0, 0.5); - border: none; - backdrop-filter: blur(5px); - /* Centers the icon inside */ + z-index: 10000; + top: 20px; + right: 20px; + backdrop-filter: blur(20px); + transition: all 0.2s ease; +} + +.menu-button:hover { + background: var(--gray-700); } .nav-side { display: none; position: fixed; - z-index: 10000; + z-index: 9999; width: 100vw; height: 100vh; - background: rgba(0, 0, 0, .85); - backdrop-filter: blur(5px); - opacity: 1; - /* display: flex; */ + background: rgba(0, 0, 0, 0.95); + backdrop-filter: blur(20px); flex-direction: column; - text-decoration: none; - list-style-type: none; justify-content: center; align-items: center; - overflow-y: auto; } .nav-side ul { list-style-type: none; padding: 0; margin: 0; - text-decoration: none; text-align: center; } +.nav-side ul li { + margin-bottom: 32px; +} + .nav-side ul li a { text-decoration: none; - color: white; - font-size: 1.25rem; - font-family: sans-serif; -} - -.nav-side ul li { - margin-bottom: 3vh; -} - -.menu-button:hover { - background-color: white; - color: black; -} - -nav { - position: fixed; - top: 0; - width: 100%; - padding-top: 1%; - display: flex; - justify-content: center; - align-items: center; - z-index: 1000; -} - -.nav-container { - display: flex; - justify-content: center; - align-items: center; - list-style: none; - gap: 1%; - padding: 1%; - width: 56vw; - height: 6vh; - border-radius: 50vh; - background-color: rgba(0, 0, 0, .25); - backdrop-filter: blur(10px); - box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); - /* border: solid 1px; - border-color: white; */ -} - - - -@media only screen and (max-width: 850px) { - - nav { - display: none; - } - - .menu-button { - display: flex; - display: block !important; - } - - .competition-header { - flex-direction: column!important; - gap: 0%!important; - margin: 0%!important; - padding: 0%!important; - } - - .middle-dot { - display: none!important; - } - - nav { - padding: 0; - } - - .nav-container { - display: flex; - align-items: center; - justify-content: left; - - } - - .header { - font-size: 1rem !important; - } - - .header2 { - font-size: 1rem !important; - } - - .sub-header { - font-size: 2.5rem !important; - } - - .sub-content { - font-size: 1.5rem !important; - } - - .stats-cards { - flex-direction: column !important; - } - - .stats-card-header { - font-size: 3rem !important; - } - - .stats-card-info { - font-size: 1.35rem !important; - } - - .stats-button { - font-size: 1.125rem !important; - width: 50% !important; - transform: translateX(50%) !important; - margin-top: 5% !important; - margin-bottom: 5% !important; - } - - .bottom-center-button { - width: 50% !important; - font-size: 1.125rem !important; - } - - .sponsors-container { - flex-direction: column !important; - } - - .sponsors-card { - margin-top: 3% !important; - max-width: 85vw !important; - max-height: 25vw !important; - margin-bottom: 15% !important; - } - - #sponsors-more-button { - width: 50% !important; - font-size: 1.125rem !important; - transform: translateX(-25%) !important; - } - - .stats-container { - margin-top: 10vh !important; - } - - .info { - font-size: 1rem !important; - } -} - -@media only screen and (max-width: 1250px) { - .nav-item-left { - display: none; - } -} - -.nav-container li { - width: 33%; - text-align: center; - padding: 2%; - transition: 0.25s; - border-radius: 5px; - font-size: 100%; -} - -.nav-container li a { - color: rgb(125, 125, 125); - text-decoration: none; - font-family: sans-serif; - transition: 0.25s; -} - -.nav-item-center:hover { - background-color: rgba(35, 35, 35, 0.5); -} - -.nav-item-center:hover a { - color: #fff; -} - -.header-line { - padding-top: 100px; - display: flex; - align-items: center; -} - -.header-line h2 { - margin: 0; - padding-right: 10px; + color: var(--white); font-size: 24px; - color: #FFFFFF; + font-family: var(--font-body); + font-weight: 500; + transition: all 0.2s ease; } -.header-line .line { - flex-grow: 1; - height: 2px; - background-color: white; +.nav-side ul li a:hover { + color: var(--accent); } -.contact { - margin: 0; - margin: 0; - padding-top: 8vh; - position: absolute; +.home, +.rob, +.contact, +.contributors, +.sponsors { + padding-top: 0; width: 100%; - /* height: 100%; */ - justify-content: center; - /* background-color: purple; */ + min-height: 100vh; } -#logo { - height: 6vh; - border-right: solid 1px #787878; -} - - -.header-container { - color: white; - display: flex; - align-items: center; - justify-content: center; - text-align: center; - margin-top: 8%; - width: 50%; - padding-left: 25%; -} - -#spon { - margin-top: 5%; -} - -#team { - margin-top: 5vh; -} - -#stats2 { - margin-bottom: 5%; -} - -.header-container4 { - color: white; - display: flex; - align-items: center; - justify-content: center; - text-align: center; - margin-top: 6%; - width: 50%; - padding-left: 25%; -} - -.line { - flex: 1; - border-top: 0.5vh solid white; - opacity: 0.5; -} - -.header { - color: white; - padding: 0 1vh; - margin: 0; - font-family: Sans-Serif; - font-size: 1.5rem; - opacity: 0.5; - /* 1.5vw */ -} - -.heading { - font-size: 1.5rem; - color: rgb(250, 250, 250); - font-family: sans-serif; - padding-left: 10%; -} - -hr { - color: rgb(64, 64, 64) -} - -.header2 { - color: white; - padding-right: 1vh; - margin: 0; - font-family: Sans-Serif; - font-size: 1.5rem; - opacity: 0.5; -} - -.contact-container { - width: 92%; - height: auto; - display: flex; - flex-wrap: wrap; - gap: 5%; - margin-left: 4%; - -} - -/* .contact-card { - display: flex; - flex-direction: column; - width: 30%; - height:100%; - transition: 1s; - padding-top: 5%; -} - -.contact-card:hover { - transform: scale(1.03); -} - -.card-img { - border-top-right-radius: 2vh; - border-top-left-radius: 2vh; - height: 50vh; -} - -.card-txt-container { - background-color: #1c1c1c; - color: whitesmoke; - margin:0; - padding:0; - align-items: center; - text-align: center; - font-size: 3vh; - font-family: Sans-Serif; - border-bottom-left-radius: 2vh; - border-bottom-right-radius: 2vh; -} - -.card-txt { - padding-top: 2%; - padding-bottom: 2%; -} - -.link { - text-decoration: none; - color: rgba(245, 245, 245, 0.25); - transition: 1s; -} - -.link:hover { - color: rgba(245, 245, 245, 0.5); -} */ - -.info { - font-family: "Orbitron", serif; - font-weight: 700; - font-style: normal; - font-size: 1.75rem; - text-align: center; -} - -.card-container { - display: flex; - justify-content: space-between; - flex-wrap: wrap; - margin-top: 2%; -} - -.card { - width: 30%; - height: 50vh; - background-color: rgb(15, 15, 15); - border-radius: 1.5vh; - overflow: hidden; - margin-bottom: 4%; - text-decoration: none; - display: inline-block; - position: relative; - transition: transform 0.3s ease; - border: white solid .25vh; -} - -.card-content { - display: flex; - justify-content: center; - align-items: center; - height: 100%; -} - -.card img { - max-width: 75%; - max-height: 100%; - height: auto; - transition: transform 0.3s ease; - filter: brightness(0) invert(1); -} - -.card:hover img { - transform: scale(1.2); -} - -.home { - margin: 0; - padding: 0; - position: absolute; - width: 100%; - height: 100%; - justify-content: center; +.rob, +.contact, +.contributors, +.sponsors { + padding-top: 100px; } .competitions { - margin: 0; - padding: 0; - position: absolute; width: 100%; - height: 100%; + min-height: 100vh; display: flex; flex-direction: row; } .bg { width: 100%; - background-color: black; + background: var(--black); height: 100vh; display: flex; justify-content: center; align-items: center; + position: relative; } .bg img { width: 100%; height: 100%; - /* object-fit: cover; - overflow: hidden; */ - transition: .5s; - filter: brightness(0.85); + transition: all 0.3s ease; + filter: brightness(0.8); } .bg::after { @@ -496,290 +199,282 @@ hr { bottom: 0; left: 0; right: 0; - height: 25%; - /* Adjust based on how much gradient you need */ - background: linear-gradient(to top, black, rgba(5, 8, 28, 0)); + height: 15%; + background: linear-gradient(to top, var(--black), transparent); pointer-events: none; - /* Ensures the gradient doesn’t block interactions */ } -/* .bg img:hover { - transform: scale(1.05); -} - */ -.header-container2 { - display: flex; - /* align-items: center; - justify-content: center; - text-align: center; */ - padding-top: 12%; +.overlay-container { + position: relative; width: 100%; - justify-content: center; - align-items: center; - color: white; + height: 100vh; + overflow: hidden; } -.header-container6 { - display: flex; - /* align-items: center; - justify-content: center; - text-align: center; */ - padding-top: 20%; +.overlay-container img { + position: absolute; + top: 0; + left: 0; width: 100%; - justify-content: center; - align-items: center; - color: white; + height: 100vh; + object-fit: cover; +} + +.overlay-container .overlay { + opacity: 0.8; + pointer-events: none; + width: 100%; + height: 100%; + object-fit: fill; +} + +.footer-text { + position: absolute; + bottom: 60px; + left: 60px; + color: var(--accent); + font-size: 32px; + font-family: var(--font-display); + z-index: 100; +} + +.home-but { + border: none; + background: rgba(38, 38, 38, 0.4); + backdrop-filter: blur(10px); + color: var(--gray-300); + transition: all 0.2s ease; + margin-top: 8px; + margin-right: 4px; + padding: 8px; + border-radius: 8px; + cursor: pointer; +} + +.home-but:hover { + color: var(--white); + background: rgba(38, 38, 38, 0.6); + transform: translateY(-2px); } hr { width: 100%; - margin: 0 auto; - padding: 0; + margin: 8px 0; + border: none; + height: 1px; + background: linear-gradient(90deg, transparent, var(--gray-700), transparent); } -.home-info { - width: 80%; - margin-top: 0; - padding-top: 0; - display: block; - margin: auto; - padding-bottom: 6%; - color: white; +.heading, +.heading2 { + font-size: clamp(24px, 3vw, 32px); + color: var(--white); + font-family: var(--font-display); + font-weight: 700; + margin: 0 0 8px; + text-align: center; +} +.home-info, +.team-info { + width: 90%; + max-width: 1200px; + margin: 40px auto 64px auto; + padding-bottom: 64px; + color: var(--white); +} + +.team-info { + padding-top: 64px; } .sub-header { - padding: 0 1vh; - font-family: "Orbitron", serif; + font-family: var(--font-display); font-weight: 700; - font-style: normal; - font-size: 4.25rem; - color: white; + font-size: clamp(32px, 5vw, 56px); + color: var(--white); text-align: center; - font-weight: 100; + margin: 32px 0; } .sub-content { - padding-left: 1vh; text-align: center; - font-size: 2rem; - font-family: Sans-Serif; - font-weight: 50; - letter-spacing: .15vw; + font-size: clamp(16px, 2vw, 20px); + font-family: var(--font-body); + color: var(--gray-300); + line-height: 1.8; + max-width: 800px; + margin: 32px auto; } -#comp { - margin-top: 19%; - padding-left: 0; +.info { + font-family: var(--font-body); + font-weight: 500; + font-size: clamp(16px, 2vw, 20px); + text-align: center; + color: var(--gray-300); + margin: 32px 0; +} + +.hero-image-section { + position: relative; + width: 100%; + height: 700px; + border-radius: 20px; + overflow: hidden; + margin: 48px 0 32px 0; +} + +.hero-team-image { + width: 100%; + height: 100%; + object-fit: cover; + position: absolute; + top: 0; + left: 0; +} + +.hero-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(to top, rgba(0, 0, 0, 0.9) 0%, rgba(0, 0, 0, 0.4) 100%); + z-index: 1; +} + +.hero-text-container { + position: absolute; + bottom: 64px; + left: 48px; + right: 48px; + z-index: 2; + max-width: 800px; +} + +.hero-title { + font-family: var(--font-display); + font-weight: 700; + font-size: clamp(32px, 5vw, 56px); + color: var(--white); + margin: 0 0 24px 0; + text-align: left; +} + +.hero-description { + font-size: clamp(16px, 2vw, 20px); + font-family: var(--font-body); + color: var(--gray-300); + line-height: 1.8; + margin: 0 0 20px 0; + text-align: left; +} + +.hero-description:last-child { + margin-bottom: 0; } .stats-container { - border: 0.25vh white solid; - width: 100%; - background-color: rgb(15, 15, 15); - border-radius: 1.5vh; - margin-top: 8%; - height: auto; - padding-bottom: 2%; -} - -.header-container3 { - display: flex; - /* align-items: center; - justify-content: center; - text-align: center; */ - padding-top: 2%; - width: 99%; - justify-content: center; - align-items: center; - color: white; -} - -.line2 { - flex: 1; - border-top: 0.5vh solid white; - opacity: 0.5; + background: var(--gray-900); + border: 1px solid var(--gray-700); + border-radius: 16px; + padding: 48px; + margin: 48px auto; } .stats-cards { - width: 100%; - display: flex; - align-items: center; - gap: 10%; - position: relative; - justify-content: center; - margin: 0; - padding-bottom: 2%; - flex-direction: row; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 24px; + margin: 48px 0; } .stats-card { - margin: 0; + text-align: center; + padding: 32px; + background: var(--gray-800); + border-radius: 12px; + border: 1px solid var(--gray-700); + transition: all 0.2s ease; +} + +.stats-card:hover { + background: var(--gray-700); + transform: translateY(-4px); } .stats-card-header { - font-family: Sans-Serif; - font-size: 4rem; - color: white; - text-align: center; - transition: 0.1s; + font-family: var(--font-display); + font-size: clamp(40px, 5vw, 64px); + color: var(--accent); + margin: 0; + font-weight: 700; } .stats-card-info { - text-align: center; - padding: 0; - margin: 0; - font-family: Sans-Serif; - font-size: 1.5rem; - color: rgba(255, 255, 255, 0.75); - padding-bottom: 5%; + margin: 16px 0 0; + font-family: var(--font-body); + font-size: clamp(14px, 1.5vw, 16px); + color: var(--gray-300); + text-transform: uppercase; + letter-spacing: 0.05em; + font-weight: 500; } -.stats-card-header:hover { - transform: scale(1.5); -} - -.sponsors-container { - width: 100%; - height: auto; - padding-right: 1%; - position: relative; - justify-content: center; - margin: 0; - display: flex; - align-items: center; - flex-direction: row; - gap: 10%; -} - -.sponsors-card { - max-width: 15%; - max-height: 15%; - filter: brightness(0) invert(1) drop-shadow(0 0 0.75rem rgb(97, 97, 97)); - flex: 1 1 calc(25% - 10px); - margin-bottom: 10px; -} - -.sponsors-more-container { - align-items: center; - justify-content: center; - width: 100%; - margin: auto; - position: relative; - margin: 0; - padding: 0; - margin-top: 5%; -} - -#sponsors-more-button { - position: relative; - margin: auto; - justify-content: center; - align-items: center; - padding: 1%; - font-size: 1.25rem; - font-family: Sans-Serif; - border-radius: 100vh; - border: none; - color: white; - width: 25%; - height: 15%; - border: solid white 0.25vh; - background-color: rgba(245, 245, 245, 0); - left: 37%; - transition: 0.5s; -} - -#sponsors-more-button:hover { - background-color: white; - color: black; +.stats-button { + color: var(--white); + font-size: 16px; + font-family: var(--font-body); + font-weight: 500; + border-radius: 12px; + padding: 16px 48px; + border: 1px solid #3b82f6; + background: transparent; cursor: pointer; + transition: all 0.2s ease; + margin: 32px auto; + display: block; } -.sponsors { - margin: 0; - padding-top: 8vh; - position: absolute; +.stats-button:hover { + background: #3b82f6; + transform: translateY(-2px); +} + +.about-section-imgp { + display: grid; + grid-template-columns: 1fr; + gap: 48px; + margin: 48px 0; +} + +.about-sec-img { width: 100%; - /* height: 100%; */ - justify-content: center; -} - -.card-container-sponsors { - display: flex; - justify-content: space-between; - flex-wrap: wrap; - margin-top: 2%; -} - -.card-sponsors { - width: 23%; - height: 50vh; - background-color: rgb(15, 15, 15); - border-radius: 1.5vh; + height: 500px; overflow: hidden; - margin-bottom: 4%; - text-decoration: none; - /* display: inline-block; */ - position: relative; - transition: transform 0.3s ease; - border: white solid .25vh; + border-radius: 16px; + border: 1px solid var(--gray-700); } -.card-content-sponsors { - display: flex; - justify-content: center; - align-items: center; - height: 100%; -} - -.card-sponsors img { - max-width: 75%; - max-height: 100%; - height: auto; - transition: transform 0.3s ease; - filter: brightness(0) invert(1); - opacity: 0.5; -} - -.sponsors-container { - width: 92%; - height: auto; - display: flex; - flex-wrap: wrap; - margin-left: 4%; -} - -.card-sponsors:hover img { - transform: scale(1.2); - opacity: 1; -} - -.contributors { - margin: 0; - padding: 0; - position: absolute; +.about-sec-img img { width: 100%; height: 100%; - justify-content: center; + object-fit: cover; + transition: all 0.3s ease; } -.contributors { - margin: 0; - padding: 0; - position: absolute; - width: 100%; - height: 100%; - justify-content: center; +.about-sec-img img:hover { + transform: scale(1.05); } .meet-our-team-container { position: relative; - border: 0.25vh white solid; + border: 1px solid var(--gray-700); width: 100%; - border-radius: 1.5vh; - margin-top: 10%; - height: 45vh; + border-radius: 16px; + margin: 48px 0; + height: 500px; overflow: hidden; } @@ -787,375 +482,366 @@ hr { width: 100%; height: 100%; object-fit: cover; - margin: 0; - padding: 0; - filter: brightness(.75); - transition: 5s; + filter: brightness(0.6); + transition: transform 3s ease; } #meet-our-team:hover { - transform: scale(1.25); + transform: scale(1.1); } .meet-our-team-container::before { content: ''; position: absolute; bottom: 0; - /* Keep the gradient at the bottom */ left: 0; width: 100%; height: 100%; - /* Adjust this value to control the gradient height */ - background: linear-gradient(to top, rgba(0, 0, 0, 1), rgba(0, 0, 0, 0)); + background: linear-gradient(to top, rgba(0, 0, 0, 0.9), transparent); z-index: 1; pointer-events: none; } .bottom-center-button { position: absolute; - bottom: 5%; + bottom: 48px; left: 50%; transform: translateX(-50%); - /* padding: 10px 20px; - background-color: #ffffff; - color: #000000; - border: none; - border-radius: 5px; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); */ - font-size: 1.25rem; - font-family: Sans-Serif; - border-radius: 100vh; - border: none; - color: white; - width: 25%; - /* height: 7vh; */ - border: solid white 0.25vh; - background-color: rgba(245, 245, 245, 0); + font-size: 16px; + font-family: var(--font-body); + font-weight: 500; + border-radius: 12px; + color: var(--white); + padding: 16px 48px; + border: 1px solid #3b82f6; + background: transparent; z-index: 2; cursor: pointer; - transition: 0.5s; - min-height: 10%; - padding: 1%; + transition: all 0.2s ease; } .bottom-center-button:hover { - background-color: white; - color: black; - cursor: pointer; + background: #3b82f6; + color: var(--white); + transform: translateX(-50%) translateY(-2px); } -/* Span stuff */ - -.stats-button { - color: white; - font-size: 1.25rem; - font-family: Sans-Serif; - border-radius: 100vh; - width: 25%; - height: 50%; - border: solid white 0.25vh; - background-color: rgba(0, 0, 0, 0); - z-index: 2; - cursor: pointer; - transition: 0.5s; - box-shadow: none; - transform: translateX(150%); - margin-top: 1%; - margin-bottom: 1%; - padding: 1%; -} - - - -.stats-button:hover { - color: black; - background-color: white; -} - -.team-info { - width: 75%; - margin-top: 14%; - padding-top: 8%; - display: block; - /* transform: translateX(16%); */ - margin: auto; -} - -.footer { +.sponsors-container { width: 100%; - height: 5vh; - align-items: center; - justify-content: center; - text-align: center; - border-top: white 0.25vh solid; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 32px; + margin: 48px 0; } -.footer-content { - font-size: 2.5vh; - justify-content: center; - align-items: center; - margin: auto; - text-decoration: none; - padding-top: .5%; - font-family: sans-serif; - font-weight: 100; +.sponsors-card { + width: 100%; + height: auto; + object-fit: contain; + filter: brightness(0) invert(1); + transition: all 0.2s ease; } -.link-to-here { - color: white; - font-weight: bold; - text-decoration: underline; +.sponsors-card:hover { + filter: brightness(0) invert(1) drop-shadow(0 0 20px rgba(255, 255, 255, 0.3)); + transform: scale(1.05); +} + +#sponnnnn { + background: #0a0a0a; + border: 1px solid #1a1a1a; + padding: 48px; + border-radius: 16px; +} + +#sponsors-more-button { + margin: 48px auto; + padding: 16px 48px; + font-size: 16px; + font-family: var(--font-body); + font-weight: 500; + border-radius: 12px; + border: 1px solid #3b82f6; + color: var(--white); + background: transparent; + display: block; + transition: all 0.2s ease; + cursor: pointer; +} + +#sponsors-more-button:hover { + background: #3b82f6; + transform: translateY(-2px); +} + +.card-container, +.card-container-sponsors { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); + gap: 40px; + margin: 64px 0; + max-width: 1200px; + margin-left: auto; + margin-right: auto; +} + +.card, +.card-sponsors { + background: var(--gray-900); + border-radius: 20px; + overflow: hidden; + border: 1px solid var(--gray-700); + transition: all 0.3s ease; + display: flex; + align-items: center; + justify-content: center; + min-height: 280px; +} + +.card:hover, +.card-sponsors:hover { + border-color: var(--accent); + transform: translateY(-8px); + box-shadow: 0 20px 40px rgba(59, 130, 246, 0.2); +} + +.card-content, +.card-content-sponsors { + display: flex; + justify-content: center; + align-items: center; + padding: 64px; + width: 100%; + height: 100%; +} + +.card img, +.card-sponsors img { + max-width: 100%; + max-height: 180px; + object-fit: contain; + filter: brightness(0) invert(1); + transition: all 0.3s ease; +} + +.card:hover img, +.card-sponsors:hover img { + transform: scale(1.15); + filter: brightness(0) invert(1) drop-shadow(0 0 20px rgba(59, 130, 246, 0.4)); } .members-container { - width: 75%; - margin-top: 0; - padding-top: 0; - display: flex; - flex-wrap: wrap; - justify-content: center; - margin: auto; - padding-bottom: 5%; - gap: 3%; - /* border-top: white 0.25vh solid; - border-right: white 0.25vh solid; - border-left: white 0.25vh solid; - border-top-right-radius: 1.5vh; - border-top-left-radius: 1.5vh; */ -} - -@media only screen and (max-width: 850px) { - .card-sponsors { - width: 100%; - height: 25vh; - } - - .card-container-sponsors { - flex-direction: column; - } - - .card { - width: 100%; - height: 25vh; - } - - .contact-container { - flex-direction: column; - } - - .member-card { - width: 100% !important; - ; - height: 50vh !important; - ; - } - - .members-container { - flex-direction: column; - } - + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 40px; + padding: 64px; + background: var(--gray-900); + border: 1px solid var(--gray-700); + border-radius: 20px; + margin: 64px auto; + max-width: 1200px; } .member-card { - width: 25%; - height: 25vh; display: flex; flex-direction: column; - font-family: sans-serif; - text-align: center; - justify-content: center; align-items: center; - margin-top: 3%; - padding: 2%; - transition: 0.5s; - background-color: rgb(15, 15, 15); - border-radius: 10px; - border: white 0.25vh solid; + padding: 48px 32px; + background: var(--gray-800); + border-radius: 16px; + border: 1px solid var(--gray-700); + transition: all 0.3s ease; + width: 100%; } .member-card:hover { - transform: scale(1.075); + background: var(--gray-700); + transform: translateY(-8px); + border-color: var(--accent); + box-shadow: 0 20px 40px rgba(59, 130, 246, 0.15); } .member-image { - width: 15vh; - height: 15vh; - border: 0.25vh white solid; - border-radius: 100vh; + width: 120px; + height: 120px; + border: 3px solid var(--accent); + border-radius: 50%; overflow: hidden; - transition: 1s; -} - -.member-name { - font-size: 3vh; - margin: 0; - padding: 1%; - margin-top: 5%; - font-weight: bold; -} - -.member-role { - font-size: 2vh; - padding: 0; - margin: 0; - margin-top: 5%; - font-weight: bold; - opacity: 0.5; + transition: all 0.3s ease; + margin-bottom: 24px; } .member-image:hover { transform: rotate(360deg); + box-shadow: 0 0 30px rgba(59, 130, 246, 0.4); +} + +.member-name { + font-size: 22px; + font-weight: 600; + margin: 0; + color: var(--white); + font-family: var(--font-body); +} + +.member-role { + font-size: 13px; + margin: 12px 0 0; + color: var(--gray-400); + text-transform: uppercase; + letter-spacing: 0.1em; + font-weight: 500; } .sidebar { position: fixed; - width: 25%; - height: 86.5%; - justify-content: center; - display: flex; - align-items: center; - bottom: 0; - /* background-color: red; */ + width: 250px; + height: calc(100vh - 150px); + top: 120px; + left: 32px; + background: #0a0a0a; + border: 1px solid #1a1a1a; + border-radius: 16px; + padding: 24px; + overflow-y: auto; + z-index: 100; +} + +.sidebar::-webkit-scrollbar { + width: 6px; +} + +.sidebar::-webkit-scrollbar-track { + background: transparent; +} + +.sidebar::-webkit-scrollbar-thumb { + background: var(--gray-700); + border-radius: 3px; } .sidebar-content { - height: 80%; - width: 85%; - margin: 37% auto; - position: relative; - overflow-y: auto; display: flex; flex-direction: column; - gap: 25%; - /* background-color: green; */ -} - -.sidebar-content::-webkit-scrollbar { - display: none; + gap: 16px; } .sidebar a { text-decoration: none; - font-size: 4vh; - color: white; - display: block; - font-family: sans-serif; - opacity: .5; - transition: .5s; - filter: drop-shadow(0 0 1rem rgb(102, 102, 102)); - width: 100%; - box-sizing: border-box; - position: relative; - margin: auto; - text-align: center; + font-size: 18px; + color: var(--gray-300); + font-family: var(--font-body); + font-weight: 500; + padding: 12px; + border-radius: 8px; + transition: all 0.2s ease; + text-align: left; } .sidebar a:hover { - opacity: 1; - transform: scale(1.1); - filter: drop-shadow(0 0 0.75rem rgb(67, 67, 67)); + color: var(--white); + background: rgba(59, 130, 246, 0.2); +} + +.sidebar a.active { + color: var(--white); + background: #3b82f6; } .competitions-container { - color: white; - margin-top: 8vh; - margin-left: 25%; - width: 100%; + color: var(--white); + padding-top: 120px; + padding-left: 300px; + padding-right: 32px; + padding-bottom: 64px; display: flex; - flex-direction: column; - /* align-items: center; */ - padding: 0; + justify-content: center; + align-items: flex-start; + width: 100%; } .competitions-inner { - width: 82%; - /* background-color: aqua; */ - margin: 0; - padding: 0; - padding-bottom: 5%; + width: 100%; + max-width: 900px; } .competition-year { - font-family: "Orbitron", serif; + font-family: var(--font-display); font-weight: 700; - font-style: normal; - font-size: 8vh; - margin-top: 2%; - padding: 0; - font-weight: 50; - /* background-color: antiquewhite; */ - margin-bottom: 0; + font-size: clamp(40px, 6vw, 64px); + margin: 0px auto 24px auto; + color: var(--white); + text-align: center; } .competition-header { display: flex; - flex-direction: row; - gap: 2%; - padding: 0; - margin: 0; - font-size: 3vh; - font-family: "Orbitron", serif; - font-weight: 700; - font-style: normal; - /* font-weight: bold; */ - text-wrap: wrap; + flex-wrap: wrap; + gap: 12px; + margin-bottom: 16px; + font-size: 18px; + font-family: var(--font-body); + font-weight: 600; +} + +.middle-dot { + color: var(--gray-400); } .competition-card { - width: 100%; - /* background-color: lightgrey; */ - margin: 0; - padding: 0; - position: relative; + background: var(--gray-900); + padding: 48px; + border: 1px solid var(--gray-700); + border-radius: 16px; + margin-bottom: 32px; + transition: all 0.2s ease; +} + +.competition-card:hover { + border-color: var(--accent); } .competition-card-img { width: 100%; - margin-top: 1%; - max-height: 40vh; - border: 0.25vh solid white; - border-radius: 10px; + margin: 32px 0; + border-radius: 12px; overflow: hidden; + border: 1px solid var(--gray-700); } .competition-card-img img { width: 100%; - object-fit: cover; + height: auto; + display: block; filter: brightness(0.8); } .competition-date { - opacity: .5; -} - -.middle-dot { - opacity: .5; - display: block; + color: var(--gray-400); } .competition-subtitle { - margin: 0; - padding: 0; - font-size: 2.55vh; - font-family: "Orbitron", serif; - font-weight: 650; - font-style: normal; - /* font-weight: 750; */ + font-size: 16px; + font-family: var(--font-body); + font-weight: 600; + color: var(--accent); + margin: 32px 0 12px; + text-transform: uppercase; + letter-spacing: 0.05em; } -.competition-description { - font-family: sans-serif; - font-size: 2.5vh; - font-weight: 100; +.competition-description, +.competition-awards { + font-family: var(--font-body); + font-size: 16px; + color: var(--gray-300); + line-height: 1.6; } .competition-awards { - font-size: 2.5vh; - font-weight: 100; - font-family: sans-serif; + padding-left: 32px; } - span.sponsors, span.contact, span.team, @@ -1165,16 +851,14 @@ span.competitions, span.gallary { position: relative; cursor: pointer; - padding: 2%; - margin: -2%; + padding: 8px; + margin: -8px; } span.emoji::before, span.emoji::after { content: ""; position: absolute; - top: -16px; - left: -8px; opacity: 0; transform: scale(0.5) rotate(-30deg); transition: opacity 120ms ease-out, transform 0.3s cubic-bezier(0.18, 0.89, 0.32, 1.5); @@ -1183,113 +867,48 @@ span.emoji::after { span.emoji::after { top: revert; left: revert; - bottom: -16px; - right: -8px; } span.contact::before, span.contact::after { content: "πŸ“±"; - font-size: 2.5vh; - top: -5%; - left: -1%; -} - -span.contact::after { - top: revert; - left: revert; - bottom: -5%; - right: -1%; + font-size: 32px; } span.about::before, span.about::after { content: "πŸ‘‹"; - font-size: 2.5vh; - top: -5%; - left: -1%; -} - -span.about::after { - top: revert; - left: revert; - bottom: -5%; - right: -1%; + font-size: 32px; } span.stats::before, span.stats::after { content: "πŸ“Š"; - font-size: 2.5vh; - top: -5%; - left: -1%; -} - -span.stats::after { - top: revert; - left: revert; - bottom: -5%; - right: -1%; + font-size: 32px; } span.gallary::before, span.gallary::after { content: "✊"; - font-size: 2.5vh; - top: -5%; - left: -1%; -} - -span.gallary::after { - top: revert; - left: revert; - bottom: -5%; - right: -1%; + font-size: 32px; } span.sponsors::before, span.sponsors::after { content: "❀️"; - font-size: 2.5vh; - top: -5%; - left: -1%; -} - -span.sponsors::after { - top: revert; - left: revert; - bottom: -5%; - right: -1%; + font-size: 32px; } span.team::before, span.team::after { content: "πŸ‘₯"; - font-size: 2.5vh; - top: -5%; - left: -1%; -} - -span.team::after { - top: revert; - left: revert; - bottom: -5%; - right: -1%; + font-size: 32px; } span.competitions::before, span.competitions::after { content: "πŸ₯‡"; - font-size: 2.5vh; - top: -5%; - left: -1%; -} - -span.competitions::after { - top: revert; - left: revert; - bottom: -5%; - right: 80%; + font-size: 32px; } span.emoji:hover::before, @@ -1298,150 +917,279 @@ span.emoji:hover::after { opacity: 1; } -span.emoji:active::before, -span.emoji:active::after { - transform: scale(1) rotate(-2deg); -} - -.about-section-imgp { - display: flex; - align-items: center; - justify-content: center; - flex-direction: column; - padding-left: 10%; - padding-bottom: 1%; - padding-right: 10%; - -} - -.about-sec-img { - +.robots-container { width: 100%; - /* Example size, adjust as needed */ - height: 75vh; + margin: 48px 0; +} + +.robots-inner { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + gap: 32px; +} + +.robo-card { + position: relative; + background: var(--gray-800); + border-radius: 16px; overflow: hidden; - transition: .5s; - border: 2px rgb(250, 250, 250) solid; - border-radius: 10px; - margin-bottom: 5%; + border: 1px solid var(--gray-700); + aspect-ratio: 4/3; + transition: all 0.2s ease; } -.about-section-imgp img { +.robo-card:hover { + border-color: var(--accent); + transform: translateY(-4px); +} + +.robo-card img { width: 100%; - transition: .5s; - height: auto; - ; + height: 100%; object-fit: cover; + filter: brightness(0.6); + transition: all 0.3s ease; } -.about-section-imgp p { - flex: 1; - text-align: center; - ; +.robo-card:hover img { + filter: brightness(0.4); + transform: scale(1.05); } -/* @media only screen and (min-width: 850px) { - .about-section-imgp p { - padding-left: 10%; +.robo-card-txt { + position: absolute; + bottom: 32px; + left: 32px; + z-index: 2; +} + +.robo-card-name { + font-size: 32px; + margin: 0; + color: var(--white); + font-family: var(--font-display); + font-weight: 700; +} + +.robo-card-desc { + font-size: 16px; + margin: 8px 0 0; + color: var(--gray-300); + font-family: var(--font-body); +} + +@media (max-width: 1200px) { + .nav-item-left { + display: none; + } +} + +@media (max-width: 992px) { + .sidebar { + display: none; } - .about-section-imgp-l p { - padding-right: 10%; + .competitions-container { + padding-left: 32px !important; + padding-right: 32px !important; + padding-top: 100px !important; } -} */ + #comp { + margin-bottom: 20px !important; + } -.about-section-imgp-l { - display: flex; - align-items: center; - justify-content: center; - padding-left: 10%; - padding-bottom: 1%; - padding-right: 10%; + #comp-hr { + display: block !important; + visibility: visible !important; + opacity: 1 !important; + width: 100% !important; + height: 1px !important; + background: var(--gray-700) !important; + margin: 0 0 40px 0 !important; + border: none !important; + } + + .stats-cards { + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + } } -.about-section-imgp-l img { - max-width: 40%; - /* Example size, adjust as needed */ - max-height: 60%; - border-radius: 5px; - transition: .5s; - border: 2px white solid; -} +@media (max-width: 850px) { + nav { + display: none; + } -.about-section-imgp img:hover { - transform: scale(1.025); -} - -.about-section-imgp-l img:hover { - transform: scale(1.025); -} - -.about-section-imgp-l p { - flex: 1; -} - -@media only screen and (max-width: 850px) { - - .about-section-imgp, - .about-section-imgp-l { + .menu-button { display: flex; - flex-direction: column; - /* Stack image and text vertically */ align-items: center; - justify-content: flex-start; - /* Align content to the top */ - padding: 0; - margin: 0; - text-align: center; - margin-bottom: 5%; + justify-content: center; } - .contact-content { - background-image: none; + .footer-text { + font-size: 24px; + bottom: 30px; + left: 30px; } - - - .about-section-imgp img, - .about-section-imgp-l img { - max-width: 80vw; - /* Image takes 80% of the viewport width */ - max-height: auto; - width: 80%; - /* Ensure the image scales properly */ - margin-bottom: 10px; - /* Adds some space between the image and the paragraph */ - border-radius: 5px; - transition: .5s; + .home-info, + .team-info { + width: 95%; + margin-top: 80px; } - .about-section-imgp-l { - flex-direction: column-reverse; + .rob, + .contact, + .contributors, + .sponsors { + padding-left: 20px; + padding-right: 20px; + padding-top: 80px; } - .about-section-imgp-l img, - .about-section-imgp img { - min-width: 100%; - height: 75vh; - border-radius: 10px; - object-fit: cover; - overflow: hidden; - margin-bottom: 5%; + .stats-cards { + grid-template-columns: 1fr; } - .about-section-imgp p, - .about-section-imgp-l p { - flex: 1; - padding: 0; - margin: 0; - padding-left: 0; - padding-right: 0; + .sponsors-container, + .card-container, + .card-container-sponsors { + grid-template-columns: 1fr; + margin: 32px 0; } - .about-section-imgp img:hover, - .about-section-imgp-l img:hover { - transform: scale(1.025); + .members-container { + grid-template-columns: 1fr; + padding: 32px 24px; + margin: 32px auto; + } + + .competition-header { + flex-direction: column; + gap: 8px; + } + + .middle-dot { + display: none; + } + + .competitions-container { + margin-left: 20px; + margin-right: 20px; + margin-top: 80px; + } + + .stats-container { + padding: 32px 24px; + margin: 32px 0; } } +@media (max-width: 576px) { + nav { + top: 12px; + width: calc(100% - 24px); + } + + .menu-button { + top: 12px; + left: 50%; + right: auto; + transform: translateX(-50%); + } + + .sub-header { + font-size: clamp(28px, 8vw, 40px); + } + + .footer-text { + font-size: 20px; + bottom: 20px; + left: 20px; + } + + .home-info, + .team-info { + margin: 80px auto 16px auto; + padding-bottom: 24px; + } + + .rob, + .contact, + .contributors, + .sponsors { + padding-left: 16px; + padding-right: 16px; + padding-top: 70px; + } + + .competitions-container { + margin-left: 16px; + margin-right: 16px; + margin-top: 70px; + } + + .competition-card { + padding: 24px; + margin-bottom: 24px; + } + + .card-container, + .card-container-sponsors { + padding: 0 8px; + margin: 24px 0; + } + + .sponsors-container { + margin: 24px 0; + } + + .members-container { + padding: 24px 16px; + margin: 24px auto; + } + + .stats-container { + padding: 24px 16px; + margin: 24px 0; + } + + .hero-image-section { + height: 600px; + margin: 24px 0 24px 0; + border-radius: 16px; + } + + .hero-text-container { + bottom: 40px; + left: 24px; + right: 24px; + } + + .hero-title { + font-size: clamp(24px, 6vw, 36px); + margin-bottom: 16px; + } + + .hero-description { + font-size: clamp(14px, 3vw, 16px); + line-height: 1.6; + } +} + +#comp { + margin: 0 0 20px 0 !important; + padding: 0 !important; + text-align: center; + width: 100%; +} + +#comp-hr { + width: 100%; + margin: 0 0 40px 0; +} + +.competition-year { + margin-top: 25px !important; + font-weight: 100 !important; +} \ No newline at end of file diff --git a/static/images/abyss.png b/static/images/abyss.png new file mode 100644 index 0000000..cdd5f47 Binary files /dev/null and b/static/images/abyss.png differ diff --git a/static/images/bggg.png b/static/images/bggg.png new file mode 100644 index 0000000..9e1d497 Binary files /dev/null and b/static/images/bggg.png differ diff --git a/static/images/frctees.png b/static/images/frctees.png new file mode 100644 index 0000000..83ef6b4 Binary files /dev/null and b/static/images/frctees.png differ diff --git a/static/images/fw.png b/static/images/fw.png new file mode 100644 index 0000000..8bf1945 Binary files /dev/null and b/static/images/fw.png differ diff --git a/static/images/gene.png b/static/images/gene.png new file mode 100644 index 0000000..9be2e1c Binary files /dev/null and b/static/images/gene.png differ diff --git a/static/images/geneh.png b/static/images/geneh.png new file mode 100644 index 0000000..cab088e Binary files /dev/null and b/static/images/geneh.png differ diff --git a/static/images/images_3.jpeg b/static/images/images_3.jpeg new file mode 100644 index 0000000..12e83f4 Binary files /dev/null and b/static/images/images_3.jpeg differ diff --git a/static/images/michiana.png b/static/images/michiana.png new file mode 100644 index 0000000..aaaa82d Binary files /dev/null and b/static/images/michiana.png differ diff --git a/static/images/sam.png b/static/images/sam.png new file mode 100644 index 0000000..c36f130 Binary files /dev/null and b/static/images/sam.png differ diff --git a/static/images/sam2.png b/static/images/sam2.png new file mode 100644 index 0000000..ece0eec Binary files /dev/null and b/static/images/sam2.png differ diff --git a/static/images/storm22222.png b/static/images/storm22222.png new file mode 100644 index 0000000..a1e0028 Binary files /dev/null and b/static/images/storm22222.png differ diff --git a/static/images/storm222222.png b/static/images/storm222222.png new file mode 100644 index 0000000..db5f2f8 Binary files /dev/null and b/static/images/storm222222.png differ diff --git a/static/images/teeaam.png b/static/images/teeaam.png new file mode 100644 index 0000000..fb52b3e Binary files /dev/null and b/static/images/teeaam.png differ diff --git a/static/images/teeaam2.png b/static/images/teeaam2.png new file mode 100644 index 0000000..8d64d00 Binary files /dev/null and b/static/images/teeaam2.png differ diff --git a/static/images/tt1.png b/static/images/tt1.png new file mode 100644 index 0000000..1a67145 Binary files /dev/null and b/static/images/tt1.png differ diff --git a/static/images/tt2.png b/static/images/tt2.png new file mode 100644 index 0000000..3ae473f Binary files /dev/null and b/static/images/tt2.png differ diff --git a/static/images/tt3.png b/static/images/tt3.png new file mode 100644 index 0000000..3a45a85 Binary files /dev/null and b/static/images/tt3.png differ diff --git a/static/images/tt4.png b/static/images/tt4.png new file mode 100644 index 0000000..cd327bf Binary files /dev/null and b/static/images/tt4.png differ diff --git a/static/images/tt5.png b/static/images/tt5.png new file mode 100644 index 0000000..36b3f8d Binary files /dev/null and b/static/images/tt5.png differ diff --git a/static/images/tt6.png b/static/images/tt6.png new file mode 100644 index 0000000..7709ea2 Binary files /dev/null and b/static/images/tt6.png differ diff --git a/static/images/tt7.png b/static/images/tt7.png new file mode 100644 index 0000000..096c4b6 Binary files /dev/null and b/static/images/tt7.png differ diff --git a/static/js/scripts.js b/static/js/scripts.js index e69de29..b1be832 100644 --- a/static/js/scripts.js +++ b/static/js/scripts.js @@ -0,0 +1,33 @@ +document.addEventListener('DOMContentLoaded', function() { + const currentPath = window.location.pathname; + + const navLinks = document.querySelectorAll('.nav-item-center, .nav-item-left'); + navLinks.forEach(navItem => { + const link = navItem.querySelector('a'); + if (link) { + const linkPath = new URL(link.href).pathname; + if (linkPath === currentPath || (currentPath === '/' && linkPath === '/')) { + navItem.classList.add('active'); + } + } + }); + + const sidebarLinks = document.querySelectorAll('.sidebar a'); + const hash = window.location.hash; + if (hash) { + sidebarLinks.forEach(link => { + if (link.getAttribute('href') === hash) { + link.classList.add('active'); + } + }); + } else if (sidebarLinks.length > 0) { + sidebarLinks[0].classList.add('active'); + } + + sidebarLinks.forEach(link => { + link.addEventListener('click', function() { + sidebarLinks.forEach(l => l.classList.remove('active')); + this.classList.add('active'); + }); + }); +}); diff --git a/templates/admin/base.html b/templates/admin/base.html new file mode 100644 index 0000000..2598a1f --- /dev/null +++ b/templates/admin/base.html @@ -0,0 +1,168 @@ + + + + + + {% block title %}Admin Panel{% endblock %} - Technical Turbulence + + {% block extra_styles %}{% endblock %} + + +
+
+ + +
+ Logout +
+
+
+ {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} +
{{ message }}
+ {% endfor %} + {% endif %} + {% endwith %} + {% block content %}{% endblock %} +
+
+ + diff --git a/templates/admin/competitions.html b/templates/admin/competitions.html new file mode 100644 index 0000000..e13a8cb --- /dev/null +++ b/templates/admin/competitions.html @@ -0,0 +1,195 @@ +{% extends "admin/base.html" %} + +{% block title %}Competitions Management{% endblock %} + +{% block extra_styles %} + +{% endblock %} + +{% block content %} +

Competitions Management

+ +
+

Add New Competition

+
+
+
+
+ + + + {% for season in seasons %} + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +

Enter each award on a new line

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

Existing Competitions

+
+ {% for comp in competitions %} +
+
+
+

{{ comp.event_name }}

+

{{ comp.season }} Β· {{ comp.date }}

+
+
+ + +
+
+

{{ comp.description }}

+ {% if comp.awards %} +

πŸ† {{ comp.awards|replace('|', ' Β· ') }}

+ {% endif %} +
+ {% endfor %} +
+
+{% endblock %} diff --git a/templates/admin/login.html b/templates/admin/login.html new file mode 100644 index 0000000..7a0c7be --- /dev/null +++ b/templates/admin/login.html @@ -0,0 +1,110 @@ + + + + + + Admin Login - Technical Turbulence + + + +
+ +
+ + diff --git a/templates/admin/members.html b/templates/admin/members.html new file mode 100644 index 0000000..9717cd3 --- /dev/null +++ b/templates/admin/members.html @@ -0,0 +1,259 @@ +{% extends "admin/base.html" %} + +{% block title %}Members & Mentors Management{% endblock %} + +{% block extra_styles %} + +{% endblock %} + +{% block content %} +

Members & Mentors Management

+ +
+

Add New Member/Mentor

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

Mentors

+
+ {% for mentor in mentors %} +
+ {{ mentor.name }} +

{{ mentor.name }}

+

{{ mentor.role }}

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

Team Members

+
+ {% for member in members %} +
+ {{ member.name }} +

{{ member.name }}

+

{{ member.role }}

+ +
+ + + +
+
+ {% endfor %} +
+
+ + + + +{% endblock %} diff --git a/templates/admin/sponsors.html b/templates/admin/sponsors.html new file mode 100644 index 0000000..c7999ac --- /dev/null +++ b/templates/admin/sponsors.html @@ -0,0 +1,163 @@ +{% extends "admin/base.html" %} + +{% block title %}Sponsors Management{% endblock %} + +{% block extra_styles %} + +{% endblock %} + +{% block content %} +

Sponsors Management

+ +
+

Add New Sponsor

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

Existing Sponsors

+
+ {% for sponsor in sponsors %} + + {% endfor %} +
+
+{% endblock %} diff --git a/templates/admin/stats.html b/templates/admin/stats.html new file mode 100644 index 0000000..5b71876 --- /dev/null +++ b/templates/admin/stats.html @@ -0,0 +1,89 @@ +{% extends "admin/base.html" %} + +{% block title %}Stats Management{% endblock %} + +{% block extra_styles %} + +{% endblock %} + +{% block content %} +

Stats Management

+ +
+ {% for stat in stats %} +
+

{{ stat.key }}

+
+ +
+ + +
+
+ + +
+ +
+
+ {% endfor %} +
+{% endblock %} diff --git a/templates/competitions.html b/templates/competitions.html index d73e148..947de46 100644 --- a/templates/competitions.html +++ b/templates/competitions.html @@ -4,156 +4,57 @@ {% block content %} - -
+
-

Competition log

-
+

Competition log

+
-

2024

+ {% for season, comps in competitions_by_season.items() %} +

{{ season }}

+ {% for comp in comps %}
- + {% if comp.image_path %} +
+ +
+ {% endif %}
-

FiT-North Early Bird Scrimmage

+

{{ comp.event_name }}

Β·

-

10/6/2024

+

{{ comp.date }}

Description

-

We participated in the FiT-North Early Bird Scrimmage where we - won two recognitions.

+

{{ comp.description }}

+ {% if comp.awards %}

Awards

    -
  • Innovate Award sponsored by RTX
  • -
  • Design Award 2nd Place
  • -
-
- - - -

2023

- -
-
-

FiT-North F-League Meets (3)

-

Β·

-

11/11/23 - 01/06/24

-
- -

Description

- -

Consisted of 3 qualification meets: FiT-North F-League Meet 1, - FiT-North F-League Meet 2, and FiT-North F-League Meet 3.

- -

Awards

- -
    -
  • Accumulated 10 wins
  • -
-
- -
-
-

FiT-North E&F Tournament

-

Β·

-

01/20/24

-
- -

Description

- -

We got 4 wins, and got Rank 3 out of 24 going into the playoffs. - We were the 1st Team Selected by Rank 2 Team 13072. We had 2 wins in the Semi-Finals and 2 wins - in the Finals. We won 3 awards and advanced straight to North Area Championship for Texas FiT - Region.

- -

Awards

- -
    -
  • Design Award 3rd Place
  • -
  • Innovate Award sponsored by RTX 2nd Place
  • -
  • Winning Alliance - 1st Team Selected
  • -
-
- -
-
-

FiT-North Area Championship

-

Β·

-

02/24/24

-
- -

Description

- -

We won our first match [But Expansion and Control hubs were - tweaking the whole time :( ]. We ended at Rank 37 of 41, and did not get picked for playoffs. We - won 1 award and advanced to the Texas State Championship.

- -

Awards

- -
    -
  • Innovate Award sponsored by RTX 2nd Place
  • -
-
- -
-
-

Texas FTC State Championship - Johnson Division

-

Β·

-

03/21/24 - 03/23/24

-
- -

Description

- -

We got 3 wins and got Rank 12 of 36 going into playoffs. We were - the 1st Team Selected by Rank 4 Team 16226. We got 2 wins in the Semi-Finals and 1 win in the - Finals. We won 1 award, but did not advance to the State Finals nor the World Championship.

- -

Awards

- -
    -
  • Johnson Division Finalist Alliance - 1st Team Selected
  • -
-
- -
-
-

Buc Days 2024 Robotics Rodeo (Off-season Tournament)

-

Β·

-

05/04/24

-
- -

Description

- -

We got 4 wins and were Rank 8 of 41 going into playoffs. We were - the st Team Selected by Rank 3 Team 16458. We got 2 wins in the Semi-Finals and 1 win in the - Finals. We won 1 award.

- -

Awards

- -
    -
  • Finalist Alliance - 1st Team Selected
  • + {% for award in comp.awards.split('|') %} +
  • {{ award }}
  • + {% endfor %}
+ {% endif %}
+ {% endfor %} + {% endfor %}
- -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/templates/contact copy.html b/templates/contact copy.html deleted file mode 100644 index 0c98524..0000000 --- a/templates/contact copy.html +++ /dev/null @@ -1,41 +0,0 @@ -{% extends "base.html" %} - -{% block title %}Communication{% endblock %} - -{% block content %} - - -
-
-
-

CONTACT US

-
-
-

you can find us on several platforms.

-
- - -
- - -
- -
- - -
- -
-
- - -{% endblock %} \ No newline at end of file diff --git a/templates/contact.html b/templates/contact.html index 04bdaf6..960a86f 100644 --- a/templates/contact.html +++ b/templates/contact.html @@ -4,8 +4,8 @@ {% block content %} - -
+
+

Contact


🌐 you can find us on several platforms! 🌐

@@ -29,6 +29,5 @@
- {% endblock %} \ No newline at end of file diff --git a/templates/contributors.html b/templates/contributors.html index 8471efc..7cc2c56 100644 --- a/templates/contributors.html +++ b/templates/contributors.html @@ -4,101 +4,66 @@ {% block content %} - -
-
-
-

Our stats

-
- -
-
-

2

-

year of robotics

-
-
-

2

-

awards this season

-
-
-

7

-

total awards won

-
-
-

9

-

team members

-
-
+
+ + +
-

Our mentor and coach

+

Our mentors and coaches


-

❀️ meet our amazing coach and mentor! ❀️

+

❀️ meet our amazing coach and mentors! ❀️

+ {% for mentor in mentors %}
- -

Mr. Kruger

-

COACH / MENTOR

+ {% if mentor.image_path %} + + {% endif %} +

{{ mentor.name }}

+

{{ mentor.role }}

+ {% endfor %}

Our team

-
+

⭐ meet our amazing team! ⭐

- + {% for member in members %}
- -

Samuel

-

HARDWARE

+ {% if member.image_path %} + + {% endif %} +

{{ member.name }}

+

{{ member.role }}

-
- -

Anish

-

HARDWARE

-
-
- -

Daniel

-

HARDWARE

-
-
- -

Stephen

-

HARDWARE

-
-
- -

Keshav

-

SOFTWARE

-
-
- -

Sujay

-

SOFTWARE

-
-
- -

Abhiram

-

SOFTWARE

-
-
- -

Caitlin

-

ALUMNI; HARDWARE

-
-
- -

Krith

-

OUTREACH

-
- + {% endfor %}
- {% endblock %} \ No newline at end of file diff --git a/templates/home.html b/templates/home.html index 7cf9503..6f7c475 100644 --- a/templates/home.html +++ b/templates/home.html @@ -4,90 +4,108 @@ {% block content %} - -
-
- -
+ +
+
+
+ + +
+ +
-

About

-
+
-

We are Technical Turbulence.

-
-
- -
-

Team 23344, Technical Turbulence, is a community-based returning team situated around Plano and Frisco Texas, comprising nine members primarily consisting of sophomores from different high schools. Our rookie year was 2023 and we hope to learn much more in the years to come. We want to grow our numbers and increase our outreach towards different communities so we can be the best that we can.

-
-
- -

Since we are designated as veterans this year, our members bring substantial experience from their involvement with FTC 12900 Quantum Claw and 22201 The Edge Robotics. Operating from our dedicated garage workshop, we uphold the fundamental values of FIRST, integrating Gracious Professionalism into our daily endeavors.

-
-
-

Our stats

-
- -
-
-

2

-

year of robotics

-
-
-

2

-

awards this season

-
-
-

7

-

total awards won

+
+ +
+
+

πŸ‘‹ We are Technical Turbulence.

+

Team 23344, Technical Turbulence, is a community-based returning team situated + around Plano and Frisco Texas, comprising nine members primarily consisting of sophomores from + different high schools. Our rookie year was 2023 and we hope to learn much more in the years to + come. We want to grow our numbers and increase our outreach towards different communities so we can + be the best that we can.

+

Since we are designated as veterans this year, our members bring substantial + experience from their involvement with FTC 12900 Quantum Claw and 22201 The Edge Robotics. Operating + from our dedicated garage workshop, we uphold the fundamental values of FIRST, integrating Gracious + Professionalism into our daily endeavors.

+
+

Our stats

+
- -
+
+ {% for stat in stats %} +
+

{{ stat.value }}

+

{{ stat.label }}

+
+ {% endfor %} +
+ + +

Our mission


-

✊ One team, one goal.

-

We aim to show individuals that with teamwork, creativity, and persistence, anyone can design, build, and code robots. Our mission is part of an outreach initiative to guide young minds toward STEM and skill-building opportunities in robotics and technology. Through this unifying goal, we want to empower young students to learn, grow, and join FTC in the future.

+

✊ One team, one goal.

+

We aim to show individuals that with teamwork, creativity, and persistence, anyone + can design, build, and code robots. Our mission is part of an outreach initiative to guide young minds + toward STEM and skill-building opportunities in robotics and technology. Through this unifying goal, we + want to empower young students to learn, grow, and join FTC in the future.

- +
- - -

Sponsors

-
+ + +

Sponsors

+
-

We, FTC team Technical Turbulence, want to inspire the next generation by sharing our journey in robotics and the exciting world of FTC. By demonstrating our robot and showcasing the engineering behind it, we hope to spark curiosity in young minds about STEM fields. -

+

❀️ We couldn't have done it without...

+ +

We, FTC team Technical Turbulence, want to inspire the next generation by sharing our + journey in robotics and the exciting world of FTC. By showcasing the + engineering behind our robot, and with the help of our sponsors, we hope to spark curiosity in young minds about STEM fields. +

-

❀️ And we couldn't have done it without...

- -
+
-
+ +
-
- -
+
+ +
-
- +
+ + {% endblock %} \ No newline at end of file diff --git a/templates/robots-storm.html b/templates/robots-storm.html new file mode 100644 index 0000000..3d3b7c8 --- /dev/null +++ b/templates/robots-storm.html @@ -0,0 +1,14 @@ +{% extends "base.html" %} + +{% block title %}Technical Turbulence - Home{% endblock %} + +{% block content %} + + +
+

Storm

+
+
+ + +{% endblock %} \ No newline at end of file diff --git a/templates/robots.html b/templates/robots.html new file mode 100644 index 0000000..9e3e7ba --- /dev/null +++ b/templates/robots.html @@ -0,0 +1,41 @@ +{% extends "base.html" %} + +{% block title %}Technical Turbulence - Home{% endblock %} + +{% block content %} + + +
+

Robots

+
+

πŸ€– we've built and designed several robots. πŸ€–

+ + + + +{% endblock %} \ No newline at end of file diff --git a/templates/sponsors.html b/templates/sponsors.html index 404735f..13ce64b 100644 --- a/templates/sponsors.html +++ b/templates/sponsors.html @@ -4,38 +4,24 @@ {% block content %} - -
+
+

Sponsors


❀️ companies of various sizes sponsor our initiatives. ❀️

- -{% endblock %} \ No newline at end of file +{% endblock %}