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 b67868f..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,13 @@ 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/') @@ -44,5 +83,265 @@ def robots(type=None): 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 8a577a9..8428e06 100644 --- a/static/css/styles.css +++ b/static/css/styles.css @@ -1,555 +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; } -.about-sec-img img:hover { - transform: scale(1.1); - transition: 0.5s; +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: 1000000000000000000000000000000000000000000000000000000000000000000000; - 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; - z-index: 1000000000000000000000000000000000000000000000000000000000000000000000; } .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; - z-index: 1000000000000000000000000000000000000000000000000000000000000000000000; -} - -.nav-container { - display: flex; - justify-content: center; - align-items: center; - list-style: none; - gap: 1%; - padding: 1%; - width: 85vw; - 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; - } - - .buttons { - top: 80%!important; - } - - .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: 85% !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%; -} - -#robo { - margin-top: 15%; -} - -#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%; -} - -.heading2 { - font-size: 1.5rem; - color: rgb(250, 250, 250); - font-family: sans-serif; - padding-left: 10%; - padding-top: 5%; -} - -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; - opacity: 1; -} - -.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: #616161 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 { - margin: 0; - padding-top: 8vh; - 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; -} - -.buttons { - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - gap: 3%; - width: 20%; - position: absolute; - top: 73%; - z-index: 1000000; -} - -.buttons button { - height: 65px; - width: 65px; - box-sizing: border-box; - color: #616161; - background-color: #0f0f0f; - border: 0.25vh solid #616161; - text-align: center; - border-radius: 10px; - transition: 0.5s; - cursor: pointer; -} - -.buttons button:hover { - color: white; + position: relative; } .bg img { width: 100%; height: 100%; - /* object-fit: cover; - overflow: hidden; */ - transition: .5s; - filter: brightness(1.0); + transition: all 0.3s ease; + filter: brightness(0.8); } .bg::after { @@ -558,299 +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 #616161 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: #616161 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: 1; -} - -.sponsors-container { - width: 92%; - height: auto; - display: flex; - flex-wrap: wrap; - margin-left: 4%; -} - -#sponnnnn { - background-color: #0f0f0f; - border: 0.25vh solid #616161; - padding: 5%; - box-sizing: border-box; - border-radius: 10px; -} - -.card-sponsors:hover img { - transform: scale(1.2); - opacity: 1; -} - -.contributors { - margin: 0; - padding: 0; - margin-bottom: 5%; - 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 #616161 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; } @@ -858,389 +482,366 @@ hr { width: 100%; height: 100%; object-fit: cover; - margin: 0; - padding: 0; - filter: brightness(.75); - border-radius: 7px; - 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: 3%; - gap: 3%; - background-color: #0f0f0f; - border: #616161 0.25vh solid; - /* 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; */ - margin-bottom: 3%; - border-radius: 10px; -} - -@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; - position: relative; - overflow: hidden; + 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; */ - padding-left: 5%; - position: relative; - background-color: #0F0F0F; - padding-top: 2%; - padding-bottom: 2%; - padding-right: 2%; - box-sizing: border-box; - border-radius: 10px; - border: #616161 0.25vh solid; - margin-top: 3%; + 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, @@ -1250,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); @@ -1268,128 +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%; -} - -span.robots::before, -span.robots::after { - content: "πŸ€–"; - font-size: 2.5vh; - top: 14%; - left: 8%; -} - -span.robots::after { - top: revert; - left: revert; - bottom: 75%; - right: 82%; + font-size: 32px; } span.stats::before, span.stats::after { content: "πŸ“Š"; - font-size: 2.5vh; - top: -1%; - 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, @@ -1398,261 +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 { - - width: 100%; - /* Example size, adjust as needed */ - height: 75vh; - overflow: hidden; - transition: .5s; - border: 2px rgb(97, 97, 97) solid; - border-radius: 10px; - margin-bottom: 5%; - /* padding: 2%; */ -} - -.about-section-imgp img { - width: 100%; - transition: .5s; - max-height: 100%; - ; - object-fit: cover; - border-radius: 7px; -} - -.about-section-imgp p { - flex: 1; - text-align: center; - ; -} - -/* @media only screen and (min-width: 850px) { - .about-section-imgp p { - padding-left: 10%; - } - - .about-section-imgp-l p { - padding-right: 10%; - } -} */ - - -.about-section-imgp-l { - display: flex; - align-items: center; - justify-content: center; - padding-left: 10%; - padding-bottom: 1%; - padding-right: 10%; -} - -.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; -} - -/* .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 { - 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%; - } - - .robo-inner { - flex-direction: column !important; - } - - .robo-card { - width: 100% !important; - height: 40vh !important; - } - - .robo-card img { - width: 100% !important; - height: auto !important; - } - - .contact-content { - background-image: none; - } - - - - .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; - } - - .about-section-imgp-l { - flex-direction: column-reverse; - } - - .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%; - } - - .about-section-imgp p, - .about-section-imgp-l p { - flex: 1; - padding: 0; - margin: 0; - padding-left: 0; - padding-right: 0; - } - - .about-section-imgp img:hover, - .about-section-imgp-l img:hover { - transform: scale(1.025); - } -} - -#robo { - margin-top: 5%; -} - .robots-container { width: 100%; - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - margin-top: 3.5%; - + margin: 48px 0; } .robots-inner { - width: 84%; - gap: 3%; - display: flex; - flex-direction: row; - align-items: center; - justify-content: center; - flex-wrap: wrap; -} - -.robo-card img { - height: 100%; - transition: .1s; - filter: brightness(0.75); -} - -.robo-card img:hover { - transform: scale(1.025); + display: grid; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + gap: 32px; } .robo-card { - width: 30.333333333333333333%; - 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; + background: var(--gray-800); + border-radius: 16px; + overflow: hidden; + border: 1px solid var(--gray-700); + aspect-ratio: 4/3; + transition: all 0.2s ease; } -/* .robo-card-img::after { - content: ''; - position: absolute; - bottom: 0; - left: 0; - right: 0; - height: 30%; - background: linear-gradient(to top, black, rgba(5, 8, 28, 0)); - pointer-events: none; - z-index: 1; -} */ +.robo-card:hover { + border-color: var(--accent); + transform: translateY(-4px); +} -.robo-card p { - z-index: 10000000000 !important; - color: white; - text-decoration: none; - background: none; +.robo-card img { + width: 100%; + height: 100%; + object-fit: cover; + filter: brightness(0.6); + transition: all 0.3s ease; +} + +.robo-card:hover img { + filter: brightness(0.4); + transform: scale(1.05); } .robo-card-txt { position: absolute; - bottom: 5%; - left: 5%; - margin: 0; - padding: 0; - background: none; - opacity: 0.90; + bottom: 32px; + left: 32px; + z-index: 2; } .robo-card-name { - font-size: 3rem; - padding: 0; + font-size: 32px; margin: 0; - color: white; - font-family: "Orbitron", serif; + color: var(--white); + font-family: var(--font-display); font-weight: 700; } .robo-card-desc { - font-size: 1.2rem; - padding: 0; - margin: 0; - color: white; - font-family: "Orbitron", serif; - font-weight: 700; - opacity: 0.70; + 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; + } + + .competitions-container { + padding-left: 32px !important; + padding-right: 32px !important; + padding-top: 100px !important; + } + + #comp { + margin-bottom: 20px !important; + } + + #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)); + } +} + +@media (max-width: 850px) { + nav { + display: none; + } + + .menu-button { + display: flex; + align-items: center; + justify-content: center; + } + + .footer-text { + font-size: 24px; + bottom: 30px; + left: 30px; + } + + .home-info, + .team-info { + width: 95%; + margin-top: 80px; + } + + .rob, + .contact, + .contributors, + .sponsors { + padding-left: 20px; + padding-right: 20px; + padding-top: 80px; + } + + .stats-cards { + grid-template-columns: 1fr; + } + + .sponsors-container, + .card-container, + .card-container-sponsors { + grid-template-columns: 1fr; + margin: 32px 0; + } + + .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/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/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/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/base.html b/templates/base.html index 7c78067..449ef3e 100644 --- a/templates/base.html +++ b/templates/base.html @@ -30,12 +30,6 @@ - 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 68efb37..7cc2c56 100644 --- a/templates/contributors.html +++ b/templates/contributors.html @@ -4,24 +4,23 @@ {% block content %} - -
-
-
+
+ + +
-

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! ⭐

- -
- -

Samuel

-

HARDWARE

+ {% for member in members %} +
+ {% 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 2168b40..6f7c475 100644 --- a/templates/home.html +++ b/templates/home.html @@ -4,61 +4,51 @@ {% block content %} - - -
-
- -
- - - -
+ +
+
+
+ + +
+
-

About

-
+
-

πŸ‘‹ We are Technical Turbulence.

-
-
- +
+ +
+
+

πŸ‘‹ 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.

-

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 %}
-

2

-

year of robotics

-
-
-

2

-

awards this season

-
-
-

7

-

total awards won

+

{{ stat.value }}

+

{{ stat.label }}

+ {% endfor %}