feat: contact form and more edits

This commit is contained in:
mohamad
2026-05-04 13:54:45 +03:30
parent 3c10f5c5ed
commit 744ef69942
49 changed files with 1073 additions and 179 deletions
+5 -5
View File
@@ -1,6 +1,6 @@
# Tecvico Website # Radiuma Website
Django MVT informational website for **Tecvico Corp** (formerly Visera), showcasing the **ViSERA** medical imaging and radiomics software suite. Django MVT informational website for **Radiuma**, showcasing the **Radiuma** medical imaging and radiomics software suite.
## Tech Stack ## Tech Stack
@@ -45,7 +45,7 @@ tecvico_website/
| URL | View | Description | | URL | View | Description |
|---|---|---| |---|---|---|
| `/` | `HomeView` | Landing page | | `/` | `HomeView` | Landing page |
| `/about/` | `AboutView` | What is Tecvico | | `/about/` | `AboutView` | What is Radiuma |
| `/products/` | `ProductOverviewView` | All main products | | `/products/` | `ProductOverviewView` | All main products |
| `/products/<main-slug>/` | `MainProductDetailView` | Main product + sub-products | | `/products/<main-slug>/` | `MainProductDetailView` | Main product + sub-products |
| `/products/<main-slug>/<sub-slug>/` | `SubProductDetailView` | Sub-product + articles | | `/products/<main-slug>/<sub-slug>/` | `SubProductDetailView` | Sub-product + articles |
@@ -248,7 +248,7 @@ Access Django Admin at `/admin/` with superuser credentials.
The `seed_content` command populates: The `seed_content` command populates:
- **ViSERA** (MainProduct) with 5 sub-products: - **Radiuma** (MainProduct) with 5 sub-products:
- Image Processing - Image Processing
- Radiomics Features - Radiomics Features
- Medical Image Visualization - Medical Image Visualization
@@ -275,4 +275,4 @@ Key CSS custom properties are in `static/css/main.css` under `:root`.
## License ## License
Content is adapted from visera.ca under CC BY-NC-SA. Software code is proprietary to Tecvico Corp. Content is adapted from visera.ca under CC BY-NC-SA. Software code is proprietary to Radiuma.
+28 -28
View File
@@ -6,14 +6,14 @@ from apps.products.models import Article, ArticleSection, MainProduct, SubProduc
MAIN_PRODUCTS = [ MAIN_PRODUCTS = [
{ {
"name": "ViSERA", "name": "Radiuma",
"slug": "visera", "slug": "radiuma",
"short_description": "Visualized & Standardized Environment for Radiomics Analysis", "short_description": "Visualized & Standardized Environment for Radiomics Analysis",
"description": ( "description": (
"ViSERA is a free, open-source software specialized for visualization, " "Radiuma is a free, open-source software specialized for visualization, "
"processing, segmentation, registration, fusion and analysis of medical and " "processing, segmentation, registration, fusion and analysis of medical and "
"biomedical images, including radiomics and machine learning analysis. " "biomedical images, including radiomics and machine learning analysis. "
"ViSERA is a major, entirely-revamped upgrade to the original SERA " "Radiuma is a major, entirely-revamped upgrade to the original SERA "
"(Matlab-based), now built on Python for broader accessibility and community " "(Matlab-based), now built on Python for broader accessibility and community "
"contribution. It enables standardized and reproducible radiomic feature " "contribution. It enables standardized and reproducible radiomic feature "
"extraction in compliance with the Image Biomarker Standardization Initiative " "extraction in compliance with the Image Biomarker Standardization Initiative "
@@ -28,7 +28,7 @@ MAIN_PRODUCTS = [
"description": ( "description": (
"Advanced image processing capabilities including standardized filtering " "Advanced image processing capabilities including standardized filtering "
"techniques compliant with IBSI 2.0, image registration, fusion, and " "techniques compliant with IBSI 2.0, image registration, fusion, and "
"Standardized Uptake Value (SUV) conversion. ViSERA employs popular " "Standardized Uptake Value (SUV) conversion. Radiuma employs popular "
"image processing algorithms to create end-to-end standardized workflows " "image processing algorithms to create end-to-end standardized workflows "
"for consistent, reproducible research outcomes." "for consistent, reproducible research outcomes."
), ),
@@ -37,7 +37,7 @@ MAIN_PRODUCTS = [
{ {
"title": "Image Filtering Techniques", "title": "Image Filtering Techniques",
"description": ( "description": (
"ViSERA implements a comprehensive set of image filtering techniques " "Radiuma implements a comprehensive set of image filtering techniques "
"fully standardized against the Image Biomarker Standardization " "fully standardized against the Image Biomarker Standardization "
"Initiative (IBSI) phase 2. These filters enable reproducible " "Initiative (IBSI) phase 2. These filters enable reproducible "
"preprocessing across institutions and studies." "preprocessing across institutions and studies."
@@ -50,13 +50,13 @@ MAIN_PRODUCTS = [
"value": "Mean, Gaussian, Laplacian of Gaussian (LoG), Laws kernels, Gabor, Wavelets (PyWavelets), Log-Sigma", "value": "Mean, Gaussian, Laplacian of Gaussian (LoG), Laws kernels, Gabor, Wavelets (PyWavelets), Log-Sigma",
"order": 2, "order": 2,
}, },
{"title": "Author", "value": "Tecvico Corp R&D Team", "order": 3}, {"title": "Author", "value": "Radiuma R&D Team", "order": 3},
], ],
}, },
{ {
"title": "Image Registration & Fusion", "title": "Image Registration & Fusion",
"description": ( "description": (
"ViSERA provides robust image registration and fusion methods, " "Radiuma provides robust image registration and fusion methods, "
"enabling multi-modal image alignment for PET/CT, PET/MRI, and " "enabling multi-modal image alignment for PET/CT, PET/MRI, and "
"other combined modality studies. Standardized Uptake Value (SUV) " "other combined modality studies. Standardized Uptake Value (SUV) "
"conversion is also supported." "conversion is also supported."
@@ -75,7 +75,7 @@ MAIN_PRODUCTS = [
"slug": "radiomics-features", "slug": "radiomics-features",
"short_description": "IBSI 1.0 compliant handcrafted radiomic feature extraction", "short_description": "IBSI 1.0 compliant handcrafted radiomic feature extraction",
"description": ( "description": (
"ViSERA provides comprehensive handcrafted radiomic feature extraction " "Radiuma provides comprehensive handcrafted radiomic feature extraction "
"fully standardized by the Image Biomarker Standardization Initiative " "fully standardized by the Image Biomarker Standardization Initiative "
"(IBSI 1.0). Features are computed from segmented regions of interest " "(IBSI 1.0). Features are computed from segmented regions of interest "
"across multiple image modalities, enabling reproducible quantitative " "across multiple image modalities, enabling reproducible quantitative "
@@ -86,7 +86,7 @@ MAIN_PRODUCTS = [
{ {
"title": "IBSI Compliant Feature Extraction", "title": "IBSI Compliant Feature Extraction",
"description": ( "description": (
"ViSERA computes a comprehensive set of radiomic features " "Radiuma computes a comprehensive set of radiomic features "
"covering all IBSI 1.0 feature classes. Features are extracted " "covering all IBSI 1.0 feature classes. Features are extracted "
"from segmented Regions of Interest (ROIs) and are fully " "from segmented Regions of Interest (ROIs) and are fully "
"reproducible across different platforms and institutions." "reproducible across different platforms and institutions."
@@ -110,7 +110,7 @@ MAIN_PRODUCTS = [
"slug": "medical-image-visualization", "slug": "medical-image-visualization",
"short_description": "Professional multi-modality medical image viewer", "short_description": "Professional multi-modality medical image viewer",
"description": ( "description": (
"ViSERA includes a professional medical image viewer that supports " "Radiuma includes a professional medical image viewer that supports "
"multiple imaging modalities and file formats. The viewer provides " "multiple imaging modalities and file formats. The viewer provides "
"comfortable, intuitive controls for slice navigation, windowing, " "comfortable, intuitive controls for slice navigation, windowing, "
"zoom, and annotation, suitable for radiation oncologists, radiologists, " "zoom, and annotation, suitable for radiation oncologists, radiologists, "
@@ -140,7 +140,7 @@ MAIN_PRODUCTS = [
"slug": "format-conversion", "slug": "format-conversion",
"short_description": "Professional converter for medical imaging file formats", "short_description": "Professional converter for medical imaging file formats",
"description": ( "description": (
"ViSERA provides a professional image format converter supporting all " "Radiuma provides a professional image format converter supporting all "
"major medical imaging standards. Seamlessly convert between DICOM, " "major medical imaging standards. Seamlessly convert between DICOM, "
"NIFTI, NRRD, MHA, and other formats without loss of spatial metadata " "NIFTI, NRRD, MHA, and other formats without loss of spatial metadata "
"or patient information integrity." "or patient information integrity."
@@ -169,7 +169,7 @@ MAIN_PRODUCTS = [
"slug": "workflow-management", "slug": "workflow-management",
"short_description": "Reproducible research workflow creation and sharing", "short_description": "Reproducible research workflow creation and sharing",
"description": ( "description": (
"ViSERA's workflow management system allows researchers to design, save, " "Radiuma's workflow management system allows researchers to design, save, "
"share, and reuse analysis pipelines. Workflows connect individual " "share, and reuse analysis pipelines. Workflows connect individual "
"processing steps — from image loading and preprocessing to feature " "processing steps — from image loading and preprocessing to feature "
"extraction and machine learning — into reproducible, shareable sequences " "extraction and machine learning — into reproducible, shareable sequences "
@@ -199,9 +199,9 @@ MAIN_PRODUCTS = [
FAQ_ENTRIES = [ FAQ_ENTRIES = [
{ {
"question": "What is the ViSERA license?", "question": "What is the Radiuma license?",
"answer": ( "answer": (
"ViSERA is free and open-source for research purposes.\n\n" "Radiuma is free and open-source for research purposes.\n\n"
"License: CC BY-NC-SA (Creative Commons Attribution-NonCommercial-ShareAlike). " "License: CC BY-NC-SA (Creative Commons Attribution-NonCommercial-ShareAlike). "
"This means you may use, share, and adapt the software for non-commercial " "This means you may use, share, and adapt the software for non-commercial "
"research purposes, provided you give appropriate credit and distribute " "research purposes, provided you give appropriate credit and distribute "
@@ -210,21 +210,21 @@ FAQ_ENTRIES = [
"order": 1, "order": 1,
}, },
{ {
"question": "How do I cite ViSERA in my research?", "question": "How do I cite Radiuma in my research?",
"answer": ( "answer": (
"Please cite the following reference if you publish results obtained with " "Please cite the following reference if you publish results obtained with "
"the help of ViSERA:\n\n" "the help of Radiuma:\n\n"
"M. R. Salmanpour, I. Shiri, M. Hosseinzadeh, H. Zaidi, S. Ashrafinia, " "M. R. Salmanpour, I. Shiri, M. Hosseinzadeh, H. Zaidi, S. Ashrafinia, "
"M. Oveisi, A. Rahmim. ViSERA: Visualized & Standardized Environment for " "M. Oveisi, A. Rahmim. Radiuma: Visualized & Standardized Environment for "
"Radiomics Analysis — A Shareable, Executable, and Reproducible Workflow " "Radiomics Analysis — A Shareable, Executable, and Reproducible Workflow "
"Generator. Proc. IEEE Medical Imaging Conference, 2023." "Generator. Proc. IEEE Medical Imaging Conference, 2023."
), ),
"order": 2, "order": 2,
}, },
{ {
"question": "Which operating systems does ViSERA support?", "question": "Which operating systems does Radiuma support?",
"answer": ( "answer": (
"ViSERA currently fully supports Windows 10 and above (64-bit). " "Radiuma currently fully supports Windows 10 and above (64-bit). "
"New versions to support macOS and Linux systems are under active development " "New versions to support macOS and Linux systems are under active development "
"and coming soon. Follow our Discord or check the Downloads page for updates." "and coming soon. Follow our Discord or check the Downloads page for updates."
), ),
@@ -241,9 +241,9 @@ FAQ_ENTRIES = [
"order": 4, "order": 4,
}, },
{ {
"question": "Is ViSERA suitable for clinical use?", "question": "Is Radiuma suitable for clinical use?",
"answer": ( "answer": (
"ViSERA is designed and intended exclusively for research purposes. " "Radiuma is designed and intended exclusively for research purposes. "
"It is not certified for clinical diagnostic use. Always consult with " "It is not certified for clinical diagnostic use. Always consult with "
"qualified medical professionals for clinical decisions." "qualified medical professionals for clinical decisions."
), ),
@@ -252,7 +252,7 @@ FAQ_ENTRIES = [
{ {
"question": "Where can I get support or report issues?", "question": "Where can I get support or report issues?",
"answer": ( "answer": (
"Support is available via email at support@tecvico.com and through our " "Support is available via email at support@visera.ca and through our "
"community Discord server. For bug reports and feature requests, please " "community Discord server. For bug reports and feature requests, please "
"use the Discord forum or contact us directly by email." "use the Discord forum or contact us directly by email."
), ),
@@ -262,16 +262,16 @@ FAQ_ENTRIES = [
DOWNLOAD_ITEMS = [ DOWNLOAD_ITEMS = [
{ {
"name": "ViSERA Desktop", "name": "Radiuma Desktop",
"platform": "windows", "platform": "windows",
"version": "1.0.0", "version": "1.0.0",
"download_url": "https://github.com/tecvico/visera/releases/latest/download/ViSERA-Setup.exe", "download_url": "https://github.com/radiuma/radiuma/releases/latest/download/Radiuma-Setup.exe",
"description": "Windows 10 and above (64-bit). Installer package.", "description": "Windows 10 and above (64-bit). Installer package.",
"is_active": True, "is_active": True,
"order": 1, "order": 1,
}, },
{ {
"name": "ViSERA Desktop", "name": "Radiuma Desktop",
"platform": "macos", "platform": "macos",
"version": "Coming Soon", "version": "Coming Soon",
"download_url": "#", "download_url": "#",
@@ -280,7 +280,7 @@ DOWNLOAD_ITEMS = [
"order": 2, "order": 2,
}, },
{ {
"name": "ViSERA Desktop", "name": "Radiuma Desktop",
"platform": "linux", "platform": "linux",
"version": "Coming Soon", "version": "Coming Soon",
"download_url": "#", "download_url": "#",
@@ -292,7 +292,7 @@ DOWNLOAD_ITEMS = [
class Command(BaseCommand): class Command(BaseCommand):
help = "Seed the database with initial Tecvico / ViSERA content from visera.ca" help = "Seed the database with initial Radiuma / Radiuma content from visera.ca"
def add_arguments(self, parser): def add_arguments(self, parser):
parser.add_argument( parser.add_argument(
+14 -1
View File
@@ -1,6 +1,19 @@
from django.contrib import admin from django.contrib import admin
from .models import DownloadItem, FAQEntry from .models import ContactSubmission, DownloadItem, FAQEntry
@admin.register(ContactSubmission)
class ContactSubmissionAdmin(admin.ModelAdmin):
list_display = ("name", "title", "email", "submitted_at", "is_read")
list_filter = ("is_read", "submitted_at")
search_fields = ("name", "title", "description", "email")
list_editable = ("is_read",)
readonly_fields = ("name", "title", "description", "email", "submitted_at")
fieldsets = (
(None, {"fields": ("name", "title", "email", "description")}),
("Meta", {"fields": ("submitted_at", "is_read")}),
)
@admin.register(FAQEntry) @admin.register(FAQEntry)
+24
View File
@@ -0,0 +1,24 @@
from django import forms
from .models import ContactSubmission
class ContactForm(forms.ModelForm):
class Meta:
model = ContactSubmission
fields = ["name", "title", "description", "email"]
widgets = {
"name": forms.TextInput(
attrs={"placeholder": "Your full name", "autocomplete": "name"}
),
"title": forms.TextInput(attrs={"placeholder": "Subject / topic"}),
"description": forms.Textarea(
attrs={"placeholder": "Write your message here…", "rows": 5}
),
"email": forms.EmailInput(
attrs={
"placeholder": "your@email.com (optional)",
"autocomplete": "email",
}
),
}
@@ -0,0 +1,30 @@
# Generated by Django 5.0.2 on 2026-05-04 09:13
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pages', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='ContactSubmission',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200)),
('title', models.CharField(max_length=300)),
('description', models.TextField()),
('email', models.EmailField(blank=True, max_length=254)),
('submitted_at', models.DateTimeField(auto_now_add=True)),
('is_read', models.BooleanField(default=False)),
],
options={
'verbose_name': 'Contact Submission',
'verbose_name_plural': 'Contact Submissions',
'ordering': ['-submitted_at'],
},
),
]
+17
View File
@@ -1,6 +1,23 @@
from django.db import models from django.db import models
class ContactSubmission(models.Model):
name = models.CharField(max_length=200)
title = models.CharField(max_length=300)
description = models.TextField()
email = models.EmailField(blank=True)
submitted_at = models.DateTimeField(auto_now_add=True)
is_read = models.BooleanField(default=False)
class Meta:
ordering = ["-submitted_at"]
verbose_name = "Contact Submission"
verbose_name_plural = "Contact Submissions"
def __str__(self):
return f"{self.name}{self.title}"
class FAQEntry(models.Model): class FAQEntry(models.Model):
question = models.CharField(max_length=500) question = models.CharField(max_length=500)
answer = models.TextField() answer = models.TextField()
+2 -2
View File
@@ -27,14 +27,14 @@ class AboutViewTest(TestCase):
class DownloadsViewTest(TestCase): class DownloadsViewTest(TestCase):
def setUp(self): def setUp(self):
DownloadItem.objects.create( DownloadItem.objects.create(
name="ViSERA Desktop", name="Radiuma Desktop",
platform="windows", platform="windows",
version="1.0", version="1.0",
download_url="https://example.com/windows", download_url="https://example.com/windows",
is_active=True, is_active=True,
) )
DownloadItem.objects.create( DownloadItem.objects.create(
name="ViSERA Desktop", name="Radiuma Desktop",
platform="macos", platform="macos",
version="Coming Soon", version="Coming Soon",
download_url="#", download_url="#",
+46 -2
View File
@@ -1,6 +1,12 @@
import random
from django.http import JsonResponse
from django.shortcuts import render
from django.views import View
from django.views.generic import ListView, TemplateView from django.views.generic import ListView, TemplateView
from .models import DownloadItem, FAQEntry from .forms import ContactForm
from .models import ContactSubmission, DownloadItem, FAQEntry
class HomeView(TemplateView): class HomeView(TemplateView):
@@ -36,5 +42,43 @@ class FAQView(ListView):
queryset = FAQEntry.objects.filter(is_active=True) queryset = FAQEntry.objects.filter(is_active=True)
class ContactView(TemplateView): class ContactView(View):
template_name = "pages/contact.html" template_name = "pages/contact.html"
def _new_captcha(self, request):
a, b = random.randint(1, 9), random.randint(1, 9)
request.session["captcha_answer"] = a + b
return f"{a} + {b}"
def get(self, request, *args, **kwargs):
return render(
request,
self.template_name,
{"form": ContactForm(), "captcha_question": self._new_captcha(request)},
)
def post(self, request, *args, **kwargs):
form = ContactForm(request.POST)
expected = request.session.get("captcha_answer")
captcha_question = self._new_captcha(request)
captcha_ok = False
try:
captcha_ok = int(request.POST.get("captcha_answer", "")) == expected
except (ValueError, TypeError):
pass
if form.is_valid() and captcha_ok:
form.save()
return JsonResponse({"success": True})
errors: dict = {}
if not captcha_ok:
errors["captcha"] = ["Incorrect answer — please try again."]
errors.update(
{field: [str(e) for e in errs] for field, errs in form.errors.items()}
)
return JsonResponse(
{"success": False, "errors": errors, "captcha_question": captcha_question},
status=400,
)
+11 -11
View File
@@ -7,8 +7,8 @@ from apps.products.models import Article, ArticleSection, MainProduct, SubProduc
class ProductViewsSetup(TestCase): class ProductViewsSetup(TestCase):
def setUp(self): def setUp(self):
self.main_product = MainProduct.objects.create( self.main_product = MainProduct.objects.create(
name="ViSERA", name="Radiuma",
slug="visera", slug="radiuma",
short_description="Radiomics software", short_description="Radiomics software",
description="Full description.", description="Full description.",
) )
@@ -57,17 +57,17 @@ class ProductOverviewViewTest(ProductViewsSetup):
class MainProductDetailViewTest(ProductViewsSetup): class MainProductDetailViewTest(ProductViewsSetup):
def test_detail_returns_200(self): def test_detail_returns_200(self):
url = reverse("products:main_product_detail", kwargs={"main_slug": "visera"}) url = reverse("products:main_product_detail", kwargs={"main_slug": "radiuma"})
response = self.client.get(url) response = self.client.get(url)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
def test_detail_uses_correct_template(self): def test_detail_uses_correct_template(self):
url = reverse("products:main_product_detail", kwargs={"main_slug": "visera"}) url = reverse("products:main_product_detail", kwargs={"main_slug": "radiuma"})
response = self.client.get(url) response = self.client.get(url)
self.assertTemplateUsed(response, "products/main_detail.html") self.assertTemplateUsed(response, "products/main_detail.html")
def test_detail_contains_product_in_context(self): def test_detail_contains_product_in_context(self):
url = reverse("products:main_product_detail", kwargs={"main_slug": "visera"}) url = reverse("products:main_product_detail", kwargs={"main_slug": "radiuma"})
response = self.client.get(url) response = self.client.get(url)
self.assertEqual(response.context["main_product"], self.main_product) self.assertEqual(response.context["main_product"], self.main_product)
@@ -79,7 +79,7 @@ class MainProductDetailViewTest(ProductViewsSetup):
def test_inactive_product_returns_404(self): def test_inactive_product_returns_404(self):
self.main_product.is_active = False self.main_product.is_active = False
self.main_product.save() self.main_product.save()
url = reverse("products:main_product_detail", kwargs={"main_slug": "visera"}) url = reverse("products:main_product_detail", kwargs={"main_slug": "radiuma"})
response = self.client.get(url) response = self.client.get(url)
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 404)
@@ -88,7 +88,7 @@ class SubProductDetailViewTest(ProductViewsSetup):
def test_sub_detail_returns_200(self): def test_sub_detail_returns_200(self):
url = reverse( url = reverse(
"products:sub_product_detail", "products:sub_product_detail",
kwargs={"main_slug": "visera", "sub_slug": "image-processing"}, kwargs={"main_slug": "radiuma", "sub_slug": "image-processing"},
) )
response = self.client.get(url) response = self.client.get(url)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
@@ -96,7 +96,7 @@ class SubProductDetailViewTest(ProductViewsSetup):
def test_sub_detail_uses_correct_template(self): def test_sub_detail_uses_correct_template(self):
url = reverse( url = reverse(
"products:sub_product_detail", "products:sub_product_detail",
kwargs={"main_slug": "visera", "sub_slug": "image-processing"}, kwargs={"main_slug": "radiuma", "sub_slug": "image-processing"},
) )
response = self.client.get(url) response = self.client.get(url)
self.assertTemplateUsed(response, "products/sub_detail.html") self.assertTemplateUsed(response, "products/sub_detail.html")
@@ -104,7 +104,7 @@ class SubProductDetailViewTest(ProductViewsSetup):
def test_sub_detail_context_keys(self): def test_sub_detail_context_keys(self):
url = reverse( url = reverse(
"products:sub_product_detail", "products:sub_product_detail",
kwargs={"main_slug": "visera", "sub_slug": "image-processing"}, kwargs={"main_slug": "radiuma", "sub_slug": "image-processing"},
) )
response = self.client.get(url) response = self.client.get(url)
self.assertIn("main_product", response.context) self.assertIn("main_product", response.context)
@@ -115,7 +115,7 @@ class SubProductDetailViewTest(ProductViewsSetup):
def test_sub_detail_articles_in_context(self): def test_sub_detail_articles_in_context(self):
url = reverse( url = reverse(
"products:sub_product_detail", "products:sub_product_detail",
kwargs={"main_slug": "visera", "sub_slug": "image-processing"}, kwargs={"main_slug": "radiuma", "sub_slug": "image-processing"},
) )
response = self.client.get(url) response = self.client.get(url)
self.assertIn(self.article, list(response.context["articles"])) self.assertIn(self.article, list(response.context["articles"]))
@@ -123,7 +123,7 @@ class SubProductDetailViewTest(ProductViewsSetup):
def test_nonexistent_sub_slug_returns_404(self): def test_nonexistent_sub_slug_returns_404(self):
url = reverse( url = reverse(
"products:sub_product_detail", "products:sub_product_detail",
kwargs={"main_slug": "visera", "sub_slug": "does-not-exist"}, kwargs={"main_slug": "radiuma", "sub_slug": "does-not-exist"},
) )
response = self.client.get(url) response = self.client.get(url)
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 404)
+3 -3
View File
@@ -5,9 +5,9 @@ from django.conf.urls.static import static
from django.contrib import admin from django.contrib import admin
from django.urls import include, path from django.urls import include, path
admin.site.site_header = "Tecvico Administration" admin.site.site_header = "Radiuma Administration"
admin.site.site_title = "Tecvico Admin" admin.site.site_title = "Radiuma Admin"
admin.site.index_title = "Welcome to Tecvico Administration" admin.site.index_title = "Welcome to Radiuma Administration"
urlpatterns = [ urlpatterns = [
path("admin/", admin.site.urls), path("admin/", admin.site.urls),
+494 -6
View File
@@ -560,6 +560,59 @@ h1, h2, h3, h4, h5, h6 {
transform: translateY(-7px) rotate(-45deg); transform: translateY(-7px) rotate(-45deg);
} }
/* ============================================================
SVG Blob Image Decorations
============================================================ */
.blob-img {
position: absolute;
pointer-events: none;
z-index: 0;
}
.blob-img--hero-left {
width: 420px;
top: 5%;
left: -80px;
opacity: 0.18;
filter: blur(2px);
animation: blob-morph 16s ease-in-out infinite;
}
.blob-img--hero-right {
width: 380px;
top: 15%;
right: -60px;
opacity: 0.13;
filter: blur(1px);
animation: blob-morph 20s ease-in-out infinite reverse;
}
.blob-img--page-hero {
width: 500px;
top: -50px;
right: -100px;
opacity: 0.15;
filter: blur(3px);
animation: blob-morph 18s ease-in-out infinite;
}
.blob-img--page-corner {
width: 280px;
bottom: -30px;
left: 5%;
opacity: 0.1;
filter: blur(2px);
animation: blob-morph 22s ease-in-out infinite reverse;
}
.blob-img--downloads {
width: 600px;
top: -80px;
right: -120px;
opacity: 0.12;
filter: blur(4px);
}
/* ============================================================ /* ============================================================
Hero Hero
============================================================ */ ============================================================ */
@@ -583,8 +636,17 @@ h1, h2, h3, h4, h5, h6 {
.hero-content { .hero-content {
position: relative; position: relative;
z-index: 2; z-index: 2;
max-width: 780px; width: 100%;
max-width: var(--container-max);
padding-top: var(--spacing-2xl); padding-top: var(--spacing-2xl);
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2.5rem;
align-items: center;
}
.hero-text {
max-width: 620px;
} }
.hero-badge { .hero-badge {
@@ -603,10 +665,10 @@ h1, h2, h3, h4, h5, h6 {
} }
.hero-title { .hero-title {
font-size: clamp(2.8rem, 7vw, 5.5rem); font-size: clamp(2rem, 4.5vw, 3.5rem);
font-weight: 800; font-weight: 800;
line-height: 1.05; line-height: 1.1;
letter-spacing: -0.04em; letter-spacing: -0.03em;
margin-bottom: 1.25rem; margin-bottom: 1.25rem;
animation: fade-slide-up 0.8s ease-out 0.2s both; animation: fade-slide-up 0.8s ease-out 0.2s both;
} }
@@ -1578,6 +1640,129 @@ h1, h2, h3, h4, h5, h6 {
/* ============================================================ /* ============================================================
Contact Page Contact Page
============================================================ */ ============================================================ */
.contact-layout {
display: grid;
grid-template-columns: 1fr 360px;
gap: 2.5rem;
align-items: start;
}
.contact-form-wrap {
padding: 2.5rem;
}
.contact-form-title {
font-size: 1.35rem;
font-weight: 700;
margin-bottom: 1.75rem;
}
.form-group {
margin-bottom: 1.2rem;
}
.form-group label {
display: block;
font-size: 0.86rem;
font-weight: 600;
margin-bottom: 0.4rem;
color: var(--text-primary);
}
.required-star {
color: var(--accent-blue-light);
}
.optional-label {
font-size: 0.78rem;
font-weight: 400;
color: var(--text-muted, var(--text-secondary));
margin-left: 0.25rem;
}
.form-group input,
.form-group textarea,
.form-group select {
width: 100%;
padding: 0.72rem 1rem;
border-radius: var(--radius-sm);
border: 1px solid var(--glass-border);
background: rgba(255, 255, 255, 0.04);
color: var(--text-primary);
font-family: inherit;
font-size: 0.95rem;
line-height: 1.5;
transition: border-color 0.2s ease, box-shadow 0.2s ease, background 0.2s ease;
outline: none;
box-sizing: border-box;
}
.form-group input:focus,
.form-group textarea:focus {
border-color: rgba(79, 142, 247, 0.5);
box-shadow: 0 0 0 3px rgba(79, 142, 247, 0.1);
background: rgba(255, 255, 255, 0.06);
}
.form-group textarea {
resize: vertical;
min-height: 120px;
}
.form-group.has-error input,
.form-group.has-error textarea {
border-color: rgba(239, 68, 68, 0.5);
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
}
.form-field-error {
display: block;
font-size: 0.78rem;
color: rgb(252, 165, 165);
margin-top: 0.3rem;
min-height: 1em;
}
.form-group--captcha {
background: rgba(79, 142, 247, 0.04);
border: 1px solid rgba(79, 142, 247, 0.15);
padding: 1rem;
border-radius: var(--radius-sm);
}
.captcha-input {
max-width: 130px !important;
}
.btn-submit {
width: 100%;
justify-content: center;
margin-top: 0.5rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.btn-spinner svg {
animation: spin 0.9s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.btn--sm {
padding: 0.45rem 1.1rem !important;
font-size: 0.85rem !important;
}
.contact-sidebar {
display: flex;
flex-direction: column;
gap: 1rem;
}
.contact-grid { .contact-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
@@ -1585,7 +1770,7 @@ h1, h2, h3, h4, h5, h6 {
} }
.contact-card { .contact-card {
padding: 2.25rem; padding: 2rem;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.75rem; gap: 0.75rem;
@@ -1620,6 +1805,84 @@ h1, h2, h3, h4, h5, h6 {
line-height: 1.8; line-height: 1.8;
} }
/* ============================================================
Modal
============================================================ */
.modal-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.72);
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
z-index: 9000;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
}
.modal-overlay[hidden] {
display: none;
}
.modal-box {
max-width: 460px;
width: 100%;
padding: 2.5rem;
text-align: center;
animation: modal-pop 0.22s ease-out both;
}
@keyframes modal-pop {
from { opacity: 0; transform: scale(0.92) translateY(10px); }
to { opacity: 1; transform: scale(1) translateY(0); }
}
.modal-icon-wrap {
width: 64px;
height: 64px;
border-radius: 50%;
background: rgba(79, 142, 247, 0.12);
border: 1px solid rgba(79, 142, 247, 0.2);
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 1.5rem;
color: var(--accent-blue-light);
}
.modal-icon-wrap--success {
background: rgba(34, 197, 94, 0.12);
border-color: rgba(34, 197, 94, 0.2);
color: rgb(134, 239, 172);
}
.modal-icon-wrap--error {
background: rgba(239, 68, 68, 0.12);
border-color: rgba(239, 68, 68, 0.2);
color: rgb(252, 165, 165);
}
.modal-title {
font-size: 1.3rem;
font-weight: 700;
margin-bottom: 0.75rem;
}
.modal-body {
font-size: 0.93rem;
color: var(--text-secondary);
line-height: 1.65;
margin-bottom: 1.75rem;
}
.modal-actions {
display: flex;
gap: 0.75rem;
justify-content: center;
flex-wrap: wrap;
}
/* ============================================================ /* ============================================================
Footer Footer
============================================================ */ ============================================================ */
@@ -1902,7 +2165,7 @@ h1, h2, h3, h4, h5, h6 {
} }
.hero-title { .hero-title {
font-size: clamp(2.2rem, 8vw, 3rem); font-size: clamp(1.7rem, 6vw, 2.4rem);
} }
.about-strip-inner { .about-strip-inner {
@@ -1918,6 +2181,219 @@ h1, h2, h3, h4, h5, h6 {
} }
} }
/* ============================================================
Hero App Preview (Screenshot)
============================================================ */
.hero-app-preview {
position: relative;
border-radius: var(--radius-xl);
overflow: hidden;
box-shadow:
0 32px 90px rgba(0, 0, 0, 0.65),
0 0 80px rgba(79, 142, 247, 0.22),
0 0 0 1px rgba(79, 142, 247, 0.12);
animation: hero-preview-in 1.1s ease-out 0.6s both;
align-self: center;
transform: perspective(1400px) rotateY(-14deg) rotateX(3deg);
transition: transform 0.7s cubic-bezier(0.22, 1, 0.36, 1),
box-shadow 0.7s ease;
}
.hero-app-preview:hover {
transform: perspective(1400px) rotateY(-5deg) rotateX(1deg);
box-shadow:
0 40px 120px rgba(0, 0, 0, 0.7),
0 0 100px rgba(79, 142, 247, 0.32),
0 0 0 1px rgba(79, 142, 247, 0.2);
}
@keyframes hero-preview-in {
from {
opacity: 0;
transform: perspective(1400px) rotateY(-20deg) rotateX(4deg) translateX(50px);
}
to {
opacity: 1;
transform: perspective(1400px) rotateY(-14deg) rotateX(3deg) translateX(0);
}
}
.hero-app-preview-glow {
position: absolute;
inset: 0;
background: linear-gradient(
to right,
rgba(7, 7, 19, 0.88) 0%,
rgba(7, 7, 19, 0.45) 28%,
transparent 62%
);
z-index: 2;
pointer-events: none;
}
.hero-app-preview img {
width: 100%;
display: block;
opacity: 0.15;
border-radius: var(--radius-xl);
transition: opacity 0.5s ease;
}
.hero-app-preview:hover img {
opacity: 0.42;
}
/* ============================================================
Screenshots Gallery
============================================================ */
.screenshots-section {
position: relative;
overflow: hidden;
}
.screenshots-section::before {
content: '';
position: absolute;
inset: 0;
background:
radial-gradient(ellipse 70% 50% at 50% 50%, rgba(79, 142, 247, 0.06) 0%, transparent 70%);
pointer-events: none;
}
.screenshots-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1.25rem;
}
.screenshot-item {
border-radius: var(--radius-md);
overflow: hidden;
border: 1px solid var(--glass-border);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.45);
transition: var(--transition-smooth);
background: var(--bg-secondary);
cursor: zoom-in;
}
.screenshot-item img {
width: 100%;
display: block;
opacity: 0.82;
transition: opacity 0.35s ease, transform 0.45s ease;
aspect-ratio: 16/10;
object-fit: cover;
}
.screenshot-item:hover {
border-color: rgba(79, 142, 247, 0.3);
box-shadow:
0 16px 60px rgba(0, 0, 0, 0.6),
0 0 20px rgba(79, 142, 247, 0.12);
transform: translateY(-3px);
}
.screenshot-item:hover img {
opacity: 1;
transform: scale(1.03);
}
/* About page screenshots */
.about-screenshots-grid {
display: grid;
grid-template-columns: 1.5fr 1fr 1fr;
gap: 1.25rem;
align-items: start;
}
.about-screenshots-grid .screenshot-item img {
aspect-ratio: 16/10;
object-fit: cover;
}
/* ============================================================
Platform Icons (Downloads)
============================================================ */
.platform-icon {
display: block;
width: 56px;
height: 56px;
object-fit: contain;
filter: brightness(0) invert(1);
opacity: 0.85;
transition: var(--transition-smooth);
}
.download-card:hover .platform-icon {
opacity: 1;
filter: brightness(0) invert(1) drop-shadow(0 0 8px rgba(79, 142, 247, 0.6));
}
.download-card--unavailable .platform-icon {
opacity: 0.35;
filter: brightness(0) invert(1);
}
.btn-icon-img {
width: 18px;
height: 18px;
object-fit: contain;
filter: brightness(0) invert(1);
flex-shrink: 0;
}
/* ============================================================
Product Logo Image (fallback on product detail)
============================================================ */
.product-logo-img {
width: 100%;
max-width: 280px;
height: auto;
object-fit: contain;
opacity: 0.9;
filter: drop-shadow(0 4px 20px rgba(79, 142, 247, 0.3));
padding: 1.5rem;
}
/* ============================================================
Responsive — Images
============================================================ */
@media (max-width: 1024px) {
.hero-content {
grid-template-columns: 1fr;
}
.hero-app-preview {
display: none;
}
.contact-layout {
grid-template-columns: 1fr;
}
.about-screenshots-grid {
grid-template-columns: 1fr 1fr;
}
.about-screenshots-grid .screenshot-item--featured {
grid-column: 1 / -1;
}
}
@media (max-width: 768px) {
.blob-img--hero-left,
.blob-img--hero-right,
.blob-img--page-hero,
.blob-img--page-corner,
.blob-img--downloads {
opacity: 0.08;
}
.about-screenshots-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 480px) { @media (max-width: 480px) {
:root { :root {
--container-padding: 1rem; --container-padding: 1rem;
@@ -1939,4 +2415,16 @@ h1, h2, h3, h4, h5, h6 {
.contact-grid { .contact-grid {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
.screenshots-grid {
grid-template-columns: 1fr;
}
.modal-box {
padding: 1.75rem 1.25rem;
}
.modal-actions {
flex-direction: column;
}
} }
+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="#000000" height="552.7" preserveAspectRatio="xMidYMid meet" version="1" viewBox="-44.4 -20.7 680.7 552.7" width="680.7" zoomAndPan="magnify"><g data-name="Layer 2"><g data-name="Layer 1" id="change1_1"><path d="M28.21,141.27c47.95-128.07,218-162,375-130C499.14,30.83,636.3,240.36,511.48,386.14s-317.73,139.9-396,31.74S-44.42,335.26,28.21,141.27Z" fill="#9cadce"/></g></g></svg>

After

Width:  |  Height:  |  Size: 520 B

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="#000000" height="503.1" preserveAspectRatio="xMidYMid meet" version="1" viewBox="40.9 -1.4 419.8 503.1" width="419.8" zoomAndPan="magnify"><g id="change1_1"><path d="M 58.464844 166.386719 C 40.9375 218.816406 47.953125 277.582031 70.871094 327.890625 C 93.789062 378.199219 131.609375 420.613281 174.550781 455.433594 C 202.554688 478.140625 234.988281 498.628906 271.015625 499.933594 C 321.9375 501.773438 365.957031 465.300781 399.199219 426.683594 C 431.734375 388.890625 460.777344 339.984375 448.472656 291.65625 C 435.9375 242.417969 385.121094 212.230469 364.699219 165.703125 C 342.992188 116.257812 354.605469 48.347656 310.761719 16.824219 C 289.902344 1.824219 262.472656 0.65625 236.789062 0.046875 C 178.226562 -1.351562 175.976562 30.410156 144.183594 68.980469 C 115.597656 103.65625 73.695312 120.832031 58.464844 166.386719" fill="#d1cfe2"/></g></svg>

After

Width:  |  Height:  |  Size: 1013 B

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="#000000" height="503.1" preserveAspectRatio="xMidYMid meet" version="1" viewBox="40.9 -1.4 419.8 503.1" width="419.8" zoomAndPan="magnify"><g id="change1_1"><path d="M 58.464844 166.386719 C 40.9375 218.816406 47.953125 277.582031 70.871094 327.890625 C 93.789062 378.199219 131.609375 420.613281 174.550781 455.433594 C 202.554688 478.140625 234.988281 498.628906 271.015625 499.933594 C 321.9375 501.773438 365.957031 465.300781 399.199219 426.683594 C 431.734375 388.890625 460.777344 339.984375 448.472656 291.65625 C 435.9375 242.417969 385.121094 212.230469 364.699219 165.703125 C 342.992188 116.257812 354.605469 48.347656 310.761719 16.824219 C 289.902344 1.824219 262.472656 0.65625 236.789062 0.046875 C 178.226562 -1.351562 175.976562 30.410156 144.183594 68.980469 C 115.597656 103.65625 73.695312 120.832031 58.464844 166.386719" fill="#7ec4cf"/></g></svg>

After

Width:  |  Height:  |  Size: 1013 B

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" data-name="Capa 1" fill="#000000" height="2952.1" preserveAspectRatio="xMidYMid meet" version="1" viewBox="-3.9 -20.1 3657.4 2952.1" width="3657.4" zoomAndPan="magnify"><g id="change1_1"><path d="M77.11,566.84C16.35,725,3.47,897.25.57,1066.66c-4.45,260,15.13,528.2,129.64,761.65,97.28,198.33,257.11,359,426.45,500.84,178.09,149.19,371.08,282.64,582.92,378,477.9,215.18,1023.91,224.89,1544.24,162.14,255-30.75,524.5-86.13,707.85-266C3626.48,2373,3653.51,2009,3652.19,1680.07c-1.38-342.79-25.87-709.39-232.92-982.6-251.06-331.27-697.11-433.95-1107-502.74C1861.38,119.08,1394.85,28.26,938.17,1.25,578-20.06,205.13,233.55,77.11,566.84Z" fill="#9cadce"/></g></svg>

After

Width:  |  Height:  |  Size: 796 B

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" data-name="Capa 1" fill="#000000" height="2952.1" preserveAspectRatio="xMidYMid meet" version="1" viewBox="-3.9 -20.1 3657.4 2952.1" width="3657.4" zoomAndPan="magnify"><g id="change1_1"><path d="M77.11,566.84C16.35,725,3.47,897.25.57,1066.66c-4.45,260,15.13,528.2,129.64,761.65,97.28,198.33,257.11,359,426.45,500.84,178.09,149.19,371.08,282.64,582.92,378,477.9,215.18,1023.91,224.89,1544.24,162.14,255-30.75,524.5-86.13,707.85-266C3626.48,2373,3653.51,2009,3652.19,1680.07c-1.38-342.79-25.87-709.39-232.92-982.6-251.06-331.27-697.11-433.95-1107-502.74C1861.38,119.08,1394.85,28.26,938.17,1.25,578-20.06,205.13,233.55,77.11,566.84Z" fill="#d1cfe2"/></g></svg>

After

Width:  |  Height:  |  Size: 796 B

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" data-name="Capa 1" fill="#000000" height="2952.1" preserveAspectRatio="xMidYMid meet" version="1" viewBox="-3.9 -20.1 3657.4 2952.1" width="3657.4" zoomAndPan="magnify"><g id="change1_1"><path d="M77.11,566.84C16.35,725,3.47,897.25.57,1066.66c-4.45,260,15.13,528.2,129.64,761.65,97.28,198.33,257.11,359,426.45,500.84,178.09,149.19,371.08,282.64,582.92,378,477.9,215.18,1023.91,224.89,1544.24,162.14,255-30.75,524.5-86.13,707.85-266C3626.48,2373,3653.51,2009,3652.19,1680.07c-1.38-342.79-25.87-709.39-232.92-982.6-251.06-331.27-697.11-433.95-1107-502.74C1861.38,119.08,1394.85,28.26,938.17,1.25,578-20.06,205.13,233.55,77.11,566.84Z" fill="#7ec4cf"/></g></svg>

After

Width:  |  Height:  |  Size: 796 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 454 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 834 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="50.0" preserveAspectRatio="xMidYMid meet" version="1.0" viewBox="-0.1 0.0 50.1 50.0" width="50.1" zoomAndPan="magnify"><g id="change1_2"><circle cx="29" cy="16" fill="inherit" r="2"/></g><g id="change1_3"><circle cx="21" cy="16" fill="inherit" r="2"/></g><g id="change1_4"><path d="M25,26.016c-0.089,0-0.177-0.012-0.264-0.035c-0.136-0.037-3.342-0.937-4.69-4.631c-0.095-0.262-0.079-0.552,0.045-0.802 s0.347-0.437,0.613-0.518c2.081-0.632,3.664-1.824,3.68-1.836c0.363-0.275,0.868-0.275,1.231,0c0.016,0.012,1.598,1.204,3.679,1.836 c0.269,0.081,0.489,0.269,0.613,0.518c0.125,0.25,0.142,0.54,0.046,0.802c-1.35,3.695-4.555,4.596-4.69,4.631 C25.177,26.004,25.089,26.016,25,26.016z" fill="inherit"/></g><g id="change1_6"><path d="M47.809,29.493c-0.437-0.606-0.91-1.285-1.383-2c-0.158,2.958-0.593,5.649-1.303,8.052 c0.363,0.063,0.736,0.107,1.131,0.107c1.229,0,2.344-0.355,3.309-1.057c0.229-0.164,0.371-0.424,0.394-0.705 C50.053,32.618,49.109,31.309,47.809,29.493z" fill="inherit"/></g><g id="change1_7"><path d="M3.574,27.493c-0.473,0.715-0.946,1.394-1.381,2c-1.304,1.813-2.245,3.125-2.148,4.397 c0.021,0.281,0.166,0.539,0.394,0.705c0.965,0.7,2.078,1.057,3.307,1.057l0,0c0.393,0,0.768-0.047,1.13-0.107 C4.167,33.143,3.733,30.451,3.574,27.493z" fill="inherit"/></g><g id="change1_8"><path d="M42.157,46.029c-0.331-0.053-0.532-0.118-0.65-0.17c0.026-0.031,0.06-0.065,0.099-0.1c0.318-0.271,0.435-0.714,0.289-1.105 C41.749,44.261,41.375,44,40.956,44c-0.149,0-0.31,0.001-0.464,0.001c-2.792,3.021-6.521,4.935-11.129,5.655 C31.92,49.896,35.633,50,40.99,50c0.006-0.001,0.014,0,0.02,0c0.553,0,1-0.447,1-1c0-0.313-0.144-0.593-0.369-0.775 c-0.016-0.021-0.031-0.045-0.048-0.07c0.106-0.045,0.285-0.103,0.572-0.149c0.483-0.081,0.837-0.5,0.835-0.989 C42.998,46.525,42.643,46.107,42.157,46.029z" fill="inherit"/></g><g id="change1_1"><path d="M9.508,44.001C9.353,44.001,9.194,44,9.044,44c-0.419,0-0.793,0.261-0.938,0.654c-0.145,0.393-0.029,0.835,0.29,1.105 c0.039,0.033,0.072,0.067,0.098,0.1c-0.118,0.052-0.32,0.117-0.651,0.17C7.358,46.107,7.002,46.523,7,47.014 c-0.002,0.491,0.352,0.909,0.835,0.99c0.287,0.048,0.466,0.105,0.572,0.15C8.374,48.206,8.333,48.26,8.295,48.3 c-0.28,0.286-0.363,0.712-0.209,1.083C8.241,49.757,8.605,50,9.01,50c5.358,0,9.07-0.104,11.627-0.344 C16.031,48.936,12.3,47.021,9.508,44.001z" fill="inherit"/></g><g id="change1_5"><path d="M43.284,20.301c-0.365-1.122-0.778-2.387-0.854-2.867C42.393,6.11,33.432,0,25,0C16.568,0,7.608,6.11,7.571,17.434 c-0.077,0.479-0.488,1.743-0.854,2.863c-0.738,2.264-1.221,3.809-1.221,4.623C5.496,40.02,12.24,48,25,48s19.504-7.98,19.504-23.08 C44.504,24.106,44.021,22.563,43.284,20.301z M25,40c-3.608,0-12-1.163-12-11.932c0-4.426,1.629-6.599,1.641-6.615 c0.204-0.257,0.268-0.599,0.172-0.913c-0.02-0.063-1.897-6.345,0.516-9.604C16.294,9.634,17.822,9,20,9 c1.053,0,2.17,0.643,3.067,1.16c0.75,0.431,1.341,0.772,1.933,0.772c0.592,0,1.184-0.341,1.933-0.772C27.83,9.643,28.947,9,30,9 c2.177,0,3.703,0.633,4.669,1.934c2.408,3.248,0.536,9.544,0.517,9.608c-0.094,0.309-0.028,0.65,0.169,0.906 C35.37,21.47,37,23.643,37,28.068C37,38.837,28.608,40,25,40z" fill="inherit"/></g></svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="#545c62" height="33" id="Layer_1" preserveAspectRatio="xMidYMid meet" version="1.1" viewBox="0.0 0.0 263.5 33.0" width="263.5" x="0" xml:space="preserve" y="0" zoomAndPan="magnify"><g id="change1_1"><path d="M247.01,0H16.52C7.41,0,0,7.41,0,16.52c0,9.11,7.41,16.52,16.52,16.52h230.5c9.11,0,16.52-7.41,16.52-16.52 C263.53,7.41,256.12,0,247.01,0z M247.01,30.04H16.52C9.06,30.04,3,23.97,3,16.52C3,9.06,9.06,3,16.52,3h230.5 c7.46,0,13.52,6.06,13.52,13.52C260.53,23.97,254.47,30.04,247.01,30.04z" fill="inherit"/><path d="M6.2,16.52c0,5.69,4.63,10.32,10.32,10.32h1.42V6.2h-1.42C10.83,6.2,6.2,10.83,6.2,16.52z" fill="inherit"/><path d="M22.12 6.2H33.86V26.84H22.12z" fill="inherit"/><path d="M38.04 6.2H49.78V26.84H38.04z" fill="inherit"/><path d="M53.96 6.2H65.7V26.84H53.96z" fill="inherit"/><path d="M69.88 6.2H81.61999999999999V26.84H69.88z" fill="inherit"/><path d="M85.8 6.2H97.55V26.84H85.8z" fill="inherit"/><path d="M101.72 6.2H113.47V26.84H101.72z" fill="inherit"/><path d="M117.64 6.2H129.39V26.84H117.64z" fill="inherit"/><path d="M133.56 6.2H145.31V26.84H133.56z" fill="inherit"/></g></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="25.5" preserveAspectRatio="xMidYMid meet" version="1.0" viewBox="1.4 0.3 22.6 25.5" width="22.6" zoomAndPan="magnify"><g id="change1_1"><path d="M23.934,18.947c-0.598,1.324-0.884,1.916-1.652,3.086c-1.073,1.634-2.588,3.673-4.461,3.687 c-1.666,0.014-2.096-1.087-4.357-1.069c-2.261,0.011-2.732,1.089-4.4,1.072c-1.873-0.017-3.307-1.854-4.381-3.485 C1.68,17.663,1.363,12.301,3.219,9.448c1.313-2.023,3.391-3.211,5.342-3.211c1.987,0,3.236,1.092,4.879,1.092 c1.594,0,2.565-1.095,4.863-1.095c1.738,0,3.576,0.947,4.889,2.581C18.896,11.169,19.594,17.305,23.934,18.947z M16.559,4.408 c0.836-1.073,1.47-2.587,1.24-4.131c-1.364,0.093-2.959,0.964-3.891,2.092c-0.844,1.027-1.544,2.553-1.271,4.029 C14.125,6.446,15.665,5.559,16.559,4.408z" fill="inherit"/></g></svg>

After

Width:  |  Height:  |  Size: 878 B

+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="18.8" preserveAspectRatio="xMidYMid meet" version="1.0" viewBox="1.3 2.6 21.3 18.8" width="21.3" zoomAndPan="magnify">
<g id="change1_1"><path d="m6.515 11.477a12.53 12.53 0 0 0 -2.959 0.642q-0.221 0.078-0.456 0.168l-1.767 6a10.292 10.292 0 0 1 3.218 -0.577 7.306 7.306 0 0 1 3.913 1.167c0.509-1.7 1.5-5.025 1.888-6.315-0.21-0.125-0.41-0.24-0.617-0.35a6.053 6.053 0 0 0 -3.22 -0.735z" fill="inherit"/></g>
<g id="change1_2"><path d="m12.492 14.051-1.8 6.128a9.29 9.29 0 0 0 3.728 1.226 10.9 10.9 0 0 0 3.916 -0.864l1.7-5.825a11.964 11.964 0 0 1 -3.252 0.485 7.98 7.98 0 0 1 -4.292 -1.15z" fill="inherit"/></g>
<g id="change1_3"><path d="m11.048 9.884c0.373-1.242 0.985-3.3 1.454-4.872l0.4-1.333a10.524 10.524 0 0 0 -2.1 -0.909 6.992 6.992 0 0 0 -1.583 -0.17 10.449 10.449 0 0 0 -3.55 0.782l-1.738 5.973a10.3 10.3 0 0 1 3.288 -0.623 7.318 7.318 0 0 1 3.829 1.152z" fill="inherit"/></g>
<g id="change1_4"><path d="m19.349 6.349a7.952 7.952 0 0 1 -4.239 -1.179l-1.835 6.222a7.983 7.983 0 0 0 7.634 0.307l1.758-5.931a10.389 10.389 0 0 1 -3.318 0.581z" fill="inherit"/></g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" clip-rule="evenodd" fill="#000000" fill-rule="evenodd" height="1345.5" image-rendering="optimizeQuality" preserveAspectRatio="xMidYMid meet" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" version="1" viewBox="-31.8 -166.3 1332.2 1345.5" width="1332.2" zoomAndPan="magnify"><g><g><g id="change1_1"><path d="M1050.7 613.2c42.3,64 40.2,151.2 -0.9,222.3 -40.1,69.5 -111.7,109.3 -186.3,111.8 -33.4,1.2 -61.4,18.8 -77,48.3 -96.6,183.6 -370.3,147.2 -416.3,-54.3 -10.8,-47.5 -57.2,-77.8 -104.9,-68.8 -50.8,9.6 -105.1,1.8 -153.4,-26.1 -107,-61.8 -143.7,-198.7 -81.9,-305.7 14.7,-25.4 33.5,-46.8 55.2,-63.9 30.7,-24.1 42.2,-63.2 29.6,-100.2 -133.8,-392.8 530.1,-542.9 581.6,-96.2 46.5,403 170.7,54.8 354.3,332.8z" fill="#9cadce"/></g><g id="change2_1"><path d="M1268.3 609.3c21.7,32.9 32.1,71.6 32,111.2 -0.2,40.3 -11.3,81.6 -32.7,118.6l0 0c-20.3,35.1 -48.4,62.9 -80.9,82.4 -33.7,20.3 -72.1,31.5 -111.3,32.9 -15.4,0.5 -29.6,4.8 -41.6,12.4 -12.1,7.6 -22.2,18.5 -29.3,32.1 -34.3,65.1 -90.1,103.4 -150.6,117.1 -27.4,6.3 -55.8,7.5 -83.6,3.9 -27.8,-3.7 -54.9,-12.2 -79.8,-25.3 -55,-28.9 -99.1,-80.3 -115.5,-151.8 -4.9,-21.8 -18.1,-39.7 -35.6,-51.2 -17.4,-11.4 -39.1,-16.3 -61.2,-12.2 -26.6,5.1 -54.2,5.5 -81.5,0.8 -26.2,-4.4 -52.2,-13.6 -76.6,-27.7l0 0c-55.2,-31.9 -92.3,-83.1 -107.6,-140.2 -15.3,-57 -8.8,-119.9 23.1,-175.1l0 0c7.4,-12.9 16,-24.8 25.4,-35.7 9.6,-11.2 20.2,-21.3 31.5,-30.2 14.2,-11.2 23.9,-25.7 28.7,-41.7 4.7,-16.1 4.5,-33.6 -1.4,-50.7 -44.5,-130.8 -2.7,-235.5 75.9,-301.5 40.5,-34 90.8,-57.7 144.1,-69.3 53.2,-11.5 109.4,-11 161.8,3.3 105.8,28.8 195.7,113.6 213.5,268.2 24.5,212.1 69.9,209.7 134.1,206.3 59.1,-3.1 132.9,-7 219.1,123.4zm17.9 111.2c0.1,-36.9 -9.6,-72.9 -29.7,-103.4 -81.7,-123.7 -151,-120 -206.6,-117.1 -72,3.8 -122.8,6.5 -148.8,-218.8 -17.1,-147.9 -102.6,-228.8 -203.2,-256.2 -50.1,-13.7 -104,-14.1 -155.1,-3 -51.1,11.1 -99.3,33.7 -138,66.3 -74.4,62.5 -113.9,161.7 -71.6,286.1 6.8,19.9 7.1,40.4 1.5,59.1 -5.5,18.8 -16.9,35.9 -33.4,48.9 -10.7,8.4 -20.6,17.9 -29.7,28.3 -8.8,10.2 -16.8,21.4 -23.8,33.6l0 -0.1c-29.9,51.9 -36,110.9 -21.6,164.5 14.3,53.6 49.1,101.7 101,131.6l-0.1 0c23,13.2 47.3,21.8 72,26 25.6,4.3 51.5,4 76.5,-0.7 25.8,-4.9 51.2,0.8 71.6,14.2 20.4,13.3 35.8,34.3 41.6,59.9 15.3,67.1 56.7,115.3 108.3,142.4 23.4,12.4 48.9,20.3 75,23.8 26.1,3.4 52.9,2.3 78.7,-3.6 56.7,-13 109,-48.9 141.2,-110 8.3,-15.9 20.1,-28.5 34.2,-37.4 14.2,-8.9 30.8,-14 48.7,-14.6 36.8,-1.3 72.9,-11.9 104.5,-30.9 30.5,-18.3 57,-44.4 76,-77.4l0 0c20.1,-34.9 30.6,-73.7 30.8,-111.5z" fill="#d1cfe2" fill-rule="nonzero"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

+15 -4
View File
@@ -64,15 +64,26 @@ function initMegaMenu() {
const megamenu = productsItem.querySelector('.megamenu'); const megamenu = productsItem.querySelector('.megamenu');
if (!trigger || !megamenu) return; if (!trigger || !megamenu) return;
productsItem.addEventListener('mouseenter', () => { let hideTimer = null;
function openMenu() {
clearTimeout(hideTimer);
productsItem.classList.add('open'); productsItem.classList.add('open');
trigger.setAttribute('aria-expanded', 'true'); trigger.setAttribute('aria-expanded', 'true');
}); }
productsItem.addEventListener('mouseleave', () => { function scheduleClose() {
hideTimer = setTimeout(() => {
productsItem.classList.remove('open'); productsItem.classList.remove('open');
trigger.setAttribute('aria-expanded', 'false'); trigger.setAttribute('aria-expanded', 'false');
}); }, 180);
}
productsItem.addEventListener('mouseenter', openMenu);
productsItem.addEventListener('mouseleave', scheduleClose);
megamenu.addEventListener('mouseenter', openMenu);
megamenu.addEventListener('mouseleave', scheduleClose);
trigger.addEventListener('keydown', (e) => { trigger.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') { if (e.key === 'Enter' || e.key === ' ') {
+5 -2
View File
@@ -4,8 +4,11 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="{% block meta_description %}Tecvico Corp — Advanced Medical Imaging & Radiomics Solutions{% endblock %}" /> <meta name="description" content="{% block meta_description %}Radiuma — Advanced Medical Imaging & Radiomics Solutions{% endblock %}" />
<title>{% block title %}Tecvico Corp{% endblock %} | Tecvico</title> <title>{% block title %}Radiuma{% endblock %} | Radiuma</title>
<link rel="icon" href="{% static 'images/favicon-16.png' %}" type="image/png" sizes="16x16" />
<link rel="icon" href="{% static 'images/favicon-32.png' %}" type="image/png" sizes="32x32" />
<link rel="apple-touch-icon" href="{% static 'images/favicon-180.png' %}" sizes="180x180" />
<link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet" /> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet" />
+30 -8
View File
@@ -1,8 +1,8 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load static %} {% load static %}
{% block title %}What is Tecvico{% endblock %} {% block title %}What is Radiuma{% endblock %}
{% block meta_description %}Learn about Tecvico Corp and ViSERA — Visualized & Standardized Environment for Radiomics Analysis, developed at UBC and BC Cancer Research Institute.{% endblock %} {% block meta_description %}Learn about Radiuma and Radiuma — Visualized & Standardized Environment for Radiomics Analysis, developed at UBC and BC Cancer Research Institute.{% endblock %}
{% block content %} {% block content %}
@@ -10,11 +10,13 @@
<div class="page-hero-blobs" aria-hidden="true"> <div class="page-hero-blobs" aria-hidden="true">
<div class="blob blob--1"></div> <div class="blob blob--1"></div>
<div class="blob blob--2"></div> <div class="blob blob--2"></div>
<img src="{% static 'images/blob-blue-2.svg' %}" class="blob-img blob-img--page-hero" alt="" />
<img src="{% static 'images/abstract-shapes-2.svg' %}" class="blob-img blob-img--page-corner" alt="" />
</div> </div>
<div class="container"> <div class="container">
<div class="page-hero-content"> <div class="page-hero-content">
<div class="section-badge">About</div> <div class="section-badge">About</div>
<h1 class="page-hero-title" id="page-hero-heading">What is Tecvico?</h1> <h1 class="page-hero-title" id="page-hero-heading">What is Radiuma?</h1>
<p class="page-hero-subtitle"> <p class="page-hero-subtitle">
Visualized &amp; Standardized Environment for Radiomics Analysis Visualized &amp; Standardized Environment for Radiomics Analysis
</p> </p>
@@ -49,7 +51,7 @@
<div class="standard-badge">IBSI 1.0</div> <div class="standard-badge">IBSI 1.0</div>
<h3 class="standard-title">Radiomic Feature Extraction</h3> <h3 class="standard-title">Radiomic Feature Extraction</h3>
<p class="standard-desc"> <p class="standard-desc">
ViSERA is a python-based open-source package that enables standardized and Radiuma is a python-based open-source package that enables standardized and
reproducible radiomic feature extraction in compliance with the Image Biomarker reproducible radiomic feature extraction in compliance with the Image Biomarker
Standardization Initiative (IBSI 1.0). Standardization Initiative (IBSI 1.0).
</p> </p>
@@ -67,7 +69,7 @@
<div class="standard-badge">Python</div> <div class="standard-badge">Python</div>
<h3 class="standard-title">Open Source</h3> <h3 class="standard-title">Open Source</h3>
<p class="standard-desc"> <p class="standard-desc">
ViSERA is a major, entirely-revamped upgrade to the original SERA (Matlab-based), Radiuma is a major, entirely-revamped upgrade to the original SERA (Matlab-based),
now built on Python for broader accessibility and community contribution. now built on Python for broader accessibility and community contribution.
</p> </p>
</div> </div>
@@ -75,7 +77,7 @@
<div class="standard-badge">End-to-End</div> <div class="standard-badge">End-to-End</div>
<h3 class="standard-title">Standardized Workflows</h3> <h3 class="standard-title">Standardized Workflows</h3>
<p class="standard-desc"> <p class="standard-desc">
ViSERA employs a number of popular image processing algorithms to create Radiuma employs a number of popular image processing algorithms to create
end-to-end standardized workflows for consistent, reproducible research results. end-to-end standardized workflows for consistent, reproducible research results.
</p> </p>
</div> </div>
@@ -83,6 +85,26 @@
</div> </div>
</section> </section>
<section class="section" aria-labelledby="screenshots-about-heading">
<div class="container">
<div class="section-header">
<div class="section-badge">Interface</div>
<h2 class="section-title" id="screenshots-about-heading">Radiuma in Action</h2>
</div>
<div class="about-screenshots-grid">
<div class="screenshot-item screenshot-item--featured fade-in">
<img src="{% static 'images/screenshot-11.jpg' %}" alt="Radiuma multi-modal viewer" loading="lazy" />
</div>
<div class="screenshot-item fade-in">
<img src="{% static 'images/screenshot-9.jpg' %}" alt="Radiuma settings panel" loading="lazy" />
</div>
<div class="screenshot-item fade-in">
<img src="{% static 'images/screenshot-10.jpg' %}" alt="Radiuma analysis results" loading="lazy" />
</div>
</div>
</div>
</section>
<section class="section" aria-labelledby="history-heading"> <section class="section" aria-labelledby="history-heading">
<div class="container"> <div class="container">
<div class="history-block glass-card fade-in"> <div class="history-block glass-card fade-in">
@@ -90,12 +112,12 @@
<div class="section-badge">History</div> <div class="section-badge">History</div>
<h2 id="history-heading">Our Origins</h2> <h2 id="history-heading">Our Origins</h2>
<p> <p>
Tecvico (formerly known as Visera) has been developing since 2021 by the Radiuma has been developing since 2021 by the
Quantitative Radiomolecular Imaging and Therapy (Qurit) lab &amp; program at the Quantitative Radiomolecular Imaging and Therapy (Qurit) lab &amp; program at the
University of British Columbia &amp; BC Cancer Research Institute, Vancouver, BC, Canada. University of British Columbia &amp; BC Cancer Research Institute, Vancouver, BC, Canada.
</p> </p>
<p> <p>
Our mission is to bridge the gap between cutting-edge radiomics research and Radiuma's mission is to bridge the gap between cutting-edge radiomics research and
practical clinical application by providing standardized, reproducible, and practical clinical application by providing standardized, reproducible, and
user-friendly software tools. user-friendly software tools.
</p> </p>
+222 -23
View File
@@ -2,7 +2,7 @@
{% load static %} {% load static %}
{% block title %}Contact{% endblock %} {% block title %}Contact{% endblock %}
{% block meta_description %}Contact Tecvico Corp — support for ViSERA software and general inquiries.{% endblock %} {% block meta_description %}Contact Radiuma — support for Radiuma software and general inquiries.{% endblock %}
{% block content %} {% block content %}
@@ -15,66 +15,265 @@
<div class="page-hero-content"> <div class="page-hero-content">
<div class="section-badge">Get in Touch</div> <div class="section-badge">Get in Touch</div>
<h1 class="page-hero-title" id="contact-heading">Contact Us</h1> <h1 class="page-hero-title" id="contact-heading">Contact Us</h1>
<p class="page-hero-subtitle">Support for ViSERA software is available via email and Discord.</p> <p class="page-hero-subtitle">Have a question or want to reach the Radiuma team? Fill out the form or contact us directly below.</p>
</div> </div>
</div> </div>
</section> </section>
<section class="section" aria-labelledby="contact-details-heading"> <section class="section" aria-labelledby="contact-form-heading">
<div class="container"> <div class="container">
<h2 class="sr-only" id="contact-details-heading">Contact Details</h2> <div class="contact-layout">
<div class="contact-grid">
<div class="contact-form-wrap glass-card fade-in">
<h2 class="contact-form-title" id="contact-form-heading">Send a Message</h2>
<form id="contactForm" novalidate action="{% url 'pages:contact' %}" method="post">
{% csrf_token %}
<div class="form-group">
<label for="id_name">Full Name <span class="required-star">*</span></label>
{{ form.name }}
<span class="form-field-error" data-field="name"></span>
</div>
<div class="form-group">
<label for="id_title">Subject / Title <span class="required-star">*</span></label>
{{ form.title }}
<span class="form-field-error" data-field="title"></span>
</div>
<div class="form-group">
<label for="id_description">Message <span class="required-star">*</span></label>
{{ form.description }}
<span class="form-field-error" data-field="description"></span>
</div>
<div class="form-group">
<label for="id_email">Email <span class="optional-label">(optional)</span></label>
{{ form.email }}
<span class="form-field-error" data-field="email"></span>
</div>
<div class="form-group form-group--captcha">
<label for="id_captcha">
Verification: what is <strong id="captchaQuestion">{{ captcha_question }}</strong>?
<span class="required-star">*</span>
</label>
<input type="number" name="captcha_answer" id="id_captcha"
placeholder="Answer" min="0" max="18" required
class="captcha-input" />
<span class="form-field-error" data-field="captcha"></span>
</div>
<button type="submit" class="btn-primary btn-submit" id="submitBtn">
<span class="btn-label">Send Message</span>
<span class="btn-spinner" hidden aria-hidden="true">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83"/>
</svg>
</span>
</button>
</form>
</div>
<aside class="contact-sidebar">
<div class="contact-card glass-card fade-in"> <div class="contact-card glass-card fade-in">
<div class="contact-icon" aria-hidden="true"> <div class="contact-icon" aria-hidden="true">
<svg width="32" height="32" viewBox="0 0 32 32" fill="none"> <svg width="28" height="28" viewBox="0 0 32 32" fill="none">
<path d="M5 8C5 6.895 5.895 6 7 6H25C26.105 6 27 6.895 27 8V22C27 23.105 26.105 24 25 24H7C5.895 24 5 23.105 5 22V8Z" stroke="currentColor" stroke-width="1.5"/> <path d="M5 8C5 6.895 5.895 6 7 6H25C26.105 6 27 6.895 27 8V22C27 23.105 26.105 24 25 24H7C5.895 24 5 23.105 5 22V8Z" stroke="currentColor" stroke-width="1.5"/>
<path d="M5 9L16 16L27 9" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> <path d="M5 9L16 16L27 9" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
</svg> </svg>
</div> </div>
<h3 class="contact-card-title">Email Support</h3> <h3 class="contact-card-title">Email Support</h3>
<p class="contact-card-desc"> <p class="contact-card-desc">For direct software support:</p>
For software support and general inquiries, reach us at: <a href="mailto:support@visera.ca" class="contact-email-link">support@visera.ca</a>
</p>
<a href="mailto:support@tecvico.com" class="contact-email-link">
support@tecvico.com
</a>
</div> </div>
<div class="contact-card glass-card fade-in"> <div class="contact-card glass-card fade-in">
<div class="contact-icon" aria-hidden="true"> <div class="contact-icon" aria-hidden="true">
<svg width="32" height="32" viewBox="0 0 24 24" fill="currentColor"> <svg width="28" height="28" viewBox="0 0 24 24" fill="currentColor">
<path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057.1 18.08.114 18.1.133 18.11a19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03z"/> <path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057.1 18.08.114 18.1.133 18.11a19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03z"/>
</svg> </svg>
</div> </div>
<h3 class="contact-card-title">Discord Community</h3> <h3 class="contact-card-title">Discord Community</h3>
<p class="contact-card-desc"> <p class="contact-card-desc">Join for community support and announcements.</p>
Join our Discord server for community support, feature discussions, and announcements. <a href="https://discord.gg/9XxA6pV9hb" class="btn-primary btn--sm" target="_blank" rel="noopener noreferrer">Join Discord</a>
</p>
<a href="https://discord.gg/9XxA6pV9hb" class="btn-primary" target="_blank" rel="noopener noreferrer">
Join Discord Forum
</a>
</div> </div>
<div class="contact-card glass-card fade-in"> <div class="contact-card glass-card fade-in">
<div class="contact-icon" aria-hidden="true"> <div class="contact-icon" aria-hidden="true">
<svg width="32" height="32" viewBox="0 0 32 32" fill="none"> <svg width="28" height="28" viewBox="0 0 32 32" fill="none">
<path d="M16 3C11.029 3 7 7.029 7 12C7 19 16 29 16 29C16 29 25 19 25 12C25 7.029 20.971 3 16 3Z" stroke="currentColor" stroke-width="1.5"/> <path d="M16 3C11.029 3 7 7.029 7 12C7 19 16 29 16 29C16 29 25 19 25 12C25 7.029 20.971 3 16 3Z" stroke="currentColor" stroke-width="1.5"/>
<circle cx="16" cy="12" r="3" stroke="currentColor" stroke-width="1.5"/> <circle cx="16" cy="12" r="3" stroke="currentColor" stroke-width="1.5"/>
</svg> </svg>
</div> </div>
<h3 class="contact-card-title">Office Address</h3> <h3 class="contact-card-title">Office</h3>
<address class="contact-address"> <address class="contact-address">
<p>BC Cancer Research Center</p> <p>BC Cancer Research Center</p>
<p>675 West 10th Ave</p> <p>675 West 10th Ave, Office 6-112</p>
<p>Office 6-112</p>
<p>Vancouver, BC, V5Z 1L3</p> <p>Vancouver, BC, V5Z 1L3</p>
<p>Canada</p> <p>Canada</p>
</address> </address>
</div> </div>
</aside>
</div> </div>
</div> </div>
</section> </section>
<div id="noEmailModal" class="modal-overlay" role="dialog" aria-modal="true" aria-labelledby="noEmailTitle" hidden>
<div class="modal-box glass-card">
<div class="modal-icon-wrap">
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<path d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
</svg>
</div>
<h3 class="modal-title" id="noEmailTitle">No reply address</h3>
<p class="modal-body">You haven't provided an email address. Without it, we won't be able to respond to your message. Would you like to add one, or send without a reply address?</p>
<div class="modal-actions">
<button class="btn-ghost" id="modalAddEmail">Add Email</button>
<button class="btn-primary" id="modalSendAnyway">Send Without Email</button>
</div>
</div>
</div>
<div id="successModal" class="modal-overlay" role="dialog" aria-modal="true" aria-labelledby="successTitle" hidden>
<div class="modal-box glass-card">
<div class="modal-icon-wrap modal-icon-wrap--success">
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<path d="M22 11.08V12a10 10 0 11-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/>
</svg>
</div>
<h3 class="modal-title" id="successTitle">Message Sent!</h3>
<p class="modal-body">Thank you for reaching out. We've received your message and will get back to you as soon as possible.</p>
<button class="btn-primary" id="successClose">Done</button>
</div>
</div>
<div id="errorModal" class="modal-overlay" role="dialog" aria-modal="true" aria-labelledby="errorTitle" hidden>
<div class="modal-box glass-card">
<div class="modal-icon-wrap modal-icon-wrap--error">
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/>
</svg>
</div>
<h3 class="modal-title" id="errorTitle">Something Went Wrong</h3>
<p class="modal-body" id="errorModalMsg">Please check your answers and try again.</p>
<button class="btn-ghost" id="errorClose">Try Again</button>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
(function () {
const form = document.getElementById('contactForm');
if (!form) return;
const emailField = form.querySelector('#id_email');
const submitBtn = form.querySelector('#submitBtn');
const btnLabel = submitBtn.querySelector('.btn-label');
const btnSpinner = submitBtn.querySelector('.btn-spinner');
function showModal(id) {
const el = document.getElementById(id);
if (el) { el.hidden = false; el.focus(); }
}
function hideModal(id) {
const el = document.getElementById(id);
if (el) el.hidden = true;
}
function clearErrors() {
form.querySelectorAll('.form-field-error').forEach(el => { el.textContent = ''; });
form.querySelectorAll('.form-group').forEach(el => el.classList.remove('has-error'));
}
function showErrors(errors) {
clearErrors();
Object.entries(errors).forEach(([field, msgs]) => {
const span = form.querySelector(`[data-field="${field}"]`);
if (span) {
span.textContent = msgs[0] || '';
span.closest('.form-group')?.classList.add('has-error');
}
});
}
function setLoading(on) {
submitBtn.disabled = on;
btnLabel.hidden = on;
btnSpinner.hidden = !on;
}
async function submitForm() {
clearErrors();
setLoading(true);
const formData = new FormData(form);
try {
const res = await fetch(form.action || window.location.href, {
method: 'POST',
body: formData,
headers: { 'X-Requested-With': 'XMLHttpRequest' },
});
const data = await res.json();
if (data.success) {
form.reset();
showModal('successModal');
} else {
if (data.errors) showErrors(data.errors);
if (data.errors?.captcha) {
document.getElementById('errorModalMsg').textContent = data.errors.captcha[0];
showModal('errorModal');
} else if (!res.ok) {
showModal('errorModal');
}
if (data.captcha_question) {
document.getElementById('captchaQuestion').textContent = data.captcha_question;
const captchaInput = form.querySelector('#id_captcha');
if (captchaInput) captchaInput.value = '';
}
}
} catch {
showModal('errorModal');
} finally {
setLoading(false);
}
}
form.addEventListener('submit', e => {
e.preventDefault();
if (!emailField.value.trim()) {
showModal('noEmailModal');
} else {
submitForm();
}
});
document.getElementById('modalAddEmail')?.addEventListener('click', () => {
hideModal('noEmailModal');
emailField.focus();
});
document.getElementById('modalSendAnyway')?.addEventListener('click', () => {
hideModal('noEmailModal');
submitForm();
});
document.getElementById('successClose')?.addEventListener('click', () => hideModal('successModal'));
document.getElementById('errorClose')?.addEventListener('click', () => hideModal('errorModal'));
document.querySelectorAll('.modal-overlay').forEach(overlay => {
overlay.addEventListener('click', e => {
if (e.target === overlay) overlay.hidden = true;
});
});
document.addEventListener('keydown', e => {
if (e.key === 'Escape') {
document.querySelectorAll('.modal-overlay').forEach(m => { m.hidden = true; });
}
});
})();
</script>
{% endblock %} {% endblock %}
+10 -21
View File
@@ -2,7 +2,7 @@
{% load static %} {% load static %}
{% block title %}Downloads{% endblock %} {% block title %}Downloads{% endblock %}
{% block meta_description %}Download ViSERA by Tecvico — available for Windows, macOS, and Linux.{% endblock %} {% block meta_description %}Download Radiuma by Radiuma — available for Windows, macOS, and Linux.{% endblock %}
{% block content %} {% block content %}
@@ -10,12 +10,13 @@
<div class="page-hero-blobs" aria-hidden="true"> <div class="page-hero-blobs" aria-hidden="true">
<div class="blob blob--1"></div> <div class="blob blob--1"></div>
<div class="blob blob--2"></div> <div class="blob blob--2"></div>
<img src="{% static 'images/modern-shape.svg' %}" class="blob-img blob-img--downloads" alt="" />
</div> </div>
<div class="container"> <div class="container">
<div class="page-hero-content"> <div class="page-hero-content">
<div class="section-badge">Software</div> <div class="section-badge">Software</div>
<h1 class="page-hero-title" id="downloads-heading">Downloads</h1> <h1 class="page-hero-title" id="downloads-heading">Downloads</h1>
<p class="page-hero-subtitle">Download the latest version of ViSERA for your platform.</p> <p class="page-hero-subtitle">Download the latest version of Radiuma for your platform.</p>
</div> </div>
</div> </div>
</section> </section>
@@ -27,9 +28,7 @@
<div class="download-card glass-card fade-in {% if not windows_items %}download-card--unavailable{% endif %}"> <div class="download-card glass-card fade-in {% if not windows_items %}download-card--unavailable{% endif %}">
<div class="download-platform-icon" aria-hidden="true"> <div class="download-platform-icon" aria-hidden="true">
<svg width="48" height="48" viewBox="0 0 24 24" fill="currentColor"> <img src="{% static 'images/icon-windows.svg' %}" alt="Windows" width="56" height="56" class="platform-icon" />
<path d="M0 3.449L9.75 2.1v9.451H0m10.949-9.602L24 0v11.4H10.949M0 12.6h9.75v9.451L0 20.699M10.949 12.6H24V24l-12.9-1.801"/>
</svg>
</div> </div>
<h3 class="download-platform-name">Windows</h3> <h3 class="download-platform-name">Windows</h3>
<p class="download-platform-desc">Windows 10 and up (64-bit)</p> <p class="download-platform-desc">Windows 10 and up (64-bit)</p>
@@ -40,9 +39,7 @@
{% if item.description %}<p class="download-item-desc">{{ item.description }}</p>{% endif %} {% if item.description %}<p class="download-item-desc">{{ item.description }}</p>{% endif %}
{% if item.download_url and item.download_url != '#' %} {% if item.download_url and item.download_url != '#' %}
<a href="{{ item.download_url }}" class="btn-primary download-btn" download> <a href="{{ item.download_url }}" class="btn-primary download-btn" download>
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" aria-hidden="true"> <img src="{% static 'images/icon-download.svg' %}" width="18" height="18" alt="" aria-hidden="true" class="btn-icon-img" />
<path d="M9 2V12M9 12L5 8M9 12L13 8M3 15H15" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
Download Download
</a> </a>
{% endif %} {% endif %}
@@ -55,9 +52,7 @@
<div class="download-card glass-card fade-in {% if not macos_items %}download-card--unavailable{% endif %}"> <div class="download-card glass-card fade-in {% if not macos_items %}download-card--unavailable{% endif %}">
<div class="download-platform-icon" aria-hidden="true"> <div class="download-platform-icon" aria-hidden="true">
<svg width="48" height="48" viewBox="0 0 24 24" fill="currentColor"> <img src="{% static 'images/icon-macos.svg' %}" alt="macOS" width="56" height="56" class="platform-icon" />
<path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.8-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z"/>
</svg>
</div> </div>
<h3 class="download-platform-name">macOS</h3> <h3 class="download-platform-name">macOS</h3>
<p class="download-platform-desc">macOS 11 Big Sur and up</p> <p class="download-platform-desc">macOS 11 Big Sur and up</p>
@@ -68,9 +63,7 @@
{% if item.description %}<p class="download-item-desc">{{ item.description }}</p>{% endif %} {% if item.description %}<p class="download-item-desc">{{ item.description }}</p>{% endif %}
{% if item.download_url and item.download_url != '#' %} {% if item.download_url and item.download_url != '#' %}
<a href="{{ item.download_url }}" class="btn-primary download-btn" download> <a href="{{ item.download_url }}" class="btn-primary download-btn" download>
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" aria-hidden="true"> <img src="{% static 'images/icon-download.svg' %}" width="18" height="18" alt="" aria-hidden="true" class="btn-icon-img" />
<path d="M9 2V12M9 12L5 8M9 12L13 8M3 15H15" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
Download Download
</a> </a>
{% endif %} {% endif %}
@@ -83,9 +76,7 @@
<div class="download-card glass-card fade-in {% if not linux_items %}download-card--unavailable{% endif %}"> <div class="download-card glass-card fade-in {% if not linux_items %}download-card--unavailable{% endif %}">
<div class="download-platform-icon" aria-hidden="true"> <div class="download-platform-icon" aria-hidden="true">
<svg width="48" height="48" viewBox="0 0 24 24" fill="currentColor"> <img src="{% static 'images/icon-linux.svg' %}" alt="Linux" width="56" height="56" class="platform-icon" />
<path d="M12.504 0c-.155 0-.315.008-.48.021-4.226.333-3.105 4.807-3.17 6.298-.076 1.092-.3 1.953-1.05 3.02-.885 1.051-2.127 2.75-2.716 4.521-.278.832-.41 1.684-.287 2.489a.424.424 0 00-.11.135c-.26.268-.45.6-.663.839-.199.199-.485.267-.797.4-.313.136-.658.269-.864.68-.09.189-.136.394-.132.602 0 .199.027.4.055.536.058.399.116.728.04.97-.249.68-.28 1.145-.106 1.484.174.334.535.47.94.601.81.2 1.91.135 2.774.6.926.466 1.866.67 2.616.47.526-.116.97-.464 1.208-.946.587-.003 1.23-.EraseINIT.3.337.572.95.23 1.681-.078.277-.21.557-.303.815-.125.35-.169.63-.046.875.315.648 1.086.698 1.728.hyperparameter698.256 0 .504-.034.72-.046.372-.022.626.026.783.17.428.39.405 1.353.504 2.408.096.99.184 2.023.618 2.813.22.391.528.717.938.939.41.22.915.316 1.44.282.52-.033 1.092-.218 1.544-.668.296-.296.522-.723.58-1.226.029-.248.017-.51-.02-.77-.15-1.168.056-2.22.617-2.82.274-.294.548-.452.748-.606.238-.186.427-.41.554-.676.247-.517.194-1.107-.026-1.633-.11-.271-.238-.517-.324-.773-.148-.448-.172-.913.053-1.377.226-.467.67-.876 1.02-1.428a.424.424 0 00.11-.134c.25-.43.31-.915.245-1.395-.065-.476-.245-.937-.49-1.37-.245-.436-.54-.838-.807-1.278-.254-.428-.47-.903-.522-1.445-.1-.994.268-2.1.368-3.158.05-.53.04-1.046-.059-1.518-.097-.47-.3-.9-.616-1.24-.636-.68-1.608-.904-2.527-.974-.45-.035-.899-.026-1.334.026zm.042 1.174c.42-.047.852-.056 1.277-.022.812.062 1.59.262 2.047.758.228.245.37.554.44.918.073.366.08.78.03 1.233-.103 1.055-.486 2.198-.36 3.34.061.567.302 1.09.575 1.56.27.46.572.876.822 1.319.25.443.43.904.483 1.362.053.45.003.895-.2 1.269a.37.37 0 00-.099.11c-.35.56-.83.993-1.1 1.57-.28.587-.295 1.218-.12 1.822.088.3.215.585.33.869.217.526.253 1.015.066 1.4a1.088 1.088 0 01-.354.43c-.22.162-.502.322-.806.638-.7.742-.924 1.963-.756 3.254.028.207.038.414.013.605-.045.38-.19.688-.41.908-.33.335-.765.497-1.178.524-.414.027-.83-.054-1.14-.22-.315-.169-.54-.43-.691-.714-.358-.647-.445-1.572-.54-2.553-.1-1.012-.118-2.066-.67-2.593-.269-.247-.645-.345-1.117-.32-.232.014-.455.047-.677.047-.44 0-.836-.054-.97-.33-.065-.133-.073-.36.043-.688.1-.29.238-.586.292-.866.133-.676.05-1.313-.313-1.75-.364-.44-1.027-.604-1.718-.777-.69-.176-1.44-.396-1.582-.862-.092-.302-.06-.666.11-1.127.087-.23.097-.482.054-.727-.083-.499.053-.986.22-1.427.246-.66 1.463-2.382 2.39-3.479.87-1.044 1.15-2.02 1.22-3.167.063-1.26-.744-5.02 2.57-5.27z"/>
</svg>
</div> </div>
<h3 class="download-platform-name">Linux</h3> <h3 class="download-platform-name">Linux</h3>
<p class="download-platform-desc">Ubuntu 20.04+ and compatible distributions</p> <p class="download-platform-desc">Ubuntu 20.04+ and compatible distributions</p>
@@ -96,9 +87,7 @@
{% if item.description %}<p class="download-item-desc">{{ item.description }}</p>{% endif %} {% if item.description %}<p class="download-item-desc">{{ item.description }}</p>{% endif %}
{% if item.download_url and item.download_url != '#' %} {% if item.download_url and item.download_url != '#' %}
<a href="{{ item.download_url }}" class="btn-primary download-btn" download> <a href="{{ item.download_url }}" class="btn-primary download-btn" download>
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" aria-hidden="true"> <img src="{% static 'images/icon-download.svg' %}" width="18" height="18" alt="" aria-hidden="true" class="btn-icon-img" />
<path d="M9 2V12M9 12L5 8M9 12L13 8M3 15H15" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
Download Download
</a> </a>
{% endif %} {% endif %}
@@ -118,7 +107,7 @@
<div class="info-block glass-card fade-in"> <div class="info-block glass-card fade-in">
<h2 id="reinstall-heading">Installation Notes</h2> <h2 id="reinstall-heading">Installation Notes</h2>
<p> <p>
If you have installed an older version of ViSERA, you can install the new version If you have installed an older version of Radiuma, you can install the new version
over it without removing the previous installation. However, if you encounter any over it without removing the previous installation. However, if you encounter any
problems, please remove the old version first before reinstalling. problems, please remove the old version first before reinstalling.
</p> </p>
+1 -1
View File
@@ -2,7 +2,7 @@
{% load static %} {% load static %}
{% block title %}FAQ{% endblock %} {% block title %}FAQ{% endblock %}
{% block meta_description %}Frequently asked questions about ViSERA by Tecvico — licensing, citation, system requirements, and more.{% endblock %} {% block meta_description %}Frequently asked questions about Radiuma by Radiuma — licensing, citation, system requirements, and more.{% endblock %}
{% block content %} {% block content %}
+43 -9
View File
@@ -2,7 +2,7 @@
{% load static %} {% load static %}
{% block title %}Home{% endblock %} {% block title %}Home{% endblock %}
{% block meta_description %}Tecvico Corp — A Powerful Workflow Generator for Standardized Radiomics Analysis and Medical Image Visualization.{% endblock %} {% block meta_description %}Radiuma — A Powerful Workflow Generator for Standardized Radiomics Analysis and Medical Image Visualization.{% endblock %}
{% block content %} {% block content %}
@@ -11,11 +11,14 @@
<div class="blob blob--1"></div> <div class="blob blob--1"></div>
<div class="blob blob--2"></div> <div class="blob blob--2"></div>
<div class="blob blob--3"></div> <div class="blob blob--3"></div>
<img src="{% static 'images/blob-blue-1.svg' %}" class="blob-img blob-img--hero-left" alt="" />
<img src="{% static 'images/abstract-shapes.svg' %}" class="blob-img blob-img--hero-right" alt="" />
</div> </div>
<div class="container hero-content"> <div class="container hero-content">
<div class="hero-text">
<div class="hero-badge">Developing since 2021</div> <div class="hero-badge">Developing since 2021</div>
<h1 class="hero-title" id="hero-heading"> <h1 class="hero-title" id="hero-heading">
Tecvico, Radiuma,
<br /> <br />
<span class="gradient-text">A Powerful Workflow Generator</span> <span class="gradient-text">A Powerful Workflow Generator</span>
</h1> </h1>
@@ -23,13 +26,18 @@
for Standardized Radiomics Analysis and Medical Image Visualization for Standardized Radiomics Analysis and Medical Image Visualization
</p> </p>
<p class="hero-description"> <p class="hero-description">
ViSERA is a free, open-source software specialized for visualization, processing, Radiuma is a free, open-source software specialized for visualization, processing,
segmentation, registration, fusion and analysis of medical and biomedical images, segmentation, registration, fusion and analysis of medical and biomedical images,
including radiomics and machine learning analysis. including radiomics and machine learning analysis.
</p> </p>
<div class="hero-actions"> <div class="hero-actions">
<a href="{% url 'pages:downloads' %}" class="btn-primary">Download Now</a> <a href="{% url 'pages:downloads' %}" class="btn-primary">Download Now</a>
<a href="{% url 'pages:about' %}" class="btn-ghost">About Tecvico</a> <a href="{% url 'pages:about' %}" class="btn-ghost">About Radiuma</a>
</div>
</div>
<div class="hero-app-preview fade-in">
<div class="hero-app-preview-glow" aria-hidden="true"></div>
<img src="{% static 'images/screenshot-1.jpg' %}" alt="Radiuma application — main workflow view" loading="eager" />
</div> </div>
</div> </div>
</section> </section>
@@ -86,6 +94,32 @@
</div> </div>
</section> </section>
<section class="section screenshots-section" aria-labelledby="screenshots-heading">
<div class="container">
<div class="section-header">
<div class="section-badge">Gallery</div>
<h2 class="section-title" id="screenshots-heading">See Radiuma in Action</h2>
<p class="section-desc">
Explore Radiuma's powerful interface, workflow builder, and multi-modal image viewer.
</p>
</div>
<div class="screenshots-grid">
<div class="screenshot-item fade-in">
<img src="{% static 'images/screenshot-2.jpg' %}" alt="Radiuma full workflow interface" loading="lazy" />
</div>
<div class="screenshot-item fade-in">
<img src="{% static 'images/screenshot-3.png' %}" alt="Radiuma panel view" loading="lazy" />
</div>
<div class="screenshot-item fade-in">
<img src="{% static 'images/screenshot-5.png' %}" alt="Radiuma segmentation view" loading="lazy" />
</div>
<div class="screenshot-item fade-in">
<img src="{% static 'images/screenshot-8.jpg' %}" alt="Radiuma radiomics results" loading="lazy" />
</div>
</div>
</div>
</section>
{% if nav_main_products %} {% if nav_main_products %}
<section class="section products-section" aria-labelledby="products-heading"> <section class="section products-section" aria-labelledby="products-heading">
<div class="container"> <div class="container">
@@ -123,14 +157,14 @@
<div class="container"> <div class="container">
<div class="section-header"> <div class="section-header">
<div class="section-badge">Value Proposition</div> <div class="section-badge">Value Proposition</div>
<h2 class="section-title" id="problems-heading">What Problems Does Tecvico Solve?</h2> <h2 class="section-title" id="problems-heading">What Problems Does Radiuma Solve?</h2>
</div> </div>
<div class="problems-grid"> <div class="problems-grid">
<div class="problem-item glass-card fade-in"> <div class="problem-item glass-card fade-in">
<div class="problem-number" aria-hidden="true">01</div> <div class="problem-number" aria-hidden="true">01</div>
<h3 class="problem-title">Accessibility</h3> <h3 class="problem-title">Accessibility</h3>
<p class="problem-desc"> <p class="problem-desc">
Tecvico provides a user-friendly interface and a wide range of tools, allowing Radiuma provides a user-friendly interface and a wide range of tools, allowing
researchers to perform complex data analysis without extensive technical knowledge researchers to perform complex data analysis without extensive technical knowledge
or programming expertise. or programming expertise.
</p> </p>
@@ -139,7 +173,7 @@
<div class="problem-number" aria-hidden="true">02</div> <div class="problem-number" aria-hidden="true">02</div>
<h3 class="problem-title">Integrated Tools</h3> <h3 class="problem-title">Integrated Tools</h3>
<p class="problem-desc"> <p class="problem-desc">
Tecvico integrates a vast collection of tools and resources from various domains Radiuma integrates a vast collection of tools and resources from various domains
of healthcare and medical imaging research in a common, unified environment. of healthcare and medical imaging research in a common, unified environment.
</p> </p>
</div> </div>
@@ -147,7 +181,7 @@
<div class="problem-number" aria-hidden="true">03</div> <div class="problem-number" aria-hidden="true">03</div>
<h3 class="problem-title">Flexibility</h3> <h3 class="problem-title">Flexibility</h3>
<p class="problem-desc"> <p class="problem-desc">
Tecvico offers flexibility in terms of tool optimization and workflow customization Radiuma offers flexibility in terms of tool optimization and workflow customization
to match your specific research requirements. to match your specific research requirements.
</p> </p>
</div> </div>
@@ -171,7 +205,7 @@
<div class="section-badge">Our Story</div> <div class="section-badge">Our Story</div>
<h2 class="section-title" id="about-strip-heading">More to Know</h2> <h2 class="section-title" id="about-strip-heading">More to Know</h2>
<p class="about-strip-text"> <p class="about-strip-text">
Tecvico has been developing since 2021 by the Quantitative Radiomolecular Imaging Radiuma has been developing since 2021 by the Quantitative Radiomolecular Imaging
and Therapy (Qurit) lab &amp; program at the University of British Columbia &amp; and Therapy (Qurit) lab &amp; program at the University of British Columbia &amp;
BC Cancer Research Institute, Vancouver, BC, Canada. BC Cancer Research Institute, Vancouver, BC, Canada.
</p> </p>
+6 -6
View File
@@ -8,9 +8,9 @@
<div class="footer-grid"> <div class="footer-grid">
<div class="footer-brand-col"> <div class="footer-brand-col">
<a href="{% url 'pages:home' %}" class="footer-logo" aria-label="Tecvico home"> <a href="{% url 'pages:home' %}" class="footer-logo" aria-label="Radiuma home">
<span class="brand-icon">T</span> <span class="brand-icon">R</span>
<span class="brand-name">Tecvico</span> <span class="brand-name">Radiuma</span>
</a> </a>
<p class="footer-tagline"> <p class="footer-tagline">
Advancing medical imaging and radiomics research through innovative, standardized software solutions. Advancing medical imaging and radiomics research through innovative, standardized software solutions.
@@ -27,7 +27,7 @@
<h3 class="footer-heading">Navigation</h3> <h3 class="footer-heading">Navigation</h3>
<ul class="footer-links" role="list"> <ul class="footer-links" role="list">
<li><a href="{% url 'pages:home' %}">Home</a></li> <li><a href="{% url 'pages:home' %}">Home</a></li>
<li><a href="{% url 'pages:about' %}">What is Tecvico</a></li> <li><a href="{% url 'pages:about' %}">What is Radiuma</a></li>
<li><a href="{% url 'products:overview' %}">Products</a></li> <li><a href="{% url 'products:overview' %}">Products</a></li>
<li><a href="{% url 'pages:downloads' %}">Downloads</a></li> <li><a href="{% url 'pages:downloads' %}">Downloads</a></li>
<li><a href="{% url 'pages:faq' %}">FAQ</a></li> <li><a href="{% url 'pages:faq' %}">FAQ</a></li>
@@ -53,14 +53,14 @@
<p>675 West 10th Ave, Office 6-112</p> <p>675 West 10th Ave, Office 6-112</p>
<p>Vancouver, BC, V5Z 1L3</p> <p>Vancouver, BC, V5Z 1L3</p>
</address> </address>
<a href="mailto:support@tecvico.com" class="footer-email">support@tecvico.com</a> <a href="mailto:support@visera.ca" class="footer-email">support@visera.ca</a>
</div> </div>
</div> </div>
<div class="footer-bottom"> <div class="footer-bottom">
<p class="footer-copy"> <p class="footer-copy">
&copy; {% now "Y" %} Tecvico Corp. All rights reserved. Formerly known as Visera. &copy; {% now "Y" %} Radiuma. All rights reserved.
</p> </p>
<p class="footer-credit"> <p class="footer-credit">
Developed at <a href="https://www.qurit.ca" target="_blank" rel="noopener noreferrer">Qurit Lab</a>, Developed at <a href="https://www.qurit.ca" target="_blank" rel="noopener noreferrer">Qurit Lab</a>,
+4 -4
View File
@@ -2,9 +2,9 @@
<nav class="navbar" id="navbar" role="navigation" aria-label="Main navigation"> <nav class="navbar" id="navbar" role="navigation" aria-label="Main navigation">
<div class="navbar-container"> <div class="navbar-container">
<a href="{% url 'pages:home' %}" class="navbar-brand" aria-label="Tecvico home"> <a href="{% url 'pages:home' %}" class="navbar-brand" aria-label="Radiuma home">
<span class="brand-icon">T</span> <span class="brand-icon">R</span>
<span class="brand-name">Tecvico</span> <span class="brand-name">Radiuma</span>
</a> </a>
<button class="navbar-toggle" id="navbarToggle" aria-expanded="false" aria-controls="navbarMenu" aria-label="Toggle navigation"> <button class="navbar-toggle" id="navbarToggle" aria-expanded="false" aria-controls="navbarMenu" aria-label="Toggle navigation">
@@ -68,7 +68,7 @@
<li class="nav-item"> <li class="nav-item">
<a href="{% url 'pages:about' %}" class="nav-link {% if request.resolver_match.url_name == 'about' %}active{% endif %}"> <a href="{% url 'pages:about' %}" class="nav-link {% if request.resolver_match.url_name == 'about' %}active{% endif %}">
What is Tecvico What is Radiuma
</a> </a>
</li> </li>
+5 -2
View File
@@ -1,6 +1,7 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load static %} {% load static %}
{% block title %}{{ main_product.name }}{% endblock %} {% block title %}{{ main_product.name }}{% endblock %}
{% block meta_description %}{{ main_product.short_description }}{% endblock %} {% block meta_description %}{{ main_product.short_description }}{% endblock %}
@@ -34,11 +35,13 @@
<div class="container"> <div class="container">
<div class="product-detail-intro glass-card fade-in"> <div class="product-detail-intro glass-card fade-in">
<h2 class="sr-only" id="product-desc-heading">About {{ main_product.name }}</h2> <h2 class="sr-only" id="product-desc-heading">About {{ main_product.name }}</h2>
{% if main_product.image %}
<div class="product-detail-image"> <div class="product-detail-image">
{% if main_product.image %}
<img src="{{ main_product.image.url }}" alt="{{ main_product.name }}" loading="lazy" /> <img src="{{ main_product.image.url }}" alt="{{ main_product.name }}" loading="lazy" />
</div> {% else %}
<img src="{% static 'images/logo.png' %}" alt="{{ main_product.name }} logo" loading="lazy" class="product-logo-img" />
{% endif %} {% endif %}
</div>
<div class="product-detail-text"> <div class="product-detail-text">
<p class="product-detail-desc">{{ main_product.description }}</p> <p class="product-detail-desc">{{ main_product.description }}</p>
</div> </div>
+1 -1
View File
@@ -2,7 +2,7 @@
{% load static %} {% load static %}
{% block title %}Products{% endblock %} {% block title %}Products{% endblock %}
{% block meta_description %}Explore Tecvico's suite of medical imaging and radiomics software products.{% endblock %} {% block meta_description %}Explore Radiuma's suite of medical imaging and radiomics software products.{% endblock %}
{% block content %} {% block content %}