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' }} - + - +
+
+
+
+ +

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 %} -
+
@@ -44,7 +44,7 @@
-
+
Weight log

Log weight

@@ -147,7 +147,7 @@
-
+
Quick log

Quick log workout

@@ -170,7 +170,7 @@
-
+
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"""""" + return f"""
Sandford caseboardNeighbourhood Watch
SWANPUBSEA MINE
  • Village-map pins and pub callouts keep the portal reading like a British police case wall.
  • Emergency arcs, spire silhouette, and evidence slips push the Hot Fuzz vibe ahead of the old stock-desk still.
Operator desk photo with corkboard, papers, and desktop gear.
Operator still · source logged in docs
""" + + +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')}

Sandford services desk

Services Directory

Operator board

Shared navigation across your sites, with Doris still acting as the operational front door. {esc(hero_summary)}

{hot_fuzz_art('services')}
For the Greater GoodEvidence locker: services
{esc(now.strftime('%a %b %-d, %-I:%M %p'))}Central time
{candidate_count}News candidates
{source_count}Sources
{esc(hero_tokens)}30-day Hermes tokens
{services_html}

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')}

Sandford services desk

Services Directory

Operator board

Shared navigation across your sites, with Doris still acting as the operational front door. {esc(hero_summary)}

{hot_fuzz_art('services')}
For the Greater GoodEvidence locker: services
{esc(now.strftime('%a %b %-d, %-I:%M %p'))}Central time
{candidate_count}News candidates
{source_count}Sources
{esc(hero_tokens)}30-day Hermes tokens
{family_html}
{services_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')}

Sandford operator desk

Doris Dashboard

Operator board
Hermes {esc((hermes.get('models') or [{}])[0].get('model') or hermes.get('default_model') or 'unknown')}Session {esc(pct_text((session_window or {}).get('remaining_percent')) if hermes.get('ok') else '—')}24h {esc(compact_number(hermes.get('last_24h_tokens',0)) if hermes.get('ok') else '—')} tokens

{esc(hero_summary)}

{hero_caseboard_html}
{esc(now.strftime('%a %b %-d, %-I:%M %p'))}Central time · refreshes every 30m
{len(candidates)}Candidates
{len(set(i.get('source','') for i in candidates))}Sources
{esc(hero_tokens)}30-day Hermes tokens

Today's operator snapshot

What matters before the scroll

Glanceable heat across chores, Hermes, and the lab.
{snapshot_html}
-
{primary_html}
+{family_html}
{primary_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' }} - + - +
+
+
+
+ +

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' }} - + - +
+
+
+
+ +

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 %}