Add Hot Fuzz graphics across Doris family sites
This commit is contained in:
@@ -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"""<div class='hot-fuzz-art' aria-hidden='true'><div class='film-grain'></div><div class='cinematic-glow'></div><div class='casefile-stamp operator-evidence-tag'>Operator Evidence Board</div><svg viewBox='0 0 320 220' class='poster-illustration' role='presentation'><defs><linearGradient id='dashboard-sunset' x1='0%' y1='0%' x2='100%' y2='100%'><stop offset='0%' stop-color='#f2c14e'></stop><stop offset='50%' stop-color='#d53600'></stop><stop offset='100%' stop-color='#910f3f'></stop></linearGradient></defs><rect x='8' y='8' width='304' height='204' rx='24' class='poster-frame'></rect><circle cx='228' cy='72' r='58' class='poster-halo'></circle><path d='M18 170 L112 100 L134 124 L40 198 Z' class='poster-tape tape-left'></path><path d='M202 82 L302 32 L314 60 L214 112 Z' class='poster-tape tape-right'></path><path d='M112 180 C110 150 116 126 132 110 C140 100 148 94 154 90 C160 94 168 100 176 110 C192 126 198 150 196 180 Z' class='constable-silhouette lead'></path><path d='M174 184 C172 154 178 132 194 118 C202 110 210 104 218 100 C226 104 234 110 242 118 C258 132 264 154 262 184 Z' class='constable-silhouette partner'></path><circle cx='84' cy='60' r='28' class='swan-stamp'></circle><path d='M76 64 C80 54 91 48 101 53 C93 52 88 57 89 63 C90 69 101 68 103 76 C94 78 83 76 78 69 L71 76 L66 71 L74 64 Z' class='swan-mark'></path><path d='M126 82 h68 v14 h-68z' class='dossier-strip'></path><path d='M126 104 h82 v10 h-82z' class='dossier-strip'></path><path d='M126 122 h54 v10 h-54z' class='dossier-strip'></path><text x='160' y='204' class='poster-callout'>{safe_label}</text></svg></div>"""
|
||||
return f"""<div class='art-stack'><div class='hot-fuzz-art' aria-hidden='true'><div class='film-grain'></div><div class='cinematic-glow'></div><div class='casefile-stamp operator-evidence-tag'>Operator Evidence Board</div><svg viewBox='0 0 320 220' class='poster-illustration' role='presentation'><defs><linearGradient id='dashboard-sunset' x1='0%' y1='0%' x2='100%' y2='100%'><stop offset='0%' stop-color='#f2c14e'></stop><stop offset='50%' stop-color='#d53600'></stop><stop offset='100%' stop-color='#910f3f'></stop></linearGradient></defs><rect x='8' y='8' width='304' height='204' rx='24' class='poster-frame'></rect><circle cx='236' cy='64' r='54' class='poster-halo'></circle><path d='M18 170 L112 100 L134 124 L40 198 Z' class='poster-tape tape-left'></path><path d='M202 82 L302 32 L314 60 L214 112 Z' class='poster-tape tape-right'></path><path d='M112 182 C109 148 116 124 134 108 C143 100 150 94 155 90 C162 94 170 100 178 108 C196 124 202 148 199 182 Z' class='constable-silhouette lead'></path><path d='M176 184 C174 154 180 132 196 116 C204 108 212 102 220 98 C229 102 236 108 244 116 C260 132 266 154 264 184 Z' class='constable-silhouette partner'></path><circle cx='72' cy='58' r='26' class='swan-stamp'></circle><path d='M64 62 C68 52 79 46 89 51 C81 50 76 55 77 61 C78 67 89 66 91 74 C82 76 71 74 66 67 L59 74 L54 69 L62 62 Z' class='swan-mark'></path><path d='M28 166 L94 124 L98 130 L32 172 Z' class='village-lane'></path><path d='M98 130 L136 150 L132 158 L88 136 Z' class='village-lane'></path><rect x='30' y='112' width='62' height='34' rx='6' class='evidence-card'></rect><path d='M38 120 h46 M38 128 h36 M38 136 h28' class='evidence-lines'></path><circle cx='120' cy='84' r='9' class='marker-dot'></circle><circle cx='138' cy='74' r='7' class='marker-dot marker-dot-alt'></circle><path d='M116 88 L104 116 L86 138' class='map-thread'></path><path d='M136 78 L160 96 L186 92' class='map-thread'></path><rect x='190' y='114' width='54' height='18' rx='6' class='radio-body'></rect><rect x='214' y='98' width='10' height='18' rx='4' class='radio-antenna'></rect><circle cx='238' cy='122' r='4' class='radio-knob'></circle><path d='M198 94 C214 86 232 86 246 94' class='beacon-arc'></path><path d='M202 86 C218 76 238 76 252 86' class='beacon-arc faint'></path><path d='M246 150 L272 130 L292 146 L292 184 L246 184 Z' class='church-block'></path><rect x='262' y='108' width='10' height='32' class='church-spire'></rect><path d='M252 150 H286 M258 160 H282' class='window-line'></path><text x='160' y='204' class='poster-callout'>{safe_label}</text></svg></div><div class='evidence-contact-grid'><section class='evidence-note-card case-contact-sheet' aria-label='Operator caseboard details'><div class='case-contact-top'><span class='case-contact-kicker'>Sandford caseboard</span><span class='case-contact-badge'>Neighbourhood Watch</span></div><svg viewBox='0 0 320 160' class='contact-sheet-illustration' role='presentation'><rect x='10' y='14' width='116' height='132' rx='16' class='contact-frame'></rect><rect x='22' y='30' width='92' height='56' rx='12' class='contact-photo'></rect><path d='M28 96 h80 M28 108 h68 M28 120 h50' class='contact-lines'></path><rect x='146' y='22' width='76' height='48' rx='10' class='contact-photo alt'></rect><rect x='154' y='84' width='108' height='52' rx='12' class='contact-photo alt'></rect><circle cx='254' cy='36' r='16' class='contact-marker'></circle><path d='M136 94 C154 82 174 80 190 88 C206 96 224 100 246 96' class='contact-thread'></path><path d='M126 42 C148 40 164 48 176 62' class='contact-thread faint'></path><text x='68' y='62' class='contact-label'>SWAN</text><text x='184' y='50' class='contact-label'>PUB</text><text x='208' y='114' class='contact-label'>SEA MINE</text></svg><ul class='case-contact-list'><li>Village-map pins and pub callouts keep the portal reading like a British police case wall.</li><li>Emergency arcs, spire silhouette, and evidence slips push the Hot Fuzz vibe ahead of the old stock-desk still.</li></ul></section><figure class='photo-evidence-card hero-photo-card supporting-photo-card'><img src='assets/hotfuzz-operator-desk.jpg' alt='Operator desk photo with corkboard, papers, and desktop gear.' loading='eager'><figcaption>Operator still · source logged in docs</figcaption></figure></div></div>"""
|
||||
|
||||
|
||||
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"<a class='{' '.join(classes)}' href='{esc(url)}' {' '.join(attrs)}><span class='family-app-kicker'>{esc(kicker)}</span><strong>{esc(title)}</strong><small>{esc(summary)}</small></a>")
|
||||
return f"<section class='family-portal evidence-board casefile-directory'><div class='section-heading-inline case-legend'><div><p class='eyebrow'>Singular front door</p><h2>Doris family directory</h2></div><span class='muted'>One shell, separate runtimes.</span></div><div class='family-directory-grid'>{''.join(cards)}</div></section>"
|
||||
|
||||
|
||||
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.',"<section class='summary-grid'><article class='summary-card'><h3>How to use this</h3><p>Use Home for today, alerts, and the briefing. Use Services when you want the rest of the stack quickly without hunting bookmarks.</p></article><article class='summary-card'><h3>Design rule</h3><p>Shared navigation and status live here; full workflows still stay in their own apps.</p></article></section>",'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"<!doctype html><html lang='en'><head><meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1'><title>Doris Services</title><link rel='stylesheet' href='style.css'></head><body><div class='incident-tape' aria-hidden='true'></div><main>{top_nav('services')}<header class='hero casefile-header evidence-board'><div><p class='eyebrow'>Sandford services desk</p><div class='case-legend'><h1>Services Directory</h1><span class='hero-chip'>Operator board</span></div><p class='sub'>Shared navigation across your sites, with Doris still acting as the operational front door. {esc(hero_summary)}</p>{hot_fuzz_art('services')}<div class='brand-badges'><span class='pill badge-hotfuzz for-the-greater-good'>For the Greater Good</span><span class='pill'>Evidence locker: services</span></div></div><div class='status'><div><strong>{esc(now.strftime('%a %b %-d, %-I:%M %p'))}</strong><span>Central time</span></div><div><strong>{candidate_count}</strong><span>News candidates</span></div><div><strong>{source_count}</strong><span>Sources</span></div><div><strong>{esc(hero_tokens)}</strong><span>30-day Hermes tokens</span></div></div></header><div class='dashboard-shell services-shell dossier-grid'><section class='primary-column'>{services_html}</section><aside class='secondary-column'>{utility_html}</aside></div><footer><p>Operator portal links are curated from the homelab docs and current live dashboard context.</p><p>Local-only on NOMAD unless John says otherwise.</p></footer></main></body></html>"
|
||||
return f"<!doctype html><html lang='en'><head><meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1'><title>Doris Services</title><link rel='stylesheet' href='style.css'></head><body><div class='incident-tape' aria-hidden='true'></div><main>{top_nav('services')}<header class='hero casefile-header evidence-board'><div><p class='eyebrow'>Sandford services desk</p><div class='case-legend'><h1>Services Directory</h1><span class='hero-chip'>Operator board</span></div><p class='sub'>Shared navigation across your sites, with Doris still acting as the operational front door. {esc(hero_summary)}</p>{hot_fuzz_art('services')}<div class='brand-badges'><span class='pill badge-hotfuzz for-the-greater-good'>For the Greater Good</span><span class='pill'>Evidence locker: services</span></div></div><div class='status'><div><strong>{esc(now.strftime('%a %b %-d, %-I:%M %p'))}</strong><span>Central time</span></div><div><strong>{candidate_count}</strong><span>News candidates</span></div><div><strong>{source_count}</strong><span>Sources</span></div><div><strong>{esc(hero_tokens)}</strong><span>30-day Hermes tokens</span></div></div></header>{family_html}<div class='dashboard-shell services-shell dossier-grid'><section class='primary-column'>{services_html}</section><aside class='secondary-column'>{utility_html}</aside></div><footer><p>Operator portal links are curated from the homelab docs and current live dashboard context.</p><p>Local-only on NOMAD unless John says otherwise.</p></footer></main></body></html>"
|
||||
|
||||
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"""<!doctype html><html lang='en'><head><meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1'><meta http-equiv='refresh' content='1800'><title>Doris Dashboard</title><link rel='stylesheet' href='style.css'></head><body data-donetick-focus-signature='{esc(focus_signature)}'><div class='incident-tape' aria-hidden='true'></div><main>{top_nav('home')}
|
||||
<header class='hero casefile-header evidence-board'><div class='hero-main'><p class='eyebrow'>Sandford operator desk</p><div class='case-legend'><h1>Doris Dashboard</h1><span class='hero-chip'>Operator board</span></div><div class='hero-title-row'><div class='hero-chips'><span class='hero-chip'>Hermes {esc((hermes.get('models') or [{}])[0].get('model') or hermes.get('default_model') or 'unknown')}</span><span class='hero-chip'>Session {esc(pct_text((session_window or {}).get('remaining_percent')) if hermes.get('ok') else '—')}</span><span class='hero-chip'>24h {esc(compact_number(hermes.get('last_24h_tokens',0)) if hermes.get('ok') else '—')} tokens</span></div></div><p class='sub'>{esc(hero_summary)}</p>{hero_caseboard_html}</div><div class='hero-rail'><div class='status'><div><strong>{esc(now.strftime('%a %b %-d, %-I:%M %p'))}</strong><span>Central time · refreshes every 30m</span></div><div><strong>{len(candidates)}</strong><span>Candidates</span></div><div><strong>{len(set(i.get('source','') for i in candidates))}</strong><span>Sources</span></div><div><strong>{esc(hero_tokens)}</strong><span>30-day Hermes tokens</span></div></div></div></header>
|
||||
<section class='snapshot-block evidence-board'><div class='section-heading-inline case-legend'><div><p class='eyebrow'>Today's operator snapshot</p><h2>What matters before the scroll</h2></div><span class='muted'>Glanceable heat across chores, Hermes, and the lab.</span></div>{snapshot_html}</section>
|
||||
<div class='dashboard-shell dossier-grid'><section class='primary-column'>{primary_html}</section><aside class='secondary-column'>{secondary_html}</aside></div>
|
||||
{family_html}<div class='dashboard-shell dossier-grid'><section class='primary-column'>{primary_html}</section><aside class='secondary-column'>{secondary_html}</aside></div>
|
||||
<footer><p>Category mix: {esc(', '.join(f'{k}: {v}' for k,v in counts.most_common(10)))}</p><p>Local-only on NOMAD unless John says otherwise. Weather from Open-Meteo; moon phase calculated locally. Hermes stats come from ~/.hermes/state.db.</p></footer></main>{feedback_bootstrap()}</body></html>"""
|
||||
|
||||
def main()->int:
|
||||
|
||||
BIN
home/doris-dashboard/public/assets/hotfuzz-operator-desk.jpg
Normal file
BIN
home/doris-dashboard/public/assets/hotfuzz-operator-desk.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 136 KiB |
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user