[object Object]

← back to Lifestyle Asset Intel

yolo tick #2: portfolio nav link + view styling

ea1adfbe3f148f1e11c108b7bd2f276ed98b1018 · 2026-05-09 23:50:44 -0700 · Steve Abrams

Add /portfolio to the hamburger nav (was previously only reachable via
direct URL) and an /api/sources/api/health pair as external-link buttons.
Active-state highlighting works for the indices and portfolio sections,
not just the console root.

Wire CSS for the classes the portfolio.ejs view was already emitting but
that had no rules: pl-pos/pl-neg coloring (good/bad CSS vars), flash
banners (ok/err with color-mix tinting), owner-form + holding-form grid
layouts, inline-remove link button, and a thicker top border on the
table totals row. color-mix in srgb keeps the tints theme-aware in both
light and dark.

21/21 tests still green. /portfolio renders with active nav state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit ea1adfbe3f148f1e11c108b7bd2f276ed98b1018
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sat May 9 23:50:44 2026 -0700

    yolo tick #2: portfolio nav link + view styling
    
    Add /portfolio to the hamburger nav (was previously only reachable via
    direct URL) and an /api/sources/api/health pair as external-link buttons.
    Active-state highlighting works for the indices and portfolio sections,
    not just the console root.
    
    Wire CSS for the classes the portfolio.ejs view was already emitting but
    that had no rules: pl-pos/pl-neg coloring (good/bad CSS vars), flash
    banners (ok/err with color-mix tinting), owner-form + holding-form grid
    layouts, inline-remove link button, and a thicker top border on the
    table totals row. color-mix in srgb keeps the tints theme-aware in both
    light and dark.
    
    21/21 tests still green. /portfolio renders with active nav state.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
 public/css/app.css      | 35 +++++++++++++++++++++++++++++++++++
 views/partials/head.ejs |  8 ++++----
 2 files changed, 39 insertions(+), 4 deletions(-)

diff --git a/public/css/app.css b/public/css/app.css
index e2d4084..58d5a80 100644
--- a/public/css/app.css
+++ b/public/css/app.css
@@ -137,3 +137,38 @@ pre.json { background: var(--card); border: 1px solid var(--line); border-radius
 /* sparkline + trend section (v0.2) */
 .spark { color: var(--accent); }
 .trend { margin: 1.4rem 0; }
+
+/* portfolio page (v0.2) */
+.pl-pos { color: var(--good); font-weight: 600; }
+.pl-neg { color: var(--bad); font-weight: 600; }
+
+.flash { padding: 0.55rem 0.8rem; border-radius: 8px; margin: 0.8rem 0; font-size: 0.92em; }
+.flash.ok  { background: color-mix(in srgb, var(--good) 12%, var(--card)); color: var(--good); border: 1px solid color-mix(in srgb, var(--good) 35%, var(--line)); }
+.flash.err { background: color-mix(in srgb, var(--bad) 12%, var(--card)); color: var(--bad); border: 1px solid color-mix(in srgb, var(--bad) 35%, var(--line)); }
+
+.owner-form, .holding-form {
+  display: grid; gap: 0.55rem; max-width: 520px; margin: 0.6rem 0 1.4rem;
+  padding: 0.9rem; border: 1px solid var(--line); border-radius: var(--radius); background: var(--card);
+}
+.owner-form input, .holding-form input, .holding-form textarea {
+  font: inherit; padding: 0.45rem 0.6rem; border: 1px solid var(--line);
+  border-radius: 6px; background: var(--bg); color: var(--fg); width: 100%;
+}
+.owner-form button, .holding-form button {
+  justify-self: start; padding: 0.5rem 0.9rem; border: 1px solid var(--accent); background: var(--accent);
+  color: var(--accent-fg); border-radius: 6px; font: inherit; font-weight: 600; cursor: pointer;
+}
+.holding-form .row { display: flex; flex-direction: column; gap: 0.25rem; }
+.holding-form label { font-size: 0.75em; color: var(--muted); text-transform: uppercase; letter-spacing: 0.04em; }
+
+.inline-remove { display: inline; }
+.inline-remove .link-btn {
+  background: none; border: none; color: var(--muted); padding: 0; cursor: pointer;
+  font: inherit; font-size: 0.85em; text-decoration: underline;
+}
+.inline-remove .link-btn:hover { color: var(--bad); }
+
+tfoot tr.totals th, tfoot tr.totals td {
+  border-top: 2px solid var(--line); border-bottom: none; padding-top: 0.6rem;
+  font-weight: 600; color: var(--fg);
+}
diff --git a/views/partials/head.ejs b/views/partials/head.ejs
index 51acdfd..8ac4fac 100644
--- a/views/partials/head.ejs
+++ b/views/partials/head.ejs
@@ -20,10 +20,10 @@
   <a class="brand" href="/">LAI <span class="brand-sub">Lifestyle Asset Intel</span></a>
   <nav class="topnav" id="topnav">
     <a href="/" class="<%= path === '/' ? 'active' : '' %>">Console</a>
-    <a href="/indices/birkin-30-togo-neutral">Indices</a>
-    <a href="/api/sources" target="_blank">Sources</a>
-    <a href="/api/portfolio?owner=demo@example.com" target="_blank">Portfolio</a>
-    <a href="/api/health" target="_blank">API</a>
+    <a href="/indices/birkin-30-togo-neutral" class="<%= path && path.indexOf('/indices') === 0 ? 'active' : '' %>">Indices</a>
+    <a href="/portfolio" class="<%= path && path.indexOf('/portfolio') === 0 ? 'active' : '' %>">Portfolio</a>
+    <a href="/api/sources" target="_blank" rel="noopener">Sources</a>
+    <a href="/api/health" target="_blank" rel="noopener">API</a>
   </nav>
   <button class="theme-toggle" id="themeToggle" aria-label="Toggle dark mode" title="Toggle dark mode">
     <svg class="icon-sun" viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">

← 1d24570 yolo tick #1: enforce transactions natural-key + seed idempo  ·  back to Lifestyle Asset Intel  ·  yolo tick #3: live liquidity_score + expected_dts (no more c 9b63e84 →