diff --git a/README.md b/README.md index cb140ce..a001a3e 100644 --- a/README.md +++ b/README.md @@ -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//` | `MainProductDetailView` | Main product + sub-products | | `/products///` | `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. diff --git a/apps/core/management/commands/seed_content.py b/apps/core/management/commands/seed_content.py index 606c319..26159cd 100644 --- a/apps/core/management/commands/seed_content.py +++ b/apps/core/management/commands/seed_content.py @@ -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( diff --git a/apps/pages/admin.py b/apps/pages/admin.py index 9d7eeb2..131b286 100644 --- a/apps/pages/admin.py +++ b/apps/pages/admin.py @@ -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) diff --git a/apps/pages/forms.py b/apps/pages/forms.py new file mode 100644 index 0000000..66c5e8d --- /dev/null +++ b/apps/pages/forms.py @@ -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", + } + ), + } diff --git a/apps/pages/migrations/0002_contactsubmission.py b/apps/pages/migrations/0002_contactsubmission.py new file mode 100644 index 0000000..45f939b --- /dev/null +++ b/apps/pages/migrations/0002_contactsubmission.py @@ -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'], + }, + ), + ] diff --git a/apps/pages/models.py b/apps/pages/models.py index e27f2c8..816f437 100644 --- a/apps/pages/models.py +++ b/apps/pages/models.py @@ -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() diff --git a/apps/pages/tests/test_views.py b/apps/pages/tests/test_views.py index 02d0427..731bfab 100644 --- a/apps/pages/tests/test_views.py +++ b/apps/pages/tests/test_views.py @@ -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="#", diff --git a/apps/pages/views.py b/apps/pages/views.py index fef3471..7828410 100644 --- a/apps/pages/views.py +++ b/apps/pages/views.py @@ -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, + ) diff --git a/apps/products/tests/test_views.py b/apps/products/tests/test_views.py index 2affea3..5c8ce10 100644 --- a/apps/products/tests/test_views.py +++ b/apps/products/tests/test_views.py @@ -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) diff --git a/config/urls.py b/config/urls.py index d669ebb..25d1759 100644 --- a/config/urls.py +++ b/config/urls.py @@ -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), diff --git a/static/css/main.css b/static/css/main.css index 71f3b81..288a2b7 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -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; + } } diff --git a/static/images/abstract-organic-shape.svg b/static/images/abstract-organic-shape.svg new file mode 100644 index 0000000..e8aabe7 --- /dev/null +++ b/static/images/abstract-organic-shape.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/images/abstract-shapes-2.svg b/static/images/abstract-shapes-2.svg new file mode 100644 index 0000000..1ec32f5 --- /dev/null +++ b/static/images/abstract-shapes-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/images/abstract-shapes.svg b/static/images/abstract-shapes.svg new file mode 100644 index 0000000..066717d --- /dev/null +++ b/static/images/abstract-shapes.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/images/blob-blue-1.svg b/static/images/blob-blue-1.svg new file mode 100644 index 0000000..6105b8e --- /dev/null +++ b/static/images/blob-blue-1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/images/blob-blue-2.svg b/static/images/blob-blue-2.svg new file mode 100644 index 0000000..e88030f --- /dev/null +++ b/static/images/blob-blue-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/images/blob-blue-3.svg b/static/images/blob-blue-3.svg new file mode 100644 index 0000000..66d7be7 --- /dev/null +++ b/static/images/blob-blue-3.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/images/favicon-16.png b/static/images/favicon-16.png new file mode 100644 index 0000000..1569638 Binary files /dev/null and b/static/images/favicon-16.png differ diff --git a/static/images/favicon-180.png b/static/images/favicon-180.png new file mode 100644 index 0000000..84a151e Binary files /dev/null and b/static/images/favicon-180.png differ diff --git a/static/images/favicon-32.png b/static/images/favicon-32.png new file mode 100644 index 0000000..6285814 Binary files /dev/null and b/static/images/favicon-32.png differ diff --git a/static/images/icon-download.svg b/static/images/icon-download.svg new file mode 100644 index 0000000..5aace1c --- /dev/null +++ b/static/images/icon-download.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/images/icon-linux.svg b/static/images/icon-linux.svg new file mode 100644 index 0000000..c931de0 --- /dev/null +++ b/static/images/icon-linux.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/images/icon-loading.svg b/static/images/icon-loading.svg new file mode 100644 index 0000000..46726f5 --- /dev/null +++ b/static/images/icon-loading.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/images/icon-macos.svg b/static/images/icon-macos.svg new file mode 100644 index 0000000..35de836 --- /dev/null +++ b/static/images/icon-macos.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/images/icon-windows.svg b/static/images/icon-windows.svg new file mode 100644 index 0000000..581f148 --- /dev/null +++ b/static/images/icon-windows.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/static/images/logo.png b/static/images/logo.png new file mode 100644 index 0000000..9063b4a Binary files /dev/null and b/static/images/logo.png differ diff --git a/static/images/modern-shape.svg b/static/images/modern-shape.svg new file mode 100644 index 0000000..e9ea717 --- /dev/null +++ b/static/images/modern-shape.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/images/screenshot-1.jpg b/static/images/screenshot-1.jpg new file mode 100644 index 0000000..bee8edd Binary files /dev/null and b/static/images/screenshot-1.jpg differ diff --git a/static/images/screenshot-10.jpg b/static/images/screenshot-10.jpg new file mode 100644 index 0000000..9008ed4 Binary files /dev/null and b/static/images/screenshot-10.jpg differ diff --git a/static/images/screenshot-11.jpg b/static/images/screenshot-11.jpg new file mode 100644 index 0000000..161e718 Binary files /dev/null and b/static/images/screenshot-11.jpg differ diff --git a/static/images/screenshot-2.jpg b/static/images/screenshot-2.jpg new file mode 100644 index 0000000..e090d50 Binary files /dev/null and b/static/images/screenshot-2.jpg differ diff --git a/static/images/screenshot-3.png b/static/images/screenshot-3.png new file mode 100644 index 0000000..5e59f85 Binary files /dev/null and b/static/images/screenshot-3.png differ diff --git a/static/images/screenshot-4.png b/static/images/screenshot-4.png new file mode 100644 index 0000000..0379f19 Binary files /dev/null and b/static/images/screenshot-4.png differ diff --git a/static/images/screenshot-5.png b/static/images/screenshot-5.png new file mode 100644 index 0000000..eab7aa0 Binary files /dev/null and b/static/images/screenshot-5.png differ diff --git a/static/images/screenshot-6.png b/static/images/screenshot-6.png new file mode 100644 index 0000000..b48f818 Binary files /dev/null and b/static/images/screenshot-6.png differ diff --git a/static/images/screenshot-7.png b/static/images/screenshot-7.png new file mode 100644 index 0000000..f21fcb8 Binary files /dev/null and b/static/images/screenshot-7.png differ diff --git a/static/images/screenshot-8.jpg b/static/images/screenshot-8.jpg new file mode 100644 index 0000000..bf3f38b Binary files /dev/null and b/static/images/screenshot-8.jpg differ diff --git a/static/images/screenshot-9.jpg b/static/images/screenshot-9.jpg new file mode 100644 index 0000000..75c32f2 Binary files /dev/null and b/static/images/screenshot-9.jpg differ diff --git a/static/js/main.js b/static/js/main.js index fa0024d..a549b29 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -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 === ' ') { diff --git a/templates/base.html b/templates/base.html index eae023d..ffc5390 100644 --- a/templates/base.html +++ b/templates/base.html @@ -4,8 +4,11 @@ - - {% block title %}Tecvico Corp{% endblock %} | Tecvico + + {% block title %}Radiuma{% endblock %} | Radiuma + + + diff --git a/templates/pages/about.html b/templates/pages/about.html index 2d8ff3b..7197bc6 100644 --- a/templates/pages/about.html +++ b/templates/pages/about.html @@ -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 @@
About
-

What is Tecvico?

+

What is Radiuma?

Visualized & Standardized Environment for Radiomics Analysis

@@ -49,7 +51,7 @@
IBSI 1.0

Radiomic Feature Extraction

- 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).

@@ -67,7 +69,7 @@
Python

Open Source

- 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.

@@ -75,7 +77,7 @@
End-to-End

Standardized Workflows

- 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.

@@ -83,6 +85,26 @@ +
+
+
+
Interface
+

Radiuma in Action

+
+
+ +
+ Radiuma settings panel +
+
+ Radiuma analysis results +
+
+
+
+
@@ -90,12 +112,12 @@
History

Our Origins

- 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 & program at the University of British Columbia & BC Cancer Research Institute, Vancouver, BC, Canada.

- 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.

diff --git a/templates/pages/contact.html b/templates/pages/contact.html index ce16fef..da04fdc 100644 --- a/templates/pages/contact.html +++ b/templates/pages/contact.html @@ -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 @@
Get in Touch

Contact Us

-

Support for ViSERA software is available via email and Discord.

+

Have a question or want to reach the Radiuma team? Fill out the form or contact us directly below.

-
+
-

Contact Details

-
+
-
- -

Email Support

-

- For software support and general inquiries, reach us at: -

- - support@tecvico.com - +
+

Send a Message

+
+ {% csrf_token %} + +
+ + {{ form.name }} + +
+ +
+ + {{ form.title }} + +
+ +
+ + {{ form.description }} + +
+ +
+ + {{ form.email }} + +
+ +
+ + + +
+ + +
-
- -
- + +
+ +

Office

+
+

BC Cancer Research Center

+

675 West 10th Ave, Office 6-112

+

Vancouver, BC, V5Z 1L3

+

Canada

+
+
+
+ + + + + + +{% endblock %} + +{% block extra_js %} + {% endblock %} diff --git a/templates/pages/downloads.html b/templates/pages/downloads.html index 94e7384..6969c95 100644 --- a/templates/pages/downloads.html +++ b/templates/pages/downloads.html @@ -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 @@
Software

Downloads

-

Download the latest version of ViSERA for your platform.

+

Download the latest version of Radiuma for your platform.

@@ -27,9 +28,7 @@

Windows

Windows 10 and up (64-bit)

@@ -40,9 +39,7 @@ {% if item.description %}

{{ item.description }}

{% endif %} {% if item.download_url and item.download_url != '#' %} - + Download {% endif %} @@ -55,9 +52,7 @@

macOS

macOS 11 Big Sur and up

@@ -68,9 +63,7 @@ {% if item.description %}

{{ item.description }}

{% endif %} {% if item.download_url and item.download_url != '#' %} - + Download {% endif %} @@ -83,9 +76,7 @@

Linux

Ubuntu 20.04+ and compatible distributions

@@ -96,9 +87,7 @@ {% if item.description %}

{{ item.description }}

{% endif %} {% if item.download_url and item.download_url != '#' %} - + Download {% endif %} @@ -118,7 +107,7 @@

Installation Notes

- 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.

diff --git a/templates/pages/faq.html b/templates/pages/faq.html index b39867f..15e6ec8 100644 --- a/templates/pages/faq.html +++ b/templates/pages/faq.html @@ -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 %} diff --git a/templates/pages/home.html b/templates/pages/home.html index 93beb59..082a9c3 100644 --- a/templates/pages/home.html +++ b/templates/pages/home.html @@ -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 @@
+ +
-
Developing since 2021
-

- Tecvico, -
- A Powerful Workflow Generator -

-

- for Standardized Radiomics Analysis and Medical Image Visualization -

-

- 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. -

-
- Download Now - About Tecvico +
+
Developing since 2021
+

+ Radiuma, +
+ A Powerful Workflow Generator +

+

+ for Standardized Radiomics Analysis and Medical Image 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. +

+ +
+
+ + Radiuma application — main workflow view
@@ -86,6 +94,32 @@
+
+
+
+
Gallery
+

See Radiuma in Action

+

+ Explore Radiuma's powerful interface, workflow builder, and multi-modal image viewer. +

+
+
+
+ Radiuma full workflow interface +
+
+ Radiuma panel view +
+
+ Radiuma segmentation view +
+
+ Radiuma radiomics results +
+
+
+
+ {% if nav_main_products %}
@@ -123,14 +157,14 @@
Value Proposition
-

What Problems Does Tecvico Solve?

+

What Problems Does Radiuma Solve?

Accessibility

- 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.

@@ -139,7 +173,7 @@

Integrated Tools

- 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.

@@ -147,7 +181,7 @@

Flexibility

- 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.

@@ -171,7 +205,7 @@
Our Story

More to Know

- 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 & program at the University of British Columbia & BC Cancer Research Institute, Vancouver, BC, Canada.

diff --git a/templates/partials/_footer.html b/templates/partials/_footer.html index efd81ff..3bd946b 100644 --- a/templates/partials/_footer.html +++ b/templates/partials/_footer.html @@ -8,9 +8,9 @@