feat: changes for citation ad dynamic

This commit is contained in:
mohamad
2026-05-17 18:51:26 +03:30
parent f0a9d5832b
commit b8ab67049f
22 changed files with 1170 additions and 287 deletions
+114 -3
View File
@@ -1,7 +1,7 @@
from django.core.management.base import BaseCommand
from django.db import transaction
from apps.pages.models import DownloadItem, FAQEntry
from apps.pages.models import DownloadItem, FAQEntry, HomepageSection, HomepageSectionItem
from apps.products.models import Article, ArticleSection, MainProduct, SubProduct
MAIN_PRODUCTS = [
@@ -33,6 +33,8 @@ MAIN_PRODUCTS = [
"for consistent, reproducible research outcomes."
),
"order": 1,
"show_on_homepage": True,
"homepage_order": 1,
"articles": [
{
"title": "Image Filtering Techniques",
@@ -252,7 +254,7 @@ FAQ_ENTRIES = [
{
"question": "Where can I get support or report issues?",
"answer": (
"Support is available via email at support@visera.ca and through our "
"Support is available via email at support@radiuma.ca and through our "
"community Discord server. For bug reports and feature requests, please "
"use the Discord forum or contact us directly by email."
),
@@ -260,6 +262,86 @@ FAQ_ENTRIES = [
},
]
HOMEPAGE_SECTIONS = [
{
"section_type": HomepageSection.TYPE_FEATURES,
"badge": "Capabilities",
"title": "Important Features",
"description": "Comprehensive tools for medical imaging research, standardized and reproducible.",
"order": 1,
"items": [
{"icon": "⚗️", "title": "Image Filtering", "content": "Standardized image filtering techniques compliant with IBSI 2.0 guidelines.", "order": 1},
{"icon": "🖥️", "title": "Professional Viewer", "content": "Comfortable, professional medical image viewer with multi-modality support.", "order": 2},
{"icon": "📊", "title": "Radiomics Features", "content": "Handcrafted radiomics feature generation standardized by IBSI 1.0.", "order": 3},
{"icon": "🔄", "title": "Format Support", "content": "NIFTI, DICOM, NRRD, and more — comprehensive multi-format support.", "order": 4},
{"icon": "🗂️", "title": "Image Registration", "content": "Advanced image registration, fusion, and standardized SUV conversion.", "order": 5},
{"icon": "🔬", "title": "RT Struct Support", "content": "Full RT struct support for radiation oncology workflows and research.", "order": 6},
],
},
{
"section_type": HomepageSection.TYPE_SCREENSHOTS,
"badge": "Gallery",
"title": "See Radiuma in Action",
"description": "Explore Radiuma's powerful interface, workflow builder, and multi-modal image viewer.",
"order": 2,
"items": [],
},
{
"section_type": HomepageSection.TYPE_PRODUCTS,
"badge": "Our Software",
"title": "Products",
"description": "Explore our suite of medical imaging and radiomics tools.",
"order": 3,
"items": [],
},
{
"section_type": HomepageSection.TYPE_PROBLEMS,
"badge": "Value Proposition",
"title": "What Problems Does Radiuma Solve?",
"description": "",
"order": 4,
"items": [
{"icon": "01", "title": "Accessibility", "content": "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.", "order": 1},
{"icon": "02", "title": "Integrated Tools", "content": "Radiuma integrates a vast collection of tools and resources from various domains of healthcare and medical imaging research in a common, unified environment.", "order": 2},
{"icon": "03", "title": "Flexibility", "content": "Radiuma offers flexibility in terms of tool optimization and workflow customization to match your specific research requirements.", "order": 3},
{"icon": "04", "title": "Reproducibility", "content": "Improve usability, reusability, and reproducibility (URR) through a workflow management system that allows researchers to easily create, share, and reuse analysis pipelines.", "order": 4},
],
},
{
"section_type": HomepageSection.TYPE_ABOUT_STRIP,
"badge": "Our Story",
"title": "More to Know",
"description": (
"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."
),
"link_text": "Learn More",
"link_url": "/about/",
"order": 5,
"items": [],
},
{
"section_type": HomepageSection.TYPE_SUPPORTERS,
"badge": "Acknowledgements",
"title": "Our Supporters",
"description": "Radiuma is made possible by the support of leading research institutions and organizations.",
"order": 6,
"items": [
{
"title": "University of British Columbia",
"content": "Faculty of Medicine and the Department of Integrative Oncology.",
"order": 1,
},
{
"title": "BC Cancer Research Institute",
"content": "Supporting cutting-edge radiomics and medical imaging research in Vancouver, BC.",
"order": 2,
},
],
},
]
DOWNLOAD_ITEMS = [
{
"name": "Radiuma Desktop",
@@ -292,7 +374,7 @@ DOWNLOAD_ITEMS = [
class Command(BaseCommand):
help = "Seed the database with initial Radiuma / Radiuma content from visera.ca"
help = "Seed the database with initial Radiuma / Radiuma content from radiuma.ca"
def add_arguments(self, parser):
parser.add_argument(
@@ -311,14 +393,21 @@ class Command(BaseCommand):
MainProduct.objects.all().delete()
FAQEntry.objects.all().delete()
DownloadItem.objects.all().delete()
HomepageSectionItem.objects.all().delete()
HomepageSection.objects.all().delete()
self._seed_products()
self._seed_faq()
self._seed_downloads()
self._seed_homepage_sections()
self.stdout.write(self.style.SUCCESS("Content seeded successfully."))
def _seed_products(self):
SubProduct.objects.filter(show_on_homepage=False, homepage_order=0).update(
show_on_homepage=True,
)
for product_data in MAIN_PRODUCTS:
sub_products_data = product_data.pop("sub_products")
main_product, created = MainProduct.objects.get_or_create(
@@ -384,6 +473,28 @@ class Command(BaseCommand):
action = "Created" if created else "Updated"
self.stdout.write(f" {action} FAQ: {faq.question[:60]}...")
def _seed_homepage_sections(self):
for section_data in HOMEPAGE_SECTIONS:
items_data = section_data.pop("items")
section, created = HomepageSection.objects.get_or_create(
section_type=section_data["section_type"],
defaults=section_data,
)
if not created:
for field, value in section_data.items():
setattr(section, field, value)
section.save()
action = "Created" if created else "Updated"
self.stdout.write(f" {action} homepage section: {section}")
for item_data in items_data:
item, _ = HomepageSectionItem.objects.get_or_create(
section=section,
title=item_data["title"],
defaults=item_data,
)
def _seed_downloads(self):
for item_data in DOWNLOAD_ITEMS:
item, created = DownloadItem.objects.get_or_create(
+49 -1
View File
@@ -1,6 +1,54 @@
from django.contrib import admin
from .models import ContactSubmission, DownloadItem, FAQEntry
from .models import (
AboutSection,
AboutSectionItem,
ContactSubmission,
DownloadItem,
FAQEntry,
HomepageSection,
HomepageSectionItem,
)
class HomepageSectionItemInline(admin.TabularInline):
model = HomepageSectionItem
extra = 1
fields = ("icon", "title", "content", "image", "order")
ordering = ("order",)
@admin.register(HomepageSection)
class HomepageSectionAdmin(admin.ModelAdmin):
list_display = ("section_type", "badge", "title", "order", "is_active")
list_filter = ("section_type", "is_active")
list_editable = ("order", "is_active")
inlines = [HomepageSectionItemInline]
fieldsets = (
(None, {"fields": ("section_type", "badge", "title", "description")}),
("CTA Link (About Strip)", {"fields": ("link_text", "link_url"), "classes": ("collapse",)}),
("Settings", {"fields": ("order", "is_active")}),
)
class AboutSectionItemInline(admin.StackedInline):
model = AboutSectionItem
extra = 1
fields = ("badge", "title", "content", "url", "image", "image_alt", "is_featured", "order")
ordering = ("order",)
@admin.register(AboutSection)
class AboutSectionAdmin(admin.ModelAdmin):
list_display = ("section_type", "badge", "title", "order", "is_active")
list_filter = ("section_type", "is_active")
list_editable = ("order", "is_active")
inlines = [AboutSectionItemInline]
fieldsets = (
(None, {"fields": ("section_type", "badge", "title", "subtitle")}),
("Content", {"fields": ("content_format", "content")}),
("Settings", {"fields": ("order", "is_active")}),
)
@admin.register(ContactSubmission)
@@ -0,0 +1,103 @@
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("pages", "0003_faqentry_answer_format"),
]
operations = [
migrations.CreateModel(
name="AboutSection",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
(
"section_type",
models.CharField(
choices=[
("hero", "Hero"),
("intro", "Intro Card"),
("grid", "Grid Cards"),
("history", "History Block"),
("custom", "Custom Content"),
],
default="custom",
max_length=30,
),
),
("badge", models.CharField(blank=True, max_length=100)),
("title", models.CharField(blank=True, max_length=300)),
(
"subtitle",
models.CharField(
blank=True,
help_text="Used as subtitle in Hero and year in History.",
max_length=500,
),
),
("content", models.TextField(blank=True)),
(
"content_format",
models.CharField(
choices=[
("plain", "Plain Text"),
("markdown", "Markdown"),
("html", "HTML"),
],
default="markdown",
max_length=20,
),
),
("order", models.PositiveIntegerField(default=0)),
("is_active", models.BooleanField(default=True)),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
],
options={
"verbose_name": "About Section",
"verbose_name_plural": "About Sections",
"ordering": ["order"],
},
),
migrations.CreateModel(
name="AboutSectionItem",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
(
"section",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="items",
to="pages.aboutsection",
),
),
("badge", models.CharField(blank=True, max_length=100)),
("title", models.CharField(blank=True, max_length=300)),
(
"content",
models.TextField(
blank=True,
help_text="Description text or link label for History links.",
),
),
("url", models.URLField(blank=True, help_text="Used for History block links.")),
("image", models.ImageField(blank=True, null=True, upload_to="about/")),
("image_alt", models.CharField(blank=True, max_length=200)),
(
"is_featured",
models.BooleanField(
default=False,
help_text="Mark as featured item (e.g. large screenshot).",
),
),
("order", models.PositiveIntegerField(default=0)),
],
options={
"verbose_name": "About Section Item",
"verbose_name_plural": "About Section Items",
"ordering": ["order"],
},
),
]
@@ -0,0 +1,49 @@
# Generated by Django 5.0.2 on 2026-05-17 14:39
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pages', '0004_aboutsection_aboutsectionitem'),
]
operations = [
migrations.CreateModel(
name='HomepageSection',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('section_type', models.CharField(choices=[('features', 'Features'), ('screenshots', 'Screenshots Gallery'), ('products', 'Products'), ('problems', 'Problems / Value Proposition'), ('about_strip', 'About Strip')], max_length=30, unique=True)),
('badge', models.CharField(blank=True, max_length=100)),
('title', models.CharField(blank=True, max_length=300)),
('description', models.TextField(blank=True)),
('link_text', models.CharField(blank=True, help_text='CTA button label (About Strip).', max_length=100)),
('link_url', models.CharField(blank=True, help_text='CTA button URL (About Strip).', max_length=300)),
('order', models.PositiveIntegerField(default=0)),
('is_active', models.BooleanField(default=True)),
],
options={
'verbose_name': 'Homepage Section',
'verbose_name_plural': 'Homepage Sections',
'ordering': ['order'],
},
),
migrations.CreateModel(
name='HomepageSectionItem',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('icon', models.CharField(blank=True, help_text='Emoji or short symbol (e.g. ⚗️).', max_length=20)),
('title', models.CharField(blank=True, max_length=300)),
('content', models.TextField(blank=True)),
('order', models.PositiveIntegerField(default=0)),
('section', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='pages.homepagesection')),
],
options={
'verbose_name': 'Homepage Section Item',
'verbose_name_plural': 'Homepage Section Items',
'ordering': ['order'],
},
),
]
@@ -0,0 +1,23 @@
# Generated by Django 5.0.2 on 2026-05-17 14:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pages', '0005_homepage_sections'),
]
operations = [
migrations.AddField(
model_name='homepagesectionitem',
name='image',
field=models.ImageField(blank=True, help_text='Logo or image (used for Supporters cards).', null=True, upload_to='homepage/items/'),
),
migrations.AlterField(
model_name='homepagesection',
name='section_type',
field=models.CharField(choices=[('features', 'Features'), ('screenshots', 'Screenshots Gallery'), ('products', 'Products'), ('problems', 'Problems / Value Proposition'), ('about_strip', 'About Strip'), ('supporters', 'Supporters')], max_length=30, unique=True),
),
]
+113 -1
View File
@@ -1,6 +1,118 @@
from django.db import models
from apps.core.utils import CONTENT_FORMAT_CHOICES, FORMAT_PLAIN, render_content
from apps.core.utils import CONTENT_FORMAT_CHOICES, FORMAT_MARKDOWN, FORMAT_PLAIN, render_content
class HomepageSection(models.Model):
TYPE_FEATURES = "features"
TYPE_SCREENSHOTS = "screenshots"
TYPE_PRODUCTS = "products"
TYPE_PROBLEMS = "problems"
TYPE_ABOUT_STRIP = "about_strip"
TYPE_SUPPORTERS = "supporters"
TYPE_CHOICES = [
(TYPE_FEATURES, "Features"),
(TYPE_SCREENSHOTS, "Screenshots Gallery"),
(TYPE_PRODUCTS, "Products"),
(TYPE_PROBLEMS, "Problems / Value Proposition"),
(TYPE_ABOUT_STRIP, "About Strip"),
(TYPE_SUPPORTERS, "Supporters"),
]
section_type = models.CharField(max_length=30, choices=TYPE_CHOICES, unique=True)
badge = models.CharField(max_length=100, blank=True)
title = models.CharField(max_length=300, blank=True)
description = models.TextField(blank=True)
link_text = models.CharField(max_length=100, blank=True, help_text="CTA button label (About Strip).")
link_url = models.CharField(max_length=300, blank=True, help_text="CTA button URL (About Strip).")
order = models.PositiveIntegerField(default=0)
is_active = models.BooleanField(default=True)
class Meta:
ordering = ["order"]
verbose_name = "Homepage Section"
verbose_name_plural = "Homepage Sections"
def __str__(self):
return f"[{self.get_section_type_display()}] {self.title or self.badge}"
class HomepageSectionItem(models.Model):
section = models.ForeignKey(HomepageSection, on_delete=models.CASCADE, related_name="items")
icon = models.CharField(max_length=20, blank=True, help_text="Emoji or short symbol (e.g. ⚗️).")
title = models.CharField(max_length=300, blank=True)
content = models.TextField(blank=True)
image = models.ImageField(upload_to="homepage/items/", blank=True, null=True, help_text="Logo or image (used for Supporters cards).")
order = models.PositiveIntegerField(default=0)
class Meta:
ordering = ["order"]
verbose_name = "Homepage Section Item"
verbose_name_plural = "Homepage Section Items"
def __str__(self):
return f"{self.section} {self.title or self.icon or '(item)'}"
class AboutSection(models.Model):
TYPE_HERO = "hero"
TYPE_INTRO = "intro"
TYPE_GRID = "grid"
TYPE_HISTORY = "history"
TYPE_CUSTOM = "custom"
TYPE_CHOICES = [
(TYPE_HERO, "Hero"),
(TYPE_INTRO, "Intro Card"),
(TYPE_GRID, "Grid Cards"),
(TYPE_HISTORY, "History Block"),
(TYPE_CUSTOM, "Custom Content"),
]
section_type = models.CharField(max_length=30, choices=TYPE_CHOICES, default=TYPE_CUSTOM)
badge = models.CharField(max_length=100, blank=True)
title = models.CharField(max_length=300, blank=True)
subtitle = models.CharField(max_length=500, blank=True, help_text="Used as subtitle in Hero and year in History.")
content = models.TextField(blank=True)
content_format = models.CharField(max_length=20, choices=CONTENT_FORMAT_CHOICES, default=FORMAT_MARKDOWN)
order = models.PositiveIntegerField(default=0)
is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ["order"]
verbose_name = "About Section"
verbose_name_plural = "About Sections"
@property
def rendered_content(self):
return render_content(self.content, self.content_format)
def __str__(self):
label = self.title or self.badge or self.get_section_type_display()
return f"[{self.get_section_type_display()}] {label}"
class AboutSectionItem(models.Model):
section = models.ForeignKey(AboutSection, on_delete=models.CASCADE, related_name="items")
badge = models.CharField(max_length=100, blank=True)
title = models.CharField(max_length=300, blank=True)
content = models.TextField(blank=True, help_text="Description text or link label for History links.")
url = models.URLField(blank=True, help_text="Used for History block links.")
image = models.ImageField(upload_to="about/", blank=True, null=True)
image_alt = models.CharField(max_length=200, blank=True)
is_featured = models.BooleanField(default=False, help_text="Mark as featured item (e.g. large screenshot).")
order = models.PositiveIntegerField(default=0)
class Meta:
ordering = ["order"]
verbose_name = "About Section Item"
verbose_name_plural = "About Section Items"
def __str__(self):
return f"{self.section} {self.title or self.badge or '(item)'}"
class ContactSubmission(models.Model):
+26 -1
View File
@@ -5,17 +5,42 @@ from django.shortcuts import render
from django.views import View
from django.views.generic import ListView, TemplateView
from apps.products.models import SubProduct
from .forms import ContactForm
from .models import ContactSubmission, FAQEntry
from .models import AboutSection, ContactSubmission, FAQEntry, HomepageSection
class HomeView(TemplateView):
template_name = "pages/home.html"
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx["homepage_sections"] = (
HomepageSection.objects.filter(is_active=True)
.prefetch_related("items")
.order_by("order")
)
ctx["sub_products"] = (
SubProduct.objects.filter(is_active=True, show_on_homepage=True)
.select_related("main_product")
.order_by("homepage_order", "order")
)
return ctx
class AboutView(TemplateView):
template_name = "pages/about.html"
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx["about_sections"] = (
AboutSection.objects.filter(is_active=True)
.prefetch_related("items")
.order_by("order")
)
return ctx
class FAQView(ListView):
model = FAQEntry
+19 -8
View File
@@ -1,6 +1,13 @@
from django.contrib import admin
from .models import Article, ArticleSection, MainProduct, SubProduct, SubProductVersion
from .models import Article, ArticleCitation, ArticleSection, MainProduct, SubProduct, SubProductVersion
class ArticleCitationInline(admin.TabularInline):
model = ArticleCitation
extra = 1
fields = ("text", "url", "order")
ordering = ("order",)
class ArticleSectionInline(admin.TabularInline):
@@ -13,7 +20,7 @@ class ArticleSectionInline(admin.TabularInline):
class ArticleInline(admin.StackedInline):
model = Article
extra = 0
fields = ("title", "description", "order")
fields = ("badge", "title", "description", "order")
ordering = ("order",)
show_change_link = True
@@ -39,7 +46,7 @@ class SubProductVersionInline(admin.TabularInline):
class SubProductInline(admin.StackedInline):
model = SubProduct
extra = 0
fields = ("name", "slug", "distribution", "short_description", "image", "order", "is_active")
fields = ("name", "slug", "distribution", "short_description", "image", "logo", "show_on_homepage", "homepage_order", "order", "is_active")
ordering = ("order",)
show_change_link = True
prepopulated_fields = {"slug": ("name",)}
@@ -67,20 +74,23 @@ class SubProductAdmin(admin.ModelAdmin):
"name",
"main_product",
"distribution",
"show_on_homepage",
"homepage_order",
"order",
"is_active",
"created_at",
)
list_filter = ("is_active", "distribution", "main_product")
list_filter = ("is_active", "distribution", "main_product", "show_on_homepage")
search_fields = ("name", "description", "main_product__name")
prepopulated_fields = {"slug": ("name",)}
list_editable = ("order", "is_active")
list_editable = ("order", "is_active", "show_on_homepage", "homepage_order")
raw_id_fields = ("main_product",)
inlines = [ArticleInline, SubProductVersionInline]
fieldsets = (
(None, {"fields": ("main_product", "name", "slug", "distribution", "short_description")}),
("Description", {"fields": ("description_format", "description")}),
("Media", {"fields": ("image",)}),
("Media", {"fields": ("image", "logo")}),
("Homepage", {"fields": ("show_on_homepage", "homepage_order")}),
("Settings", {"fields": ("order", "is_active")}),
)
@@ -92,10 +102,11 @@ class ArticleAdmin(admin.ModelAdmin):
search_fields = ("title", "description")
list_editable = ("order",)
raw_id_fields = ("sub_product",)
inlines = [ArticleSectionInline]
inlines = [ArticleSectionInline, ArticleCitationInline]
fieldsets = (
(None, {"fields": ("sub_product", "title")}),
(None, {"fields": ("sub_product", "badge", "title")}),
("Description", {"fields": ("description_format", "description")}),
("Citation Badge", {"fields": ("citation_count_display",), "description": "Set a number to show the dashed citation circle badge at the bottom-right of the article card. Leave blank to hide it."}),
("Settings", {"fields": ("order",)}),
)
@@ -0,0 +1,22 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("products", "0004_distribution_and_versions"),
]
operations = [
migrations.AddField(
model_name="article",
name="badge",
field=models.CharField(
blank=True,
default="",
help_text="Optional label shown above the article title (e.g. 'Overview', 'Note').",
max_length=100,
),
preserve_default=False,
),
]
@@ -0,0 +1,49 @@
# Generated by Django 5.0.2 on 2026-05-17 14:59
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('products', '0005_article_badge'),
]
operations = [
migrations.AddField(
model_name='subproduct',
name='homepage_order',
field=models.PositiveIntegerField(default=0, help_text='Order within the homepage Products section.'),
),
migrations.AddField(
model_name='subproduct',
name='logo',
field=models.ImageField(blank=True, help_text='Small logo shown as a corner badge on homepage product cards.', null=True, upload_to='products/sub_logos/'),
),
migrations.AddField(
model_name='subproduct',
name='show_on_homepage',
field=models.BooleanField(default=False, help_text='Display this sub-product in the homepage Products section.'),
),
migrations.AlterField(
model_name='subproductversion',
name='package_resource_url',
field=models.URLField(blank=True, help_text='Shown on the site when set. For Resources it is the main Open link; for Installable it appears under the platform grid.'),
),
migrations.CreateModel(
name='ArticleCitation',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('text', models.TextField(help_text='Full citation text.')),
('url', models.URLField(blank=True, help_text='Optional link to the cited source.')),
('order', models.PositiveIntegerField(default=0)),
('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='citations', to='products.article')),
],
options={
'verbose_name': 'Article Citation',
'verbose_name_plural': 'Article Citations',
'ordering': ['order', 'pk'],
},
),
]
@@ -0,0 +1,18 @@
# Generated by Django 5.0.2 on 2026-05-17 15:16
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('products', '0006_subproduct_logo_homepage_citations'),
]
operations = [
migrations.AddField(
model_name='article',
name='citation_count_display',
field=models.PositiveSmallIntegerField(blank=True, help_text='Optional number shown in the dashed citation circle badge at the bottom of the article card. Leave blank to hide the badge.', null=True),
),
]
+32
View File
@@ -69,8 +69,11 @@ class SubProduct(models.Model):
max_length=20, choices=CONTENT_FORMAT_CHOICES, default=FORMAT_PLAIN
)
image = models.ImageField(upload_to="products/sub/", blank=True, null=True)
logo = models.ImageField(upload_to="products/sub_logos/", blank=True, null=True, help_text="Small logo shown as a corner badge on homepage product cards.")
order = models.PositiveIntegerField(default=0)
is_active = models.BooleanField(default=True)
show_on_homepage = models.BooleanField(default=False, help_text="Display this sub-product in the homepage Products section.")
homepage_order = models.PositiveIntegerField(default=0, help_text="Order within the homepage Products section.")
distribution = models.CharField(
max_length=20,
choices=DISTRIBUTION_CHOICES,
@@ -123,11 +126,21 @@ class Article(models.Model):
on_delete=models.CASCADE,
related_name="articles",
)
badge = models.CharField(
max_length=100,
blank=True,
help_text="Optional label shown above the article title (e.g. 'Overview', 'Note').",
)
title = models.CharField(max_length=300)
description = models.TextField()
description_format = models.CharField(
max_length=20, choices=CONTENT_FORMAT_CHOICES, default=FORMAT_PLAIN
)
citation_count_display = models.PositiveSmallIntegerField(
null=True,
blank=True,
help_text="Optional number shown in the dashed citation circle badge at the bottom of the article card. Leave blank to hide the badge.",
)
order = models.PositiveIntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
@@ -219,3 +232,22 @@ class ArticleSection(models.Model):
def __str__(self):
return f"{self.article.title} {self.title}"
class ArticleCitation(models.Model):
article = models.ForeignKey(
Article,
on_delete=models.CASCADE,
related_name="citations",
)
text = models.TextField(help_text="Full citation text.")
url = models.URLField(blank=True, help_text="Optional link to the cited source.")
order = models.PositiveIntegerField(default=0)
class Meta:
ordering = ["order", "pk"]
verbose_name = "Article Citation"
verbose_name_plural = "Article Citations"
def __str__(self):
return f"{self.article.title} — citation {self.order or self.pk}"
+244 -13
View File
@@ -264,14 +264,14 @@ h1, h2, h3, h4, h5, h6 {
.blob--1 {
width: 520px; height: 520px;
background: radial-gradient(circle, rgba(79, 142, 247, 0.4) 0%, rgba(79, 142, 247, 0.1) 70%);
background: radial-gradient(circle, rgba(79, 142, 247, 0.18) 0%, rgba(79, 142, 247, 0.05) 70%);
top: -120px; left: -120px;
animation-delay: 0s, 0s;
}
.blob--2 {
width: 440px; height: 440px;
background: radial-gradient(circle, rgba(167, 139, 250, 0.35) 0%, rgba(167, 139, 250, 0.08) 70%);
background: radial-gradient(circle, rgba(167, 139, 250, 0.15) 0%, rgba(167, 139, 250, 0.04) 70%);
top: 80px; right: -100px;
animation-delay: 0.3s, 3s;
animation-duration: 1.2s, 18s;
@@ -279,7 +279,7 @@ h1, h2, h3, h4, h5, h6 {
.blob--3 {
width: 340px; height: 340px;
background: radial-gradient(circle, rgba(34, 211, 238, 0.25) 0%, rgba(34, 211, 238, 0.06) 70%);
background: radial-gradient(circle, rgba(34, 211, 238, 0.12) 0%, rgba(34, 211, 238, 0.03) 70%);
bottom: 0; left: 35%;
animation-delay: 0.6s, 6s;
animation-duration: 1.2s, 20s;
@@ -342,14 +342,12 @@ h1, h2, h3, h4, h5, h6 {
}
.brand-icon {
width: 34px; height: 34px;
display: flex; align-items: center; justify-content: center;
background: var(--gradient-brand);
border-radius: 9px;
font-size: 1rem;
font-weight: 800;
color: #fff;
letter-spacing: -0.03em;
width: 34px;
height: 34px;
border-radius: 8px;
object-fit: cover;
display: block;
flex-shrink: 0;
}
.brand-name {
@@ -822,6 +820,18 @@ h1, h2, h3, h4, h5, h6 {
flex: 1;
display: flex;
flex-direction: column;
position: relative;
}
.product-card-logo {
position: absolute;
top: 1rem;
right: 1rem;
width: 32px;
height: 32px;
object-fit: contain;
border-radius: var(--radius-sm);
opacity: 0.85;
}
.product-card-title {
@@ -883,6 +893,57 @@ h1, h2, h3, h4, h5, h6 {
line-height: 1.65;
}
/* ============================================================
Supporters Grid
============================================================ */
.supporters-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 1.25rem;
}
.supporter-card {
padding: 1.75rem;
display: flex;
align-items: flex-start;
gap: 1rem;
}
.supporter-logo {
flex-shrink: 0;
width: 52px;
height: 52px;
border-radius: var(--radius-sm);
overflow: hidden;
background: rgba(255, 255, 255, 0.05);
display: flex;
align-items: center;
justify-content: center;
}
.supporter-logo img {
width: 100%;
height: 100%;
object-fit: contain;
}
.supporter-body {
min-width: 0;
}
.supporter-name {
font-size: 0.95rem;
font-weight: 700;
margin-bottom: 0.35rem;
color: var(--text-primary);
}
.supporter-desc {
font-size: 0.83rem;
color: var(--text-secondary);
line-height: 1.55;
}
/* ============================================================
About Strip
============================================================ */
@@ -1120,7 +1181,119 @@ h1, h2, h3, h4, h5, h6 {
}
.article-card {
padding: 2rem;
padding: 2rem 2rem 5.5rem 2rem;
position: relative;
}
.article-card-header {
margin-bottom: 0.5rem;
}
.citation-count-badge {
position: absolute;
bottom: 1.25rem;
right: 1.5rem;
width: 64px;
height: 64px;
border-radius: 50%;
border: 2px dashed rgba(79, 142, 247, 0.4);
background: rgba(79, 142, 247, 0.06);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 1px;
opacity: 0.7;
text-decoration: none;
color: var(--accent-blue-light);
transition: opacity 0.2s, border-color 0.2s, background 0.2s;
cursor: default;
}
a.citation-count-badge {
cursor: pointer;
}
a.citation-count-badge:hover {
opacity: 1;
border-color: rgba(79, 142, 247, 0.65);
background: rgba(79, 142, 247, 0.12);
}
.citation-count-number {
font-size: 1.3rem;
font-weight: 800;
line-height: 1;
letter-spacing: -0.02em;
}
.citation-count-label {
font-size: 0.6rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
opacity: 0.8;
}
.article-citations {
margin-top: 1.25rem;
padding-top: 1.25rem;
border-top: 1px solid var(--glass-border);
}
.article-citations-title {
font-size: 0.75rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.07em;
color: var(--accent-blue-light);
margin-bottom: 0.75rem;
}
.article-citations-list {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 0.6rem;
counter-reset: citations;
}
.article-citation-item {
display: flex;
align-items: baseline;
gap: 0.5rem;
font-size: 0.8rem;
color: var(--text-secondary);
line-height: 1.55;
counter-increment: citations;
}
.article-citation-item::before {
content: counter(citations) ".";
font-size: 0.72rem;
font-weight: 700;
color: var(--accent-blue-light);
flex-shrink: 0;
min-width: 1.2em;
}
.citation-text {
flex: 1;
}
.citation-link {
flex-shrink: 0;
color: var(--accent-blue-light);
opacity: 0.75;
transition: opacity 0.2s;
display: flex;
align-items: center;
}
.citation-link:hover {
opacity: 1;
}
.article-title {
@@ -1640,11 +1813,21 @@ h1, h2, h3, h4, h5, h6 {
.rich-content h2 { font-size: 1.25rem; }
.rich-content h3 { font-size: 1.05rem; }
.rich-content ul, .rich-content ol {
.rich-content ul {
list-style-type: disc;
padding-left: 1.5em;
margin-bottom: 0.85em;
}
.rich-content ol {
list-style-type: decimal;
padding-left: 1.5em;
margin-bottom: 0.85em;
}
.rich-content ul ul { list-style-type: circle; }
.rich-content ul ul ul { list-style-type: square; }
.rich-content li { margin-bottom: 0.3em; line-height: 1.65; }
.rich-content a {
@@ -2868,3 +3051,51 @@ h1, h2, h3, h4, h5, h6 {
flex-direction: column;
}
}
/* ============================================================
Read More / Content Truncation
============================================================ */
.readmore-wrap {
position: relative;
overflow: hidden;
transition: max-height 0.35s cubic-bezier(0.4, 0, 0.2, 1);
}
.readmore-wrap.is-clamped {
-webkit-mask-image: linear-gradient(to bottom, black 45%, transparent 92%);
mask-image: linear-gradient(to bottom, black 45%, transparent 92%);
}
.readmore-wrap:not(.is-clamped) {
-webkit-mask-image: none;
mask-image: none;
}
.readmore-btn {
display: inline-flex;
align-items: center;
gap: 0.3em;
font-size: 0.8rem;
font-weight: 600;
color: var(--accent-blue-light);
background: none;
border: none;
cursor: pointer;
padding: 0.3rem 0;
margin-top: 0.5rem;
line-height: 1;
transition: color 0.18s ease;
}
.readmore-btn:hover {
color: var(--accent-cyan);
}
.readmore-btn svg {
flex-shrink: 0;
transition: transform 0.22s ease;
}
.readmore-btn.is-open svg {
transform: rotate(180deg);
}
+39
View File
@@ -152,12 +152,51 @@ function initIntersectionObserver() {
});
}
function initReadMore() {
var chevronSVG = '<svg width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true"><path d="M2 4L6 8L10 4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>';
document.querySelectorAll('[data-readmore]').forEach(function (el) {
var threshold = parseInt(el.getAttribute('data-readmore'), 10) || 120;
var fullHeight = el.scrollHeight;
if (fullHeight <= threshold + 12) return;
var storedHeight = fullHeight;
el.classList.add('readmore-wrap', 'is-clamped');
el.style.maxHeight = threshold + 'px';
var btn = document.createElement('button');
btn.type = 'button';
btn.className = 'readmore-btn';
btn.setAttribute('aria-expanded', 'false');
btn.innerHTML = 'Read more ' + chevronSVG;
el.insertAdjacentElement('afterend', btn);
btn.addEventListener('click', function () {
var collapsed = el.classList.toggle('is-clamped');
if (collapsed) {
el.style.maxHeight = threshold + 'px';
btn.innerHTML = 'Read more ' + chevronSVG;
btn.setAttribute('aria-expanded', 'false');
btn.classList.remove('is-open');
} else {
el.style.maxHeight = storedHeight + 'px';
btn.innerHTML = 'Read less ' + chevronSVG;
btn.setAttribute('aria-expanded', 'true');
btn.classList.add('is-open');
}
});
});
}
function init() {
initNavbarScroll();
initMobileMenu();
initMegaMenu();
initFAQAccordion();
initIntersectionObserver();
initReadMore();
}
if (document.readyState === 'loading') {
+88 -87
View File
@@ -1,11 +1,14 @@
{% extends "base.html" %}
{% load static %}
{% 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 title %}About{% endblock %}
{% block meta_description %}Learn about Radiuma — Visualized & Standardized Environment for Radiomics Analysis, developed at UBC and BC Cancer Research Institute.{% endblock %}
{% block content %}
{% for section in about_sections %}
{% if section.section_type == "hero" %}
<section class="page-hero" aria-labelledby="page-hero-heading">
<div class="page-hero-blobs" aria-hidden="true">
<div class="blob blob--1"></div>
@@ -15,125 +18,123 @@
</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 Radiuma?</h1>
<p class="page-hero-subtitle">
Visualized &amp; Standardized Environment for Radiomics Analysis
</p>
{% if section.badge %}<div class="section-badge">{{ section.badge }}</div>{% endif %}
{% if section.title %}<h1 class="page-hero-title" id="page-hero-heading">{{ section.title }}</h1>{% endif %}
{% if section.subtitle %}<p class="page-hero-subtitle">{{ section.subtitle }}</p>{% endif %}
{% if section.content %}<div class="rich-content page-hero-body" data-readmore="160">{{ section.rendered_content }}</div>{% endif %}
</div>
</div>
</section>
<section class="section" aria-labelledby="about-intro-heading">
{% elif section.section_type == "intro" %}
<section class="section" aria-labelledby="about-intro-{{ section.pk }}-heading">
<div class="container">
<div class="about-intro glass-card fade-in">
<div class="about-intro-text">
<h2 id="about-intro-heading">Desktop Software for Medical Imaging Research</h2>
<ul class="about-list">
<li>Desktop software to improve usability, reusability, and reproducibility in medical imaging &amp; healthcare research.</li>
<li>Development platform to create reproducible research workflows by connecting different tools.</li>
<li>Useful for collaborative research projects and for ensuring consistency across different studies.</li>
<li>User-friendly for different expertise levels, including radiation oncologists, radiologists, physicists &amp; data scientists.</li>
</ul>
{% if section.title %}<h2 id="about-intro-{{ section.pk }}-heading">{{ section.title }}</h2>{% endif %}
{% if section.content %}
<div class="rich-content" data-readmore="200">{{ section.rendered_content }}</div>
{% endif %}
</div>
</div>
</div>
</section>
<section class="section" aria-labelledby="standardization-heading">
{% elif section.section_type == "grid" %}
<section class="section" aria-labelledby="grid-{{ section.pk }}-heading">
<div class="container">
{% if section.badge or section.title %}
<div class="section-header">
<div class="section-badge">Standards</div>
<h2 class="section-title" id="standardization-heading">Standardization</h2>
{% if section.badge %}<div class="section-badge">{{ section.badge }}</div>{% endif %}
{% if section.title %}<h2 class="section-title" id="grid-{{ section.pk }}-heading">{{ section.title }}</h2>{% endif %}
</div>
{% endif %}
{% if section.content %}<div class="rich-content" data-readmore="200">{{ section.rendered_content }}</div>{% endif %}
{% with items=section.items.all %}
{% if items %}
<div class="standards-grid">
{% for item in items %}
<div class="standard-card glass-card fade-in">
<div class="standard-badge">IBSI 1.0</div>
<h3 class="standard-title">Radiomic Feature Extraction</h3>
<p class="standard-desc">
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>
</div>
<div class="standard-card glass-card fade-in">
<div class="standard-badge">IBSI 2.0</div>
<h3 class="standard-title">Image Filtering</h3>
<p class="standard-desc">
Image filters have been standardized against IBSI 2.0 by implementing and
validating several filter options, ensuring reproducibility across research
institutions worldwide.
</p>
</div>
<div class="standard-card glass-card fade-in">
<div class="standard-badge">Python</div>
<h3 class="standard-title">Open Source</h3>
<p class="standard-desc">
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>
<div class="standard-card glass-card fade-in">
<div class="standard-badge">End-to-End</div>
<h3 class="standard-title">Standardized Workflows</h3>
<p class="standard-desc">
Radiuma employs a number of popular image processing algorithms to create
end-to-end standardized workflows for consistent, reproducible research results.
</p>
{% if item.badge %}<div class="standard-badge">{{ item.badge }}</div>{% endif %}
{% if item.title %}<h3 class="standard-title">{{ item.title }}</h3>{% endif %}
{% if item.content %}<p class="standard-desc" data-readmore="88">{{ item.content }}</p>{% endif %}
</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
</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">
{% elif section.section_type == "history" %}
<section class="section" aria-labelledby="history-{{ section.pk }}-heading">
<div class="container">
<div class="history-block glass-card fade-in">
<div class="history-content">
<div class="section-badge">History</div>
<h2 id="history-heading">Our Origins</h2>
<p>
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>
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>
{% if section.badge %}<div class="section-badge">{{ section.badge }}</div>{% endif %}
{% if section.title %}<h2 id="history-{{ section.pk }}-heading">{{ section.title }}</h2>{% endif %}
{% if section.content %}<div class="rich-content" data-readmore="200">{{ section.rendered_content }}</div>{% endif %}
{% with links=section.items.all %}
{% if links %}
<div class="history-links">
<a href="https://www.qurit.ca" class="btn-ghost" target="_blank" rel="noopener noreferrer">Qurit Lab</a>
<a href="https://www.ubc.ca" class="btn-ghost" target="_blank" rel="noopener noreferrer">UBC</a>
<a href="https://www.bccrc.ca" class="btn-ghost" target="_blank" rel="noopener noreferrer">BC Cancer</a>
{% for link in links %}
{% if link.url %}
<a href="{{ link.url }}" class="btn-ghost" target="_blank" rel="noopener noreferrer">{{ link.title }}</a>
{% endif %}
{% endfor %}
</div>
{% endif %}
{% endwith %}
</div>
<div class="history-visual" aria-hidden="true">
<div class="history-blob history-blob--1"></div>
<div class="history-blob history-blob--2"></div>
<div class="history-year">2021</div>
{% if section.subtitle %}<div class="history-year">{{ section.subtitle }}</div>{% endif %}
</div>
</div>
</div>
</section>
{% elif section.section_type == "custom" %}
<section class="section" aria-labelledby="custom-{{ section.pk }}-heading">
<div class="container">
{% if section.badge or section.title %}
<div class="section-header">
{% if section.badge %}<div class="section-badge">{{ section.badge }}</div>{% endif %}
{% if section.title %}<h2 class="section-title" id="custom-{{ section.pk }}-heading">{{ section.title }}</h2>{% endif %}
</div>
{% endif %}
{% if section.content %}
<div class="glass-card fade-in">
<div class="rich-content" style="padding: 2rem;" data-readmore="200">{{ section.rendered_content }}</div>
</div>
{% endif %}
{% with items=section.items.all %}
{% if items %}
<div class="standards-grid" style="margin-top: 1.5rem;">
{% for item in items %}
<div class="standard-card glass-card fade-in">
{% if item.badge %}<div class="standard-badge">{{ item.badge }}</div>{% endif %}
{% if item.title %}<h3 class="standard-title">{{ item.title }}</h3>{% endif %}
{% if item.content %}<p class="standard-desc" data-readmore="88">{{ item.content }}</p>{% endif %}
{% if item.url %}<a href="{{ item.url }}" class="btn-ghost btn--sm" style="margin-top: 0.75rem;" target="_blank" rel="noopener noreferrer">{{ item.title }}</a>{% endif %}
</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
</div>
</section>
{% endif %}
{% empty %}
<section class="section">
<div class="container">
<p style="color: var(--text-secondary); text-align: center; padding: 4rem 0;">
No content configured yet. Add sections in the admin panel.
</p>
</div>
</section>
{% endfor %}
{% endblock %}
+1 -1
View File
@@ -85,7 +85,7 @@
</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>
<a href="mailto:support@radiuma.ca" class="contact-email-link">support@radiuma.ca</a>
</div>
<div class="contact-card glass-card fade-in">
+74 -98
View File
@@ -42,66 +42,35 @@
</div>
</section>
<section class="section features-section" aria-labelledby="features-heading">
{% for section in homepage_sections %}
{% if section.section_type == "features" %}
<section class="section features-section" aria-labelledby="features-heading-{{ section.pk }}">
<div class="container">
<div class="section-header">
<div class="section-badge">Capabilities</div>
<h2 class="section-title" id="features-heading">Important Features</h2>
<p class="section-desc">
Comprehensive tools for medical imaging research, standardized and reproducible.
</p>
{% if section.badge %}<div class="section-badge">{{ section.badge }}</div>{% endif %}
<h2 class="section-title" id="features-heading-{{ section.pk }}">{{ section.title }}</h2>
{% if section.description %}<p class="section-desc">{{ section.description }}</p>{% endif %}
</div>
<div class="features-grid">
{% for feature in features %}
{% for item in section.items.all %}
<div class="feature-card glass-card fade-in">
<div class="feature-icon" aria-hidden="true">{{ feature.icon }}</div>
<h3 class="feature-title">{{ feature.title }}</h3>
<p class="feature-desc">{{ feature.desc }}</p>
</div>
{% empty %}
<div class="feature-card glass-card fade-in">
<div class="feature-icon" aria-hidden="true">⚗️</div>
<h3 class="feature-title">Image Filtering</h3>
<p class="feature-desc">Standardized image filtering techniques compliant with IBSI 2.0 guidelines.</p>
</div>
<div class="feature-card glass-card fade-in">
<div class="feature-icon" aria-hidden="true">🖥️</div>
<h3 class="feature-title">Professional Viewer</h3>
<p class="feature-desc">Comfortable, professional medical image viewer with multi-modality support.</p>
</div>
<div class="feature-card glass-card fade-in">
<div class="feature-icon" aria-hidden="true">📊</div>
<h3 class="feature-title">Radiomics Features</h3>
<p class="feature-desc">Handcrafted radiomics feature generation standardized by IBSI 1.0.</p>
</div>
<div class="feature-card glass-card fade-in">
<div class="feature-icon" aria-hidden="true">🔄</div>
<h3 class="feature-title">Format Support</h3>
<p class="feature-desc">NIFTI, DICOM, NRRD, and more — comprehensive multi-format support.</p>
</div>
<div class="feature-card glass-card fade-in">
<div class="feature-icon" aria-hidden="true">🗂️</div>
<h3 class="feature-title">Image Registration</h3>
<p class="feature-desc">Advanced image registration, fusion, and standardized SUV conversion.</p>
</div>
<div class="feature-card glass-card fade-in">
<div class="feature-icon" aria-hidden="true">🔬</div>
<h3 class="feature-title">RT Struct Support</h3>
<p class="feature-desc">Full RT struct support for radiation oncology workflows and research.</p>
{% if item.icon %}<div class="feature-icon" aria-hidden="true">{{ item.icon }}</div>{% endif %}
<h3 class="feature-title">{{ item.title }}</h3>
<p class="feature-desc" data-readmore="88">{{ item.content }}</p>
</div>
{% endfor %}
</div>
</div>
</section>
<section class="section screenshots-section" aria-labelledby="screenshots-heading">
{% elif section.section_type == "screenshots" %}
<section class="section screenshots-section" aria-labelledby="screenshots-heading-{{ section.pk }}">
<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>
{% if section.badge %}<div class="section-badge">{{ section.badge }}</div>{% endif %}
<h2 class="section-title" id="screenshots-heading-{{ section.pk }}">{{ section.title }}</h2>
{% if section.description %}<p class="section-desc">{{ section.description }}</p>{% endif %}
</div>
<div class="screenshots-grid">
<div class="screenshot-item fade-in">
@@ -120,16 +89,17 @@
</div>
</section>
{% if nav_main_products %}
<section class="section products-section" aria-labelledby="products-heading">
{% elif section.section_type == "products" %}
{% if sub_products %}
<section class="section products-section" aria-labelledby="products-heading-{{ section.pk }}">
<div class="container">
<div class="section-header">
<div class="section-badge">Our Software</div>
<h2 class="section-title" id="products-heading">Products</h2>
<p class="section-desc">Explore our suite of medical imaging and radiomics tools.</p>
{% if section.badge %}<div class="section-badge">{{ section.badge }}</div>{% endif %}
<h2 class="section-title" id="products-heading-{{ section.pk }}">{{ section.title }}</h2>
{% if section.description %}<p class="section-desc">{{ section.description }}</p>{% endif %}
</div>
<div class="products-grid">
{% for product in nav_main_products %}
{% for product in sub_products %}
<a href="{{ product.get_absolute_url }}" class="product-card glass-card fade-in">
{% if product.image %}
<div class="product-card-image">
@@ -137,8 +107,11 @@
</div>
{% endif %}
<div class="product-card-body">
{% if product.logo %}
<img src="{{ product.logo.url }}" alt="" class="product-card-logo" aria-hidden="true" loading="lazy" />
{% endif %}
<h3 class="product-card-title">{{ product.name }}</h3>
<p class="product-card-desc">{{ product.short_description }}</p>
<p class="product-card-desc" data-readmore="88">{{ product.short_description }}</p>
<span class="product-card-link">
Explore
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
@@ -153,63 +126,63 @@
</section>
{% endif %}
<section class="section problems-section" aria-labelledby="problems-heading">
{% elif section.section_type == "problems" %}
<section class="section problems-section" aria-labelledby="problems-heading-{{ section.pk }}">
<div class="container">
<div class="section-header">
<div class="section-badge">Value Proposition</div>
<h2 class="section-title" id="problems-heading">What Problems Does Radiuma Solve?</h2>
{% if section.badge %}<div class="section-badge">{{ section.badge }}</div>{% endif %}
<h2 class="section-title" id="problems-heading-{{ section.pk }}">{{ section.title }}</h2>
{% if section.description %}<p class="section-desc">{{ section.description }}</p>{% endif %}
</div>
<div class="problems-grid">
{% for item in section.items.all %}
<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">
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>
</div>
<div class="problem-item glass-card fade-in">
<div class="problem-number" aria-hidden="true">02</div>
<h3 class="problem-title">Integrated Tools</h3>
<p class="problem-desc">
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>
<div class="problem-item glass-card fade-in">
<div class="problem-number" aria-hidden="true">03</div>
<h3 class="problem-title">Flexibility</h3>
<p class="problem-desc">
Radiuma offers flexibility in terms of tool optimization and workflow customization
to match your specific research requirements.
</p>
</div>
<div class="problem-item glass-card fade-in">
<div class="problem-number" aria-hidden="true">04</div>
<h3 class="problem-title">Reproducibility</h3>
<p class="problem-desc">
Improve usability, reusability, and reproducibility (URR) through a workflow
management system that allows researchers to easily create, share, and reuse
analysis pipelines.
</p>
{% if item.icon %}<div class="problem-number" aria-hidden="true">{{ item.icon }}</div>{% endif %}
<h3 class="problem-title">{{ item.title }}</h3>
<p class="problem-desc" data-readmore="88">{{ item.content }}</p>
</div>
{% endfor %}
</div>
</div>
</section>
<section class="section about-strip" aria-labelledby="about-strip-heading">
{% elif section.section_type == "supporters" %}
<section class="section supporters-section" aria-labelledby="supporters-heading-{{ section.pk }}">
<div class="container">
<div class="section-header">
{% if section.badge %}<div class="section-badge">{{ section.badge }}</div>{% endif %}
<h2 class="section-title" id="supporters-heading-{{ section.pk }}">{{ section.title }}</h2>
{% if section.description %}<p class="section-desc">{{ section.description }}</p>{% endif %}
</div>
<div class="supporters-grid">
{% for item in section.items.all %}
<div class="supporter-card glass-card fade-in">
{% if item.image %}
<div class="supporter-logo">
<img src="{{ item.image.url }}" alt="{{ item.title }} logo" loading="lazy" />
</div>
{% endif %}
<div class="supporter-body">
<h3 class="supporter-name">{{ item.title }}</h3>
{% if item.content %}<p class="supporter-desc">{{ item.content }}</p>{% endif %}
</div>
</div>
{% endfor %}
</div>
</div>
</section>
{% elif section.section_type == "about_strip" %}
<section class="section about-strip" aria-labelledby="about-strip-heading-{{ section.pk }}">
<div class="container">
<div class="about-strip-inner glass-card">
<div class="about-strip-content">
<div class="section-badge">Our Story</div>
<h2 class="section-title" id="about-strip-heading">More to Know</h2>
<p class="about-strip-text">
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>
<a href="{% url 'pages:about' %}" class="btn-primary">Learn More</a>
{% if section.badge %}<div class="section-badge">{{ section.badge }}</div>{% endif %}
<h2 class="section-title" id="about-strip-heading-{{ section.pk }}">{{ section.title }}</h2>
{% if section.description %}<p class="about-strip-text">{{ section.description }}</p>{% endif %}
{% if section.link_text and section.link_url %}
<a href="{{ section.link_url }}" class="btn-primary">{{ section.link_text }}</a>
{% endif %}
</div>
<div class="about-strip-blobs" aria-hidden="true">
<div class="strip-blob strip-blob--1"></div>
@@ -219,4 +192,7 @@
</div>
</section>
{% endif %}
{% endfor %}
{% endblock %}
+1 -1
View File
@@ -52,7 +52,7 @@
<p>675 West 10th Ave, Office 6-112</p>
<p>Vancouver, BC, V5Z 1L3</p>
</address>
<a href="mailto:support@visera.ca" class="footer-email">support@visera.ca</a>
<a href="mailto:support@radiuma.ca" class="footer-email">support@radiuma.ca</a>
</div>
</div>
+2 -2
View File
@@ -3,7 +3,7 @@
<div class="navbar-container">
<a href="{% url 'pages:home' %}" class="navbar-brand" aria-label="Radiuma home">
<span class="brand-icon">R</span>
<img src="{% static 'images/favicon-180.png' %}" alt="" class="brand-icon" width="34" height="34" aria-hidden="true" />
<span class="brand-name">Radiuma</span>
</a>
@@ -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 Radiuma
About
</a>
</li>
+2 -2
View File
@@ -43,7 +43,7 @@
{% endif %}
</div>
<div class="product-detail-text">
<div class="product-detail-desc rich-content">{{ main_product.rendered_description }}</div>
<div class="product-detail-desc rich-content" data-readmore="220">{{ main_product.rendered_description }}</div>
</div>
</div>
</div>
@@ -71,7 +71,7 @@
{% endif %}
<div class="subproduct-card-body">
<h3 class="subproduct-card-title">{{ sub.name }}</h3>
<p class="subproduct-card-desc">{{ sub.short_description }}</p>
<p class="subproduct-card-desc" data-readmore="88">{{ sub.short_description }}</p>
<span class="subproduct-card-link">
Learn More
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true">
+1 -1
View File
@@ -35,7 +35,7 @@
<div class="product-overview-body">
<h2 class="product-overview-title">{{ product.name }}</h2>
<p class="product-overview-short">{{ product.short_description }}</p>
<p class="product-overview-desc">{{ product.description }}</p>
<p class="product-overview-desc" data-readmore="110">{{ product.description }}</p>
<a href="{{ product.get_absolute_url }}" class="btn-primary">Explore {{ product.name }}</a>
</div>
{% with subs=product.sub_products.all %}
+36 -3
View File
@@ -44,7 +44,7 @@
<div class="sub-product-desc glass-card fade-in">
<h2>About {{ sub_product.name }}</h2>
<div class="rich-content">{{ sub_product.rendered_description }}</div>
<div class="rich-content" data-readmore="220">{{ sub_product.rendered_description }}</div>
</div>
{% if show_releases_section %}
@@ -154,20 +154,53 @@
<div class="articles-list">
{% for article in articles %}
<article class="article-card glass-card fade-in" aria-labelledby="article-{{ article.pk }}-title">
{% if article.badge %}<div class="article-card-header"><div class="section-badge article-badge">{{ article.badge }}</div></div>{% endif %}
<h3 class="article-title" id="article-{{ article.pk }}-title">{{ article.title }}</h3>
<div class="article-description rich-content">{{ article.rendered_description }}</div>
<div class="article-description rich-content" data-readmore="260">{{ article.rendered_description }}</div>
{% if article.sections.all %}
<div class="article-sections">
<dl class="article-sections-list">
{% for section in article.sections.all %}
<div class="article-section-item">
<dt class="section-key">{{ section.title }}</dt>
<dd class="section-value rich-content">{{ section.rendered_value }}</dd>
<dd class="section-value rich-content" data-readmore="160">{{ section.rendered_value }}</dd>
</div>
{% endfor %}
</dl>
</div>
{% endif %}
{% if article.citations.all %}
<div class="article-citations" id="article-{{ article.pk }}-citations">
<h4 class="article-citations-title">Citations</h4>
<ol class="article-citations-list">
{% for citation in article.citations.all %}
<li class="article-citation-item">
<span class="citation-text">{{ citation.text }}</span>
{% if citation.url %}
<a href="{{ citation.url }}" class="citation-link" target="_blank" rel="noopener noreferrer" aria-label="Open citation source">
<svg width="13" height="13" viewBox="0 0 16 16" fill="none" aria-hidden="true">
<path d="M7 3H3a1 1 0 00-1 1v9a1 1 0 001 1h9a1 1 0 001-1V9M10 2h4m0 0v4m0-4L7 9" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</a>
{% endif %}
</li>
{% endfor %}
</ol>
</div>
{% endif %}
{% if article.citation_count_display is not None %}
{% if article.citations.exists %}
<a href="#article-{{ article.pk }}-citations" class="citation-count-badge" aria-label="{{ article.citation_count_display }} citation{{ article.citation_count_display|pluralize }}">
<span class="citation-count-number">{{ article.citation_count_display }}</span>
<span class="citation-count-label">cited</span>
</a>
{% else %}
<div class="citation-count-badge" aria-label="{{ article.citation_count_display }} citation{{ article.citation_count_display|pluralize }}">
<span class="citation-count-number">{{ article.citation_count_display }}</span>
<span class="citation-count-label">cited</span>
</div>
{% endif %}
{% endif %}
</article>
{% endfor %}
</div>