diff --git a/corpus/corpus/decorators.py b/corpus/corpus/decorators.py index c2d99ba2..2a2c2b00 100644 --- a/corpus/corpus/decorators.py +++ b/corpus/corpus/decorators.py @@ -1,7 +1,13 @@ +from datetime import datetime +from functools import wraps +from zoneinfo import ZoneInfo + from accounts.models import ExecutiveMember from config.models import ModuleConfiguration from django.contrib import messages from django.shortcuts import redirect +from django.shortcuts import render +from django.utils import timezone def module_enabled(module_name): @@ -77,11 +83,14 @@ def wrapper(request, *args, **kwargs): return decorator + def ensure_view_current_envision(): def decorator(view_func): @wraps(view_func) def wrapper(request, *args, **kwargs): - config = ModuleConfiguration.objects.get(module_name="virtual_expo").module_config + config = ModuleConfiguration.objects.get( + module_name="virtual_expo" + ).module_config try: ExecutiveMember.objects.get(user=request.user.id) @@ -90,8 +99,40 @@ def wrapper(request, *args, **kwargs): exec_member = False can_view_current_envision = exec_member or config.get( - "view_current_envision") - kwargs['can_view_current_envision '] = can_view_current_envision + "view_current_envision" + ) + kwargs["can_view_current_envision"] = can_view_current_envision return view_func(request, *args, **kwargs) + return wrapper + + return decorator + + +def event_time_gate( + start_time, + end_time=None, + pre_template="tlm/403.html", + post_template="tlm/event_ended.html", + context=None, +): + def decorator(view_func): + @wraps(view_func) + def wrapper(request, *args, **kwargs): + now = timezone.now() + ctx = context or {} + + # Before launch + if now < start_time: + return render(request, pre_template, ctx, status=403) + + # After event end + if end_time and now > end_time: + return render(request, post_template, ctx, status=403) + + # During event + return view_func(request, *args, **kwargs) + + return wrapper + return decorator diff --git a/corpus/corpus/settings.py b/corpus/corpus/settings.py index a61b776e..39da39a9 100644 --- a/corpus/corpus/settings.py +++ b/corpus/corpus/settings.py @@ -201,14 +201,14 @@ # Celery Settings -CELERY_BROKER_URL = 'redis://redis:6379/0' +CELERY_BROKER_URL = "redis://redis:6379/0" # CELERY_RESULT_BACKEND = 'redis://redis:6379/0' # CELERY_BROKER_URL="redis://default:AZZ-AAIjcDExZTljY2M1NTI5MmU0OGMxOGUzMDYwNmRlZjhkZGRjZXAxMA@divine-puma-38526.upstash.io:6379" # CELERY_RESULT_BACKEND = "redis://default:AZZ-AAIjcDExZTljY2M1NTI5MmU0OGMxOGUzMDYwNmRlZjhkZGRjZXAxMA@divine-puma-38526.upstash.io:6379" -CELERY_TIMEZONE='UTC' -CELERY_ACCEPT_CONTENT=['json'] -CELERY_TASK_SERIALIZER = 'json' +CELERY_TIMEZONE = "UTC" +CELERY_ACCEPT_CONTENT = ["json"] +CELERY_TASK_SERIALIZER = "json" USE_TAILWIND_CDN_LINK = os.getenv("LIVECYCLE") is not None diff --git a/corpus/templates/static/css/tlm.css b/corpus/templates/static/css/tlm.css index 96ccc7b6..5b31e272 100644 --- a/corpus/templates/static/css/tlm.css +++ b/corpus/templates/static/css/tlm.css @@ -513,3 +513,107 @@ body { animation: none !important; } } + + +/* ═══════════════════════════════════════════════════════════════════ + TRACK CARDS (landing page) + All selectors are scoped to .tlm-track-card so these rules only + apply inside a card and never leak to the hero or other sections. + ═══════════════════════════════════════════════════════════════════ */ + +/* Card shell — portrait 3:4, clips everything inside */ +.tlm-track-card { + position: relative; + overflow: hidden; /* clips img zoom AND any text that would escape */ + cursor: pointer; + border: 2px solid #a07830; + box-shadow: 4px 4px 0 #3a2d10; + flex-shrink: 0; + /* Default size used by home.html — landing.html overrides with aspect-ratio */ + width: 180px; + height: 240px; +} + +/* Image — sepia tint at rest, smooth transition for hover */ +.tlm-track-card img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; + filter: sepia(55%) brightness(0.85) contrast(1.05); + /* Smooth zoom + desaturate on hover */ + transition: transform 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94), + filter 0.5s ease; + transform-origin: center center; +} + +/* Hover: smooth zoom-in + full greyscale */ +.tlm-track-card:hover img { + transform: scale(1.1); + filter: grayscale(100%) brightness(0.72) contrast(1.15); +} + +/* Dark vignette overlay — left strip + bottom fade. + Scoped to .tlm-track-card so it never affects other elements. */ +.tlm-track-card .tlm-track-overlay { + position: absolute; + inset: 0; + pointer-events: none; + z-index: 1; + background: + linear-gradient(to right, rgba(10, 8, 2, 0.55) 0%, transparent 38%), + linear-gradient(to top, rgba(10, 8, 2, 0.60) 0%, transparent 38%); +} + +/* Name strip — a 22 px column flush to the left edge, full card height. + Uses writing-mode so text flows vertically without transform hacks, + which means the text is always contained within the strip's box and + overflow:hidden on .tlm-track-card clips it safely. */ +.tlm-track-card .tlm-track-name-strip { + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: 22px; + z-index: 2; + pointer-events: none; + /* Stack children bottom-to-top, centred in the strip */ + display: flex; + align-items: center; + justify-content: center; + writing-mode: vertical-rl; + transform: rotate(180deg); /* flip: text now reads bottom → top (CCW) */ + padding: 0.5rem 0; +} + +/* The name text itself — static inside the strip, no absolute positioning */ +.tlm-track-card .tlm-track-name { + position: static; + transform: none; + left: auto; + top: auto; + margin-left: 0; + font-family: 'JetBrains Mono', 'Courier New', monospace; + font-size: 0.72rem; + font-weight: 700; + color: #ffffff; + letter-spacing: 0.1em; + text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.9); + white-space: nowrap; +} + +/* Points — bottom-right corner, always inside the card */ +.tlm-track-card .tlm-track-pts { + position: absolute; + bottom: 0.45rem; + right: 0.55rem; + left: auto; + top: auto; + z-index: 2; + font-family: 'JetBrains Mono', 'Courier New', monospace; + font-size: 0.72rem; + font-weight: 700; + color: #ffffff; + letter-spacing: 0.08em; + text-shadow: 1px 1px 4px rgba(0, 0, 0, 0.95); +} \ No newline at end of file diff --git a/corpus/templates/static/img/tlm/track1.jpeg b/corpus/templates/static/img/tlm/track1.jpeg new file mode 100644 index 00000000..c061c69e Binary files /dev/null and b/corpus/templates/static/img/tlm/track1.jpeg differ diff --git a/corpus/templates/static/img/tlm/track2.jpeg b/corpus/templates/static/img/tlm/track2.jpeg new file mode 100644 index 00000000..df7af540 Binary files /dev/null and b/corpus/templates/static/img/tlm/track2.jpeg differ diff --git a/corpus/templates/static/img/tlm/track3.png b/corpus/templates/static/img/tlm/track3.png new file mode 100644 index 00000000..d5240730 Binary files /dev/null and b/corpus/templates/static/img/tlm/track3.png differ diff --git a/corpus/templates/static/img/tlm/track4.jpeg b/corpus/templates/static/img/tlm/track4.jpeg new file mode 100644 index 00000000..602a33a7 Binary files /dev/null and b/corpus/templates/static/img/tlm/track4.jpeg differ diff --git a/corpus/templates/tlm/403.html b/corpus/templates/tlm/403.html new file mode 100644 index 00000000..3294d507 --- /dev/null +++ b/corpus/templates/tlm/403.html @@ -0,0 +1,46 @@ +{% extends 'tlm/base.html' %} +{% load static %} + +{% block title %}Access Denied{% endblock %} + +{% block style %} + +{# Set theme synchronously — same pattern as landing.html #} + + + + + + + + + + +{% endblock %} + + +{% block content %} + +
+
+ Error +

403

+

Access Denied

+

The mission has not begun yet.

+

Return on Mar 20, 2026 at 18:00 IST.

+
+
+ +{% endblock %} \ No newline at end of file diff --git a/corpus/templates/tlm/403_end.html b/corpus/templates/tlm/403_end.html new file mode 100644 index 00000000..3dac338e --- /dev/null +++ b/corpus/templates/tlm/403_end.html @@ -0,0 +1,45 @@ +{% extends 'tlm/base.html' %} +{% load static %} + +{% block title %}Access Denied{% endblock %} + +{% block style %} + +{# Set theme synchronously — same pattern as landing.html #} + + + + + + + + + + +{% endblock %} + + +{% block content %} + +
+
+ Error +

403

+

Access Denied

+

The mission has ended.

+
+
+ +{% endblock %} diff --git a/corpus/templates/tlm/base.html b/corpus/templates/tlm/base.html index 0d9a87d0..28e1a9b4 100644 --- a/corpus/templates/tlm/base.html +++ b/corpus/templates/tlm/base.html @@ -9,6 +9,12 @@ {% block script %} {{ block.super }} {% endblock %} + + + + diff --git a/corpus/templates/tlm/home.html b/corpus/templates/tlm/home.html index 0b505356..f226b191 100644 --- a/corpus/templates/tlm/home.html +++ b/corpus/templates/tlm/home.html @@ -73,9 +73,34 @@

LAZARUS MISSIONS

alt="IEEE NITK Student Branch" class="tlm-hero-org-logo"> +
+ +

+ Join our Discord channel +

+ + + + Discord + + Join Discord + +
{# Register button - REPLACE # WITH REAL REGISTRATION LINK #} - LAZARUS MISSIONS Begin Mission. -{% comment %} - {# Doomsday countdown #} -
- -

- Doomsday Timer -

- - {# Inline monospace countdown. #} - {# JS updates the four number spans (tlm-days, tlm-hours, #} - {# tlm-minutes, tlm-seconds) - the same IDs used previously, #} - {# so the countdown script requires zero changes. #} -
- --d - --h - --m - --s -
- - {# Shown after countdown hits zero, hidden by default #} - - -
{% endcomment %} - @@ -406,6 +405,8 @@

Contact Us

Swaraj + + swarajsingh.231cs158@nitk.edu.in +91 94497 87896 @@ -414,6 +415,8 @@

Contact Us

Jayanth + + hsjayanth.231it024@nitk.edu.in +91 74830 17553 @@ -565,26 +568,33 @@

Contact Us

}()); {% endblock %} diff --git a/corpus/templates/tlm/landing.html b/corpus/templates/tlm/landing.html new file mode 100644 index 00000000..90be0c6e --- /dev/null +++ b/corpus/templates/tlm/landing.html @@ -0,0 +1,390 @@ +{% extends 'tlm/base.html' %} +{% load static %} + +{% block title %}The Lazarus Missions{% endblock %} + +{% block style %} + + + + + + + + + + + {# TLM stylesheet — loaded last so it wins the cascade over inline styles #} + + + {% endblock %} + + + {% block content %} + +
+ +
+ + {# Event logo — PLACEHOLDER: file at templates/static/img/tlm/tlm-logo.svg #} + + +
+ THE +

LAZARUS MISSIONS

+
+ +

Nuclear Winter

+ +
+
+ +
+ +

Tracks

+ + {# ── Top row ── #} +
+ + {# PLACEHOLDER: replace src with {% static 'img/tlm/track1.jpg' %} #} +
+ Track 1 +
+
Vardø Signal Array
+ 150 Pts +
+ + {# PLACEHOLDER: replace src with {% static 'img/tlm/track2.jpg' %} #} +
+ Track 2 +
+
Kolyma Transit Grid
+ 250 Pts +
+ + {# PLACEHOLDER: replace src with {% static 'img/tlm/track3.jpg' %} #} +
+ Track 3 +
+
Semipalatinsk Archive
+ 300 Pts +
+ +
+ + {# ── Bottom row: 1 card centred ── #} +
+ + {# PLACEHOLDER: replace src with {% static 'img/tlm/track4.jpg' %} #} +
+ Track 4 +
+
Spiti Exclusion Zone
+ 300 Pts +
+ +
+ +
+ +
+ +

Doomsday Timer

+ +
+ 00d + 00h + 00m + 00s +
+ + + +
+ + {# ───────────── MODALS ───────────── #} + + {% include 'tlm/modal_card.html' with id="track1-modal" title="Vardø Signal Array, Norway" points=150 mission_brief="Vardø was never meant to be noticed. It sat at the edge of the world, listening more than speaking, tracking patterns no one else had the patience to observe. The systems there didn’t predict anything, they simply recorded, weighed, and passed things along. For years, it worked quietly, assigning meaning to events that looked identical to the untrained eye. When the rest of the network failed, Vardø didn’t shut down. It kept receiving signals long after there was no one left to interpret them. Some say it still does, continuing to measure importance in a world that no longer responds." kaggle_link="https://www.kaggle.com/t/248c7683efbd42689139964f9138dd30" %} + + {% include 'tlm/modal_card.html' with id="track2-modal" title="Kolyma Transit Grid, Russia" points=250 mission_brief="Kolyma was always a place people passed through, not somewhere they stayed. The roads cut through frozen ground that shifted beneath them, connecting settlements that depended on movement more than stability. In its final years, it became a testing ground for systems that could navigate without human presence. Vehicles moved, observed, and recorded everything around them, building a memory of a world that still functioned. Now the roads are buried, the structures partially visible, and the routes no longer lead anywhere. But the recordings remain, fragments of a landscape that could once be understood in full." kaggle_link="https://www.kaggle.com/t/22bbf9d7bf1b4a8ca221751d1cfa81f6" %} + + {% include 'tlm/modal_card.html' with id="track3-modal" title="Semipalatinsk Clinical Archive, Kazakhstan" points=300 mission_brief="The archive was not designed to last. It was meant to be part of a system, one that stored, updated, and eventually discarded records as patients moved through it. When that system disappeared, the archive remained, sealed and untouched. Inside are histories written without the expectation of permanence: diagnoses, treatments, observations, all recorded as part of routine care. Some entries are precise. Others contradict themselves. Time moves forward, then back, then forward again. There is no single narrative, only fragments that overlap and diverge. What remains is not a record of medicine, but of lives that were never meant to be reconstructed this way." kaggle_link="https://www.kaggle.com/t/c9f2e331e90f489e9296feb96a5069b6" %} + + {% include 'tlm/modal_card.html' with id="track4-modal" title="Spiti Exclusion Zone, India" points=300 mission_brief="Spiti was always defined by its limits. The land allowed habitation, but only under strict conditions, i.e., timing, supply, and weather had to align. When those conditions stopped holding, the settlements emptied faster than they had been built. What remains is a landscape that looks unchanged at a distance, but no longer supports what once existed within it. Structures stand, but not for use. Paths remain, but lead nowhere. The environment hasn’t grown harsher; it has simply stopped forgiving mistakes. Movement through this region is still possible, but only for things that do not depend on return." kaggle_link="https://www.kaggle.com/t/b7384854471845bfb3a44a78f599c857" %} + + {% endblock %} + + {% block script %} + {{ block.super }} + + + + + + {% endblock %} diff --git a/corpus/templates/tlm/modal_card.html b/corpus/templates/tlm/modal_card.html new file mode 100644 index 00000000..8bd60f6a --- /dev/null +++ b/corpus/templates/tlm/modal_card.html @@ -0,0 +1,70 @@ + diff --git a/corpus/tlm/urls.py b/corpus/tlm/urls.py index a7db407b..488edcb9 100644 --- a/corpus/tlm/urls.py +++ b/corpus/tlm/urls.py @@ -4,4 +4,5 @@ urlpatterns = [ path("", views.home, name="tlm_home"), + path("landing/", views.landing, name="tlm_landing"), ] diff --git a/corpus/tlm/views.py b/corpus/tlm/views.py index 9f433531..52fdb2f6 100644 --- a/corpus/tlm/views.py +++ b/corpus/tlm/views.py @@ -1,5 +1,30 @@ +from datetime import datetime +from zoneinfo import ZoneInfo + from django.shortcuts import render +from corpus.decorators import event_time_gate + def home(request): return render(request, "tlm/home.html") + + +tz = ZoneInfo("Asia/Kolkata") + +launch_time = datetime(2026, 3, 20, 18, 0, 0, tzinfo=tz) +end_time = datetime(2026, 3, 24, 18, 0, 0, tzinfo=tz) + + +@event_time_gate( + start_time=launch_time, + end_time=end_time, + pre_template="tlm/403.html", + post_template="tlm/403_end.html", + context={"message": "Event access restricted"}, +) +def landing(request): + context = { + "countdown_target": int(end_time.timestamp() * 1000), + } + return render(request, "tlm/landing.html", context)