Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 69 additions & 3 deletions home/home.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,33 @@ func RefreshHandler(w http.ResponseWriter, r *http.Request) {
}

func Handler(w http.ResponseWriter, r *http.Request) {
// JSON endpoint for auto-refresh polling
if app.WantsJSON(r) {
RefreshCards()
cacheMutex.RLock()
type cardData struct {
ID string `json:"id"`
Title string `json:"title"`
HTML string `json:"html"`
Column string `json:"column"`
}
var result []cardData
for _, card := range Cards {
if strings.TrimSpace(card.CachedHTML) == "" {
continue
}
result = append(result, cardData{
ID: card.ID,
Title: card.Title,
HTML: card.CachedHTML,
Column: card.Column,
})
}
cacheMutex.RUnlock()
app.RespondJSON(w, result)
return
}

// Refresh cards if cache expired (2 minute TTL)
RefreshCards()

Expand Down Expand Up @@ -356,10 +383,49 @@ func Handler(w http.ResponseWriter, r *http.Request) {
strings.Join(rightHTML, "\n")))
}

// Use RenderHTMLWithLang directly to inject a body class that hides the page title,
// keeping the agent prompt as the primary visual element.
// Auto-refresh: poll every 2 minutes, update card content in-place
displayMode := r.URL.Query().Get("mode") == "display"
refreshInterval := 120000 // 2 minutes
if displayMode {
refreshInterval = 60000 // 1 minute in display mode
}
wakeLockJS := ""
if displayMode {
wakeLockJS = `
// Screen Wake Lock — keep display on in kiosk mode
if('wakeLock' in navigator){
var wl=null;
function reqWake(){navigator.wakeLock.request('screen').then(function(l){wl=l;l.addEventListener('release',function(){setTimeout(reqWake,1000)})}).catch(function(){})}
reqWake();document.addEventListener('visibilitychange',function(){if(document.visibilityState==='visible')reqWake()});
}`
}
b.WriteString(fmt.Sprintf(`<script>
(function(){
var interval = %d;
setInterval(function(){
fetch('/home', {headers:{Accept:'application/json'}})
.then(function(r){return r.json()})
.then(function(cards){
cards.forEach(function(c){
var el = document.getElementById(c.id);
if(el){
var content = el.querySelector('.card-content');
if(content) content.innerHTML = c.html;
}
});
}).catch(function(){});
}, interval);%s
})();
</script>`, refreshInterval, wakeLockJS))

// Display mode: hide nav, header, footer for kiosk/wall display
bodyClass := ` class="page-home"`
if displayMode {
bodyClass = ` class="page-home display-mode"`
}

lang := app.GetUserLanguage(r)
html := app.RenderHTMLWithLangAndBody("Home", "The home screen", b.String(), lang, ` class="page-home"`)
html := app.RenderHTMLWithLangAndBody("Home", "The home screen", b.String(), lang, bodyClass)
w.Write([]byte(html))
}

Expand Down
8 changes: 7 additions & 1 deletion internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,12 @@ var Template = `
<meta name="viewport" content="width=device-width, initial-scale=1, interactive-widget=resizes-content, viewport-fit=cover" />
<meta name="description" content="%s">
<meta name="referrer" content="no-referrer"/>
<meta name="theme-color" content="#ffffff">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="apple-mobile-web-app-title" content="Mu">
<meta name="application-name" content="Mu">
<link rel="apple-touch-icon" href="/icon-192.png">
<link rel="preload" href="/home.png?` + Version + `" as="image">
<link rel="preload" href="/mail.png?` + Version + `" as="image">
<link rel="preload" href="/chat.png?` + Version + `" as="image">
Expand Down Expand Up @@ -277,7 +283,7 @@ var CardTemplate = `
<!-- %s -->
<div id="%s" class="card">
<h4>%s</h4>
%s
<div class="card-content">%s</div>
</div>
`

Expand Down
52 changes: 48 additions & 4 deletions internal/app/html/mu.css
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,12 @@ button {
color: #fff;
border: 1px solid var(--btn-primary);
border-radius: var(--border-radius);
padding: 6px 12px;
padding: 10px 16px;
cursor: pointer;
font-size: 14px;
line-height: 1.4;
text-decoration: none;
min-height: 44px;
transition: all var(--transition-fast);
}

Expand All @@ -134,8 +135,9 @@ a.btn {
color: #fff !important;
border: 1px solid var(--btn-primary);
border-radius: var(--border-radius);
padding: 6px 12px;
padding: 10px 16px;
cursor: pointer;
min-height: 44px;
font-size: 14px;
font-weight: normal;
line-height: 1.4;
Expand Down Expand Up @@ -391,10 +393,11 @@ td {
color: var(--text-primary);
font-weight: var(--font-weight-medium);
text-decoration: none;
padding: 10px 14px;
padding: 12px 14px;
border-radius: var(--border-radius);
display: flex;
align-items: center;
min-height: 44px;
transition: all var(--transition-fast);
}

Expand Down Expand Up @@ -2194,8 +2197,9 @@ a.highlight {
}

#nav a {
padding: 8px 14px;
padding: 12px 14px;
flex-direction: row;
min-height: 44px;
}

#nav img {
Expand Down Expand Up @@ -4916,3 +4920,43 @@ a.btn-secondary:hover, .btn-secondary:hover {
grid-template-columns: 1fr;
}
}

/* ============================================
DISPLAY / KIOSK MODE
Activated via ?mode=display on /home
Hides nav, header, footer for wall-mounted displays
============================================ */
body.display-mode #head,
body.display-mode #nav-container,
body.display-mode #nav-overlay,
body.display-mode #footer,
body.display-mode #menu-toggle,
body.display-mode #page-title,
body.display-mode #home-status-form,
body.display-mode #head-mail {
display: none !important;
}

body.display-mode #container {
margin: 0;
padding: 0;
max-width: 100%;
}

body.display-mode #content {
margin: 0;
padding: 16px;
max-width: 100%;
}

body.display-mode #home {
gap: 16px;
}

body.display-mode .card {
font-size: 15px;
}

body.display-mode .card h4 {
font-size: 16px;
}
Loading