From b93c8630939c6e5fb49b1f1313e61bc3ef1f68e3 Mon Sep 17 00:00:00 2001 From: Y Date: Wed, 1 Apr 2026 18:14:47 -0400 Subject: [PATCH 01/46] update cover image generation logic to show uncropped images --- apps/iiif/canvases/models.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/apps/iiif/canvases/models.py b/apps/iiif/canvases/models.py index 12e0c996..eb550e6d 100644 --- a/apps/iiif/canvases/models.py +++ b/apps/iiif/canvases/models.py @@ -131,12 +131,10 @@ def thumbnail_crop_tallwide(self): @property def thumbnail_crop_volume(self): - """Concatenated property for cropped volume URI""" - if self.height > self.width: - # portrait - return f"{self.resource_id}/pct:15,15,70,70/,600/0/default.jpg" - # landscape - return f"{self.resource_id}/pct:25,15,50,85/,600/0/default.jpg" + """Concatenated property for volume URI without cropping""" + # Return full image resized to fit within 600px, maintaining aspect ratio + # Let CSS handle fitting within the square container with object-fit: contain + return f"{self.resource_id}/full/600,/0/default.jpg" @cached_property def result(self): From f244792b2fc41e6a5ba5cc012727f129e9648c57 Mon Sep 17 00:00:00 2001 From: Y Date: Wed, 1 Apr 2026 18:17:51 -0400 Subject: [PATCH 02/46] update cover image generation logic to show uncropped images (collections) --- apps/iiif/kollections/models.py | 37 ++++++++++++++------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/apps/iiif/kollections/models.py b/apps/iiif/kollections/models.py index 77ca5e8c..267b2c30 100644 --- a/apps/iiif/kollections/models.py +++ b/apps/iiif/kollections/models.py @@ -91,8 +91,9 @@ def __make_thumbnail(self): :return: True if file is created successfully. :rtype: bool """ - # If height is higher we resize vertically, if not we resize horizontally - size = (400, 500) + # Resize to fit within max bounds while preserving aspect ratio (no cropping) + max_width = 400 + max_height = 500 image = Image.open(self.thumbnail) # We need to test that this if else works correctly so that new files can be @@ -100,25 +101,19 @@ def __make_thumbnail(self): if 'thumbnails/' in self.thumbnail.name: return True else: - # Get current and desired ratio for the images - img_ratio = image.size[0] / float(image.size[1]) - ratio = size[0] / float(size[1]) - #The image is scaled/cropped vertically or horizontally depending on the ratio - if ratio > img_ratio: - image = image.resize( - (int(size[0]), int(size[0] * image.size[1] / image.size[0])), - Image.Resampling.LANCZOS) - # Crop in the top, middle or bottom - box = (0, (image.size[1] - size[1]) / 2, image.size[0], (image.size[1] + size[1]) / 2) - image = image.crop(box) - elif ratio < img_ratio: - imageratio = (int(size[1] * image.size[0] / image.size[1]), int(size[1])) - image = image.resize(imageratio, Image.Resampling.LANCZOS) - box = ((image.size[0] - size[0]) / 2, 0, (image.size[0] + size[0]) / 2, image.size[1]) - image = image.crop(box) - else: - image = image.resize((size[0], size[1]), Image.Resampling.LANCZOS) - # If the scale is the same, we do not need to crop + # Calculate new size maintaining aspect ratio + img_width, img_height = image.size + width_ratio = max_width / img_width + height_ratio = max_height / img_height + # Use the smaller ratio to ensure image fits within both dimensions + scale_ratio = min(width_ratio, height_ratio) + + new_width = int(img_width * scale_ratio) + new_height = int(img_height * scale_ratio) + + # Resize without cropping, maintaining aspect ratio + image = image.resize((new_width, new_height), Image.Resampling.LANCZOS) + thumb_name, thumb_extension = os.path.splitext(self.thumbnail.name) thumb_extension = thumb_extension.lower() From a79161fafc19021f4ca206dacc7953b6394f9dd0 Mon Sep 17 00:00:00 2001 From: Y Date: Thu, 2 Apr 2026 11:49:53 -0400 Subject: [PATCH 03/46] adjust foreground-background contrast for full description modal --- apps/static/css/components/collection.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/static/css/components/collection.scss b/apps/static/css/components/collection.scss index 385b5a35..e3f01821 100644 --- a/apps/static/css/components/collection.scss +++ b/apps/static/css/components/collection.scss @@ -71,8 +71,8 @@ } .modal-overlay { - background: rgba($color-black, 0.6); - /* Dark overlay */ + background: rgba($color-black, 0.85); + /* Dark overlay adjusted for higher contrast */ height: 100%; width: 100%; position: absolute; From 51bac2d267d2f42f7ea35ed4130369aee3a35d2a Mon Sep 17 00:00:00 2001 From: Y Date: Thu, 2 Apr 2026 11:55:37 -0400 Subject: [PATCH 04/46] add an unescape filter to render html entities for collection descriptions --- apps/readux/templatetags/readux_extras.py | 10 ++++++++++ apps/templates/collection.html | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/readux/templatetags/readux_extras.py b/apps/readux/templatetags/readux_extras.py index 1e2912f5..4d5fb1c3 100644 --- a/apps/readux/templatetags/readux_extras.py +++ b/apps/readux/templatetags/readux_extras.py @@ -1,4 +1,5 @@ import re +from html import unescape from django.template import Library register = Library() @@ -131,6 +132,15 @@ def spaced_semicolons(value): return value +@register.filter +def unescape_html(value): + """Decode HTML entities to their corresponding characters.""" + try: + return unescape(str(value)) + except Exception: + return value + + @register.filter def strip_trailing_commas(value): """Remove any trailing commas and surrounding whitespace.""" diff --git a/apps/templates/collection.html b/apps/templates/collection.html index 9da1e6a4..7033eb01 100644 --- a/apps/templates/collection.html +++ b/apps/templates/collection.html @@ -30,7 +30,7 @@

{{ collection.label }}

{{ volumes.paginator.count }} volume{{ volumes.paginator.count|pluralize }}
- {% with summary_plain=collection.summary|striptags %} + {% with summary_plain=collection.summary|striptags|unescape_html %} {% if summary_plain %}

{% if summary_plain|length > 200 %} @@ -64,7 +64,7 @@

{{ col

From 380689d949d4966b51e0a0142070eafed3397a59 Mon Sep 17 00:00:00 2001 From: Y Date: Thu, 2 Apr 2026 11:56:40 -0400 Subject: [PATCH 05/46] add spacing between paragraphs in collection full description modal --- apps/static/css/components/collection.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/static/css/components/collection.scss b/apps/static/css/components/collection.scss index e3f01821..158535d6 100644 --- a/apps/static/css/components/collection.scss +++ b/apps/static/css/components/collection.scss @@ -91,6 +91,10 @@ z-index: 1; } +.modal-overlay p { + margin-bottom: 1rem; +} + .rx-collection-modal { h1, h2, From ab0a3e83b38fbec7379dafc35f778a8b3aff20c8 Mon Sep 17 00:00:00 2001 From: Y Date: Thu, 2 Apr 2026 12:18:09 -0400 Subject: [PATCH 06/46] add a button/modal for collection metadata --- apps/readux/templatetags/readux_extras.py | 21 ++++++++++++++++++ apps/static/css/components/collection.scss | 19 ++++++++++++++++ apps/templates/collection.html | 25 ++++++++++++++++++++++ 3 files changed, 65 insertions(+) diff --git a/apps/readux/templatetags/readux_extras.py b/apps/readux/templatetags/readux_extras.py index 4d5fb1c3..b5b32cb2 100644 --- a/apps/readux/templatetags/readux_extras.py +++ b/apps/readux/templatetags/readux_extras.py @@ -141,6 +141,27 @@ def unescape_html(value): return value +@register.filter +def metadata_items(value): + """Normalize collection metadata for template display.""" + if not value: + return [] + + if isinstance(value, list): + items = [] + for entry in value: + if isinstance(entry, dict): + label = entry.get("label", "") + content = entry.get("value", "") + items.append((label, content)) + return items + + if isinstance(value, dict): + return list(value.items()) + + return [] + + @register.filter def strip_trailing_commas(value): """Remove any trailing commas and surrounding whitespace.""" diff --git a/apps/static/css/components/collection.scss b/apps/static/css/components/collection.scss index 158535d6..519fac54 100644 --- a/apps/static/css/components/collection.scss +++ b/apps/static/css/components/collection.scss @@ -61,6 +61,25 @@ transition: background-color 0.3s ease; } +.metadata-button { + margin-top: 20px; + background-color: $rx-color-cement-gray; + border: none; + color: $color-white; + font-weight: bold; + height: 40px; + border-radius: 5px; + font-size: 1em; + display: flex; + align-items: center; + justify-content: center; + transition: background-color 0.3s ease; +} + +.description-button + .metadata-button { + margin-left: 0.75rem; +} + .modal-bg { background-size: cover; background-position: center; diff --git a/apps/templates/collection.html b/apps/templates/collection.html index 7033eb01..5e3db85c 100644 --- a/apps/templates/collection.html +++ b/apps/templates/collection.html @@ -42,6 +42,9 @@

{{ col {% if summary_plain|length > 200 %} {% endif %} + {% if collection.metadata %} + + {% endif %} {% endif %} {% endwith %} @@ -72,6 +75,28 @@

{{ collection.label }}

{% endif %} {% endwith %} + {% if collection.metadata %} + + {% endif %} +
{% include "snippets/_volumes_page_information.html" %} {% include "snippets/_volumes_grid.html" %} From 9a781d726d52052e86510639550f4317a4b410eb Mon Sep 17 00:00:00 2001 From: Y Date: Tue, 7 Apr 2026 01:58:59 -0400 Subject: [PATCH 07/46] fix a missing include --- apps/templates/collection.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/templates/collection.html b/apps/templates/collection.html index 5e3db85c..176a084e 100644 --- a/apps/templates/collection.html +++ b/apps/templates/collection.html @@ -1,5 +1,5 @@ {% extends "base.html" %} -{% load has_user_annotations sass_tags static user_annotation_count wagtailcore_tags %} +{% load has_user_annotations sass_tags static user_annotation_count wagtailcore_tags readux_extras %} {% block content %} From ce3890b8675b6a2f2e2f971fbf0fe82ed728cb12 Mon Sep 17 00:00:00 2001 From: Y Date: Tue, 7 Apr 2026 01:59:13 -0400 Subject: [PATCH 08/46] improve contrast for flatpage links --- apps/static/css/components/flatpage.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/static/css/components/flatpage.scss b/apps/static/css/components/flatpage.scss index af373fb8..d0737694 100644 --- a/apps/static/css/components/flatpage.scss +++ b/apps/static/css/components/flatpage.scss @@ -132,6 +132,12 @@ $content-heading-sizes: ( list-style: decimal !important; } +.content a { + color: $rx-color-rose-red; + text-decoration: underline; + &:hover { color: darken($rx-color-rose-red, 10%); } +} + blockquote { border-left: 4px solid $rx-color-linen-blue !important; padding-left: 1rem !important; From 69c6b816b28f72f7e4657b45d6bcb68417976e9b Mon Sep 17 00:00:00 2001 From: Y Date: Sun, 10 May 2026 18:29:17 -0400 Subject: [PATCH 09/46] update the collection hero layout and styles --- apps/static/css/components/collection.scss | 364 +++++++++++++++++---- apps/static/css/partials/_colors.scss | 1 + apps/static/js/project.js | 52 +++ apps/templates/collection.html | 103 +++--- 4 files changed, 406 insertions(+), 114 deletions(-) diff --git a/apps/static/css/components/collection.scss b/apps/static/css/components/collection.scss index 519fac54..87819d53 100644 --- a/apps/static/css/components/collection.scss +++ b/apps/static/css/components/collection.scss @@ -1,15 +1,6 @@ @import '../partials/colors'; @import '../partials/_mixin.scss'; -#modal-full p { - color: $color-white; - ; -} - -#modal-full h2 { - font-size: xx-large; -} - .full-width-bg { background-size: cover; background-position: center; @@ -18,102 +9,201 @@ color: $color-white; } -.overlay { - background: linear-gradient(to right, rgba($color-black, 0.8) 0%, rgba($color-black, 0.6) 100%); - width: 33%; - position: relative; - height: auto; - min-height: 0; +/* Hero Container - Two Column Layout */ +.hero-container { + display: flex; + align-items: stretch; + min-height: 400px; + + @media (max-width: 768px) { + flex-direction: column; + min-height: auto; + } +} + +/* Left Column - Text Content */ +.hero-text-column { + flex: 1; + background: linear-gradient(to right, rgba($color-black, 0.85) 0%, rgba($color-black, 0.7) 100%); padding: 50px; box-sizing: border-box; + display: flex; + flex-direction: column; + justify-content: flex-start; color: $color-white; + position: relative; + z-index: 2; + + @media (max-width: 768px) { + padding: 30px; + min-height: auto; + } } -.overlay h1 { - font-size: 2rem; - /* Reduced title size */ +/* Collection Title - Responsive Font Size */ +.collection-title { + font-size: 2.5rem; margin-bottom: 15px; color: $color-white; + word-wrap: break-word; + overflow-wrap: break-word; + line-height: 1.2; + + @media (max-width: 1200px) { + font-size: 2rem; + } + + @media (max-width: 768px) { + font-size: 1.75rem; + } + + /* When title is very long, reduce font size */ + &.title-long { + font-size: 1.75rem; + + @media (max-width: 1200px) { + font-size: 1.5rem; + } + } + + /* When title is truncated */ + &.title-truncated { + font-size: 1.5rem; + + @media (max-width: 1200px) { + font-size: 1.25rem; + } + } +} +.rx-title-tagline { + font-size: 1rem; + color: rgba($color-white, 0.9); + font-weight: 500; } -.overlay p { +/* Collection Description */ +.collection-description { font-size: 1rem; - /* Adjusted font size */ - line-height: 1.5; - /* Reduced line-height */ - color: $color-white; + line-height: 1.6; + color: rgba($color-white, 0.95); + margin-top: 20px; + margin-bottom: 25px; + flex-grow: 1; + + .description-text { + display: block; + } + + /* When description is truncated */ + &.description-truncated .description-text::after { + content: ''; + } } +/* Description Button */ .description-button { - margin-top: 20px; + margin-top: auto; background-color: $rx-color-mario-red; - /* Removed gradient background */ border: none; color: $color-white; font-weight: bold; height: 40px; + padding: 0 20px; border-radius: 5px; font-size: 1em; - display: flex; + display: inline-flex; align-items: center; justify-content: center; transition: background-color 0.3s ease; + cursor: pointer; + width: fit-content; + + &:hover { + background-color: darken($rx-color-mario-red, 10%); + } } -.metadata-button { - margin-top: 20px; - background-color: $rx-color-cement-gray; - border: none; - color: $color-white; - font-weight: bold; - height: 40px; - border-radius: 5px; - font-size: 1em; +/* Right Column - Image and Info Icon */ +.hero-image-column { + flex: 1; + background-size: cover; + background-position: center; + position: relative; display: flex; align-items: center; justify-content: center; - transition: background-color 0.3s ease; -} - -.description-button + .metadata-button { - margin-left: 0.75rem; + overflow: hidden; + + @media (max-width: 768px) { + min-height: 300px; + } } -.modal-bg { - background-size: cover; - background-position: center; +.hero-image-wrapper { position: relative; - height: 100%; width: 100%; - color: $color-white; -} - -.modal-overlay { - background: rgba($color-black, 0.85); - /* Dark overlay adjusted for higher contrast */ height: 100%; - width: 100%; - position: absolute; - top: 0; - left: 0; - padding: 50px; - box-sizing: border-box; display: flex; align-items: center; justify-content: center; } -.modal-overlay h2, -.modal-overlay p { - position: relative; +.image-placeholder { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba($color-black, 0.2); z-index: 1; } -.modal-overlay p { - margin-bottom: 1rem; +/* Info Icon Button */ +.info-icon-button { + position: absolute; + bottom: 20px; + left: 20px; + z-index: 3; + background: transparent; + border: none; + border-radius: 50%; + width: 60px; + height: 60px; + padding: 0; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.3s ease; + color: rgba($color-white, 0.8); + + span { + display: flex; + align-items: center; + justify-content: center; + } + + &:hover { + background: rgba($color-white, 0.25); + border: 2px solid rgba($color-white, 0.7); + color: $color-white; + box-shadow: 0 0 20px rgba($color-white, 0.3); + } + + @media (max-width: 768px) { + bottom: 15px; + left: 15px; + width: 50px; + height: 50px; + + span { + font-size: 1.2rem; + } + } } +/* Modal Styles */ .rx-collection-modal { h1, h2, @@ -121,21 +211,30 @@ h4, h5, h6, - p { + p, + div { color: $color-white; } h2 { + margin-bottom: 0.5rem; + font-size: 2rem; + } + + h3 { + margin-top: 0; margin-bottom: 1rem; + font-size: 1.1rem; } a { - color: $rx-color-faded-mint; + color: $rx-color-sky-blue; text-decoration: underline; - @include link-variant($rx-color-faded-mint, 600, true); + transition: color 0.3s ease; + &:hover, &:active { - color: $color-white; + color: lighten($rx-color-sky-blue, 10%); text-decoration: underline; } } @@ -149,3 +248,138 @@ border: none; } } + +.modal-bg { + background-size: cover; + background-position: center; + position: relative; + height: 100%; + width: 100%; + color: $color-white; +} + +.modal-overlay { + background: rgba($color-black, 0.85); + height: 100%; + width: 100%; + position: absolute; + top: 0; + left: 0; + padding: 30px; + box-sizing: border-box; + display: flex; + align-items: flex-start; + justify-content: center; + overflow-y: auto; + + @media (max-width: 768px) { + padding: 20px; + } +} + +.modal-content { + max-width: 1000px; + width: 100%; + margin-top: 50px; + + @media (max-width: 768px) { + margin-top: 30px; + } +} + +.modal-grid { + display: grid; + grid-template-columns: 2fr 1fr; + gap: 3rem; + + @media (max-width: 1024px) { + grid-template-columns: 1fr; + gap: 2rem; + } + + @media (max-width: 768px) { + grid-template-columns: 1fr; + gap: 1.5rem; + } +} + +.modal-description-section { + grid-column: 1; +} + +.modal-metadata-section { + grid-column: 2; + padding-top: 6rem; + + @media (max-width: 1024px) { + grid-column: 1; + padding-top: 0; + } +} + +.modal-section { + margin-bottom: 0; +} + +.volume-count { + font-size: 1rem; + color: rgba($color-white, 0.8); + margin-bottom: 1.5rem; + font-weight: 500; +} + +.modal-description { + font-size: 1rem; + line-height: 1.8; + color: rgba($color-white, 0.95); + margin-bottom: 1rem; + + p { + margin-bottom: 1rem; + + &:last-child { + margin-bottom: 0; + } + } +} + +.metadata-section { + padding-top: 0; + border-top: none; +} + +.metadata-content { + display: grid; + grid-template-columns: 1fr; + gap: 0.75rem; +} + +.metadata-item { + display: flex; + gap: 0.5rem; + font-size: 0.95rem; + + strong { + color: rgba($color-white, 0.9); + min-width: 200px; + flex-shrink: 0; + + @media (max-width: 768px) { + min-width: 150px; + } + } + + span { + color: rgba($color-white, 0.85); + word-wrap: break-word; + overflow-wrap: break-word; + } +} + +#modal-full p { + color: $color-white; +} + +#modal-full h2 { + font-size: xx-large; +} diff --git a/apps/static/css/partials/_colors.scss b/apps/static/css/partials/_colors.scss index 1bc4958b..09c89831 100644 --- a/apps/static/css/partials/_colors.scss +++ b/apps/static/css/partials/_colors.scss @@ -8,6 +8,7 @@ $rx-color-faded-mint: #F1FAEE; // Auxilary // ============================== $rx-color-rose-red: #f0506e; +$rx-color-sky-blue: #77caff; // ============================== // Base diff --git a/apps/static/js/project.js b/apps/static/js/project.js index dcc10856..8f7073bb 100644 --- a/apps/static/js/project.js +++ b/apps/static/js/project.js @@ -118,6 +118,58 @@ const documentReady = function() { } }) } + + // Handle collection title and description truncation with tooltips + const titleElement = document.getElementById('collection-title'); + if (titleElement) { + const fullTitle = titleElement.textContent.trim(); + const titleLength = fullTitle.length; + + // Title is "really long" - reduce font size + if (titleLength > 60) { + titleElement.classList.add('title-long'); + } + + // Title is "super long" - truncate and add tooltip + if (titleLength > 100) { + titleElement.classList.add('title-truncated'); + const truncatedTitle = fullTitle.substring(0, 80) + '...'; + titleElement.textContent = truncatedTitle; + titleElement.setAttribute('uk-tooltip', `title: ${fullTitle}; pos: bottom; delay: 500`); + // Reinitialize UIKit tooltip + if (window.UIkit) { + window.UIkit.util.ready(() => { + window.UIkit.tooltip(titleElement); + }); + } + } + } + + // Handle collection description truncation with tooltip + const descriptionElement = document.getElementById('collection-description'); + if (descriptionElement) { + const descriptionTextSpan = descriptionElement.querySelector('.description-text'); + if (descriptionTextSpan) { + const fullDescription = descriptionTextSpan.textContent.trim(); + const descriptionLength = fullDescription.length; + + // Description is long - check if it's already truncated or add tooltip + if (descriptionLength > 300) { + descriptionElement.classList.add('description-truncated'); + // Check if text ends with ellipsis (already truncated by Django template) + if (fullDescription.endsWith('...')) { + // Already truncated, add tooltip with full text + const untruncatedText = fullDescription.replace(/\.\.\.$/, ''); + descriptionElement.setAttribute('uk-tooltip', `title: ${untruncatedText}; pos: top; delay: 500`); + if (window.UIkit) { + window.UIkit.util.ready(() => { + window.UIkit.tooltip(descriptionElement); + }); + } + } + } + } + } } if (document.readyState === 'loading') { diff --git a/apps/templates/collection.html b/apps/templates/collection.html index 176a084e..24217be7 100644 --- a/apps/templates/collection.html +++ b/apps/templates/collection.html @@ -23,60 +23,45 @@
-
-
-

{{ collection.label }}

+
+
+ +
+

{{ collection.label }}

{{ volumes.paginator.count }} volume{{ volumes.paginator.count|pluralize }}
{% with summary_plain=collection.summary|striptags|unescape_html %} {% if summary_plain %} -

- {% if summary_plain|length > 200 %} - {{ summary_plain|truncatechars:200 }} +

+ {% if summary_plain|length > 300 %} + {{ summary_plain|truncatechars:300 }} {% else %} - {{ summary_plain }} + {{ summary_plain }} {% endif %}

- {% if summary_plain|length > 200 %} - - {% endif %} - {% if collection.metadata %} - - {% endif %} + {% endif %} {% endwith %} +
- {# TODO: image metadata, style or remove #} - {% if collection.collection_image_title %}

{{ collection.collection_image_title }}

{% endif %} - {% if collection.collection_image_summary %}

{{ collection.collection_image_summary }}

{% endif %} - {% if collection.collection_image_summary %}

{{ collection.collection_image_creator }}

{% endif %} + +
+
+
+ +
+
{% with summary_plain=collection.summary|striptags %} - {% if summary_plain and summary_plain|length > 200 %} - - {% endif %} - {% endwith %} - - {% if collection.metadata %} -