diff --git a/core/templates/core/report.html b/core/templates/core/report.html
index 743af73..d58a9d7 100644
--- a/core/templates/core/report.html
+++ b/core/templates/core/report.html
@@ -333,6 +333,53 @@
+{# === CHAPTER IV — Team × Project Activity === #}
+
Back to Dashboard
diff --git a/core/templatetags/format_tags.py b/core/templatetags/format_tags.py
index c80d202..5c2ec9b 100644
--- a/core/templatetags/format_tags.py
+++ b/core/templatetags/format_tags.py
@@ -25,3 +25,20 @@ def money(value):
# Python's :, format gives comma separators — swap commas for spaces
formatted = f"{num:,.2f}"
return formatted.replace(",", " ")
+
+
+@register.filter
+def dictlookup(d, key):
+ """Look up a dict value by a dynamic key.
+
+ Plain-English: in Django templates, `{{ mydict.foo }}` only works when
+ `foo` is a literal key. For `mydict[col.id]` with a variable key you need
+ a filter — this one. Returns None if the key is missing or the input
+ isn't a dict.
+ """
+ if d is None:
+ return None
+ try:
+ return d.get(key)
+ except (AttributeError, TypeError):
+ return None
diff --git a/static/css/custom.css b/static/css/custom.css
index 93cfcc6..f103bf9 100644
--- a/static/css/custom.css
+++ b/static/css/custom.css
@@ -1721,3 +1721,15 @@ body, .card, .modal-content, .form-control, .form-select,
.report-numeric th {
font-variant-numeric: tabular-nums;
}
+
+/* === Pivot-table footer totals (Chapter IV) === */
+/*
+ Bold, slightly-lifted row at the bottom of the Team × Project pivot
+ that holds the column totals + grand total. The 2px top border
+ visually separates totals from the data rows; the inset background
+ is the same --bg-inset used by other "slightly raised" surfaces.
+*/
+.table-total-row td {
+ border-top: 2px solid var(--border-default) !important;
+ background: var(--bg-inset);
+}