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
@@ -45,7 +45,7 @@ tecvico_website/
| URL | View | Description |
|---|---|---|
| `/` | `HomeView` | Landing page |
| `/about/` | `AboutView` | What is Tecvico |
| `/about/` | `AboutView` | What is Radiuma |
| `/products/` | `ProductOverviewView` | All main products |
| `/products/<main-slug>/` | `MainProductDetailView` | Main product + sub-products |
| `/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:
- **ViSERA** (MainProduct) with 5 sub-products:
- **Radiuma** (MainProduct) with 5 sub-products:
- Image Processing
- Radiomics Features
- Medical Image Visualization
@@ -275,4 +275,4 @@ Key CSS custom properties are in `static/css/main.css` under `:root`.
## 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 = [
{
"name": "ViSERA",
"slug": "visera",
"name": "Radiuma",
"slug": "radiuma",
"short_description": "Visualized & Standardized Environment for Radiomics Analysis",
"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 "
"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 "
"contribution. It enables standardized and reproducible radiomic feature "
"extraction in compliance with the Image Biomarker Standardization Initiative "
@@ -28,7 +28,7 @@ MAIN_PRODUCTS = [
"description": (
"Advanced image processing capabilities including standardized filtering "
"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 "
"for consistent, reproducible research outcomes."
),
@@ -37,7 +37,7 @@ MAIN_PRODUCTS = [
{
"title": "Image Filtering Techniques",
"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 "
"Initiative (IBSI) phase 2. These filters enable reproducible "
"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",
"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",
"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 "
"other combined modality studies. Standardized Uptake Value (SUV) "
"conversion is also supported."
@@ -75,7 +75,7 @@ MAIN_PRODUCTS = [
"slug": "radiomics-features",
"short_description": "IBSI 1.0 compliant handcrafted radiomic feature extraction",
"description": (
"ViSERA provides comprehensive handcrafted radiomic feature extraction "
"Radiuma provides comprehensive handcrafted radiomic feature extraction "
"fully standardized by the Image Biomarker Standardization Initiative "
"(IBSI 1.0). Features are computed from segmented regions of interest "
"across multiple image modalities, enabling reproducible quantitative "
@@ -86,7 +86,7 @@ MAIN_PRODUCTS = [
{
"title": "IBSI Compliant Feature Extraction",
"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 "
"from segmented Regions of Interest (ROIs) and are fully "
"reproducible across different platforms and institutions."
@@ -110,7 +110,7 @@ MAIN_PRODUCTS = [
"slug": "medical-image-visualization",
"short_description": "Professional multi-modality medical image viewer",
"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 "
"comfortable, intuitive controls for slice navigation, windowing, "
"zoom, and annotation, suitable for radiation oncologists, radiologists, "
@@ -140,7 +140,7 @@ MAIN_PRODUCTS = [
"slug": "format-conversion",
"short_description": "Professional converter for medical imaging file formats",
"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, "
"NIFTI, NRRD, MHA, and other formats without loss of spatial metadata "
"or patient information integrity."
@@ -169,7 +169,7 @@ MAIN_PRODUCTS = [
"slug": "workflow-management",
"short_description": "Reproducible research workflow creation and sharing",
"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 "
"processing steps — from image loading and preprocessing to feature "
"extraction and machine learning — into reproducible, shareable sequences "
@@ -199,9 +199,9 @@ MAIN_PRODUCTS = [
FAQ_ENTRIES = [
{
"question": "What is the ViSERA license?",
"question": "What is the Radiuma license?",
"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). "
"This means you may use, share, and adapt the software for non-commercial "
"research purposes, provided you give appropriate credit and distribute "
@@ -210,21 +210,21 @@ FAQ_ENTRIES = [
"order": 1,
},
{
"question": "How do I cite ViSERA in my research?",
"question": "How do I cite Radiuma in my research?",
"answer": (
"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. 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 "
"Generator. Proc. IEEE Medical Imaging Conference, 2023."
),
"order": 2,
},
{
"question": "Which operating systems does ViSERA support?",
"question": "Which operating systems does Radiuma support?",
"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 "
"and coming soon. Follow our Discord or check the Downloads page for updates."
),
@@ -241,9 +241,9 @@ FAQ_ENTRIES = [
"order": 4,
},
{
"question": "Is ViSERA suitable for clinical use?",
"question": "Is Radiuma suitable for clinical use?",
"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 "
"qualified medical professionals for clinical decisions."
),
@@ -252,7 +252,7 @@ FAQ_ENTRIES = [
{
"question": "Where can I get support or report issues?",
"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 "
"use the Discord forum or contact us directly by email."
),
@@ -262,16 +262,16 @@ FAQ_ENTRIES = [
DOWNLOAD_ITEMS = [
{
"name": "ViSERA Desktop",
"name": "Radiuma Desktop",
"platform": "windows",
"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.",
"is_active": True,
"order": 1,
},
{
"name": "ViSERA Desktop",
"name": "Radiuma Desktop",
"platform": "macos",
"version": "Coming Soon",
"download_url": "#",
@@ -280,7 +280,7 @@ DOWNLOAD_ITEMS = [
"order": 2,
},
{
"name": "ViSERA Desktop",
"name": "Radiuma Desktop",
"platform": "linux",
"version": "Coming Soon",
"download_url": "#",
@@ -292,7 +292,7 @@ DOWNLOAD_ITEMS = [
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):
parser.add_argument(
+14 -1
View File
@@ -1,6 +1,19 @@
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)
+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
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):
question = models.CharField(max_length=500)
answer = models.TextField()
+2 -2
View File
@@ -27,14 +27,14 @@ class AboutViewTest(TestCase):
class DownloadsViewTest(TestCase):
def setUp(self):
DownloadItem.objects.create(
name="ViSERA Desktop",
name="Radiuma Desktop",
platform="windows",
version="1.0",
download_url="https://example.com/windows",
is_active=True,
)
DownloadItem.objects.create(
name="ViSERA Desktop",
name="Radiuma Desktop",
platform="macos",
version="Coming Soon",
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 .models import DownloadItem, FAQEntry
from .forms import ContactForm
from .models import ContactSubmission, DownloadItem, FAQEntry
class HomeView(TemplateView):
@@ -36,5 +42,43 @@ class FAQView(ListView):
queryset = FAQEntry.objects.filter(is_active=True)
class ContactView(TemplateView):
class ContactView(View):
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):
def setUp(self):
self.main_product = MainProduct.objects.create(
name="ViSERA",
slug="visera",
name="Radiuma",
slug="radiuma",
short_description="Radiomics software",
description="Full description.",
)
@@ -57,17 +57,17 @@ class ProductOverviewViewTest(ProductViewsSetup):
class MainProductDetailViewTest(ProductViewsSetup):
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)
self.assertEqual(response.status_code, 200)
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)
self.assertTemplateUsed(response, "products/main_detail.html")
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)
self.assertEqual(response.context["main_product"], self.main_product)
@@ -79,7 +79,7 @@ class MainProductDetailViewTest(ProductViewsSetup):
def test_inactive_product_returns_404(self):
self.main_product.is_active = False
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)
self.assertEqual(response.status_code, 404)
@@ -88,7 +88,7 @@ class SubProductDetailViewTest(ProductViewsSetup):
def test_sub_detail_returns_200(self):
url = reverse(
"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)
self.assertEqual(response.status_code, 200)
@@ -96,7 +96,7 @@ class SubProductDetailViewTest(ProductViewsSetup):
def test_sub_detail_uses_correct_template(self):
url = reverse(
"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)
self.assertTemplateUsed(response, "products/sub_detail.html")
@@ -104,7 +104,7 @@ class SubProductDetailViewTest(ProductViewsSetup):
def test_sub_detail_context_keys(self):
url = reverse(
"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)
self.assertIn("main_product", response.context)
@@ -115,7 +115,7 @@ class SubProductDetailViewTest(ProductViewsSetup):
def test_sub_detail_articles_in_context(self):
url = reverse(
"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)
self.assertIn(self.article, list(response.context["articles"]))
@@ -123,7 +123,7 @@ class SubProductDetailViewTest(ProductViewsSetup):
def test_nonexistent_sub_slug_returns_404(self):
url = reverse(
"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)
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.urls import include, path
admin.site.site_header = "Tecvico Administration"
admin.site.site_title = "Tecvico Admin"
admin.site.index_title = "Welcome to Tecvico Administration"
admin.site.site_header = "Radiuma Administration"
admin.site.site_title = "Radiuma Admin"
admin.site.index_title = "Welcome to Radiuma Administration"
urlpatterns = [
path("admin/", admin.site.urls),
+494 -6
View File
@@ -560,6 +560,59 @@ h1, h2, h3, h4, h5, h6 {
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
============================================================ */
@@ -583,8 +636,17 @@ h1, h2, h3, h4, h5, h6 {
.hero-content {
position: relative;
z-index: 2;
max-width: 780px;
width: 100%;
max-width: var(--container-max);
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 {
@@ -603,10 +665,10 @@ h1, h2, h3, h4, h5, h6 {
}
.hero-title {
font-size: clamp(2.8rem, 7vw, 5.5rem);
font-size: clamp(2rem, 4.5vw, 3.5rem);
font-weight: 800;
line-height: 1.05;
letter-spacing: -0.04em;
line-height: 1.1;
letter-spacing: -0.03em;
margin-bottom: 1.25rem;
animation: fade-slide-up 0.8s ease-out 0.2s both;
}
@@ -1578,6 +1640,129 @@ h1, h2, h3, h4, h5, h6 {
/* ============================================================
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 {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
@@ -1585,7 +1770,7 @@ h1, h2, h3, h4, h5, h6 {
}
.contact-card {
padding: 2.25rem;
padding: 2rem;
display: flex;
flex-direction: column;
gap: 0.75rem;
@@ -1620,6 +1805,84 @@ h1, h2, h3, h4, h5, h6 {
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
============================================================ */
@@ -1902,7 +2165,7 @@ h1, h2, h3, h4, h5, h6 {
}
.hero-title {
font-size: clamp(2.2rem, 8vw, 3rem);
font-size: clamp(1.7rem, 6vw, 2.4rem);
}
.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) {
:root {
--container-padding: 1rem;
@@ -1939,4 +2415,16 @@ h1, h2, h3, h4, h5, h6 {
.contact-grid {
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

+17 -6
View File
@@ -64,15 +64,26 @@ function initMegaMenu() {
const megamenu = productsItem.querySelector('.megamenu');
if (!trigger || !megamenu) return;
productsItem.addEventListener('mouseenter', () => {
let hideTimer = null;
function openMenu() {
clearTimeout(hideTimer);
productsItem.classList.add('open');
trigger.setAttribute('aria-expanded', 'true');
});
}
productsItem.addEventListener('mouseleave', () => {
productsItem.classList.remove('open');
trigger.setAttribute('aria-expanded', 'false');
});
function scheduleClose() {
hideTimer = setTimeout(() => {
productsItem.classList.remove('open');
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) => {
if (e.key === 'Enter' || e.key === ' ') {
+5 -2
View File
@@ -4,8 +4,11 @@
<head>
<meta charset="UTF-8" />
<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 %}" />
<title>{% block title %}Tecvico Corp{% endblock %} | Tecvico</title>
<meta name="description" content="{% block meta_description %}Radiuma — Advanced Medical Imaging & Radiomics Solutions{% endblock %}" />
<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.gstatic.com" crossorigin />
<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" %}
{% load static %}
{% block title %}What is Tecvico{% 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 title %}What is Radiuma{% 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 %}
@@ -10,11 +10,13 @@
<div class="page-hero-blobs" aria-hidden="true">
<div class="blob blob--1"></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 class="container">
<div class="page-hero-content">
<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">
Visualized &amp; Standardized Environment for Radiomics Analysis
</p>
@@ -49,7 +51,7 @@
<div class="standard-badge">IBSI 1.0</div>
<h3 class="standard-title">Radiomic Feature Extraction</h3>
<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
Standardization Initiative (IBSI 1.0).
</p>
@@ -67,7 +69,7 @@
<div class="standard-badge">Python</div>
<h3 class="standard-title">Open Source</h3>
<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.
</p>
</div>
@@ -75,7 +77,7 @@
<div class="standard-badge">End-to-End</div>
<h3 class="standard-title">Standardized Workflows</h3>
<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.
</p>
</div>
@@ -83,6 +85,26 @@
</div>
</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">
<div class="container">
<div class="history-block glass-card fade-in">
@@ -90,12 +112,12 @@
<div class="section-badge">History</div>
<h2 id="history-heading">Our Origins</h2>
<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
University of British Columbia &amp; BC Cancer Research Institute, Vancouver, BC, Canada.
</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
user-friendly software tools.
</p>
+246 -47
View File
@@ -2,7 +2,7 @@
{% load static %}
{% 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 %}
@@ -15,66 +15,265 @@
<div class="page-hero-content">
<div class="section-badge">Get in Touch</div>
<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>
</section>
<section class="section" aria-labelledby="contact-details-heading">
<section class="section" aria-labelledby="contact-form-heading">
<div class="container">
<h2 class="sr-only" id="contact-details-heading">Contact Details</h2>
<div class="contact-grid">
<div class="contact-layout">
<div class="contact-card glass-card fade-in">
<div class="contact-icon" aria-hidden="true">
<svg width="32" height="32" 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 9L16 16L27 9" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
</svg>
</div>
<h3 class="contact-card-title">Email Support</h3>
<p class="contact-card-desc">
For software support and general inquiries, reach us at:
</p>
<a href="mailto:support@tecvico.com" class="contact-email-link">
support@tecvico.com
</a>
<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>
<div class="contact-card glass-card fade-in">
<div class="contact-icon" aria-hidden="true">
<svg width="32" height="32" 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"/>
</svg>
<aside class="contact-sidebar">
<div class="contact-card glass-card fade-in">
<div class="contact-icon" aria-hidden="true">
<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 9L16 16L27 9" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
</svg>
</div>
<h3 class="contact-card-title">Email Support</h3>
<p class="contact-card-desc">For direct software support:</p>
<a href="mailto:support@visera.ca" class="contact-email-link">support@visera.ca</a>
</div>
<h3 class="contact-card-title">Discord Community</h3>
<p class="contact-card-desc">
Join our Discord server for community support, feature discussions, and announcements.
</p>
<a href="https://discord.gg/9XxA6pV9hb" class="btn-primary" target="_blank" rel="noopener noreferrer">
Join Discord Forum
</a>
</div>
<div class="contact-card glass-card fade-in">
<div class="contact-icon" aria-hidden="true">
<svg width="32" height="32" 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"/>
<circle cx="16" cy="12" r="3" stroke="currentColor" stroke-width="1.5"/>
</svg>
<div class="contact-card glass-card fade-in">
<div class="contact-icon" aria-hidden="true">
<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"/>
</svg>
</div>
<h3 class="contact-card-title">Discord Community</h3>
<p class="contact-card-desc">Join for community support and announcements.</p>
<a href="https://discord.gg/9XxA6pV9hb" class="btn-primary btn--sm" target="_blank" rel="noopener noreferrer">Join Discord</a>
</div>
<h3 class="contact-card-title">Office Address</h3>
<address class="contact-address">
<p>BC Cancer Research Center</p>
<p>675 West 10th Ave</p>
<p>Office 6-112</p>
<p>Vancouver, BC, V5Z 1L3</p>
<p>Canada</p>
</address>
</div>
<div class="contact-card glass-card fade-in">
<div class="contact-icon" aria-hidden="true">
<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"/>
<circle cx="16" cy="12" r="3" stroke="currentColor" stroke-width="1.5"/>
</svg>
</div>
<h3 class="contact-card-title">Office</h3>
<address class="contact-address">
<p>BC Cancer Research Center</p>
<p>675 West 10th Ave, Office 6-112</p>
<p>Vancouver, BC, V5Z 1L3</p>
<p>Canada</p>
</address>
</div>
</aside>
</div>
</div>
</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 %}
+10 -21
View File
@@ -2,7 +2,7 @@
{% load static %}
{% 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 %}
@@ -10,12 +10,13 @@
<div class="page-hero-blobs" aria-hidden="true">
<div class="blob blob--1"></div>
<div class="blob blob--2"></div>
<img src="{% static 'images/modern-shape.svg' %}" class="blob-img blob-img--downloads" alt="" />
</div>
<div class="container">
<div class="page-hero-content">
<div class="section-badge">Software</div>
<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>
</section>
@@ -27,9 +28,7 @@
<div class="download-card glass-card fade-in {% if not windows_items %}download-card--unavailable{% endif %}">
<div class="download-platform-icon" aria-hidden="true">
<svg width="48" height="48" viewBox="0 0 24 24" fill="currentColor">
<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>
<img src="{% static 'images/icon-windows.svg' %}" alt="Windows" width="56" height="56" class="platform-icon" />
</div>
<h3 class="download-platform-name">Windows</h3>
<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.download_url and item.download_url != '#' %}
<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">
<path d="M9 2V12M9 12L5 8M9 12L13 8M3 15H15" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<img src="{% static 'images/icon-download.svg' %}" width="18" height="18" alt="" aria-hidden="true" class="btn-icon-img" />
Download
</a>
{% endif %}
@@ -55,9 +52,7 @@
<div class="download-card glass-card fade-in {% if not macos_items %}download-card--unavailable{% endif %}">
<div class="download-platform-icon" aria-hidden="true">
<svg width="48" height="48" viewBox="0 0 24 24" fill="currentColor">
<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>
<img src="{% static 'images/icon-macos.svg' %}" alt="macOS" width="56" height="56" class="platform-icon" />
</div>
<h3 class="download-platform-name">macOS</h3>
<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.download_url and item.download_url != '#' %}
<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">
<path d="M9 2V12M9 12L5 8M9 12L13 8M3 15H15" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<img src="{% static 'images/icon-download.svg' %}" width="18" height="18" alt="" aria-hidden="true" class="btn-icon-img" />
Download
</a>
{% endif %}
@@ -83,9 +76,7 @@
<div class="download-card glass-card fade-in {% if not linux_items %}download-card--unavailable{% endif %}">
<div class="download-platform-icon" aria-hidden="true">
<svg width="48" height="48" viewBox="0 0 24 24" fill="currentColor">
<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>
<img src="{% static 'images/icon-linux.svg' %}" alt="Linux" width="56" height="56" class="platform-icon" />
</div>
<h3 class="download-platform-name">Linux</h3>
<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.download_url and item.download_url != '#' %}
<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">
<path d="M9 2V12M9 12L5 8M9 12L13 8M3 15H15" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<img src="{% static 'images/icon-download.svg' %}" width="18" height="18" alt="" aria-hidden="true" class="btn-icon-img" />
Download
</a>
{% endif %}
@@ -118,7 +107,7 @@
<div class="info-block glass-card fade-in">
<h2 id="reinstall-heading">Installation Notes</h2>
<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
problems, please remove the old version first before reinstalling.
</p>
+1 -1
View File
@@ -2,7 +2,7 @@
{% load static %}
{% 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 %}
+57 -23
View File
@@ -2,7 +2,7 @@
{% load static %}
{% 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 %}
@@ -11,25 +11,33 @@
<div class="blob blob--1"></div>
<div class="blob blob--2"></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 class="container hero-content">
<div class="hero-badge">Developing since 2021</div>
<h1 class="hero-title" id="hero-heading">
Tecvico,
<br />
<span class="gradient-text">A Powerful Workflow Generator</span>
</h1>
<p class="hero-subtitle">
for Standardized Radiomics Analysis and Medical Image Visualization
</p>
<p class="hero-description">
ViSERA is a free, open-source software specialized for visualization, processing,
segmentation, registration, fusion and analysis of medical and biomedical images,
including radiomics and machine learning analysis.
</p>
<div class="hero-actions">
<a href="{% url 'pages:downloads' %}" class="btn-primary">Download Now</a>
<a href="{% url 'pages:about' %}" class="btn-ghost">About Tecvico</a>
<div class="hero-text">
<div class="hero-badge">Developing since 2021</div>
<h1 class="hero-title" id="hero-heading">
Radiuma,
<br />
<span class="gradient-text">A Powerful Workflow Generator</span>
</h1>
<p class="hero-subtitle">
for Standardized Radiomics Analysis and Medical Image Visualization
</p>
<p class="hero-description">
Radiuma is a free, open-source software specialized for visualization, processing,
segmentation, registration, fusion and analysis of medical and biomedical images,
including radiomics and machine learning analysis.
</p>
<div class="hero-actions">
<a href="{% url 'pages:downloads' %}" class="btn-primary">Download Now</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>
</section>
@@ -86,6 +94,32 @@
</div>
</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 %}
<section class="section products-section" aria-labelledby="products-heading">
<div class="container">
@@ -123,14 +157,14 @@
<div class="container">
<div class="section-header">
<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 class="problems-grid">
<div class="problem-item glass-card fade-in">
<div class="problem-number" aria-hidden="true">01</div>
<h3 class="problem-title">Accessibility</h3>
<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
or programming expertise.
</p>
@@ -139,7 +173,7 @@
<div class="problem-number" aria-hidden="true">02</div>
<h3 class="problem-title">Integrated Tools</h3>
<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.
</p>
</div>
@@ -147,7 +181,7 @@
<div class="problem-number" aria-hidden="true">03</div>
<h3 class="problem-title">Flexibility</h3>
<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.
</p>
</div>
@@ -171,7 +205,7 @@
<div class="section-badge">Our Story</div>
<h2 class="section-title" id="about-strip-heading">More to Know</h2>
<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;
BC Cancer Research Institute, Vancouver, BC, Canada.
</p>
+6 -6
View File
@@ -8,9 +8,9 @@
<div class="footer-grid">
<div class="footer-brand-col">
<a href="{% url 'pages:home' %}" class="footer-logo" aria-label="Tecvico home">
<span class="brand-icon">T</span>
<span class="brand-name">Tecvico</span>
<a href="{% url 'pages:home' %}" class="footer-logo" aria-label="Radiuma home">
<span class="brand-icon">R</span>
<span class="brand-name">Radiuma</span>
</a>
<p class="footer-tagline">
Advancing medical imaging and radiomics research through innovative, standardized software solutions.
@@ -27,7 +27,7 @@
<h3 class="footer-heading">Navigation</h3>
<ul class="footer-links" role="list">
<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 'pages:downloads' %}">Downloads</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>Vancouver, BC, V5Z 1L3</p>
</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 class="footer-bottom">
<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 class="footer-credit">
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">
<div class="navbar-container">
<a href="{% url 'pages:home' %}" class="navbar-brand" aria-label="Tecvico home">
<span class="brand-icon">T</span>
<span class="brand-name">Tecvico</span>
<a href="{% url 'pages:home' %}" class="navbar-brand" aria-label="Radiuma home">
<span class="brand-icon">R</span>
<span class="brand-name">Radiuma</span>
</a>
<button class="navbar-toggle" id="navbarToggle" aria-expanded="false" aria-controls="navbarMenu" aria-label="Toggle navigation">
@@ -68,7 +68,7 @@
<li class="nav-item">
<a href="{% url 'pages:about' %}" class="nav-link {% if request.resolver_match.url_name == 'about' %}active{% endif %}">
What is Tecvico
What is Radiuma
</a>
</li>
+5 -2
View File
@@ -1,6 +1,7 @@
{% extends "base.html" %}
{% load static %}
{% block title %}{{ main_product.name }}{% endblock %}
{% block meta_description %}{{ main_product.short_description }}{% endblock %}
@@ -34,11 +35,13 @@
<div class="container">
<div class="product-detail-intro glass-card fade-in">
<h2 class="sr-only" id="product-desc-heading">About {{ main_product.name }}</h2>
{% if main_product.image %}
<div class="product-detail-image">
{% if main_product.image %}
<img src="{{ main_product.image.url }}" alt="{{ main_product.name }}" loading="lazy" />
{% else %}
<img src="{% static 'images/logo.png' %}" alt="{{ main_product.name }} logo" loading="lazy" class="product-logo-img" />
{% endif %}
</div>
{% endif %}
<div class="product-detail-text">
<p class="product-detail-desc">{{ main_product.description }}</p>
</div>
+1 -1
View File
@@ -2,7 +2,7 @@
{% load static %}
{% 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 %}