Commit graph

11 commits

Author SHA1 Message Date
652995feea ui: log panel bottom-aligns with portfolio via contain:size
Third attempt at fixing the dashboard's right-column alignment, this
time with the structural cause identified explicitly.

Previous attempts (a55168d, 8347c90) changed align-self on #log-panel
to control how the panel filled its grid area. They got the box
edges aligned, but the underlying problem was a different one:
CSS Grid auto-sizes each row by MAX(intrinsic content height across
items in that row). When the log content is taller than indicators +
portfolio combined, the grid grows rows 2-3 to fit it; portfolio
ends up in a stretched row with empty space below the actual content.

The fix is to stop the log's content from contributing to the grid
row sizing at all. `contain: size` on the log panel declares "my
contents do not affect my intrinsic size" — the grid then sizes rows
2-3 from indicators + portfolio alone, and the log stretches to
inhabit that combined height. A flex column inside the panel
(min-height: 0 on every level of the chain) lets .panel-body fill
the remaining height below the header and scroll instead of
overflowing.

The 1100px mobile breakpoint undoes the constraint: at that width
the grid restructures to a single column, the log no longer shares
a row with indicators + portfolio, and `contain: size` would just
collapse the panel to zero. There the log expands naturally and
page scroll handles it.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 14:56:11 +02:00
a55168d20a ui: log panel stretches to portfolio bottom; AI analysis stays expanded
Two small fixes to the dashboard right column based on user feedback:

1. layout.css — drop align-self:start from #log-panel.
   The panel previously shrank to its content, leaving the right-hand
   column visually shorter than the indicators+portfolio stack on the
   left. Removing the override lets the grid stretch the panel to the
   full row span so the two columns now bottom-align. The log content
   still sits at the top of the panel; any extra height is empty
   padding inside the box.

2. portfolio.js — re-hydrate AI analysis expanded.
   The 60s auto-refresh rebuilds the portfolio mount and re-attaches
   the previously-generated analysis from localStorage, but the
   <details> element was re-attached with open:false — collapsing it
   under the user's cursor every minute. Users reasonably perceived
   that as "the analysis disappeared". Hydrate as open:true so the
   body stays visible; the user can still click the summary to
   collapse manually within a refresh window.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 12:35:10 +02:00
3e1a14f334 ui: flip tone relabel — "Pro" now maps to INTERMEDIATE, not NOVICE
Reverses the polarity of 71155a6 to match the actual semantics:

- "Novice" stays labelled "Novice" → glossary tooltips, plainer prose.
- "Intermediate" is relabelled "Pro" → terse, assumes fluency, no
  hand-holding. This is the mode an expert reader wants, so the "Pro"
  badge actually fits.

Backend tone values (NOVICE, INTERMEDIATE) are unchanged — no API,
prompt, or stored-preference impact. Only the display strings flip.

Also drops the .tone-toggle button min-width: 10em override added in
71155a6. With "Intermediate" gone from the visible label, the longest
remaining label is "Novice" (6 chars), which fits the shared 5.5em
just like the theme and language toggles.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 11:23:52 +02:00
71155a67be ui: rename tone "Novice" → "Pro"; fit tone-toggle to longest option
User-visible relabel only. Backend tone value stays NOVICE — no API
contract change, no migration on stored user.digest_tone, the
glossary/plain-prose depth of analysis is unchanged. The marketing
intent is that "Pro" reads better than "Novice" on the dashboard
header; landing/pricing/privacy copy still uses the word "Novice" in
flowing prose, so leaving those alone keeps the existing explanations
coherent until they get a copy pass.

Toggle width: the popup expansion (positioned left:0/right:0) is
sized by the container, which previously sized to the active button.
When "Pro" was active the popup was too narrow to fit "Intermediate".
Bumped .tone-toggle button min-width to 10em so both buttons reserve
enough room for the longest label regardless of which one is active.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 11:17:43 +02:00
f57c863145 ui: header toggles expand downward, not sideways
Hovering a toggle (tone, theme, language) previously revealed the
non-active option inline next to the active one, which widened the
toggle and pushed its neighbours sideways. Now the non-active option
appears as a popup ABSOLUTELY POSITIONED below the active one — the
toggle's in-flow footprint stays exactly one button wide and tall, so
the other two toggles next to it never move when the user mouses over
one of them.

Mechanism: inside @media (hover: hover) the container becomes
position:relative and every button defaults to display:none. The
:hover/:focus-within rule renders all options as position:absolute
under the container. Specificity (.X[data=Y] btn[data=Y]) on the
active-button rule then pins the active option back into the static
flow at the top, so only the non-active end(s) up absolute — popup
grows downward only. margin-top:-1px makes the popup's top border
overlap the container's bottom border for a single shared edge.
z-index:60 sits above the markets bar (z-50). Touch devices keep
both options side-by-side (the @media gate); the mobile drawer keeps
both visible too.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 11:11:46 +02:00
31a8efc27d ui: regroup topbar + unify the three header toggles
Header layout was visibly broken on desktop after the mobile-drawer
change: flex space-between distributed brand, BETA, tone-toggle, nav
and header-right across the bar, so BETA drifted away from the brand
wordmark and the tone-toggle landed in the middle of the row.

Markup: brand + BETA are now wrapped in .header-left so they ride
together. The tone-toggle moves back inside .header-right next to
theme + lang where it logically belongs. CSS: the header switches to
grid (1fr auto 1fr) on desktop, which truly centres the nav regardless
of side-group widths. The mobile @media block reverts to flex so the
hamburger + slide-out drawer still work.

Toggle redesign (tone, theme, language):

- The single-button theme widget becomes a Light | Dark segmented
  control matching the other two so all three read as one cluster.
  cassandraToggleTheme is replaced by cassandraSetTheme(theme), the
  toggle's data-theme attribute is synced on page load.
- All three share one CSS rule set: same padding, font, border, and
  a min-width so the active-only width matches the expanded width
  (no layout jump on hover).
- On hover-capable devices each toggle collapses to just the active
  option; hovering (or keyboard focus-within) reveals both. Touch
  devices keep both visible — the @media (hover: hover) gate handles
  that and the mobile-drawer block overrides it explicitly so the
  drawer-stacked controls remain full-width with both options shown.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 11:00:11 +02:00
6459e8c43d mobile: wrap tabs, trim portfolio + markets bar columns
Three pieces of phone-side feedback:

1. Indicator group tabs wrap onto multiple rows instead of
   horizontal-scrolling — every group is visible at a glance. Each
   button keeps its own bottom border so wrapped rows stay
   visually delimited; the container's bottom border is removed.

2. Portfolio holdings table hides Qty and Avg columns on mobile via
   the mobile-hide class (same mechanism as the indicator table).
   Remaining columns are the actionable ones: Ticker, Name, Last,
   P/L, %.

3. Markets bar at the bottom compacts to one row per chip —
   dot + code + change% only. The state word ("open" / "closed")
   is implied by the dot colour; the index label, price, and
   until-time are dropped on mobile. Grid columns drop their 220px
   floor so the full set fits the viewport without horizontal
   scroll (previously the bar scrolled within itself).
2026-05-28 19:10:58 +02:00
8ec4ea1c72 mobile: clamp grid items + table cells to viewport width
User reported the page rendering at ~3x viewport width on Android
Chrome with overflow-x:hidden clipping off most of the content.
Root cause: CSS grid items default to min-width:min-content, and the
indicator table inside the indicators panel has white-space:nowrap
cells. A long Symbol/Label value forces the table wider than its
panel; the panel propagates that minimum width up the grid; the grid
expands the .app-main; .app-main pushes the page wider than the
viewport. overflow-x:hidden then just chops the right portion off.

Fix has three parts:

1. .app and .app-main get min-width:0 and max-width:100vw so the
   shell can't be wider than the viewport regardless of descendants.
2. Every direct child of .app-main (each panel) gets min-width:0
   on mobile so individual panels can shrink past their min-content.
3. table.dense drops white-space:nowrap on text cells at ≤480px —
   long symbols wrap to two lines instead of forcing the table wide.
   Numeric cells keep nowrap (negative percentages reading as
   "−12\n.34%" would be unreadable).

Also adds an overflow-x:auto fallback on .panel-body pre/code so
any code block in AI output scrolls within the panel instead of
blowing the page out.
2026-05-28 19:02:30 +02:00
5ceee96135 mobile: fix drawer stacking + horizontal page overflow
Two related bugs reported on phone:

1. Drawer was unclickable — backdrop covered it. Root cause: the
   .app-header (position:sticky, z-index:50) creates a stacking
   context, so the drawer inside it had its z-index:100 clamped to
   "above other things inside the header" but NOT above siblings of
   the header. The backdrop at root-level z:90 then sat over the
   drawer subtree.

   Fix: when body.drawer-open, raise .app-header z-index to 110
   so its entire descendant tree (drawer included) draws above the
   z:90 backdrop. The page body under the header stays dimmed.

2. Horizontal scrolling on the dashboard. Root cause: the bottom
   markets bar used `grid-template-columns: repeat(auto-fit,
   minmax(220px, 1fr))`, which at 4+ markets blows out to 880px+ and
   forces the page wider than the viewport.

   Fix: on ≤480px the markets bar becomes a horizontally scrolling
   flex strip with min-width:160px per chip — page stays narrow,
   user swipes the bar to see more markets.

Also added overflow-x:hidden to html/body as a defensive net against
the fixed off-screen drawer creating overflow on Safari iOS.
2026-05-28 18:55:04 +02:00
2b3ea33884 mobile: hamburger drawer (right-side slide-out)
≤480px gets a hamburger button in the topbar and a fixed slide-out
panel from the right edge (width min(82vw, 320px)). The topbar keeps
only brand + tone toggle + hamburger visible; nav and the
header-right widgets (theme, lang, user menu, version meta) move
into the drawer.

Markup change: nav and .header-right are now wrapped in
.mobile-drawer, which is display:contents on desktop (no layout
effect) and a fixed translateX panel on mobile. The user-menu
dropdown chip hides on mobile and its links surface flat inside the
drawer.

JS: ~50 lines of vanilla. Tap hamburger / backdrop / ESC / swipe-
right-on-drawer all close. Clicking a nav link inside the drawer
closes it after the navigation kicks off so the panel doesn't linger
on the next page.

CSS: per-file @media block at the bottom of layout.css per the
agreed-upon organisation.
2026-05-28 18:36:37 +02:00
355593c4f7 css: split cassandra.css into per-section files
Splits the 2571-line cassandra.css into ten focused stylesheets:
tokens (palette + fonts), layout (chrome), panels, dashboard,
portfolio, log-chat, auth, settings, news, public. base.html and
public_base.html load only what they need; auth pages (login,
verify, unsubscribe confirm) load tokens + layout + auth.

Brand drift-detection test repointed at tokens.css (where the
palette now lives). 291 tests still pass.
2026-05-28 12:31:29 +02:00