diff --git a/home/doris-barbell/app/static/app.css b/home/doris-barbell/app/static/app.css
index a7e9b7c..1b5eb69 100644
--- a/home/doris-barbell/app/static/app.css
+++ b/home/doris-barbell/app/static/app.css
@@ -89,6 +89,171 @@ body {
flex: 0 1 320px;
min-width: min(100%, 280px);
}
+.art-stack {
+ display: grid;
+ gap: 12px;
+ flex: 0 1 320px;
+ min-width: min(100%, 280px);
+}
+.art-stack .hot-fuzz-art {
+ flex: initial;
+ min-width: 0;
+}
+.photo-evidence-card {
+ position: relative;
+ margin: 0;
+ padding: 10px;
+ border-radius: 22px;
+ background: linear-gradient(180deg, rgba(255,255,255,0.06), rgba(255,255,255,0.02));
+ border: 1px solid rgba(242, 239, 221, 0.14);
+ box-shadow: 0 18px 36px rgba(0, 0, 0, 0.28);
+ overflow: hidden;
+}
+.photo-evidence-card::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ background: linear-gradient(180deg, rgba(242, 193, 78, 0.08), transparent 28%, rgba(145, 15, 63, 0.16));
+ pointer-events: none;
+}
+.photo-evidence-card img {
+ width: 100%;
+ aspect-ratio: 16 / 10;
+ object-fit: cover;
+ display: block;
+ border-radius: 14px;
+ filter: saturate(0.82) sepia(0.18) contrast(1.02);
+}
+.photo-evidence-card figcaption {
+ position: absolute;
+ left: 18px;
+ right: 18px;
+ bottom: 16px;
+ z-index: 1;
+ margin: 0;
+ font-size: 0.72rem;
+ letter-spacing: 0.16em;
+ text-transform: uppercase;
+ color: #fff0cb;
+ text-shadow: 0 2px 10px rgba(0, 0, 0, 0.75);
+}
+.evidence-contact-grid {
+ display: grid;
+ grid-template-columns: minmax(0, 1.15fr) minmax(140px, 0.85fr);
+ gap: 12px;
+ align-items: stretch;
+}
+.evidence-note-card {
+ position: relative;
+ padding: 14px 16px;
+ border-radius: 22px;
+ background: linear-gradient(180deg, rgba(255,255,255,0.05), rgba(255,255,255,0.015)), rgba(15,18,25,0.96);
+ border: 1px solid rgba(242, 239, 221, 0.11);
+ box-shadow: 0 16px 30px rgba(0, 0, 0, 0.24);
+ overflow: hidden;
+}
+.evidence-note-card::before {
+ content: '';
+ position: absolute;
+ inset: 0 auto auto 0;
+ width: 100%;
+ height: 4px;
+ background: linear-gradient(90deg, var(--hf-red), var(--hf-amber), var(--hf-blue));
+}
+.case-contact-top {
+ display: flex;
+ justify-content: space-between;
+ gap: 10px;
+ align-items: center;
+ flex-wrap: wrap;
+ margin-bottom: 10px;
+}
+.case-contact-kicker,
+.case-contact-badge {
+ display: inline-flex;
+ align-items: center;
+ border-radius: 999px;
+ padding: 6px 10px;
+ font-size: 0.74rem;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+}
+.case-contact-kicker {
+ background: rgba(242, 193, 78, 0.12);
+ border: 1px solid rgba(242, 193, 78, 0.24);
+ color: #ffe6aa;
+}
+.case-contact-badge {
+ background: rgba(123, 143, 178, 0.16);
+ border: 1px solid rgba(123, 143, 178, 0.24);
+ color: #dbe5ff;
+}
+.contact-sheet-illustration {
+ width: 100%;
+ height: auto;
+ display: block;
+ margin-bottom: 10px;
+ filter: drop-shadow(0 14px 22px rgba(0, 0, 0, 0.24));
+}
+.contact-frame {
+ fill: rgba(11, 14, 20, 0.92);
+ stroke: rgba(242, 239, 221, 0.12);
+ stroke-width: 2;
+}
+.contact-photo {
+ fill: rgba(123, 143, 178, 0.22);
+ stroke: rgba(242, 239, 221, 0.16);
+ stroke-width: 1.5;
+}
+.contact-photo.alt {
+ fill: rgba(213, 54, 0, 0.18);
+}
+.contact-lines,
+.contact-thread,
+.beacon-arc {
+ fill: none;
+ stroke-linecap: round;
+}
+.contact-lines {
+ stroke: rgba(242, 239, 221, 0.86);
+ stroke-width: 4;
+}
+.contact-thread,
+.map-thread {
+ stroke: rgba(213, 54, 0, 0.82);
+ stroke-width: 3;
+ fill: none;
+ stroke-linecap: round;
+ stroke-linejoin: round;
+}
+.contact-marker {
+ fill: rgba(242, 193, 78, 0.18);
+ stroke: rgba(242, 193, 78, 0.84);
+ stroke-width: 2;
+}
+.contact-label {
+ fill: rgba(242, 239, 221, 0.9);
+ font-size: 15px;
+ font-weight: 800;
+ letter-spacing: 0.16em;
+ text-anchor: middle;
+}
+.case-contact-list {
+ margin: 0;
+ padding-left: 18px;
+ color: #d5dded;
+ display: grid;
+ gap: 8px;
+ line-height: 1.4;
+}
+.supporting-photo-card { min-height: 100%; }
+.evidence-card,
+.radio-body { fill: rgba(242, 239, 221, 0.92); }
+.evidence-lines { fill: none; stroke: rgba(10, 12, 18, 0.82); stroke-width: 4; stroke-linecap: round; }
+.marker-dot { fill: rgba(242, 193, 78, 0.94); }
+.marker-dot-alt { fill: rgba(123, 143, 178, 0.9); }
+.beacon-arc { stroke: rgba(242, 193, 78, 0.9); stroke-width: 4; }
+.radio-antenna { fill: rgba(242, 193, 78, 0.9); }
.poster-illustration {
width: 100%;
height: auto;
@@ -251,7 +416,10 @@ button:focus, input:focus, select:focus, textarea:focus { outline: 2px solid rgb
@media (max-width: 700px) {
.page-header,
.page-shell {
- width: min(100% - 1rem, 1100px);
+ width: min(100% - 1.25rem, 1100px);
+ }
+ .evidence-contact-grid {
+ grid-template-columns: 1fr;
}
.brand-row,
@@ -264,3 +432,94 @@ button:focus, input:focus, select:focus, textarea:focus { outline: 2px solid rgb
min-width: 0;
}
}
+
+.doris-family-shell .case-nav {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+}
+
+.doris-family-shell .case-nav-link,
+.doris-family-shell .family-app-card {
+ text-decoration: none;
+}
+
+.doris-family-shell .case-nav-link {
+ display: inline-flex;
+ align-items: center;
+ padding: 0.58rem 0.9rem;
+ border-radius: 999px;
+ border: 1px solid rgba(242, 193, 78, 0.18);
+ background: rgba(13, 19, 33, 0.72);
+ color: var(--muted);
+ font-size: 0.88rem;
+ letter-spacing: 0.02em;
+}
+
+.doris-family-shell .case-nav-link:hover,
+.doris-family-shell .case-nav-link.active {
+ color: var(--ink);
+ border-color: rgba(242, 193, 78, 0.48);
+ background: linear-gradient(135deg, rgba(213, 54, 0, 0.24), rgba(145, 15, 63, 0.24));
+}
+
+.doris-family-shell .nav-link.active {
+ color: var(--ink);
+ border-color: rgba(242, 193, 78, 0.58);
+ background: linear-gradient(135deg, rgba(213, 54, 0, 0.24), rgba(145, 15, 63, 0.24));
+}
+
+.family-portal {
+ display: grid;
+ gap: 14px;
+}
+
+.family-directory-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
+ gap: 12px;
+}
+
+.family-app-card {
+ display: grid;
+ gap: 6px;
+ padding: 14px;
+ border-radius: 16px;
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ background: linear-gradient(180deg, rgba(17, 24, 39, 0.92), rgba(8, 12, 22, 0.96));
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04), 0 14px 28px rgba(0, 0, 0, 0.22);
+ color: var(--ink);
+}
+
+.family-app-card:hover,
+.family-app-card.current-desk {
+ border-color: rgba(242, 193, 78, 0.4);
+ transform: translateY(-1px);
+}
+
+.family-app-kicker {
+ font-size: 0.72rem;
+ letter-spacing: 0.14em;
+ text-transform: uppercase;
+ color: var(--hf-amber);
+}
+
+.family-app-card strong {
+ font-size: 1rem;
+}
+
+.family-app-card small {
+ color: var(--muted);
+ line-height: 1.45;
+}
+
+@media (max-width: 768px) {
+ .doris-family-shell .case-nav {
+ overflow-x: auto;
+ padding-bottom: 4px;
+ }
+
+ .family-directory-grid {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/home/doris-barbell/app/static/images/hotfuzz-training-dossier.jpg b/home/doris-barbell/app/static/images/hotfuzz-training-dossier.jpg
new file mode 100644
index 0000000..e1bb56e
Binary files /dev/null and b/home/doris-barbell/app/static/images/hotfuzz-training-dossier.jpg differ
diff --git a/home/doris-barbell/app/templates/base.html b/home/doris-barbell/app/templates/base.html
index 1a11019..2a9c54e 100644
--- a/home/doris-barbell/app/templates/base.html
+++ b/home/doris-barbell/app/templates/base.html
@@ -4,9 +4,9 @@
{{ title or 'Doris Barbell' }}
-
+
-
+
+
+
+
+
Singular front door
+
Doris family directory
+
+
Casefile directory
+
+ One shell, separate runtimes. Training reports live here, but the rest of the Doris family is one jump away.
+
+
{% block content %}{% endblock %}
diff --git a/home/doris-barbell/app/templates/dashboard.html b/home/doris-barbell/app/templates/dashboard.html
index 21aed48..7d71903 100644
--- a/home/doris-barbell/app/templates/dashboard.html
+++ b/home/doris-barbell/app/templates/dashboard.html
@@ -1,7 +1,7 @@
{% extends 'base.html' %}
{% block content %}
-
+
Training floor board
@@ -44,7 +44,7 @@
-
+
Intake docket Weight log
Log weight
@@ -147,7 +147,7 @@
-
+
Workout report Quick log
Quick log workout
@@ -170,7 +170,7 @@
-
+
Current split Routine board
Current routine board
John's current five-day split is now loaded as the default routine library with a default prescription of 3 sets of 10 reps. Weights are still TBD.
diff --git a/home/doris-barbell/tests/test_doris_family_singular_site.py b/home/doris-barbell/tests/test_doris_family_singular_site.py
new file mode 100644
index 0000000..9147a12
--- /dev/null
+++ b/home/doris-barbell/tests/test_doris_family_singular_site.py
@@ -0,0 +1,116 @@
+from pathlib import Path
+
+ROOT = Path(__file__).resolve().parents[2]
+
+
+def read(relative_path: str) -> str:
+ return (ROOT / relative_path).read_text()
+
+
+def test_family_directory_markers_exist_across_doris_apps() -> None:
+ expectations = {
+ 'doris-kitchen/app/templates/base.html': [
+ 'doris-family-shell',
+ 'doris-family-nav',
+ 'Singular front door',
+ 'Doris family directory',
+ 'Casefile directory',
+ 'family-directory-grid',
+ 'Doris Dashboard',
+ 'Services Directory',
+ 'Doris Schoolhouse',
+ 'Doris Barbell',
+ 'case-nav',
+ 'Queue board',
+ ],
+ 'doris-schoolhouse/app/templates/base.html': [
+ 'doris-family-shell',
+ 'doris-family-nav',
+ 'Singular front door',
+ 'Doris family directory',
+ 'Casefile directory',
+ 'family-directory-grid',
+ 'Doris Dashboard',
+ 'Services Directory',
+ 'Doris Kitchen',
+ 'Doris Barbell',
+ 'case-nav',
+ 'Submission packet',
+ ],
+ 'doris-barbell/app/templates/base.html': [
+ 'doris-family-shell',
+ 'doris-family-nav',
+ 'Singular front door',
+ 'Doris family directory',
+ 'Casefile directory',
+ 'family-directory-grid',
+ 'Doris Dashboard',
+ 'Services Directory',
+ 'Doris Kitchen',
+ 'Doris Schoolhouse',
+ 'case-nav',
+ 'Workout reports',
+ ],
+ 'doris-dashboard/bin/generate_dashboard.py': [
+ 'render_family_directory',
+ 'Doris family directory',
+ 'Services Directory',
+ 'Doris Kitchen',
+ 'Doris Schoolhouse',
+ 'Doris Barbell',
+ 'family-directory-grid',
+ ],
+ }
+
+ for relative_path, markers in expectations.items():
+ content = read(relative_path)
+ for marker in markers:
+ assert marker in content, f'{marker} missing from {relative_path}'
+
+
+def test_family_navigation_styles_exist() -> None:
+ expectations = {
+ 'doris-kitchen/app/static/app.css': ['.doris-family-shell .case-nav', '.family-directory-grid', '.family-app-card', '.family-app-kicker'],
+ 'doris-schoolhouse/app/static/app.css': ['.doris-family-shell .case-nav', '.family-directory-grid', '.family-app-card', '.family-app-kicker'],
+ 'doris-barbell/app/static/app.css': ['.doris-family-shell .case-nav', '.family-directory-grid', '.family-app-card', '.family-app-kicker'],
+ 'doris-dashboard/public/style.css': ['.family-portal', '.family-directory-grid', '.family-app-card', '.family-app-kicker'],
+ }
+
+ for relative_path, markers in expectations.items():
+ content = read(relative_path)
+ for marker in markers:
+ assert marker in content, f'{marker} missing from {relative_path}'
+
+
+def test_hotfuzz_photo_assets_exist_and_are_wired() -> None:
+ assets = {
+ 'doris-dashboard/public/assets/hotfuzz-operator-desk.jpg': 'Operator still · source logged in docs',
+ 'doris-barbell/app/static/images/hotfuzz-training-dossier.jpg': 'Training still · source logged in docs',
+ 'doris-kitchen/app/static/images/hotfuzz-pantry-evidence.jpg': 'Pantry still · source logged in docs',
+ 'doris-schoolhouse/app/static/images/hotfuzz-records-desk.jpg': 'Records still · source logged in docs',
+ }
+
+ for relative_path, marker in assets.items():
+ assert (ROOT / relative_path).exists(), f'missing asset file: {relative_path}'
+ if relative_path.startswith('doris-dashboard/'):
+ content = read('doris-dashboard/bin/generate_dashboard.py')
+ elif 'barbell' in relative_path:
+ content = read('doris-barbell/app/templates/base.html')
+ elif 'kitchen' in relative_path:
+ content = read('doris-kitchen/app/templates/base.html')
+ else:
+ content = read('doris-schoolhouse/app/templates/base.html')
+ assert Path(relative_path).name in content, f'{relative_path} not wired into template/generator'
+ assert marker in content, f'{marker} missing where {relative_path} is referenced'
+
+ provenance_doc = ROOT / 'doris-family-hotfuzz-graphics-sources-2026-05-24.md'
+ assert provenance_doc.exists(), 'graphics provenance doc missing'
+ provenance_text = provenance_doc.read_text()
+ for marker in ['CC BY-SA', 'CC BY-NC-SA', 'CC BY-NC-ND', 'CC BY-NC']:
+ assert marker in provenance_text, f'{marker} missing from provenance doc'
+
+
+def test_barbell_interior_anchor_markers_exist() -> None:
+ content = read('doris-barbell/app/templates/dashboard.html')
+ for marker in ['id="body-metrics"', 'id="weight-log"', 'id="routine-board"', 'id="recent-workouts"']:
+ assert marker in content, f'{marker} missing from Doris Barbell dashboard'
diff --git a/home/doris-dashboard/bin/generate_dashboard.py b/home/doris-dashboard/bin/generate_dashboard.py
index 2f4b7db..327f242 100755
--- a/home/doris-dashboard/bin/generate_dashboard.py
+++ b/home/doris-dashboard/bin/generate_dashboard.py
@@ -2020,12 +2020,14 @@ def top_nav(active:str)->str:
items=[
('Home','index.html','home'),
('Services','services.html','services'),
+ ('Kitchen','http://10.5.30.7:8092','kitchen'),
+ ('Schoolhouse','https://schoolhouse.paccoco.com','schoolhouse'),
+ ('Barbell','http://10.5.30.6:8093','barbell'),
('Donetick','https://donetick.paccoco.com','donetick'),
('Paperless','https://paperless.paccoco.com','paperless'),
('n8n','https://n8n.paccoco.com','n8n'),
('Grafana','https://grafana.paccoco.com','grafana'),
('Gitea','https://gitea.paccoco.com','gitea'),
- ('Schoolhouse','https://schoolhouse.paccoco.com','schoolhouse'),
]
links=[]
for label,url,key in items:
@@ -2043,7 +2045,30 @@ def top_nav(active:str)->str:
def hot_fuzz_art(label:str)->str:
safe_label=esc(label.upper())
- return f"""
Operator Evidence Board
{safe_label} """
+ return f"""
Operator Evidence Board
{safe_label} """
+
+
+def render_family_directory(active:str)->str:
+ entries=[
+ ('Doris Dashboard','index.html','dashboard','Operator portal','Canonical front door for briefings, alerts, and launch control.'),
+ ('Services Directory','services.html','services','Switchboard','Dense launch wall for the wider stack once you know where you need to go.'),
+ ('Doris Kitchen','http://10.5.30.7:8092','kitchen','Pantry desk','Recipe leads, import repair, and meal-planning evidence review.'),
+ ('Doris Schoolhouse','https://schoolhouse.paccoco.com','schoolhouse','Records desk','Assignments, recordings, D2L sync, and archive intake.'),
+ ('Doris Barbell','http://10.5.30.6:8093','barbell','Training desk','Body metrics, routines, workouts, and progression dossiers.'),
+ ]
+ cards=[]
+ active_key='dashboard' if active=='home' else active
+ for title,url,key,kicker,summary in entries:
+ classes=['family-app-card']
+ if key==active_key:
+ classes.append('current-desk')
+ kicker='Current desk'
+ attrs=[]
+ if url.startswith('http'):
+ attrs.append("target='_blank'")
+ attrs.append("rel='noreferrer'")
+ cards.append(f"{esc(kicker)} {esc(title)} {esc(summary)} ")
+ return f"Singular front door
Doris family directory One shell, separate runtimes. {''.join(cards)}
"
def build_services_inventory()->tuple[list[dict[str,Any]],list[dict[str,Any]]]:
@@ -2119,8 +2144,9 @@ def render_services_page(hero_summary:str,candidate_count:int,source_count:int,h
groups_data, flat_items=build_services_inventory()
services_html=build_services_directory(groups_data)
inventory_html=render_services_inventory(flat_items)
+ family_html=render_family_directory('services')
utility_html=collapsible_section('Portal notes','Keep Doris as the front door, not a monolith.',"How to use this Use Home for today, alerts, and the briefing. Use Services when you want the rest of the stack quickly without hunting bookmarks.
Design rule Shared navigation and status live here; full workflows still stay in their own apps.
",'portal-notes',open_by_default=True)+collapsible_section('Inventory','Coverage, access model, and host distribution.',inventory_html,'services-inventory',open_by_default=True)
- return f"Doris Services
{top_nav('services')}Operator portal links are curated from the homelab docs and current live dashboard context.
Local-only on NOMAD unless John says otherwise.
"
+ return f"Doris Services
{top_nav('services')}{family_html}Operator portal links are curated from the homelab docs and current live dashboard context.
Local-only on NOMAD unless John says otherwise.
"
def render()->str:
candidates=load_json(DIGEST_ROOT/'data/candidates.json',[]); prefs=load_json(DIGEST_ROOT/'data/preferences.json',{})
@@ -2154,6 +2180,7 @@ def render()->str:
system_html=build_system_section(system)
hero_tokens=compact_number(hermes.get('total_tokens',0)) if hermes.get('ok') else '—'
snapshot_html=build_operator_snapshot(todos,pulse,paperless,hermes)
+ family_html=render_family_directory('home')
hero_caseboard_html=build_hero_caseboard(feature_item,todos,paperless)
feature_html=build_feature_story(secondary_feature_item)
session_window=find_usage_window(hermes.get('account_windows',[]),'session') if hermes.get('ok') else None
@@ -2180,7 +2207,7 @@ def render()->str:
return f"""Doris Dashboard
{top_nav('home')}
Today's operator snapshot
What matters before the scroll Glanceable heat across chores, Hermes, and the lab. {snapshot_html}
-
+{family_html}
Category mix: {esc(', '.join(f'{k}: {v}' for k,v in counts.most_common(10)))}
Local-only on NOMAD unless John says otherwise. Weather from Open-Meteo; moon phase calculated locally. Hermes stats come from ~/.hermes/state.db.
{feedback_bootstrap()}"""
def main()->int:
diff --git a/home/doris-dashboard/public/assets/hotfuzz-operator-desk.jpg b/home/doris-dashboard/public/assets/hotfuzz-operator-desk.jpg
new file mode 100644
index 0000000..25b5b35
Binary files /dev/null and b/home/doris-dashboard/public/assets/hotfuzz-operator-desk.jpg differ
diff --git a/home/doris-dashboard/public/style.css b/home/doris-dashboard/public/style.css
index f840b82..a55c41e 100644
--- a/home/doris-dashboard/public/style.css
+++ b/home/doris-dashboard/public/style.css
@@ -41,6 +41,40 @@ main{max-width:1380px;margin:0 auto;padding:18px}
.incident-tape{height:14px;background:repeating-linear-gradient(135deg,var(--hf-amber) 0 18px,#111 18px 36px);box-shadow:0 3px 14px rgba(0,0,0,.32)}
.casefile-header{position:relative}
.hot-fuzz-art{position:relative;max-width:320px;margin:14px 0 0}
+.art-stack{display:grid;gap:12px}
+.photo-evidence-card{position:relative;margin:0;padding:10px;border-radius:22px;background:linear-gradient(180deg, rgba(255,255,255,.06), rgba(255,255,255,.02));border:1px solid rgba(242,239,221,.14);box-shadow:0 18px 36px rgba(0,0,0,.28);overflow:hidden}
+.photo-evidence-card::before{content:'';position:absolute;inset:0;background:linear-gradient(180deg, rgba(242,193,78,.08), transparent 28%, rgba(145,15,63,.16));pointer-events:none}
+.photo-evidence-card img{width:100%;aspect-ratio:16/10;object-fit:cover;display:block;border-radius:14px;filter:saturate(.82) sepia(.18) contrast(1.02)}
+.photo-evidence-card figcaption{position:absolute;left:18px;right:18px;bottom:16px;z-index:1;margin:0;font-size:.72rem;letter-spacing:.16em;text-transform:uppercase;color:#fff0cb;text-shadow:0 2px 10px rgba(0,0,0,.75)}
+.evidence-contact-grid{display:grid;grid-template-columns:minmax(0,1.15fr) minmax(140px,.85fr);gap:12px;align-items:stretch}
+.evidence-note-card{position:relative;padding:14px 16px;border-radius:22px;background:linear-gradient(180deg,rgba(255,255,255,.05),rgba(255,255,255,.015)),rgba(15,18,25,.96);border:1px solid rgba(242,239,221,.11);box-shadow:0 16px 30px rgba(0,0,0,.24);overflow:hidden}
+.evidence-note-card::before{content:'';position:absolute;inset:0 auto auto 0;width:100%;height:4px;background:linear-gradient(90deg,var(--hf-red),var(--hf-amber),var(--hf-blue));opacity:.95}
+.case-contact-top{display:flex;justify-content:space-between;gap:10px;align-items:center;flex-wrap:wrap;margin-bottom:10px}
+.case-contact-kicker,.case-contact-badge{display:inline-flex;align-items:center;border-radius:999px;padding:6px 10px;font-size:.74rem;letter-spacing:.08em;text-transform:uppercase}
+.case-contact-kicker{background:rgba(242,193,78,.12);border:1px solid rgba(242,193,78,.24);color:#ffe6aa}
+.case-contact-badge{background:rgba(123,143,178,.16);border:1px solid rgba(123,143,178,.24);color:#dbe5ff}
+.contact-sheet-illustration{width:100%;height:auto;display:block;margin-bottom:10px;filter:drop-shadow(0 14px 22px rgba(0,0,0,.24))}
+.contact-frame{fill:rgba(11,14,20,.92);stroke:rgba(242,239,221,.12);stroke-width:2}
+.contact-photo{fill:rgba(123,143,178,.22);stroke:rgba(242,239,221,.16);stroke-width:1.5}
+.contact-photo.alt{fill:rgba(213,54,0,.18)}
+.contact-lines,.contact-thread{fill:none;stroke:rgba(242,239,221,.86);stroke-width:4;stroke-linecap:round}
+.contact-thread{stroke:rgba(213,54,0,.82);stroke-width:3}
+.contact-thread.faint{stroke:rgba(123,143,178,.66)}
+.contact-marker{fill:rgba(242,193,78,.18);stroke:rgba(242,193,78,.84);stroke-width:2}
+.contact-label{fill:rgba(242,239,221,.9);font-size:15px;font-weight:800;letter-spacing:.16em;text-anchor:middle}
+.case-contact-list{margin:0;padding-left:18px;color:#d5dded;display:grid;gap:8px;line-height:1.4}
+.supporting-photo-card{min-height:100%}
+.village-lane,.map-thread,.beacon-arc{fill:none;stroke-linecap:round;stroke-linejoin:round}
+.village-lane{stroke:rgba(242,193,78,.88);stroke-width:7}
+.map-thread{stroke:rgba(213,54,0,.86);stroke-width:3}
+.beacon-arc{stroke:rgba(242,193,78,.9);stroke-width:4}
+.beacon-arc.faint{stroke:rgba(123,143,178,.68)}
+.evidence-card,.church-block,.radio-body{fill:rgba(242,239,221,.92)}
+.evidence-lines,.window-line{fill:none;stroke:rgba(10,12,18,.82);stroke-width:4;stroke-linecap:round}
+.window-line{stroke-width:3}
+.marker-dot{fill:rgba(242,193,78,.94)}
+.marker-dot-alt{fill:rgba(123,143,178,.9)}
+.radio-antenna,.radio-knob,.church-spire{fill:rgba(242,193,78,.9)}
.poster-illustration{width:100%;height:auto;display:block;filter:drop-shadow(0 24px 30px rgba(0,0,0,.35))}
.poster-frame{fill:rgba(10,12,18,.88);stroke:rgba(242,239,221,.2);stroke-width:2}
.poster-halo{fill:url(#dashboard-sunset);opacity:.9}
@@ -202,6 +236,11 @@ h1{
margin:0;
}
+.hero-poster-stack .art-stack,
+.hero-poster-stack .photo-evidence-card{
+ max-width:none;
+}
+
.hero-badges{justify-content:flex-start}
.hero-dossier-stack{justify-content:stretch}
@@ -856,7 +895,7 @@ footer p{margin:.25rem 0}
@media (max-width:920px){
.hero{grid-template-columns:1fr}
- .status,.metrics,.metrics.metrics-compact,.focus-grid,.summary-grid,.news-list,.grid,.forecast-grid{grid-template-columns:1fr}
+ .status,.metrics,.metrics.metrics-compact,.focus-grid,.summary-grid,.news-list,.grid,.forecast-grid,.evidence-contact-grid{grid-template-columns:1fr}
}
@media (max-width:720px){
@@ -1097,3 +1136,11 @@ footer p{margin:.25rem 0}
.hero-chips{justify-content:flex-start}
.snapshot-strip,.hero-slip-grid{grid-template-columns:1fr}
}
+
+.family-portal{margin:18px 0 20px;display:grid;gap:12px}
+.family-directory-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:12px}
+.family-app-card{display:grid;gap:6px;padding:14px;border-radius:16px;border:1px solid var(--line-soft);background:linear-gradient(180deg,rgba(17,24,39,.92),rgba(8,12,22,.96));box-shadow:inset 0 1px 0 rgba(255,255,255,.04),0 14px 28px rgba(0,0,0,.22);color:var(--text);text-decoration:none}
+.family-app-card:hover,.family-app-card.current-desk{border-color:rgba(242,193,78,.42);transform:translateY(-1px)}
+.family-app-kicker{font-size:.7rem;letter-spacing:.15em;text-transform:uppercase;color:var(--hf-amber)}
+.family-app-card strong{font-size:1rem}
+.family-app-card small{color:var(--muted);line-height:1.45}
diff --git a/home/doris-family-hotfuzz-graphics-sources-2026-05-24.md b/home/doris-family-hotfuzz-graphics-sources-2026-05-24.md
new file mode 100644
index 0000000..65b38e2
--- /dev/null
+++ b/home/doris-family-hotfuzz-graphics-sources-2026-05-24.md
@@ -0,0 +1,49 @@
+# Doris family Hot Fuzz graphics sources (2026-05-24)
+
+Purpose: provenance/handoff for the first real-photo graphics pass across the Doris family sites.
+
+Selection notes:
+- Images were sourced through Openverse and fetched from the linked Flickr originals.
+- This is a private household-only stack, so the goal here was practical themed imagery and a clean handoff, not a polished public redistribution package.
+- The photos are intentionally treated as supporting stills now, not the primary thematic payload.
+- The corrected pass also adds original in-repo SVG caseboard/contact-sheet graphics authored directly inside the Doris templates and dashboard generator on 2026-05-24. Those authored graphics are the main Hot Fuzz / British police procedural read; the sourced photos remain secondary texture under the same asset hooks.
+
+## Doris Dashboard
+- App path: `home/doris-dashboard/public/assets/hotfuzz-operator-desk.jpg`
+- UI usage: operator-still card beneath the existing poster art in the dashboard hero stack
+- Openverse query: `corkboard desk`
+- Source title: `The new workspace`
+- Creator: `lovelihood`
+- License: `CC BY-SA`
+- Source URL: `https://live.staticflickr.com/4148/5063431636_53c8b20835_b.jpg`
+
+## Doris Barbell
+- App path: `home/doris-barbell/app/static/images/hotfuzz-training-dossier.jpg`
+- UI usage: training-still card beneath the existing dossier poster art
+- Openverse query: `barbell plates gym`
+- Source title: `Pause`
+- Creator: `licornenoir`
+- License: `CC BY-NC-SA`
+- Source URL: `https://live.staticflickr.com/65535/49477674042_905e3f89b2_b.jpg`
+
+## Doris Kitchen
+- App path: `home/doris-kitchen/app/static/images/hotfuzz-pantry-evidence.jpg`
+- UI usage: pantry-still card beneath the existing poster art
+- Openverse query: `pantry spices shelf`
+- Source title: `Larder: The Spice Shelf`
+- Creator: `velodenz`
+- License: `CC BY-NC-ND`
+- Source URL: `https://live.staticflickr.com/2893/33377054132_873b33ee32_b.jpg`
+
+## Doris Schoolhouse
+- App path: `home/doris-schoolhouse/app/static/images/hotfuzz-records-desk.jpg`
+- UI usage: records-still card beneath the existing poster art
+- Openverse query: `study desk papers headphones`
+- Source title: `(untitled)`
+- Creator: `Kevin_Morris`
+- License: `CC BY-NC`
+- Source URL: `https://live.staticflickr.com/6168/6171438106_ae3c9e1291.jpg`
+
+## Follow-up ideas
+- If the sourcing/review lane wants a stronger exact-Hot-Fuzz vibe later, swap these with tighter police-procedural stills or custom-generated composites while keeping the same asset hooks and CSS card treatment.
+- Dashboard and Schoolhouse are the weakest thematic matches; those are the first two worth upgrading if better source material lands.
diff --git a/home/doris-kitchen/app/static/app.css b/home/doris-kitchen/app/static/app.css
index 2a51e33..a349304 100644
--- a/home/doris-kitchen/app/static/app.css
+++ b/home/doris-kitchen/app/static/app.css
@@ -149,6 +149,223 @@ label,
min-width: min(100%, 280px);
}
+.art-stack {
+ display: grid;
+ gap: 12px;
+ flex: 0 1 320px;
+ min-width: min(100%, 280px);
+}
+
+.art-stack .hot-fuzz-art {
+ flex: initial;
+ min-width: 0;
+}
+
+.photo-evidence-card {
+ position: relative;
+ margin: 0;
+ padding: 10px;
+ border-radius: 22px;
+ background: linear-gradient(180deg, rgba(255,255,255,0.06), rgba(255,255,255,0.02));
+ border: 1px solid rgba(242, 239, 221, 0.14);
+ box-shadow: 0 18px 36px rgba(0, 0, 0, 0.28);
+ overflow: hidden;
+}
+
+.photo-evidence-card::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ background: linear-gradient(180deg, rgba(242, 193, 78, 0.08), transparent 28%, rgba(145, 15, 63, 0.16));
+ pointer-events: none;
+}
+
+.photo-evidence-card img {
+ width: 100%;
+ aspect-ratio: 16 / 10;
+ object-fit: cover;
+ display: block;
+ border-radius: 14px;
+ filter: saturate(0.82) sepia(0.18) contrast(1.02);
+}
+
+.photo-evidence-card figcaption {
+ position: absolute;
+ left: 18px;
+ right: 18px;
+ bottom: 16px;
+ z-index: 1;
+ margin: 0;
+ font-size: 0.72rem;
+ letter-spacing: 0.16em;
+ text-transform: uppercase;
+ color: #fff0cb;
+ text-shadow: 0 2px 10px rgba(0, 0, 0, 0.75);
+}
+
+.evidence-contact-grid {
+ display: grid;
+ grid-template-columns: minmax(0, 1.15fr) minmax(140px, 0.85fr);
+ gap: 12px;
+ align-items: stretch;
+}
+
+.evidence-note-card {
+ position: relative;
+ padding: 14px 16px;
+ border-radius: 22px;
+ background: linear-gradient(180deg, rgba(255,255,255,0.05), rgba(255,255,255,0.015)), rgba(15,18,25,0.96);
+ border: 1px solid rgba(242, 239, 221, 0.11);
+ box-shadow: 0 16px 30px rgba(0, 0, 0, 0.24);
+ overflow: hidden;
+}
+
+.evidence-note-card::before {
+ content: '';
+ position: absolute;
+ inset: 0 auto auto 0;
+ width: 100%;
+ height: 4px;
+ background: linear-gradient(90deg, var(--hf-red), var(--hf-amber), var(--hf-blue));
+}
+
+.case-contact-top {
+ display: flex;
+ justify-content: space-between;
+ gap: 10px;
+ align-items: center;
+ flex-wrap: wrap;
+ margin-bottom: 10px;
+}
+
+.case-contact-kicker,
+.case-contact-badge {
+ display: inline-flex;
+ align-items: center;
+ border-radius: 999px;
+ padding: 6px 10px;
+ font-size: 0.74rem;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+}
+
+.case-contact-kicker {
+ background: rgba(242, 193, 78, 0.12);
+ border: 1px solid rgba(242, 193, 78, 0.24);
+ color: #ffe6aa;
+}
+
+.case-contact-badge {
+ background: rgba(123, 143, 178, 0.16);
+ border: 1px solid rgba(123, 143, 178, 0.24);
+ color: #dbe5ff;
+}
+
+.contact-sheet-illustration {
+ width: 100%;
+ height: auto;
+ display: block;
+ margin-bottom: 10px;
+ filter: drop-shadow(0 14px 22px rgba(0, 0, 0, 0.24));
+}
+
+.contact-frame {
+ fill: rgba(11, 14, 20, 0.92);
+ stroke: rgba(242, 239, 221, 0.12);
+ stroke-width: 2;
+}
+
+.contact-photo {
+ fill: rgba(123, 143, 178, 0.22);
+ stroke: rgba(242, 239, 221, 0.16);
+ stroke-width: 1.5;
+}
+
+.contact-photo.alt {
+ fill: rgba(213, 54, 0, 0.18);
+}
+
+.contact-lines,
+.contact-thread {
+ fill: none;
+ stroke: rgba(242, 239, 221, 0.86);
+ stroke-width: 4;
+ stroke-linecap: round;
+}
+
+.contact-thread {
+ stroke: rgba(213, 54, 0, 0.82);
+ stroke-width: 3;
+}
+
+.contact-marker {
+ fill: rgba(242, 193, 78, 0.18);
+ stroke: rgba(242, 193, 78, 0.84);
+ stroke-width: 2;
+}
+
+.contact-label {
+ fill: rgba(242, 239, 221, 0.9);
+ font-size: 15px;
+ font-weight: 800;
+ letter-spacing: 0.16em;
+ text-anchor: middle;
+}
+
+.case-contact-list {
+ margin: 0;
+ padding-left: 18px;
+ color: #d5dded;
+ display: grid;
+ gap: 8px;
+ line-height: 1.4;
+}
+
+.supporting-photo-card {
+ min-height: 100%;
+}
+
+.village-lane,
+.map-thread {
+ fill: none;
+ stroke-linecap: round;
+ stroke-linejoin: round;
+}
+
+.village-lane {
+ stroke: rgba(242, 193, 78, 0.88);
+ stroke-width: 7;
+}
+
+.map-thread {
+ stroke: rgba(213, 54, 0, 0.86);
+ stroke-width: 3;
+}
+
+.evidence-card,
+.radio-body {
+ fill: rgba(242, 239, 221, 0.92);
+}
+
+.evidence-lines {
+ fill: none;
+ stroke: rgba(10, 12, 18, 0.82);
+ stroke-width: 4;
+ stroke-linecap: round;
+}
+
+.marker-dot {
+ fill: rgba(242, 193, 78, 0.94);
+}
+
+.marker-dot-alt {
+ fill: rgba(123, 143, 178, 0.9);
+}
+
+.radio-antenna {
+ fill: rgba(242, 193, 78, 0.9);
+}
+
.poster-illustration {
width: 100%;
height: auto;
@@ -525,6 +742,10 @@ button.danger { color: #ffd7d1; }
padding: 18px;
}
+ .evidence-contact-grid {
+ grid-template-columns: 1fr;
+ }
+
.hot-fuzz-art {
flex-basis: 100%;
min-width: 0;
@@ -534,3 +755,94 @@ button.danger { color: #ffd7d1; }
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
+
+.doris-family-shell .case-nav {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+}
+
+.doris-family-shell .case-nav-link,
+.doris-family-shell .family-app-card {
+ text-decoration: none;
+}
+
+.doris-family-shell .case-nav-link {
+ display: inline-flex;
+ align-items: center;
+ padding: 0.58rem 0.9rem;
+ border-radius: 999px;
+ border: 1px solid rgba(242, 193, 78, 0.18);
+ background: rgba(13, 19, 33, 0.72);
+ color: var(--muted);
+ font-size: 0.88rem;
+ letter-spacing: 0.02em;
+}
+
+.doris-family-shell .case-nav-link:hover,
+.doris-family-shell .case-nav-link.active {
+ color: var(--ink);
+ border-color: rgba(242, 193, 78, 0.48);
+ background: linear-gradient(135deg, rgba(213, 54, 0, 0.24), rgba(145, 15, 63, 0.24));
+}
+
+.doris-family-shell .nav-link.active {
+ color: var(--ink);
+ border-color: rgba(242, 193, 78, 0.58);
+ background: linear-gradient(135deg, rgba(213, 54, 0, 0.24), rgba(145, 15, 63, 0.24));
+}
+
+.family-portal {
+ display: grid;
+ gap: 14px;
+}
+
+.family-directory-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
+ gap: 12px;
+}
+
+.family-app-card {
+ display: grid;
+ gap: 6px;
+ padding: 14px;
+ border-radius: 16px;
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ background: linear-gradient(180deg, rgba(17, 24, 39, 0.92), rgba(8, 12, 22, 0.96));
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04), 0 14px 28px rgba(0, 0, 0, 0.22);
+ color: var(--ink);
+}
+
+.family-app-card:hover,
+.family-app-card.current-desk {
+ border-color: rgba(242, 193, 78, 0.4);
+ transform: translateY(-1px);
+}
+
+.family-app-kicker {
+ font-size: 0.72rem;
+ letter-spacing: 0.14em;
+ text-transform: uppercase;
+ color: var(--hf-amber);
+}
+
+.family-app-card strong {
+ font-size: 1rem;
+}
+
+.family-app-card small {
+ color: var(--muted);
+ line-height: 1.45;
+}
+
+@media (max-width: 768px) {
+ .doris-family-shell .case-nav {
+ overflow-x: auto;
+ padding-bottom: 4px;
+ }
+
+ .family-directory-grid {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/home/doris-kitchen/app/static/images/hotfuzz-pantry-evidence.jpg b/home/doris-kitchen/app/static/images/hotfuzz-pantry-evidence.jpg
new file mode 100644
index 0000000..724900a
Binary files /dev/null and b/home/doris-kitchen/app/static/images/hotfuzz-pantry-evidence.jpg differ
diff --git a/home/doris-kitchen/app/templates/base.html b/home/doris-kitchen/app/templates/base.html
index 2406777..daa3d04 100644
--- a/home/doris-kitchen/app/templates/base.html
+++ b/home/doris-kitchen/app/templates/base.html
@@ -4,9 +4,9 @@
{{ title or 'Doris Kitchen' }}
-
+
-
+
-
+
+ Kitchen
Dashboard
- Queue
- Search
- Planner
- Imports
- Failures
+ Services
+ Schoolhouse
+ Barbell
@@ -37,32 +36,114 @@
Case type: Pantry Evidence
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- PANTRY EVIDENCE
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PANTRY EVIDENCE
+
+
+
+
+ Queue board
+ Lead intake
+ Planner
+ Import repair
+ Failures
+
+
+
+
+
Singular front door
+
Doris family directory
+
+
Casefile directory
+
+ One shell, separate runtimes. Jump to the right desk without losing the family look.
+
+
{% block content %}{% endblock %}
diff --git a/home/doris-schoolhouse/app/static/app.css b/home/doris-schoolhouse/app/static/app.css
index 62f6932..e03cdac 100644
--- a/home/doris-schoolhouse/app/static/app.css
+++ b/home/doris-schoolhouse/app/static/app.css
@@ -60,6 +60,173 @@ body {
flex: 0 1 320px;
min-width: min(100%, 280px);
}
+.art-stack {
+ display: grid;
+ gap: 12px;
+ flex: 0 1 320px;
+ min-width: min(100%, 280px);
+}
+.art-stack .hot-fuzz-art {
+ flex: initial;
+ min-width: 0;
+}
+.photo-evidence-card {
+ position: relative;
+ margin: 0;
+ padding: 10px;
+ border-radius: 22px;
+ background: linear-gradient(180deg, rgba(255,255,255,0.06), rgba(255,255,255,0.02));
+ border: 1px solid rgba(242, 239, 221, 0.14);
+ box-shadow: 0 18px 36px rgba(0, 0, 0, 0.28);
+ overflow: hidden;
+}
+.photo-evidence-card::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ background: linear-gradient(180deg, rgba(242, 193, 78, 0.08), transparent 28%, rgba(145, 15, 63, 0.16));
+ pointer-events: none;
+}
+.photo-evidence-card img {
+ width: 100%;
+ aspect-ratio: 16 / 10;
+ object-fit: cover;
+ display: block;
+ border-radius: 14px;
+ filter: saturate(0.82) sepia(0.18) contrast(1.02);
+}
+.photo-evidence-card figcaption {
+ position: absolute;
+ left: 18px;
+ right: 18px;
+ bottom: 16px;
+ z-index: 1;
+ margin: 0;
+ font-size: 0.72rem;
+ letter-spacing: 0.16em;
+ text-transform: uppercase;
+ color: #fff0cb;
+ text-shadow: 0 2px 10px rgba(0, 0, 0, 0.75);
+}
+.evidence-contact-grid {
+ display: grid;
+ grid-template-columns: minmax(0, 1.15fr) minmax(140px, 0.85fr);
+ gap: 12px;
+ align-items: stretch;
+}
+.evidence-note-card {
+ position: relative;
+ padding: 14px 16px;
+ border-radius: 22px;
+ background: linear-gradient(180deg, rgba(255,255,255,0.05), rgba(255,255,255,0.015)), rgba(15,18,25,0.96);
+ border: 1px solid rgba(242, 239, 221, 0.11);
+ box-shadow: 0 16px 30px rgba(0, 0, 0, 0.24);
+ overflow: hidden;
+}
+.evidence-note-card::before {
+ content: '';
+ position: absolute;
+ inset: 0 auto auto 0;
+ width: 100%;
+ height: 4px;
+ background: linear-gradient(90deg, var(--hf-red), var(--hf-amber), var(--hf-blue));
+}
+.case-contact-top {
+ display: flex;
+ justify-content: space-between;
+ gap: 10px;
+ align-items: center;
+ flex-wrap: wrap;
+ margin-bottom: 10px;
+}
+.case-contact-kicker,
+.case-contact-badge {
+ display: inline-flex;
+ align-items: center;
+ border-radius: 999px;
+ padding: 6px 10px;
+ font-size: 0.74rem;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+}
+.case-contact-kicker {
+ background: rgba(242, 193, 78, 0.12);
+ border: 1px solid rgba(242, 193, 78, 0.24);
+ color: #ffe6aa;
+}
+.case-contact-badge {
+ background: rgba(123, 143, 178, 0.16);
+ border: 1px solid rgba(123, 143, 178, 0.24);
+ color: #dbe5ff;
+}
+.contact-sheet-illustration {
+ width: 100%;
+ height: auto;
+ display: block;
+ margin-bottom: 10px;
+ filter: drop-shadow(0 14px 22px rgba(0, 0, 0, 0.24));
+}
+.contact-frame {
+ fill: rgba(11, 14, 20, 0.92);
+ stroke: rgba(242, 239, 221, 0.12);
+ stroke-width: 2;
+}
+.contact-photo {
+ fill: rgba(123, 143, 178, 0.22);
+ stroke: rgba(242, 239, 221, 0.16);
+ stroke-width: 1.5;
+}
+.contact-photo.alt {
+ fill: rgba(213, 54, 0, 0.18);
+}
+.contact-lines,
+.contact-thread,
+.village-lane,
+.map-thread {
+ fill: none;
+ stroke-linecap: round;
+ stroke-linejoin: round;
+}
+.contact-lines {
+ stroke: rgba(242, 239, 221, 0.86);
+ stroke-width: 4;
+}
+.contact-thread,
+.map-thread {
+ stroke: rgba(213, 54, 0, 0.82);
+ stroke-width: 3;
+}
+.village-lane {
+ stroke: rgba(242, 193, 78, 0.88);
+ stroke-width: 7;
+}
+.contact-marker {
+ fill: rgba(242, 193, 78, 0.18);
+ stroke: rgba(242, 193, 78, 0.84);
+ stroke-width: 2;
+}
+.contact-label {
+ fill: rgba(242, 239, 221, 0.9);
+ font-size: 15px;
+ font-weight: 800;
+ letter-spacing: 0.16em;
+ text-anchor: middle;
+}
+.case-contact-list {
+ margin: 0;
+ padding-left: 18px;
+ color: #d5dded;
+ display: grid;
+ gap: 8px;
+ line-height: 1.4;
+}
+.supporting-photo-card { min-height: 100%; }
+.evidence-card,
+.radio-body { fill: rgba(242, 239, 221, 0.92); }
+.evidence-lines { fill: none; stroke: rgba(10, 12, 18, 0.82); stroke-width: 4; stroke-linecap: round; }
+.marker-dot { fill: rgba(242, 193, 78, 0.94); }
+.marker-dot-alt { fill: rgba(123, 143, 178, 0.9); }
+.radio-antenna { fill: rgba(242, 193, 78, 0.9); }
.poster-illustration {
width: 100%;
height: auto;
@@ -223,6 +390,100 @@ button:hover { filter: brightness(1.06); }
@media (max-width: 768px) {
.site-header, .page-shell { padding: 18px; }
+ .evidence-contact-grid { grid-template-columns: 1fr; }
.stat-grid { grid-template-columns: 1fr; }
.hot-fuzz-art { flex-basis: 100%; min-width: 0; }
+ .casefile-title-row,
+ .brand-row { align-items: flex-start; }
+}
+
+.doris-family-shell .case-nav {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+}
+
+.doris-family-shell .case-nav-link,
+.doris-family-shell .family-app-card {
+ text-decoration: none;
+}
+
+.doris-family-shell .case-nav-link {
+ display: inline-flex;
+ align-items: center;
+ padding: 0.58rem 0.9rem;
+ border-radius: 999px;
+ border: 1px solid rgba(242, 193, 78, 0.18);
+ background: rgba(13, 19, 33, 0.72);
+ color: var(--muted);
+ font-size: 0.88rem;
+ letter-spacing: 0.02em;
+}
+
+.doris-family-shell .case-nav-link:hover,
+.doris-family-shell .case-nav-link.active {
+ color: var(--ink);
+ border-color: rgba(242, 193, 78, 0.48);
+ background: linear-gradient(135deg, rgba(213, 54, 0, 0.24), rgba(145, 15, 63, 0.24));
+}
+
+.doris-family-shell .nav-link.active {
+ color: var(--ink);
+ border-color: rgba(242, 193, 78, 0.58);
+ background: linear-gradient(135deg, rgba(213, 54, 0, 0.24), rgba(145, 15, 63, 0.24));
+}
+
+.family-portal {
+ display: grid;
+ gap: 14px;
+}
+
+.family-directory-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
+ gap: 12px;
+}
+
+.family-app-card {
+ display: grid;
+ gap: 6px;
+ padding: 14px;
+ border-radius: 16px;
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ background: linear-gradient(180deg, rgba(17, 24, 39, 0.92), rgba(8, 12, 22, 0.96));
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04), 0 14px 28px rgba(0, 0, 0, 0.22);
+ color: var(--ink);
+}
+
+.family-app-card:hover,
+.family-app-card.current-desk {
+ border-color: rgba(242, 193, 78, 0.4);
+ transform: translateY(-1px);
+}
+
+.family-app-kicker {
+ font-size: 0.72rem;
+ letter-spacing: 0.14em;
+ text-transform: uppercase;
+ color: var(--hf-amber);
+}
+
+.family-app-card strong {
+ font-size: 1rem;
+}
+
+.family-app-card small {
+ color: var(--muted);
+ line-height: 1.45;
+}
+
+@media (max-width: 768px) {
+ .doris-family-shell .case-nav {
+ overflow-x: auto;
+ padding-bottom: 4px;
+ }
+
+ .family-directory-grid {
+ grid-template-columns: 1fr;
+ }
}
diff --git a/home/doris-schoolhouse/app/static/images/hotfuzz-records-desk.jpg b/home/doris-schoolhouse/app/static/images/hotfuzz-records-desk.jpg
new file mode 100644
index 0000000..1b57807
Binary files /dev/null and b/home/doris-schoolhouse/app/static/images/hotfuzz-records-desk.jpg differ
diff --git a/home/doris-schoolhouse/app/templates/base.html b/home/doris-schoolhouse/app/templates/base.html
index b24a70c..fc05046 100644
--- a/home/doris-schoolhouse/app/templates/base.html
+++ b/home/doris-schoolhouse/app/templates/base.html
@@ -4,9 +4,9 @@
{{ title or 'Doris Schoolhouse' }}
-
+
-
+
+
+
+
+
Singular front door
+
Doris family directory
+
+
Casefile directory
+
+ One shell, separate runtimes. Jump between coursework, household ops, and training without losing the same evidence-room language.
+
+
{% block content %}{% endblock %}