feat: changes for citation ad dynamic
This commit is contained in:
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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),
|
||||
),
|
||||
]
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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 & 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 & 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 & 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 & program at the
|
||||
University of British Columbia & 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 %}
|
||||
|
||||
@@ -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
@@ -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 & program at the University of British Columbia &
|
||||
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 %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user