fix(ui): user dropdown with logout, fix script injection, pink outlines
This commit is contained in:
parent
9527eb0ee4
commit
889329c52e
2 changed files with 74 additions and 21 deletions
|
|
@ -41,7 +41,7 @@ templ LauncherPage(userName string, role string, apps []AppTile) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>@launcherJS()</script>
|
@launcherJS()
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
}
|
}
|
||||||
|
|
@ -66,8 +66,15 @@ templ topBar(userName string, role string) {
|
||||||
<button class="icon-btn" onclick="showPWA()" title="Install app">
|
<button class="icon-btn" onclick="showPWA()" title="Install app">
|
||||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
|
||||||
</button>
|
</button>
|
||||||
<div class="user-menu">
|
<div class="user-menu" onclick="toggleUserMenu(event)">
|
||||||
<div class="avatar sm">{ initials(userName) }</div>
|
<div class="avatar sm">{ initials(userName) }</div>
|
||||||
|
<div class="dropdown" id="userDropdown">
|
||||||
|
<div class="dropdown-header">{ userName }</div>
|
||||||
|
<div class="dropdown-item" onclick="window.location.href='/auth/logout'">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 21H5a2 2 0 01-2-2V5a2 2 0 012-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>
|
||||||
|
Logout
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
@ -115,6 +122,22 @@ templ workspaceCSS() {
|
||||||
font-weight:600;font-size:15px;color:#fff;flex-shrink:0
|
font-weight:600;font-size:15px;color:#fff;flex-shrink:0
|
||||||
}
|
}
|
||||||
.avatar.sm{width:32px;height:32px;font-size:13px}
|
.avatar.sm{width:32px;height:32px;font-size:13px}
|
||||||
|
|
||||||
|
/* User Dropdown */
|
||||||
|
.user-menu{position:relative;cursor:pointer}
|
||||||
|
.dropdown{
|
||||||
|
display:none;position:absolute;right:0;top:44px;
|
||||||
|
background:var(--surface);border:1px solid var(--border);border-radius:12px;
|
||||||
|
min-width:180px;padding:6px;z-index:200;box-shadow:0 8px 24px rgba(0,0,0,0.4)
|
||||||
|
}
|
||||||
|
.dropdown.show{display:block}
|
||||||
|
.dropdown-header{padding:10px 12px;font-size:0.85rem;font-weight:500;border-bottom:1px solid var(--border);margin-bottom:4px}
|
||||||
|
.dropdown-item{
|
||||||
|
display:flex;align-items:center;gap:10px;padding:10px 12px;
|
||||||
|
border-radius:8px;font-size:0.85rem;color:var(--text);cursor:pointer;transition:background 0.1s
|
||||||
|
}
|
||||||
|
.dropdown-item:hover{background:var(--surface2)}
|
||||||
|
.dropdown-item svg{flex-shrink:0}
|
||||||
.content{flex:1;padding:24px 16px;max-width:900px;margin:0 auto;width:100%}
|
.content{flex:1;padding:24px 16px;max-width:900px;margin:0 auto;width:100%}
|
||||||
.greeting{display:flex;align-items:center;gap:12px;margin-bottom:24px}
|
.greeting{display:flex;align-items:center;gap:12px;margin-bottom:24px}
|
||||||
@media(min-width:600px){.greeting{margin-bottom:32px}}
|
@media(min-width:600px){.greeting{margin-bottom:32px}}
|
||||||
|
|
@ -228,6 +251,15 @@ templ launcherJS() {
|
||||||
document.getElementById('pwa-overlay').style.display = 'none';
|
document.getElementById('pwa-overlay').style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleUserMenu(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
document.getElementById('userDropdown').classList.toggle('show');
|
||||||
|
}
|
||||||
|
document.addEventListener('click', function() {
|
||||||
|
var d = document.getElementById('userDropdown');
|
||||||
|
if (d) d.classList.remove('show');
|
||||||
|
});
|
||||||
|
|
||||||
// PWA install handler
|
// PWA install handler
|
||||||
let deferredPrompt = null;
|
let deferredPrompt = null;
|
||||||
window.addEventListener('beforeinstallprompt', function(e) {
|
window.addEventListener('beforeinstallprompt', function(e) {
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,15 @@ func LauncherPage(userName string, role string, apps []AppTile) templ.Component
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</div></div></div><script>@launcherJS()</script></body></html>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</div></div></div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = launcherJS().Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</body></html>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
|
@ -109,7 +117,7 @@ func greetingSection(userName string) templ.Component {
|
||||||
templ_7745c5c3_Var3 = templ.NopComponent
|
templ_7745c5c3_Var3 = templ.NopComponent
|
||||||
}
|
}
|
||||||
ctx = templ.ClearChildren(ctx)
|
ctx = templ.ClearChildren(ctx)
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<div class=\"greeting\"><div class=\"avatar\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<div class=\"greeting\"><div class=\"avatar\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
|
@ -122,7 +130,7 @@ func greetingSection(userName string) templ.Component {
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</div><div class=\"greeting-text\"><h1>Good ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</div><div class=\"greeting-text\"><h1>Good ")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
|
@ -135,7 +143,7 @@ func greetingSection(userName string) templ.Component {
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, ", ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, ", ")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
|
@ -148,7 +156,7 @@ func greetingSection(userName string) templ.Component {
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</h1><p>Your workspace is ready</p></div></div>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</h1><p>Your workspace is ready</p></div></div>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
|
@ -177,7 +185,7 @@ func topBar(userName string, role string) templ.Component {
|
||||||
templ_7745c5c3_Var7 = templ.NopComponent
|
templ_7745c5c3_Var7 = templ.NopComponent
|
||||||
}
|
}
|
||||||
ctx = templ.ClearChildren(ctx)
|
ctx = templ.ClearChildren(ctx)
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<header class=\"topbar\"><div class=\"topbar-left\"><div class=\"logo-icon\">N</div><span class=\"logo-text\">NextWks</span></div><div class=\"topbar-right\"><button class=\"icon-btn\" onclick=\"showPWA()\" title=\"Install app\"><svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4\"></path><polyline points=\"7 10 12 15 17 10\"></polyline><line x1=\"12\" y1=\"15\" x2=\"12\" y2=\"3\"></line></svg></button><div class=\"user-menu\"><div class=\"avatar sm\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<header class=\"topbar\"><div class=\"topbar-left\"><div class=\"logo-icon\">N</div><span class=\"logo-text\">NextWks</span></div><div class=\"topbar-right\"><button class=\"icon-btn\" onclick=\"showPWA()\" title=\"Install app\"><svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4\"></path><polyline points=\"7 10 12 15 17 10\"></polyline><line x1=\"12\" y1=\"15\" x2=\"12\" y2=\"3\"></line></svg></button><div class=\"user-menu\" onclick=\"toggleUserMenu(event)\"><div class=\"avatar sm\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
|
@ -190,7 +198,20 @@ func topBar(userName string, role string) templ.Component {
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</div></div></div></header>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</div><div class=\"dropdown\" id=\"userDropdown\"><div class=\"dropdown-header\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var9 string
|
||||||
|
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(userName)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/ui/launcher.templ`, Line: 72, Col: 44}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</div><div class=\"dropdown-item\" onclick=\"window.location.href='/auth/logout'\"><svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M9 21H5a2 2 0 01-2-2V5a2 2 0 012-2h4\"></path><polyline points=\"16 17 21 12 16 7\"></polyline><line x1=\"21\" y1=\"12\" x2=\"9\" y2=\"12\"></line></svg> Logout</div></div></div></div></header>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
|
@ -214,12 +235,12 @@ func workspaceCSS() templ.Component {
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
ctx = templ.InitializeContext(ctx)
|
ctx = templ.InitializeContext(ctx)
|
||||||
templ_7745c5c3_Var9 := templ.GetChildren(ctx)
|
templ_7745c5c3_Var10 := templ.GetChildren(ctx)
|
||||||
if templ_7745c5c3_Var9 == nil {
|
if templ_7745c5c3_Var10 == nil {
|
||||||
templ_7745c5c3_Var9 = templ.NopComponent
|
templ_7745c5c3_Var10 = templ.NopComponent
|
||||||
}
|
}
|
||||||
ctx = templ.ClearChildren(ctx)
|
ctx = templ.ClearChildren(ctx)
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<style type=\"text/css\">\n\t\t*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}\n\t\t:root{\n\t\t\t--bg:#0d1117; --surface:#161b22; --surface2:#21262d; --surface3:#30363d;\n\t\t\t--border:#30363d; --text:#e6edf3; --text2:#8b949e; --text3:#6e7681;\n\t\t\t--blue:#58a6ff; --green:#3fb950; --orange:#d2991d; --red:#f85149;\n\t\t\t--purple:#a371f7; --pink:#db61a2; --teal:#39d353;\n\t\t\t--radius:16px; --radius-sm:10px\n\t\t}\n\t\thtml{font-size:15px}\n\t\tbody{\n\t\t\tfont-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',sans-serif;\n\t\t\tbackground:var(--bg); color:var(--text); min-height:100vh; min-height:100dvh;\n\t\t\t-webkit-tap-highlight-color:transparent; -webkit-user-select:none; user-select:none\n\t\t}\n\t\t.shell{display:flex;flex-direction:column;min-height:100vh;min-height:100dvh}\n\t\t.topbar{\n\t\t\tdisplay:flex; justify-content:space-between; align-items:center;\n\t\t\tpadding:12px 16px; background:var(--surface); border-bottom:1px solid var(--border);\n\t\t\tposition:sticky; top:0; z-index:100\n\t\t}\n\t\t.topbar-left{display:flex;align-items:center;gap:10px}\n\t\t.logo-icon{\n\t\t\twidth:32px;height:32px;border-radius:8px;background:var(--blue);\n\t\t\tdisplay:flex;align-items:center;justify-content:center;\n\t\t\tfont-weight:700;font-size:16px;color:#fff\n\t\t}\n\t\t.logo-text{font-weight:600;font-size:18px;color:var(--text)}\n\t\t.topbar-right{display:flex;align-items:center;gap:8px}\n\t\t.icon-btn{\n\t\t\twidth:36px;height:36px;border-radius:50%;border:none;background:transparent;\n\t\t\tcolor:var(--text2);cursor:pointer;display:flex;align-items:center;justify-content:center;\n\t\t\ttransition:all 0.15s\n\t\t}\n\t\t.icon-btn:hover{background:var(--surface2);color:var(--text)}\n\t\t.avatar{\n\t\t\twidth:40px;height:40px;border-radius:50%;background:var(--purple);\n\t\t\tdisplay:flex;align-items:center;justify-content:center;\n\t\t\tfont-weight:600;font-size:15px;color:#fff;flex-shrink:0\n\t\t}\n\t\t.avatar.sm{width:32px;height:32px;font-size:13px}\n\t\t.content{flex:1;padding:24px 16px;max-width:900px;margin:0 auto;width:100%}\n\t\t.greeting{display:flex;align-items:center;gap:12px;margin-bottom:24px}\n\t\t@media(min-width:600px){.greeting{margin-bottom:32px}}\n\t\t.greeting-text h1{font-size:1.25rem;font-weight:600;color:var(--text);line-height:1.3}\n\t\t.greeting-text p{color:var(--text2);font-size:0.85rem;margin-top:2px}\n\n\t\t/* App Grid — RADICAL REDESIGN */\n\t\t.app-grid{\n\t\t\tdisplay:grid;\n\t\t\tgrid-template-columns:repeat(3,1fr);\n\t\t\tgap:0px; padding:0;\n\t\t}\n\t\t@media(min-width:600px){.app-grid{grid-template-columns:repeat(4,1fr)}}\n\t\t@media(min-width:900px){.app-grid{grid-template-columns:repeat(6,1fr)}}\n\n\t\t.app-card{\n\t\t\tdisplay:flex; flex-direction:column; align-items:center; gap:4px;\n\t\t\tcursor:pointer; text-decoration:none; color:inherit; padding:12px 6px;\n\t\t\tborder-radius:16px; transition:all 0.15s; border:none; background:none\n\t\t}\n\t\t.app-card:active{transform:scale(0.9)}\n\t\t.app-card .app-icon{\n\t\t\twidth:56px;height:56px;border-radius:14px;\n\t\t\tdisplay:flex;align-items:center;justify-content:center;flex-shrink:0\n\t\t}\n\t\t.app-card .app-icon svg{width:28px;height:28px}\n\t\t.app-card .app-name{font-size:0.72rem;font-weight:400;color:var(--text);text-align:center;line-height:1.2;max-width:68px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n\t\t.app-card .app-badge{display:none}\n\t\t.app-card.disabled{opacity:0.3;cursor:default}\n\t\t.app-card.disabled:active{transform:none}\n\n\t\t/* RADICAL: hot pink border on every card to verify CSS updates */\n\t\t.app-card .app-icon{outline:2px solid #ff1493}\n\n\t\t/* Footer */\n\t\t.footer{\n\t\t\ttext-align:center;padding:16px;color:var(--text3);font-size:0.7rem;\n\t\t\tborder-top:1px solid var(--border);margin-top:auto\n\t\t}\n\t</style>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<style type=\"text/css\">\n\t\t*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}\n\t\t:root{\n\t\t\t--bg:#0d1117; --surface:#161b22; --surface2:#21262d; --surface3:#30363d;\n\t\t\t--border:#30363d; --text:#e6edf3; --text2:#8b949e; --text3:#6e7681;\n\t\t\t--blue:#58a6ff; --green:#3fb950; --orange:#d2991d; --red:#f85149;\n\t\t\t--purple:#a371f7; --pink:#db61a2; --teal:#39d353;\n\t\t\t--radius:16px; --radius-sm:10px\n\t\t}\n\t\thtml{font-size:15px}\n\t\tbody{\n\t\t\tfont-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',sans-serif;\n\t\t\tbackground:var(--bg); color:var(--text); min-height:100vh; min-height:100dvh;\n\t\t\t-webkit-tap-highlight-color:transparent; -webkit-user-select:none; user-select:none\n\t\t}\n\t\t.shell{display:flex;flex-direction:column;min-height:100vh;min-height:100dvh}\n\t\t.topbar{\n\t\t\tdisplay:flex; justify-content:space-between; align-items:center;\n\t\t\tpadding:12px 16px; background:var(--surface); border-bottom:1px solid var(--border);\n\t\t\tposition:sticky; top:0; z-index:100\n\t\t}\n\t\t.topbar-left{display:flex;align-items:center;gap:10px}\n\t\t.logo-icon{\n\t\t\twidth:32px;height:32px;border-radius:8px;background:var(--blue);\n\t\t\tdisplay:flex;align-items:center;justify-content:center;\n\t\t\tfont-weight:700;font-size:16px;color:#fff\n\t\t}\n\t\t.logo-text{font-weight:600;font-size:18px;color:var(--text)}\n\t\t.topbar-right{display:flex;align-items:center;gap:8px}\n\t\t.icon-btn{\n\t\t\twidth:36px;height:36px;border-radius:50%;border:none;background:transparent;\n\t\t\tcolor:var(--text2);cursor:pointer;display:flex;align-items:center;justify-content:center;\n\t\t\ttransition:all 0.15s\n\t\t}\n\t\t.icon-btn:hover{background:var(--surface2);color:var(--text)}\n\t\t.avatar{\n\t\t\twidth:40px;height:40px;border-radius:50%;background:var(--purple);\n\t\t\tdisplay:flex;align-items:center;justify-content:center;\n\t\t\tfont-weight:600;font-size:15px;color:#fff;flex-shrink:0\n\t\t}\n\t\t.avatar.sm{width:32px;height:32px;font-size:13px}\n\n\t\t/* User Dropdown */\n\t\t.user-menu{position:relative;cursor:pointer}\n\t\t.dropdown{\n\t\t\tdisplay:none;position:absolute;right:0;top:44px;\n\t\t\tbackground:var(--surface);border:1px solid var(--border);border-radius:12px;\n\t\t\tmin-width:180px;padding:6px;z-index:200;box-shadow:0 8px 24px rgba(0,0,0,0.4)\n\t\t}\n\t\t.dropdown.show{display:block}\n\t\t.dropdown-header{padding:10px 12px;font-size:0.85rem;font-weight:500;border-bottom:1px solid var(--border);margin-bottom:4px}\n\t\t.dropdown-item{\n\t\t\tdisplay:flex;align-items:center;gap:10px;padding:10px 12px;\n\t\t\tborder-radius:8px;font-size:0.85rem;color:var(--text);cursor:pointer;transition:background 0.1s\n\t\t}\n\t\t.dropdown-item:hover{background:var(--surface2)}\n\t\t.dropdown-item svg{flex-shrink:0}\n\t\t.content{flex:1;padding:24px 16px;max-width:900px;margin:0 auto;width:100%}\n\t\t.greeting{display:flex;align-items:center;gap:12px;margin-bottom:24px}\n\t\t@media(min-width:600px){.greeting{margin-bottom:32px}}\n\t\t.greeting-text h1{font-size:1.25rem;font-weight:600;color:var(--text);line-height:1.3}\n\t\t.greeting-text p{color:var(--text2);font-size:0.85rem;margin-top:2px}\n\n\t\t/* App Grid — RADICAL REDESIGN */\n\t\t.app-grid{\n\t\t\tdisplay:grid;\n\t\t\tgrid-template-columns:repeat(3,1fr);\n\t\t\tgap:0px; padding:0;\n\t\t}\n\t\t@media(min-width:600px){.app-grid{grid-template-columns:repeat(4,1fr)}}\n\t\t@media(min-width:900px){.app-grid{grid-template-columns:repeat(6,1fr)}}\n\n\t\t.app-card{\n\t\t\tdisplay:flex; flex-direction:column; align-items:center; gap:4px;\n\t\t\tcursor:pointer; text-decoration:none; color:inherit; padding:12px 6px;\n\t\t\tborder-radius:16px; transition:all 0.15s; border:none; background:none\n\t\t}\n\t\t.app-card:active{transform:scale(0.9)}\n\t\t.app-card .app-icon{\n\t\t\twidth:56px;height:56px;border-radius:14px;\n\t\t\tdisplay:flex;align-items:center;justify-content:center;flex-shrink:0\n\t\t}\n\t\t.app-card .app-icon svg{width:28px;height:28px}\n\t\t.app-card .app-name{font-size:0.72rem;font-weight:400;color:var(--text);text-align:center;line-height:1.2;max-width:68px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n\t\t.app-card .app-badge{display:none}\n\t\t.app-card.disabled{opacity:0.3;cursor:default}\n\t\t.app-card.disabled:active{transform:none}\n\n\t\t/* RADICAL: hot pink border on every card to verify CSS updates */\n\t\t.app-card .app-icon{outline:2px solid #ff1493}\n\n\t\t/* Footer */\n\t\t.footer{\n\t\t\ttext-align:center;padding:16px;color:var(--text3);font-size:0.7rem;\n\t\t\tborder-top:1px solid var(--border);margin-top:auto\n\t\t}\n\t</style>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
|
@ -265,12 +286,12 @@ func pwaInstructions() templ.Component {
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
ctx = templ.InitializeContext(ctx)
|
ctx = templ.InitializeContext(ctx)
|
||||||
templ_7745c5c3_Var10 := templ.GetChildren(ctx)
|
templ_7745c5c3_Var11 := templ.GetChildren(ctx)
|
||||||
if templ_7745c5c3_Var10 == nil {
|
if templ_7745c5c3_Var11 == nil {
|
||||||
templ_7745c5c3_Var10 = templ.NopComponent
|
templ_7745c5c3_Var11 = templ.NopComponent
|
||||||
}
|
}
|
||||||
ctx = templ.ClearChildren(ctx)
|
ctx = templ.ClearChildren(ctx)
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<h3>Desktop Chrome/Edge</h3><ol><li>Click the <code>⊕</code> icon in the address bar</li><li>Click <strong>Install</strong></li></ol><h3>iOS Safari</h3><ol><li>Tap <strong>Share</strong> <code>⎋</code></li><li>Tap <strong>Add to Home Screen</strong></li></ol><h3>Android Chrome</h3><ol><li>Tap <strong>⋮</strong> menu</li><li>Tap <strong>Install app</strong></li></ol><button class=\"btn\" onclick=\"closePWA()\">Got it</button>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<h3>Desktop Chrome/Edge</h3><ol><li>Click the <code>⊕</code> icon in the address bar</li><li>Click <strong>Install</strong></li></ol><h3>iOS Safari</h3><ol><li>Tap <strong>Share</strong> <code>⎋</code></li><li>Tap <strong>Add to Home Screen</strong></li></ol><h3>Android Chrome</h3><ol><li>Tap <strong>⋮</strong> menu</li><li>Tap <strong>Install app</strong></li></ol><button class=\"btn\" onclick=\"closePWA()\">Got it</button>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
|
@ -295,12 +316,12 @@ func launcherJS() templ.Component {
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
ctx = templ.InitializeContext(ctx)
|
ctx = templ.InitializeContext(ctx)
|
||||||
templ_7745c5c3_Var11 := templ.GetChildren(ctx)
|
templ_7745c5c3_Var12 := templ.GetChildren(ctx)
|
||||||
if templ_7745c5c3_Var11 == nil {
|
if templ_7745c5c3_Var12 == nil {
|
||||||
templ_7745c5c3_Var11 = templ.NopComponent
|
templ_7745c5c3_Var12 = templ.NopComponent
|
||||||
}
|
}
|
||||||
ctx = templ.ClearChildren(ctx)
|
ctx = templ.ClearChildren(ctx)
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<script type=\"text/javascript\">\n\t\tlet longPressTimer = null;\n\t\tlet currentUrl = null;\n\n\t\tfunction openApp(url) {\n\t\t\twindow.open(url, '_blank');\n\t\t}\n\n\t\tfunction startLongPress(e, url) {\n\t\t\tcurrentUrl = url || null;\n\t\t\tlongPressTimer = setTimeout(function() {\n\t\t\t\tshowPWA();\n\t\t\t}, 600);\n\t\t}\n\n\t\tfunction cancelLongPress() {\n\t\t\tif (longPressTimer) {\n\t\t\t\tclearTimeout(longPressTimer);\n\t\t\t\tlongPressTimer = null;\n\t\t\t}\n\t\t}\n\n\t\tfunction showPWA() {\n\t\t\tdocument.getElementById('pwa-overlay').style.display = 'flex';\n\t\t}\n\n\t\tfunction closePWA() {\n\t\t\tdocument.getElementById('pwa-overlay').style.display = 'none';\n\t\t}\n\n\t\t// PWA install handler\n\t\tlet deferredPrompt = null;\n\t\twindow.addEventListener('beforeinstallprompt', function(e) {\n\t\t\te.preventDefault();\n\t\t\tdeferredPrompt = e;\n\t\t});\n\n\t\t// Override showPWA to try native prompt first\n\t\tdocument.addEventListener('DOMContentLoaded', function() {\n\t\t\tvar origShowPWA = showPWA;\n\t\t\tshowPWA = function() {\n\t\t\t\tif (deferredPrompt) {\n\t\t\t\t\tdeferredPrompt.prompt();\n\t\t\t\t\tdeferredPrompt.userChoice.then(function() { deferredPrompt = null; });\n\t\t\t\t} else {\n\t\t\t\t\torigShowPWA();\n\t\t\t\t}\n\t\t\t};\n\t\t});\n\t</script>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<script type=\"text/javascript\">\n\t\tlet longPressTimer = null;\n\t\tlet currentUrl = null;\n\n\t\tfunction openApp(url) {\n\t\t\twindow.open(url, '_blank');\n\t\t}\n\n\t\tfunction startLongPress(e, url) {\n\t\t\tcurrentUrl = url || null;\n\t\t\tlongPressTimer = setTimeout(function() {\n\t\t\t\tshowPWA();\n\t\t\t}, 600);\n\t\t}\n\n\t\tfunction cancelLongPress() {\n\t\t\tif (longPressTimer) {\n\t\t\t\tclearTimeout(longPressTimer);\n\t\t\t\tlongPressTimer = null;\n\t\t\t}\n\t\t}\n\n\t\tfunction showPWA() {\n\t\t\tdocument.getElementById('pwa-overlay').style.display = 'flex';\n\t\t}\n\n\t\tfunction closePWA() {\n\t\t\tdocument.getElementById('pwa-overlay').style.display = 'none';\n\t\t}\n\n\t\tfunction toggleUserMenu(e) {\n\t\t\te.stopPropagation();\n\t\t\tdocument.getElementById('userDropdown').classList.toggle('show');\n\t\t}\n\t\tdocument.addEventListener('click', function() {\n\t\t\tvar d = document.getElementById('userDropdown');\n\t\t\tif (d) d.classList.remove('show');\n\t\t});\n\n\t\t// PWA install handler\n\t\tlet deferredPrompt = null;\n\t\twindow.addEventListener('beforeinstallprompt', function(e) {\n\t\t\te.preventDefault();\n\t\t\tdeferredPrompt = e;\n\t\t});\n\n\t\t// Override showPWA to try native prompt first\n\t\tdocument.addEventListener('DOMContentLoaded', function() {\n\t\t\tvar origShowPWA = showPWA;\n\t\t\tshowPWA = function() {\n\t\t\t\tif (deferredPrompt) {\n\t\t\t\t\tdeferredPrompt.prompt();\n\t\t\t\t\tdeferredPrompt.userChoice.then(function() { deferredPrompt = null; });\n\t\t\t\t} else {\n\t\t\t\t\torigShowPWA();\n\t\t\t\t}\n\t\t\t};\n\t\t});\n\t</script>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue