/* 01_variables.css */
/* ======================== */ /* CSS VARIABLES - MUST LOAD FIRST */ /* File: 00_variables.css */ /* ======================== */ :root { /* ======================== */ /* DARK THEME COLORS */ /* ======================== */ /* Background colors */ --bg-primary: rgb(25, 25, 25); --bg-secondary: rgb(35, 35, 35); --bg-tertiary: rgb(15, 15, 15); --bg-dark: rgb(10, 10, 10); --bg-hover: rgba(255, 255, 255, 0.1); --bg-modal: #2a2a2a; --bg-modal-header: #1a1a1a; /* Border colors */ --border-primary: #444; --border-secondary: #666; --border-tertiary: #888; --border-focus: #0d6efd; --border-focus-shadow: rgba(13, 110, 253, 0.25); /* Text colors */ --text-primary: #ffffff; --text-secondary: #e0e0e0; --text-tertiary: #d0d0d0; --text-muted: #888; --text-disabled: #666; /* Status colors */ --color-success: #00bc8c; --color-success-hover: #009670; --color-danger: #e74c3c; --color-danger-hover: #dc3545; --color-warning: #f39c12; --color-warning-hover: #ffd700; --color-info: #3498db; --color-info-hover: #66b3ff; --color-primary: #0d6efd; --color-primary-hover: #0a58ca; --color-primary-light: #66b3ff; /* Value colors for tables */ --value-positive: #28a745; --value-negative: #dc3545; /* Tables (05_tables.css — the one stylesheet for dbc.Table + DataTable) */ --table-header-bg: #1c1c1c; /* rows sit on #222: header just a shade darker (founder, 2026-09-13) */ --table-border: #3a3a3a; --table-row-hover: var(--bg-hover); --table-stripe-bg: rgba(255, 255, 255, 0.03); --table-sticky-bg: var(--bg-secondary); /* frozen first column */ --table-sticky-hover-bg: rgb(50, 50, 50); /* opaque hover on it (DataTable) */ --table-control-bg: #1a1a1a; /* filter inputs, paging, export */ --table-control-hover-bg: #2a2a2a; /* Component specific */ --shadow-default: 0 4px 8px rgba(0, 0, 0, 0.3); --shadow-hover: 0 6px 12px rgba(0, 0, 0, 0.2); --shadow-lg: 0 4px 12px rgba(0, 0, 0, 0.5); /* Transitions */ --transition-default: all 0.2s ease; --transition-slow: all 0.3s ease; /* Font sizes: defined ONLY in 02_global_dynamic_fonts.css (responsive, mobile-first, all six --font-size-* tokens + the two --font-size-prose* tokens). 02 loads right after this file in the bundle and is the single source of truth — do not re-add font-size variables here. */ /* Spacing */ --spacing-xs: 4px; --spacing-sm: 8px; --spacing-md: 12px; --spacing-lg: 16px; --spacing-xl: 20px; /* Border radius */ --radius-sm: 3px; --radius-base: 4px; --radius-md: 6px; --radius-lg: 8px; --radius-xl: 12px; /* Z-index layers */ --z-sticky-1: 1; --z-sticky-2: 2; --z-sticky-3: 3; --z-dropdown: 10; --z-modal: 100; --z-tooltip: 1000; --z-toast: 2000; /* above Bootstrap modals (1055) — see .app-toast-slot */ }
/* 02_global_dynamic_fonts.css */
/* ======================== */ /* GLOBAL DYNAMIC FONT SIZING */ /* File: 01_global_dynamic_fonts.css */ /* ======================== */ /* * This file sets dynamic font sizing for ALL components * Load this AFTER variables.css but BEFORE all other component CSS files * This ensures all components inherit these responsive sizes */ /* ======================== */ /* OVERRIDE CSS VARIABLES BASED ON SCREEN SIZE */ /* ======================== */ /* Mobile First - Smallest sizes */ :root { --font-size-xs: 9px; --font-size-sm: 10px; --font-size-base: 10px; /* Down from 12px */ --font-size-md: 11px; /* Down from 13px */ --font-size-lg: 12px; /* Down from 14px */ --font-size-xl: 14px; /* Down from 16px */ /* Prose tokens — reading sizes for long-form text (mirrors the public blog: ~16px body / 13px small print). Used ONLY via the .text-readable / .text-readable-small utilities (03_typography.css) and the learn-more modal rules (09_modals.css). Data tables, dropdowns and controls keep the dense tokens above. */ --font-size-prose: 15px; --font-size-prose-sm: 12px; } /* Small devices (landscape phones, 544px and up) */ @media (min-width: 544px) { :root { --font-size-xs: 10px; --font-size-sm: 11px; --font-size-base: 11px; --font-size-md: 12px; --font-size-lg: 13px; --font-size-xl: 15px; } } /* Medium devices (tablets, 768px and up) */ @media (min-width: 768px) { :root { --font-size-xs: 10px; --font-size-sm: 11px; --font-size-base: 12px; --font-size-md: 13px; --font-size-lg: 14px; --font-size-xl: 16px; --font-size-prose: 16px; --font-size-prose-sm: 13px; } } /* (No 992px/1200px blocks: values stay the same as the 768px step above, so the tablet block also covers desktops.) */ /* ======================== */ /* UNIVERSAL FONT SIZE APPLICATION */ /* ======================== */ /* Apply to ALL text elements by default. * LOAD-BEARING: this rule flattens every font-size in the app (including * inline style={'fontSize': ...} set from Python — stylesheet !important * beats inline styles). Component rules below re-add sizes with their own * !important + higher specificity. Do NOT remove it: hundreds of legacy * inline sizes would resurface unpredictably (see .text-readable comment * in 03_typography.css for the sanctioned opt-out pattern). */ body, body * { font-size: var(--font-size-base) !important; } /* ======================== */ /* SPECIFIC COMPONENT OVERRIDES */ /* ======================== */ /* (p and span need no dedicated rules — `body *` above already sets them to the same base token.) */ /* Headers - Slightly larger but still dynamic */ h1 { font-size: calc(var(--font-size-xl) * 1.5) !important; } h2 { font-size: calc(var(--font-size-xl) * 1.25) !important; } h3 { font-size: calc(var(--font-size-lg) * 1.2) !important; } h4 { font-size: var(--font-size-lg) !important; } h5 { font-size: var(--font-size-md) !important; } h6 { font-size: var(--font-size-base) !important; } /* ======================== */ /* TABLES (DataTable & dbc.Table) */ /* ======================== */ /* Table body cells */ .table tbody td, .dash-spreadsheet-container td, div.dash-spreadsheet-container .dash-spreadsheet-inner table td { font-size: var(--font-size-base) !important; } /* Table headers - slightly larger */ .table thead th, .dash-spreadsheet-container th, div.dash-spreadsheet-container .dash-spreadsheet-inner table th { font-size: var(--font-size-md) !important; } /* ======================== */ /* FORMS & INPUTS */ /* ======================== */ /* All form controls */ .form-control, .form-select, input[type="text"], input[type="number"], input[type="email"], input[type="password"], textarea, select { font-size: var(--font-size-base) !important; } /* Labels — control captions are questions the user must read to operate the page, so they use the supporting-text token --font-size-prose-sm (12px phones / 13px desktop): a label is then never SMALLER than the .text-readable-small description under it (the md token was 11px on phones, which inverted the hierarchy). `label.small` out-specifies the `.small` xs rule further down, so legacy `className='small'` labels get the same size instead of badge-size text. */ label, .form-label, label.small { font-size: var(--font-size-prose-sm) !important; } /* ======================== */ /* DROPDOWNS & SELECT MENUS */ /* ======================== */ /* Keep dropdowns small as you like them */ .Select-control, .Select-menu-outer, .Select-value-label, .Select-option, .Select-input input, .Select-placeholder { font-size: var(--font-size-sm) !important; /* 10-11px dynamic */ } /* ======================== */ /* BUTTONS */ /* ======================== */ /* All buttons */ .btn, button, [type="button"], [type="submit"], .export { font-size: var(--font-size-base) !important; } /* Small buttons */ .btn-sm { font-size: var(--font-size-sm) !important; } /* Large buttons */ .btn-lg { font-size: var(--font-size-md) !important; } /* ======================== */ /* NAVIGATION & TABS */ /* ======================== */ .nav-link, .nav-tabs .nav-link, .navbar-nav { font-size: var(--font-size-base) !important; } /* ======================== */ /* CARDS */ /* ======================== */ .card-body, .card-title, .card-text { font-size: var(--font-size-base) !important; } .card-header, .card-footer { font-size: var(--font-size-sm) !important; } /* ======================== */ /* MODALS */ /* ======================== */ .modal-body { font-size: var(--font-size-base) !important; } /* Modal titles sit on the section-heading tier of the ladder (like card headers): with sections inside modals promoted to .text-section-heading (16px, weight 600), the old --font-size-lg (14px) title rendered SMALLER than its own sections — inverted hierarchy. Same token + weight keeps the title at least level with any section heading below it. */ .modal-title { font-size: var(--font-size-prose) !important; font-weight: 600; } /* ======================== */ /* PAGINATION */ /* ======================== */ .page-number, .current-page-container input, .pagination { font-size: var(--font-size-sm) !important; } /* ======================== */ /* TOOLTIPS & POPOVERS */ /* ======================== */ .tooltip, .popover, .dash-tooltip { font-size: var(--font-size-sm) !important; } /* ======================== */ /* OVERRIDE FOR SPECIFIC SMALL ELEMENTS */ /* ======================== */ /* Keep these extra small */ .badge, .form-text, small, .small { font-size: var(--font-size-xs) !important; } /* ======================== */ /* PLOTLY GRAPH OVERRIDES */ /* ======================== */ /* Axis ticks, axis titles, legend, range-selector buttons: keep the app's small chart font. Hover labels and annotations are EXCLUDED on purpose so the sizes set in Python (settings_graphs.HOVER_LABEL_STYLE, baseline / empty-state annotations) actually apply — the old blanket rule shrank hover text to 9-10px on desktop and silently overrode every Python font setting (2026-08-21). */ .js-plotly-plot .plotly text:not(.hoverlayer text):not(.annotation-text), .js-plotly-plot .legend text, .js-plotly-plot .legendtext, g.legend text.legendtext { font-size: var(--font-size-xs) !important; }
/* 03_typography.css */
/* ======================== */ /* TYPOGRAPHY AND TEXT STYLES */ /* File: 02_typography.css */ /* ======================== */ /* Base font family inheritance */ .dash-table-container, .dash-spreadsheet-container, .dash-spreadsheet-container table, .dash-spreadsheet-container input { font-family: "Lato", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; } /* (The old p/span/h1-h5 responsive ladders were removed 2026-07: they had no !important, so the global `body * { font-size: ... !important }` and h1-h6 rules in 02_global_dynamic_fonts.css always won — the ladders never took effect anywhere. Element sizing lives in 02.) */ /* ======================== */ /* COLOR CODING FOR VALUES */ /* ======================== */ /* Positive values */ .cell-value-positive { color: var(--value-positive); font-weight: 500; } /* Negative values */ .cell-value-negative { color: var(--value-negative); font-weight: 500; } /* Primary text color in modals */ .learn-more-content .text-primary { color: var(--color-info-hover); } /* ======================== */ /* READABLE PROSE UTILITIES */ /* ======================== */ /* * `.text-readable` — opt-in utility for long blocks of prose (AI answers, * explanations, account/settings copy, calculator methodology text). Brings * text to the public blog's reading size: --font-size-prose (15px mobile / * 16px desktop, defined in 02_global_dynamic_fonts.css). * * Why a dedicated rule is unavoidable: the global * `body * { font-size: var(--font-size-base) !important }` in * 02_global_dynamic_fonts.css forces every descendant back to base, so a * parent-level class alone can't cascade. We out-specify it with class(+ * element) selectors plus `!important`. Reuse these two classes for future * reading blocks instead of adding per-feature font rules. * * `.text-readable-small` — the matching supporting-text size (12px mobile / * 13px desktop, --font-size-prose-sm): disclaimers, captions, status notes and * secondary guidance that sit inside or next to readable prose. Works * standalone or nested. * * `.text-section-heading` — a compact 15-16px heading for card/section titles. * It deliberately reuses the prose token instead of introducing another * numeric size. * * Controls are pinned back on purpose: a prose container may embed buttons, * inputs, dropdowns, badges or tables — those keep the dense app sizes. */ .text-readable { line-height: 1.6; } .text-readable, .text-readable p, .text-readable li, .text-readable span, .text-readable a, .text-readable em, .text-readable strong, .text-readable b, .text-readable i { font-size: var(--font-size-prose) !important; } /* Raw model/log output inside a prose block: readable but subordinate */ .text-readable pre { font-size: var(--font-size-prose-sm) !important; } /* Headings inside prose stay a step above the body text (without this, 02's h5/h6 rules would render them SMALLER than the 16px prose). */ .text-readable h5, .text-readable h6 { font-size: calc(var(--font-size-prose) * 1.1) !important; } /* Supporting text — standalone utility, and subordinate elements in prose */ .text-readable-small, .text-readable-small p, .text-readable-small li, .text-readable-small span, .text-readable-small a, .text-readable-small em, .text-readable-small strong, .text-readable-small b, .text-readable-small i, .text-readable-small small, .text-readable small, .text-readable .small, .text-readable .text-readable-small, .text-readable .text-readable-small p, .text-readable .text-readable-small li, .text-readable .text-readable-small span, .text-readable .text-readable-small a, .text-readable .text-readable-small em, .text-readable .text-readable-small strong, .text-readable .text-readable-small b, .text-readable .text-readable-small i, .text-readable .text-readable-small small { font-size: var(--font-size-prose-sm) !important; } .text-readable-small { line-height: 1.5; } /* Card and section titles: visibly distinct from dense controls/tables while staying quieter than the document-level h1-h3 ladder. Apply this class to the heading container; Python chooses the semantic role, CSS owns the size. */ .text-section-heading { font-size: var(--font-size-prose) !important; font-weight: 600; line-height: 1.3; } /* Controls inside a prose block keep the dense app sizes */ .text-readable .btn, .text-readable button, .text-readable input, .text-readable select, .text-readable textarea, .text-readable .form-control, .text-readable .form-select, .text-readable td, .text-readable th, .text-readable td span, .text-readable th span { font-size: var(--font-size-base) !important; } .text-readable .Select-control span, .text-readable .Select-menu-outer span, .text-readable .Select-value-label, .text-readable .Select-option, .text-readable .Select-placeholder { font-size: var(--font-size-sm) !important; } .text-readable .badge { font-size: var(--font-size-xs) !important; }
/* 04_forms_inputs.css */
/* ======================== */ /* FORM ELEMENTS AND INPUTS */ /* File: 03_forms-inputs.css */ /* ======================== */ /* Base form controls */ .form-control, .form-control:focus, .form-control:valid, input[type="number"], input[type="text"], input.form-control { background-color: var(--bg-primary); color: var(--text-primary); border: 1px solid var(--border-primary); } .form-control:focus { border-color: var(--border-focus); box-shadow: 0 0 0 0.25rem var(--border-focus-shadow); } /* Form control responsive sizing */ @media (min-width: 1px) { .form-control:valid { font-size: var(--font-size-xs); } } @media (min-width: 768px) { .form-control:valid { font-size: var(--font-size-sm); } } /* Disabled input styling */ input:disabled, .form-control:disabled { background-color: var(--bg-tertiary); color: var(--text-disabled); cursor: not-allowed; opacity: 0.6; } /* ======================== */ /* SELECT DROPDOWNS */ /* ======================== */ /* Select control base styling */ .Select-control { background-color: var(--bg-primary); border: 1px solid var(--border-primary); min-height: 30px; font-size: var(--font-size-base); /* font-size changed to var(--font-size-base): old design was font-size: 11px;*/ } /* Select menu styling */ .Select-menu-outer { background-color: var(--bg-primary); border: 1px solid var(--border-primary); margin-top: 2px; font-size: var(--font-size-sm); /* font-size: 11px; Changed to var(--font-size-sm) to match new design */ } /* Select value and label */ .Select-value, .Select-value-label { color: var(--text-primary); } /* Select input */ .Select-input input { color: var(--text-primary); font-size: var(--font-size-sm); /* font-size: 11px; changed to var(--font-size-base): old design was font-size: 11px;* */ } /* Select options */ .Select-option { background-color: var(--bg-primary); color: var(--text-primary); padding: var(--spacing-sm) var(--spacing-md); } .Select-option:hover { background-color: var(--bg-hover); } .Select-option.is-selected { background-color: var(--color-primary); color: var(--text-primary); } /* Dropdown container */ .dash-dropdown-container { background-color: var(--bg-primary); } /* * Custom form select (covers all sizes — `dbc.Select` renders as * `.form-select`, with `.form-select-sm` and `.form-select-lg` as * size modifiers). The dark-theme rules live on the base class so * any select in the app inherits them, regardless of size. Bootstrap's * size-modifier classes only adjust padding/font-size, so a single * color rule is enough for every variant. */ .form-select { background-color: #222; color: var(--text-primary); border: 1px solid var(--border-primary); transition: var(--transition-default); } .form-select:focus { background-color: #2a2a2a; border-color: var(--color-success); box-shadow: 0 0 0 0.25rem rgba(0, 188, 140, 0.25); color: var(--text-primary); } .form-select option { background-color: #222; color: var(--text-primary); } .form-select:hover { border-color: var(--border-secondary); } /* ======================== */ /* EXPORT BUTTONS */ /* ======================== */ .export { background-color: var(--bg-primary); color: var(--text-primary); border: 1px solid var(--border-primary); padding: 6px 12px; border-radius: var(--radius-base); font-size: var(--font-size-base); cursor: pointer; transition: var(--transition-default); margin: var(--spacing-xs); } .export:hover { background-color: var(--bg-hover); border-color: var(--border-secondary); } /* ======================== */ /* DCC.DROPDOWN FIXES */ /* ======================== */ /* Fix focus state - prevent white background */ .Select.is-focused > .Select-control { background-color: rgb(25, 25, 25) !important; border-color: var(--border-focus) !important; box-shadow: 0 0 0 0.25rem var(--border-focus-shadow) !important; } /* Fix selected value text color for single select */ .Select--single > .Select-control .Select-value { color: var(--text-primary) !important; } /* Ensure text remains white in all states */ .Select-value-label { color: var(--text-primary) !important; } /* Fix placeholder text color */ .Select-placeholder { color: var(--text-muted) !important; } /* Fix dropdown arrow color */ .Select-arrow { border-color: var(--text-muted) transparent transparent !important; } /* Fix dropdown arrow on hover */ .Select-control:hover .Select-arrow { border-color: var(--text-primary) transparent transparent !important; } /* Additional fixes for multi-select if needed */ .Select--multi .Select-value { background-color: var(--bg-secondary) !important; border-color: var(--border-primary) !important; color: var(--text-primary) !important; } .Select--multi .Select-value-icon { border-color: var(--border-primary) !important; } .Select--multi .Select-value-icon:hover { background-color: var(--bg-hover) !important; color: var(--text-primary) !important; } /* ======================== */ /* DROPDOWN STATE FIXES - Add these to the end of 03_forms_inputs.css */ /* ======================== */ /* Fix for dropdown background turning white after callbacks */ .Select-control, .Select.is-open > .Select-control, .Select.is-focused:not(.is-open) > .Select-control { background-color: var(--bg-primary) !important; border-color: var(--border-primary) !important; } /* Ensure menu stays dark */ .Select-menu-outer, .Select.is-open .Select-menu-outer { background-color: var(--bg-primary) !important; border-color: var(--border-primary) !important; } /* Fix for multi-select specific issues */ .Select--multi .Select-control, .Select--multi.is-open > .Select-control, .Select--multi.is-focused:not(.is-open) > .Select-control { background-color: var(--bg-primary) !important; } /* Handle React-Select's dynamic classes */ .Select-control, .Select-control:hover, .Select--single > .Select-control, .Select--multi > .Select-control { background-color: rgb(25, 25, 25) !important; } /* Ensure dropdown arrow and clear button stay visible */ .Select-arrow-zone, .Select-clear-zone { background-color: transparent !important; } /* Handle the input field inside the dropdown */ .Select-input > input, .Select--multi .Select-input > input { background-color: transparent !important; color: var(--text-primary) !important; } /* * Mobile: prevent iOS Safari auto-zoom on focused inputs/selects with * font-size < 16px. * * Note: server.py's viewport meta tag uses `maximum-scale=1.0`, which * already disables iOS auto-zoom universally. CLAUDE.md flags the * viewport lock as a "last-resort" workaround (it reduces accessibility), * but it IS in effect today, so the per-element 16px overrides are * defense-in-depth rather than load-bearing. We keep them ONLY for the * React-Select internal input (dcc.Dropdown) so that if the viewport * lock is ever relaxed, the dropdown stays zoom-safe. * * `.form-select` (dbc.Select / native <select>) is intentionally not * overridden here — letting it inherit `var(--font-size-base)` from * 02_global_dynamic_fonts.css makes the picker text match the * surrounding `html.P` at every breakpoint instead of jumping to 16px * on mobile. */ @media (max-width: 768px) { .dash-dropdown-container .Select-input input { font-size: 16px !important; } } /* Fix for when dropdown is disabled */ .Select.is-disabled > .Select-control { background-color: var(--bg-tertiary) !important; opacity: 0.6; } /* Additional specificity for problematic states */ .dash-dropdown .Select-control, .dash-dropdown .Select.is-open > .Select-control, .dash-dropdown .Select.is-focused > .Select-control { background-color: rgb(25, 25, 25) !important; color: var(--text-primary) !important; } /* Force dark background on all Select elements regardless of state */ div[class*="Select"] .Select-control { background-color: rgb(25, 25, 25) !important; } /* Handle Dash's wrapper divs */ .dash-dropdown-container .Select-control, .dash-dropdown-container .Select-menu-outer { background-color: var(--bg-primary) !important; } /* Ensure the dropdown doesn't inherit any white backgrounds from parent elements */ .Select-control * { background-color: transparent !important; } /* Fix for dropdown options */ .Select-option, .Select-option.is-focused, .Select-option.is-selected { background-color: var(--bg-primary) !important; } .Select-option.is-focused:not(.is-selected) { background-color: var(--bg-hover) !important; } .Select-option.is-selected { background-color: var(--color-primary) !important; } /* Ensure no white background bleeds through from Bootstrap or other sources */ .form-control.Select-input, .form-control .Select-control { background-color: transparent !important; } /* ======================== */ /* SAFER DASH RADIO/CHECKBOX SPACING */ /* This version ONLY affects radio buttons and checkboxes */ /* ======================== */ /* Only target labels that CONTAIN radio buttons */ label:has(input[type="radio"]) { margin-right: 8px !important; margin-bottom: 8px !important; display: inline-flex !important; align-items: center !important; } /* Only target labels that CONTAIN checkboxes */ label:has(input[type="checkbox"]) { margin-right: 8px !important; margin-bottom: 8px !important; display: inline-flex !important; align-items: center !important; } /* Spacing for the radio/checkbox inputs themselves */ input[type="radio"], input[type="checkbox"] { margin-right: 8px !important; /*margin-left: 2px !important;*/ cursor: pointer; } /* For browsers that don't support :has() - more specific targeting */ label > input[type="radio"], label > input[type="checkbox"] { margin-right: 8px !important; } /* Only affect labels with inline-block IF they contain radio/checkbox */ label[style*="inline-block"]:has(input[type="radio"]), label[style*="inline-block"]:has(input[type="checkbox"]) { margin-right: 8px !important; margin-bottom: 8px !important; } /* Remove margin from last radio/checkbox label in a group */ div.mb-3 > label:last-child:has(input[type="radio"]), div.mb-3 > label:last-child:has(input[type="checkbox"]) { margin-right: 0 !important; } /* Alternative approach using attribute selectors for Dash components */ /* This targets RadioItems and Checklist components specifically */ div[id*="-radioitems"] label, div[id*="-checklist"] label, div[id*="radio"] label:has(input[type="radio"]), div[id*="check"] label:has(input[type="checkbox"]) { margin-right: 8px !important; margin-bottom: 8px !important; } /* Fallback for older browsers without :has() support */ /* Only applies to labels that are direct children of common Dash containers */ .dash-radioitems > label, .dash-checklist > label, div[class*="radio"] > label, div[class*="check"] > label { margin-right: 8px !important; margin-bottom: 8px !important; } /* Ensure we don't affect any other inline-block elements */ /* Reset margins for non-radio/checkbox labels */ label:not(:has(input[type="radio"])):not(:has(input[type="checkbox"])) { /* These labels keep their default styling */ } /* ======================== */ /* SWITCH + LABEL ALIGNMENT (ML module; generalize during the broad pass) */ /* ======================== */ /* Bootstrap's float-based .form-switch misaligns the toggle pill and a wrapping label (the text drops below the pill). Flex keeps the pill and the possibly-multi-line text on one row, top-aligned. Scoped to the ML page wrapper until the founder approves it app-wide. */ .ml-prediction-page .form-check.form-switch { display: flex; align-items: flex-start; padding-left: 0; gap: 0.6em; } .ml-prediction-page .form-check.form-switch .form-check-input { float: none; flex-shrink: 0; margin-left: 0; margin-top: 0.2em; } .ml-prediction-page .form-check.form-switch .form-check-label { margin-bottom: 0 !important; }
/* 05_tables.css */
/* ======================================================================== */ /* TABLES — the ONE stylesheet for every table in the app */ /* File: 05_tables.css (tables programme 2026-09; replaced 06_datatable, */ /* 10_scrollbars, 11_dbc_table and 12_sticky_headers) */ /* */ /* Consumers: functions/function_tables.py */ /* build_table → dbc.Table inside html.Div(className="table-scroll") */ /* build_datatable → DataTable inside html.Div(className="datatable-wrap") */ /* and the sticky-header clone made by assets/custom_js/table-sticky-headers */ /* (.sticky-header-wrapper). */ /* */ /* Tokens: 01_variables.css (--table-*). Font sizes: 02_global_dynamic_fonts */ /* ONLY (never set font-size here). Sign colours: 03_typography.css */ /* (.cell-value-positive / .cell-value-negative). */ /* */ /* !important is used only where the DataTable's own stylesheet (injected at */ /* runtime, after this bundle) would otherwise win the tie. */ /* ======================================================================== */ /* ======================== */ /* 1. BOOTSTRAP TABLE LOOK */ /* ======================== */ /* Bootstrap 5 paints cells from these variables; setting them here gives hover / striped / bordered the app colours with no extra selectors. A transparent body lets the table sit on any card. */ .table { --bs-table-bg: transparent; --bs-table-color: var(--text-primary); --bs-table-border-color: var(--table-border); --bs-table-striped-bg: var(--table-stripe-bg); --bs-table-striped-color: var(--text-primary); --bs-table-hover-bg: var(--table-row-hover); --bs-table-hover-color: var(--text-primary); --bs-table-active-bg: var(--table-row-hover); --bs-table-active-color: var(--text-primary); } .table thead th { background-color: var(--table-header-bg); color: var(--text-primary); font-weight: 600; letter-spacing: 0.3px; vertical-align: middle; white-space: nowrap; border-bottom: 2px solid var(--table-border); } .table tbody td { vertical-align: middle; } /* Compact spacing (every builder table is size="sm") */ .table.table-sm > :not(caption) > * > * { padding: 3px 6px; } .table.table-sm thead th { padding: 5px 6px; } /* Collapsible tables: the hidden detail rows behind the "+" button */ .table .table-detail-row { font-style: italic; } /* Truncated long text (build_table truncate={col: n}): a native <details> whose summary shows "first n chars…"; open = the full text in place. Hover shows the full text through the title attribute. */ .table .cell-truncate { display: inline; } .table .cell-truncate > summary { display: inline; list-style: none; cursor: pointer; text-decoration: underline dotted; text-underline-offset: 2px; } .table .cell-truncate > summary::-webkit-details-marker { display: none; } .table .cell-truncate .cell-full { display: none; } .table .cell-truncate[open] .cell-short { display: none; } .table .cell-truncate[open] .cell-full { display: inline; white-space: normal; } /* Sign colours on a cell (build_table sign_color_cols): Bootstrap paints every cell's colour with `.table > :not(caption) > * > *`, which outranks the bare `.cell-value-*` class of 03_typography.css — restate it at cell level so the design tokens win. */ .table td.cell-value-positive, .table th.cell-value-positive { color: var(--value-positive); font-weight: 500; } .table td.cell-value-negative, .table th.cell-value-negative { color: var(--value-negative); font-weight: 500; } /* Click-to-sort header buttons (build_table(sortable=True)): look like header text, no button chrome */ .table thead th .table-sort-btn, .table thead th .table-sort-btn:focus, .table thead th .table-sort-btn:active { border: none; padding: 0; margin: 0; background: transparent; color: inherit; font-weight: 600; box-shadow: none; white-space: nowrap; } .table thead th .table-sort-btn:hover { color: var(--color-primary-light); background: transparent; } /* ======================== */ /* 2. SCROLL WRAPPERS */ /* ======================== */ /* Every builder table lives in html.Div(className="table-scroll"): wide tables scroll inside it instead of bleeding out of the card on phones. Never put overflow-x on a container that also holds headings or captions — those texts then scroll with the table (ML bugfix 2026-08-05). */ .table-scroll, .table-responsive { overflow-x: auto; -webkit-overflow-scrolling: touch; } /* One scrollbar look for every table container */ .table-scroll::-webkit-scrollbar, .table-responsive::-webkit-scrollbar, .dash-spreadsheet-container::-webkit-scrollbar, .dash-spreadsheet-inner::-webkit-scrollbar, .dash-table-viewport::-webkit-scrollbar { width: 8px; height: 8px; } .table-scroll::-webkit-scrollbar-track, .table-responsive::-webkit-scrollbar-track, .dash-spreadsheet-container::-webkit-scrollbar-track, .dash-spreadsheet-inner::-webkit-scrollbar-track, .dash-table-viewport::-webkit-scrollbar-track { background-color: var(--bg-primary); } .table-scroll::-webkit-scrollbar-thumb, .table-responsive::-webkit-scrollbar-thumb, .dash-spreadsheet-container::-webkit-scrollbar-thumb, .dash-spreadsheet-inner::-webkit-scrollbar-thumb, .dash-table-viewport::-webkit-scrollbar-thumb { background-color: var(--border-secondary); border-radius: var(--radius-base); } .table-scroll::-webkit-scrollbar-thumb:hover, .table-responsive::-webkit-scrollbar-thumb:hover, .dash-spreadsheet-container::-webkit-scrollbar-thumb:hover, .dash-spreadsheet-inner::-webkit-scrollbar-thumb:hover, .dash-table-viewport::-webkit-scrollbar-thumb:hover { background-color: var(--border-tertiary); } /* ======================== */ /* 3. STICKY FIRST COLUMN */ /* ======================== */ /* Scoped to the builder wrappers (and the sticky-header clone) — a bare <table> elsewhere (markdown reports, prose) keeps a normal first column. */ .table-scroll th:first-child, .table-scroll td:first-child, .dash-table-container th:first-child, .dash-table-container td:first-child, .sticky-header-wrapper th:first-child { position: sticky !important; left: 0; z-index: var(--z-sticky-1); white-space: nowrap; } /* Body cells need an opaque background to hide the columns scrolling underneath; header cells keep the header colour (rule 1 / rule 5). */ .table-scroll td:first-child { background-color: var(--table-sticky-bg); } /* Hover on a Bootstrap row reaches the frozen cell through Bootstrap's inset box-shadow, so no extra hover rule is needed here. */ /* ======================== */ /* 4. STICKY CONTROL COLUMNS (build_table: "+", "✎", "X") */ /* Ladder classes set by the builder from the number of control columns: */ /* two-columns-sticky = 1 control column + the first data column */ /* three-columns-sticky = 2 control columns + the first data column */ /* four-columns-sticky = 3 control columns + the first data column */ /* Offsets = the fixed widths of the control columns below. */ /* ======================== */ .two-columns-sticky th:nth-child(-n+2), .two-columns-sticky td:nth-child(-n+2), .three-columns-sticky th:nth-child(-n+3), .three-columns-sticky td:nth-child(-n+3), .four-columns-sticky th:nth-child(-n+4), .four-columns-sticky td:nth-child(-n+4) { position: sticky !important; white-space: nowrap; } .two-columns-sticky td:nth-child(-n+2), .three-columns-sticky td:nth-child(-n+3), .four-columns-sticky td:nth-child(-n+4) { background-color: var(--table-sticky-bg); } .two-columns-sticky th:nth-child(1), .two-columns-sticky td:nth-child(1) { left: 0; z-index: var(--z-sticky-2); } .two-columns-sticky th:nth-child(2), .two-columns-sticky td:nth-child(2) { left: 20px; z-index: var(--z-sticky-1); } .three-columns-sticky th:nth-child(1), .three-columns-sticky td:nth-child(1) { left: 0; z-index: var(--z-sticky-3); } .three-columns-sticky th:nth-child(2), .three-columns-sticky td:nth-child(2) { left: 25px; z-index: var(--z-sticky-2); } .three-columns-sticky th:nth-child(3), .three-columns-sticky td:nth-child(3) { left: 50px; z-index: var(--z-sticky-1); } .four-columns-sticky th:nth-child(1), .four-columns-sticky td:nth-child(1) { left: 0; z-index: var(--z-sticky-3); } .four-columns-sticky th:nth-child(2), .four-columns-sticky td:nth-child(2) { left: 20px; z-index: var(--z-sticky-3); } .four-columns-sticky th:nth-child(3), .four-columns-sticky td:nth-child(3) { left: 45px; z-index: var(--z-sticky-2); } .four-columns-sticky th:nth-child(4), .four-columns-sticky td:nth-child(4) { left: 70px; z-index: var(--z-sticky-1); } /* Fixed-width control columns */ .plus-col, .plus-col > button { width: 20px; min-width: 20px; max-width: 20px; text-align: center; padding: 0; } .edit-col, .edit-col > button, .remove-col, .remove-col > button { width: 25px; min-width: 25px; max-width: 25px; text-align: center; padding: 0; } /* The control CELLS take no side padding: the compact rule above (`.table.table-sm > :not(caption) > * > *`, 6px each side) outranks the plain `.plus-col` / `.edit-col` / `.remove-col` padding, so on a phone the "+" cell grew to 32px while the sticky offsets above assume 20 / 25px — the first data column slid 12px under it on a sideways swipe (founder's iPhone, 2026-09-29). This selector outranks the compact rule; widths = offsets. */ .table-scroll .table :is(th, td):is(.plus-col, .edit-col, .remove-col) { padding-left: 0; padding-right: 0; } /* Seam mask: hides the sub-pixel text bleed at the right edge of the LAST sticky column while scrolling horizontally */ .table-scroll td:first-child::after, .two-columns-sticky td:nth-child(2)::after, .three-columns-sticky td:nth-child(3)::after, .four-columns-sticky td:nth-child(4)::after { content: ""; position: absolute; top: 0; right: -2px; bottom: 0; width: 2px; background: var(--table-sticky-bg); pointer-events: none; } .two-columns-sticky td:nth-child(1)::after, .three-columns-sticky td:nth-child(1)::after, .three-columns-sticky td:nth-child(2)::after, .four-columns-sticky td:nth-child(1)::after, .four-columns-sticky td:nth-child(2)::after, .four-columns-sticky td:nth-child(3)::after { display: none; } /* ======================== */ /* 5. DASH DATATABLE */ /* (build_datatable; no inline style dicts — this is the look) */ /* ======================== */ /* The DataTable paints itself from CSS variables that its own stylesheet declares on the inner <table> (together with a monospace font). A custom property is inherited from the nearest ancestor that sets it, so the overrides must sit on that same element — hence the full selector. */ .dash-table-container .dash-spreadsheet-container .dash-spreadsheet-inner table { --accent: var(--color-primary) !important; --border: var(--table-border) !important; --text-color: var(--text-secondary) !important; --background-color: transparent !important; --hover: var(--table-row-hover) !important; --background-color-ellipses: transparent !important; --faded-text: var(--text-muted) !important; --faded-text-header: var(--text-muted) !important; --muted: var(--text-muted) !important; font-family: "Lato", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important; color: var(--text-secondary); } .dash-table-container { font-family: "Lato", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important; overflow-x: auto !important; width: 100% !important; max-width: 100%; } .dash-spreadsheet-inner td, .dash-spreadsheet-inner th, .dash-table-container .page-number, .dash-table-container .current-page-container input, .dash-table-container .dash-filter input { font-family: "Lato", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important; } .dash-spreadsheet-container { border: none !important; border-radius: 0 !important; overflow-x: auto !important; } .dash-spreadsheet-inner, .dash-spreadsheet-inner table { background-color: transparent !important; border-collapse: collapse !important; border-spacing: 0 !important; } .dash-table-container .dash-spreadsheet-inner { min-width: 100%; width: auto !important; } .dash-table-viewport { overflow-x: auto !important; } /* Headers */ .dash-spreadsheet .dash-header, .dash-spreadsheet-inner th, .dash-header th, .dash-filter th { background-color: var(--table-header-bg) !important; color: var(--text-primary) !important; font-weight: 600 !important; padding: 5px 8px !important; border: none !important; border-bottom: 2px solid var(--table-border) !important; border-radius: 0 !important; text-align: left !important; } .column-header-name { color: var(--text-primary) !important; font-weight: 600 !important; } /* Cell colour and background: the grid's own stylesheet (injected at runtime, after this bundle) paints every cell white and dark-grey with a `.dash-table-container .dash-spreadsheet-container .dash-spreadsheet-inner td` rule. Out-rank it through the builder wrapper (one class more), WITHOUT !important: build_datatable's conditional styles (sign colours, flags) arrive as inline styles and must keep winning. The second selector (one element more: `table td`) out-ranks it WITHOUT the wrapper: a Focus page that pulls a grid out of the classic tree by id (function_focus_parts, pull_table) leaves `.datatable-wrap` behind, and the grid then showed white cells with the dark theme's light text (founder, iPhone 2026-09-30: Portfolio > Correlations > All pairs, Rebalance's optimiser and metrics tables). */ .datatable-wrap .dash-table-container .dash-spreadsheet-container .dash-spreadsheet-inner td, .dash-table-container .dash-spreadsheet-container .dash-spreadsheet-inner table td { background-color: transparent; color: var(--text-secondary); font-weight: 400; } /* Cells: horizontal rules only (forced — never conditional per cell) */ .dash-spreadsheet-inner td, .dash-cell-value, .dash-select-cell { border: none !important; border-bottom: 1px solid var(--table-border) !important; border-radius: 0 !important; padding: 3px 8px !important; line-height: 1.4 !important; transition: none !important; text-transform: none !important; letter-spacing: normal !important; } .dash-cell-value { padding: 0 !important; border: none !important; } .dash-spreadsheet-inner tbody tr { background-color: transparent !important; } .dash-spreadsheet-inner tbody tr:nth-child(even) { background-color: var(--table-stripe-bg) !important; } .dash-spreadsheet-inner tbody tr:hover { background-color: var(--table-row-hover) !important; } .dash-spreadsheet-inner tbody tr:hover td { transform: none !important; box-shadow: none !important; } /* Frozen first column (rule 3 makes it sticky): opaque, and opaque on hover */ .dash-spreadsheet-inner td:first-child { background-color: var(--table-sticky-bg) !important; } .dash-spreadsheet-inner tbody tr:hover td:first-child { background-color: var(--table-sticky-hover-bg) !important; } .dash-spreadsheet-inner td:first-child .dash-cell-value { background-color: transparent !important; } /* Selected / active cells */ .dash-spreadsheet td.focused { background-color: var(--border-focus-shadow) !important; box-shadow: inset 0 0 0 1px var(--color-primary) !important; } .dash-spreadsheet td.cell--selected { background-color: rgba(13, 110, 253, 0.08) !important; } /* Filter row */ .dash-filter, .dash-filter--case { background-color: var(--table-control-bg) !important; } .dash-filter input { background-color: var(--table-control-bg) !important; color: var(--text-primary) !important; border: 1px solid var(--table-border) !important; border-radius: 2px !important; padding: 4px 6px !important; } .dash-filter input:focus { border-color: var(--color-primary) !important; outline: none !important; } /* Pagination */ .previous-next-container { background-color: transparent !important; padding: 8px 0 !important; border-top: 1px solid var(--table-border) !important; display: flex !important; align-items: center !important; gap: 4px !important; } .previous-page, .next-page, .first-page, .last-page, .page-number { border-radius: 3px !important; background: var(--table-control-bg) !important; color: var(--text-primary) !important; border: 1px solid var(--table-border) !important; padding: 4px 8px !important; height: 28px !important; box-sizing: border-box !important; font-weight: 400 !important; line-height: 20px !important; cursor: pointer !important; display: inline-flex !important; align-items: center !important; justify-content: center !important; vertical-align: middle !important; margin: 0 2px !important; } .page-number:hover, .previous-page:hover:not([disabled]), .next-page:hover:not([disabled]), .first-page:hover:not([disabled]), .last-page:hover:not([disabled]) { background: var(--table-control-hover-bg) !important; border-color: var(--border-primary) !important; transform: none !important; box-shadow: none !important; } .previous-page[disabled], .next-page[disabled], .first-page[disabled], .last-page[disabled] { opacity: 0.5 !important; cursor: not-allowed !important; } .current-page-container::before, .current-page-container::after, .current-page-container *::before, .current-page-container *::after { content: none !important; display: none !important; } .current-page-container { position: relative !important; display: inline-flex !important; align-items: center !important; justify-content: center !important; vertical-align: middle !important; background: transparent !important; height: 28px !important; line-height: 28px !important; margin: 0 2px !important; } .current-page-container > *:not(input) { display: none !important; } .current-page-container input { border-radius: 3px !important; background: var(--color-primary) !important; border: 1px solid var(--color-primary) !important; color: var(--text-primary) !important; -webkit-text-fill-color: var(--text-primary) !important; font-weight: 500 !important; padding: 4px 8px !important; width: 50px !important; height: 28px !important; box-sizing: border-box !important; text-align: center !important; line-height: 20px !important; box-shadow: none !important; margin: 0 !important; vertical-align: middle !important; text-shadow: none !important; } .current-page-container input:hover, .current-page-container input:focus { background: var(--color-primary-hover) !important; border-color: var(--color-primary-hover) !important; box-shadow: none !important; outline: none !important; } /* Sort indicators */ .dash-spreadsheet .sort { color: var(--color-primary) !important; font-weight: 600 !important; padding-left: 4px !important; } .dash-spreadsheet .sort.descending::after { content: ' ↓' !important; } .dash-spreadsheet .sort.ascending::after { content: ' ↑' !important; } /* Editable cells (none in the app today; kept so a future one matches) */ .dash-spreadsheet-inner .cell--editing { background-color: var(--table-control-bg) !important; box-shadow: inset 0 0 0 1px var(--color-primary) !important; } .dash-spreadsheet-inner .cell--editing input { background-color: transparent !important; color: var(--text-primary) !important; } /* Export button */ .dash-table-container .export { background-color: var(--table-control-bg) !important; color: var(--text-primary) !important; border: 1px solid var(--table-border) !important; border-radius: 3px !important; padding: 4px 8px !important; margin: 2px 0 6px !important; cursor: pointer !important; } .dash-table-container .export:hover { background-color: var(--table-control-hover-bg) !important; border-color: var(--border-primary) !important; } /* ======================== */ /* 6. STICKY HEADER CLONE */ /* (assets/custom_js/table-sticky-headers.js clones the <thead> of a long */ /* table into a fixed wrapper while the page scrolls; geometry is set by */ /* the script, the look comes from here) */ /* ======================== */ .sticky-header-wrapper { background-color: var(--table-header-bg); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4); } .sticky-header-inner { scrollbar-width: none; -ms-overflow-style: none; } .sticky-header-inner::-webkit-scrollbar { display: none; } .sticky-header-wrapper table { margin-bottom: 0 !important; background-color: var(--table-header-bg); } .sticky-header-wrapper thead th, .sticky-header-wrapper .dash-header th { background-color: var(--table-header-bg) !important; border-bottom: 2px solid var(--table-border) !important; } /* ======================== */ /* 7. PHONES */ /* ======================== */ /* The frozen first column is nowrap; on a phone a long row label would eat the screen. Cap it at about a third of the viewport and let it wrap — the full text stays visible and the sticky background keeps it readable while the other columns scroll underneath. */ @media (max-width: 767px) { .table-scroll th:first-child, .table-scroll td:first-child { max-width: 34vw; white-space: normal; } }
/* 07_components.css */
/* ======================== */ /* UI COMPONENTS */ /* File: 05_components.css */ /* ======================== */ /* ======================== */ /* CARD HOVER EFFECTS */ /* ======================== */ /* Hover effect for cards with 'hover-card' class */ .hover-card { transition: transform 0.2s ease, box-shadow 0.2s ease; } .hover-card:hover { transform: scale(1.05); box-shadow: var(--shadow-hover); } /* Custom card shadow */ .custom-card-shadow { box-shadow: var(--shadow-default); } /* ETF module cards use the same subtle shadow treatment as other app cards */ #etf-module .card { box-shadow: var(--shadow-default); } /* ======================== */ /* LINK HOVER EFFECTS */ /* ======================== */ .hover-link:hover { cursor: pointer; } /* ======================== */ /* NAVIGATION TABS */ /* ======================== */ /* Pill-style modern tabs */ .nav-tabs { border-bottom: none; background-color: rgba(0, 0, 0, 0.2); padding: var(--spacing-sm); border-radius: var(--radius-xl); display: inline-flex; gap: var(--spacing-xs); } .nav-tabs .nav-link { border: none; border-radius: var(--radius-lg); background-color: transparent; color: rgba(255, 255, 255, 0.7); padding: 10px 18px; transition: var(--transition-slow); font-size: var(--font-size-md); font-weight: 500; } .nav-tabs .nav-link:hover { background-color: var(--bg-hover); color: var(--text-primary); } .nav-tabs .nav-link.active { background-color: var(--color-primary); color: var(--text-primary); box-shadow: 0 2px 8px rgba(13, 110, 253, 0.3); } /* ======================== */ /* TOOLTIPS */ /* ======================== */ .dash-tooltip { background-color: var(--bg-primary); color: var(--text-primary); border: 1px solid var(--border-primary); border-radius: var(--radius-base); padding: var(--spacing-sm) var(--spacing-md); font-size: var(--font-size-base); box-shadow: var(--shadow-default); } /* ======================== */ /* LOADING STATE */ /* ======================== */ .dash-loading { background-color: rgba(35, 35, 35, 0.9); color: var(--text-primary); } .dash-loading-spinner { border-color: var(--border-primary); border-top-color: var(--color-primary); } /* ======================== */ /* SCORE CARD HOVER */ /* ======================== */ /* Hover effect for clickable score cards */ [id^="score-card-click"] .card:hover, [id*="score-card-click"] .card:hover { transform: scale(1.02); box-shadow: 0 6px 20px rgba(0, 0, 0, 0.6); } /* ======================== */ /* INFO DOT (icon-font-free circled "i" for card-header info buttons) */ /* ======================== */ /* The app loads NO icon font (external_stylesheets is empty in server.py), so every `fas fa-*` <i> renders as nothing. The progressive-disclosure info buttons therefore draw their glyph in CSS: a soft-filled circle with a bold app-font "i" — restyle it HERE and it changes everywhere (markup lives only in functions/function_card_info.py). Width/height are in em, so the dot scales with the globally-forced font tokens. */ .info-dot { display: inline-flex; align-items: center; justify-content: center; width: 1.6em; height: 1.6em; border-radius: 50%; background: rgba(255, 255, 255, 0.10); font-weight: 700; font-style: normal; line-height: 1; transition: background 0.15s ease; } button:hover .info-dot, button:focus-visible .info-dot { background: rgba(255, 255, 255, 0.22); } /* ======================== */ /* TRANSIENT TOASTS */ /* ======================== */ /* The app's one confirmation pop-up ("Ticker added to the watchlist", "Alert set for X"). Markup lives only in functions/function_toast.py (toast_slot() mounts the slot, make_toast() returns the dbc.Alert); restyle it HERE and every page follows. Bottom-centre, above modals. */ .app-toast-slot { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); z-index: var(--z-toast); width: max-content; max-width: min(90vw, 520px); pointer-events: none; /* an empty slot must never block clicks */ } .app-toast-slot .alert { pointer-events: auto; margin-bottom: 0; border-radius: var(--radius-lg); box-shadow: var(--shadow-lg); text-align: center; line-height: 1.5; } .app-toast-slot .alert, .app-toast-slot .alert * { font-size: var(--font-size-prose-sm) !important; /* outranks 02_global_dynamic_fonts */ } /* ======================== */ /* CARD HEADERS (chart cards) — a shade darker than the body so the title band reads as a header (founder polish request 2026-08-24) */ /* ======================== */ .custom-card-shadow > .card-header { background-color: rgba(0, 0, 0, 0.34); border-bottom: 1px solid rgba(255, 255, 255, 0.06); } /* ======================== */ /* CORRELATION MATRIX (functions/function_correlation_matrix.py) — ONE grid for every correlation matrix of the app (founder, 2026-09-30): a rounded cell per pair, the sign colour set per cell by the builder (green positive, red negative, deeper = stronger), the number printed, "—" on the diagonal. Both skins: the cells sit on the card, the names in the muted text. */ /* ======================== */ .cmx-wrap { --cmx-cell-min: 44px; --cmx-cell-max: 76px; /* square cells: the grid never stretches to the card */ overflow-x: auto; -webkit-overflow-scrolling: touch; padding: 2px 0 6px; } /* the tracks are minmax(min, max): the cells grow to --cmx-cell-max while the card has room and shrink to --cmx-cell-min before the grid overflows into the wrapper's sideways scroll (a phone with six names) */ .cmx { display: grid; gap: 5px; align-items: stretch; justify-content: start; width: 100%; } .cmx-head { color: var(--text-muted); font-size: var(--font-size-xs) !important; line-height: 1.2; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } /* a column head wraps over up to three lines (a track is 44-76px wide: "Unemployment rate" must stay readable, not "Unemplo…") */ .cmx-col-head { align-self: end; text-align: center; padding: 0 2px 4px; white-space: normal; overflow-wrap: break-word; /* wraps at spaces; a ticker stays whole */ display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; line-clamp: 3; } .cmx-row-head { align-self: center; text-align: right; padding-right: 8px; max-width: 140px; } .cmx-cell { display: flex; align-items: center; justify-content: center; min-height: var(--cmx-cell-min); aspect-ratio: 1 / 1; max-height: var(--cmx-cell-max); border-radius: 8px; color: #fff; font-size: var(--font-size-sm) !important; font-variant-numeric: tabular-nums; } .cmx-cell-diag { background: rgba(255, 255, 255, 0.06); color: var(--text-muted); } .cmx-cell-na { background: transparent; border: 1px dashed rgba(255, 255, 255, 0.18); color: var(--text-muted); } .cmx-hl { font-weight: 600; } .cmx-head.cmx-hl { color: var(--text-primary); } .cmx-empty { display: flex; align-items: center; justify-content: center; min-height: 160px; } @media (max-width: 575.98px) { .cmx-wrap { --cmx-cell-min: 40px; } .cmx { gap: 4px; } .cmx-cell { border-radius: 6px; } .cmx-row-head { max-width: 96px; } .cmx-col-head { max-width: 72px; } } /* Stock financials: a chart the Financials tab has and the Ratios tab has not. The two tabs are ONE mounted body, so the chart stays loaded and is hidden while Ratios (the second tab) is the active one — no callback (settings_financials.analysis_graph_slots). */ body:has(#financials-main-tabs .nav-item:nth-child(2) > .nav-link.active) .fin-slot-fin-only { display: none !important; }
/* 08_date_picker.css */
/* ============================================================ */ /* SLOWIO DATE PICKER (slowio_components.SlowioDatePicker) */ /* Classes sdp-*. The popover is portalled onto <body>, so */ /* nothing here relies on a parent container. */ /* ============================================================ */ /* ---- field(s) ---------------------------------------------- */ .sdp { position: relative; display: block; width: 100%; max-width: 100%; } .sdp--single { max-width: 200px; } .sdp--range { max-width: 440px; } .sdp.w-100 { max-width: none; } .sdp-fields { display: flex; align-items: center; gap: 6px; } .sdp-sep { color: var(--text-muted); flex: 0 0 auto; user-select: none; } .sdp-field { position: relative; display: flex; align-items: center; flex: 1 1 0; min-width: 0; height: 30px; background-color: var(--bg-primary); border: 1px solid var(--border-primary); border-radius: var(--radius-base); transition: border-color 0.15s ease, box-shadow 0.15s ease; } .sdp-field:hover { border-color: var(--border-secondary); } .sdp-field--active, .sdp-field:focus-within { border-color: var(--border-focus); box-shadow: 0 0 0 0.2rem var(--border-focus-shadow); } .sdp--disabled .sdp-field { background-color: var(--bg-tertiary); opacity: 0.6; cursor: not-allowed; } /* `.sdp .sdp-input` outranks the global input[type="text"] rule (04_forms_inputs.css), which would otherwise paint a second border and a solid inner field. */ .sdp .sdp-input { flex: 1 1 auto; min-width: 0; width: 100%; height: 100%; padding: 0 0 0 0.6rem; background: transparent; border: 0; outline: 0; color: var(--text-primary); font-family: inherit; letter-spacing: 0.01em; cursor: pointer; } .sdp .sdp-input::placeholder { color: var(--text-muted); opacity: 1; } .sdp .sdp-input:disabled { cursor: not-allowed; color: var(--text-disabled); } /* CSS-drawn calendar glyph (the app loads no icon font). */ .sdp-icon { position: relative; flex: 0 0 auto; width: 12px; height: 11px; margin: 0 0.6rem 0 0.4rem; border: 1.5px solid var(--text-muted); border-top-width: 3px; border-radius: 2px; opacity: 0.8; pointer-events: none; } .sdp-icon::after { content: ""; position: absolute; left: 2px; top: 2px; width: 3px; height: 2px; background: var(--text-muted); } .sdp-field--active .sdp-icon, .sdp-field:hover .sdp-icon { border-color: var(--text-secondary); } .sdp-clear { flex: 0 0 auto; width: 22px; height: 22px; margin-right: 4px; padding: 0; border: 0; border-radius: 50%; background: transparent; color: var(--text-muted); line-height: 1; cursor: pointer; } .sdp-clear:hover { background: var(--bg-hover); color: var(--text-primary); } /* ---- presets (chips) --------------------------------------- */ .sdp-presets { display: flex; flex-wrap: wrap; gap: 4px; } .sdp-presets--inline { margin-top: 6px; } .sdp-chip { padding: 2px 10px; border: 1px solid var(--border-primary); border-radius: 999px; background: transparent; color: var(--text-secondary); line-height: 1.5; cursor: pointer; transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease; } .sdp-chip:hover { border-color: var(--border-secondary); background: var(--bg-hover); color: var(--text-primary); } .sdp-chip--active, .sdp-chip--active:hover { border-color: var(--color-primary); background: var(--color-primary); color: #fff; } .sdp-chip:disabled { opacity: 0.5; cursor: not-allowed; } /* ---- popover ------------------------------------------------ */ .sdp-layer { position: fixed; inset: 0; z-index: 1090; /* above Bootstrap modals (1055) and their backdrop */ pointer-events: none; } .sdp-layer > * { pointer-events: auto; } .sdp-pop { position: fixed; width: 292px; padding: 10px 12px 12px; background-color: var(--bg-secondary); border: 1px solid var(--border-primary); border-radius: var(--radius-lg); box-shadow: var(--shadow-lg); color: var(--text-primary); user-select: none; visibility: hidden; max-height: calc(100vh - 16px); overflow-y: auto; } .sdp-pop[data-ready="1"] { visibility: visible; } /* range: the two endpoints */ .sdp-ends { display: flex; gap: 6px; margin-bottom: 8px; } .sdp-end { flex: 1 1 0; display: flex; flex-direction: column; align-items: flex-start; gap: 1px; padding: 5px 8px; border: 1px solid transparent; border-radius: var(--radius-md); background: var(--bg-primary); color: var(--text-primary); text-align: left; cursor: pointer; } .sdp-end:hover { border-color: var(--border-primary); } .sdp-end--active { border-color: var(--color-primary); } .sdp-end-label { color: var(--text-muted); font-size: var(--font-size-xs) !important; text-transform: uppercase; letter-spacing: 0.06em; } .sdp-end-value { font-variant-numeric: tabular-nums; } /* header: ‹ month year › */ .sdp-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; } .sdp-title { display: flex; gap: 4px; min-width: 0; } .sdp-select { appearance: none; -webkit-appearance: none; padding: 3px 16px 3px 6px; border: 1px solid transparent; border-radius: var(--radius-sm); background-color: transparent; background-image: linear-gradient(45deg, transparent 50%, var(--text-muted) 50%), linear-gradient(135deg, var(--text-muted) 50%, transparent 50%); background-position: right 8px top 55%, right 4px top 55%; background-size: 4px 4px, 4px 4px; background-repeat: no-repeat; color: var(--text-primary); font-weight: 600; cursor: pointer; } .sdp-select:hover, .sdp-select:focus { border-color: var(--border-primary); background-color: var(--bg-primary); outline: 0; } .sdp-select option { background-color: var(--bg-primary); color: var(--text-primary); font-weight: 400; } .sdp-nav { position: relative; flex: 0 0 auto; width: 28px; height: 28px; padding: 0; border: 1px solid transparent; border-radius: 50%; background: transparent; cursor: pointer; } .sdp-nav::before { content: ""; position: absolute; top: 50%; left: 50%; width: 7px; height: 7px; border: solid var(--text-secondary); border-width: 1.5px 1.5px 0 0; } .sdp-nav--prev::before { transform: translate(-30%, -50%) rotate(-135deg); } .sdp-nav--next::before { transform: translate(-70%, -50%) rotate(45deg); } .sdp-nav:hover { background: var(--bg-hover); } .sdp-nav:disabled { opacity: 0.25; cursor: default; } .sdp-nav:disabled:hover { background: transparent; } /* grid */ .sdp-weekdays, .sdp-days { display: grid; grid-template-columns: repeat(7, 1fr); } .sdp-weekday { padding: 4px 0 6px; text-align: center; color: var(--text-muted); font-size: var(--font-size-xs) !important; text-transform: uppercase; letter-spacing: 0.06em; } .sdp-day { position: relative; height: 36px; padding: 0; border: 0; border-radius: 50%; background: transparent; color: var(--text-primary); font-variant-numeric: tabular-nums; cursor: pointer; transition: background-color 0.1s ease; } .sdp-day--empty { cursor: default; } .sdp-day:not(.sdp-day--empty):not(:disabled):hover { background: var(--bg-hover); } .sdp-day--today::after { content: ""; position: absolute; left: 50%; bottom: 5px; width: 4px; height: 4px; border-radius: 50%; background: var(--color-primary-light); transform: translateX(-50%); } .sdp-day--in-range { border-radius: 0; background: rgba(13, 110, 253, 0.18); } .sdp-day--in-range:hover { background: rgba(13, 110, 253, 0.3) !important; } .sdp-day--range-start { border-radius: 50% 0 0 50%; } .sdp-day--range-end { border-radius: 0 50% 50% 0; } .sdp-day--selected, .sdp-day--selected:hover { background: var(--color-primary) !important; color: #fff; font-weight: 600; } .sdp-day--selected.sdp-day--today::after { background: #fff; } .sdp-day:disabled { color: var(--text-disabled); opacity: 0.45; cursor: not-allowed; } /* footer */ .sdp-foot { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 6px; margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border-primary); } .sdp-actions { display: flex; gap: 10px; margin-left: auto; } .sdp-link { padding: 2px 4px; border: 0; background: transparent; color: var(--color-primary-light); cursor: pointer; } .sdp-link:hover { text-decoration: underline; } /* ---- phones: centred sheet with backdrop --------------------- */ .sdp-layer--mobile { display: flex; align-items: center; justify-content: center; padding: 16px; } .sdp-backdrop { position: absolute; inset: 0; background: rgba(0, 0, 0, 0.55); } .sdp-layer--mobile .sdp-pop { position: relative; width: min(320px, 100%); padding: 12px 14px 14px; } .sdp-layer--mobile .sdp-day { height: 40px; }
/* 09_modals.css */
/* ======================== */ /* MODAL DIALOGS */ /* File: 07_modals.css */ /* ======================== */ /* Modal base styling */ .modal-content { background-color: var(--bg-modal); border: 1px solid var(--border-primary); } .modal-header { background-color: var(--bg-modal-header); border-bottom: 1px solid var(--border-primary); color: var(--text-primary); /* This will use white ?*/ } .modal-body { background-color: var(--bg-modal); color: var(--text-secondary); } .modal-footer { background-color: var(--bg-modal-header); border-top: 1px solid var(--border-primary); } /* Close button */ #close-learn-more { background-color: var(--border-primary); border-color: var(--border-primary); } #close-learn-more:hover { background-color: #555; border-color: #555; } /* ======================== */ /* LEARN MORE MODAL SPECIFIC */ /* ======================== */ /* Learn More content styling. Font sizes need `!important` to beat the global `body * { font-size: ... !important }` nuke in 02_global_dynamic_fonts.css (without it these rules are silently ignored — that was the case until 2026-07 and modal prose rendered at the 12px base). Reading size comes from the prose tokens (15px mobile / 16px desktop), like the public blog. */ .learn-more-content, .educational-content { font-size: var(--font-size-prose) !important; line-height: 1.6; color: var(--text-secondary); } /* Markdown-authored content mostly uses ### (h3) headings — without these rules 02's global h rules would render h3/h4 SMALLER than the 16px prose on mobile. Simple ladder above the prose size. */ .learn-more-content h1, .educational-content h1 { font-size: calc(var(--font-size-prose) * 1.6) !important; } .learn-more-content h2, .educational-content h2 { font-size: calc(var(--font-size-prose) * 1.45) !important; } .learn-more-content h3, .educational-content h3 { font-size: calc(var(--font-size-prose) * 1.3) !important; } .learn-more-content h4, .educational-content h4 { font-size: calc(var(--font-size-prose) * 1.25) !important; } .learn-more-content h5, .educational-content h5 { color: var(--color-primary-light); font-weight: 600; font-size: calc(var(--font-size-prose) * 1.2) !important; border-bottom: 2px solid var(--border-primary); padding-bottom: var(--spacing-sm); } .learn-more-content h6, .educational-content h6 { color: #ffcc66; font-size: calc(var(--font-size-prose) * 1.1) !important; margin-top: var(--spacing-xl); } .learn-more-content p, .educational-content p { color: var(--text-tertiary); font-size: var(--font-size-prose) !important; margin-bottom: 15px; } .learn-more-content ul, .educational-content ul { margin-left: var(--spacing-xl); margin-bottom: 15px; } /* Inline elements need the size too: the global nuke hits every descendant, so em/strong/span inside a 16px paragraph would otherwise drop to 12px. */ .learn-more-content li, .educational-content li, .learn-more-content span, .educational-content span, .learn-more-content a, .educational-content a, .learn-more-content em, .educational-content em { font-size: var(--font-size-prose) !important; } .learn-more-content li, .educational-content li { margin-bottom: var(--spacing-sm); color: var(--text-tertiary); } .learn-more-content code, .educational-content code { font-size: var(--font-size-prose-sm) !important; } /* MathJax (dcc.Markdown mathjax=True — financials/options learn-more): the SVG output scales via ex units resolved against the container's font-size, which the global nuke pins to 12px. Both the container AND its descendants need the stamp (the inner <svg> is nuked too), or formulas render visibly smaller than the surrounding 16px prose. */ .learn-more-content mjx-container, .educational-content mjx-container, .learn-more-content mjx-container *, .educational-content mjx-container * { font-size: var(--font-size-prose) !important; } .learn-more-content strong, .educational-content strong { color: var(--color-warning-hover); font-weight: 600; font-size: var(--font-size-prose) !important; } /* Controls/badges embedded in learn-more content keep the dense app sizes (some educational modals carry nav buttons, accordions or badges). */ .learn-more-content .btn, .educational-content .btn, .learn-more-content button, .educational-content button { font-size: var(--font-size-base) !important; } .learn-more-content .badge, .educational-content .badge { font-size: var(--font-size-xs) !important; } .learn-more-content td, .educational-content td, .learn-more-content th, .educational-content th, .learn-more-content td span, .educational-content td span, .learn-more-content td em, .educational-content td em, .learn-more-content td strong, .educational-content td strong { font-size: var(--font-size-base) !important; } /* Shared disclaimer block at the end of Learn More modals. Scoping (2026-07): the old `.modal-body` scope leaked the muted/centered tail styling into ORDINARY modals that merely contain an <hr>, so the tail now only styles educational bodies (where the shared disclaimer from settings_educational.py actually lives). The CHILD combinator (`>`) is load-bearing: `hr:last-of-type` is evaluated per-parent, so without it any nested card/accordion that uses <hr> as a mid-content separator (e.g. the DCF educational tabs) would have its own "last hr" tail restyled. Real disclaimers always sit at the top level of the classed container — the dcc.Markdown div renders children directly, the div builders append Hr+P to the classed root. */ .learn-more-content > hr:last-of-type, .educational-content > hr:last-of-type { margin: 1.75rem auto 0.85rem; max-width: 180px; border-top: 1px solid var(--border-primary); opacity: 0.65; } /* Sizes carry !important so the disclaimer stays deliberately small inside the 16px prose (these selectors out-specify the prose rules above). */ .learn-more-content > hr:last-of-type ~ h3, .learn-more-content > hr:last-of-type ~ h4, .learn-more-content > hr:last-of-type ~ h5, .learn-more-content > hr:last-of-type ~ h6, .educational-content > hr:last-of-type ~ h3, .educational-content > hr:last-of-type ~ h4, .educational-content > hr:last-of-type ~ h5, .educational-content > hr:last-of-type ~ h6 { border-bottom: none; padding-bottom: 0; margin-top: 0; margin-bottom: 0.45rem; color: var(--text-muted); font-size: var(--font-size-sm) !important; font-weight: 700; letter-spacing: 0.08em; text-align: center; text-transform: uppercase; } /* The `~ ul li` twins are required: the global nuke stamps font-size on every <li> directly, so the size never inherits from the <ul>. */ .learn-more-content > hr:last-of-type ~ p, .learn-more-content > hr:last-of-type ~ ul, .learn-more-content > hr:last-of-type ~ ol, .learn-more-content > hr:last-of-type ~ ul li, .learn-more-content > hr:last-of-type ~ ol li, .learn-more-content > hr:last-of-type ~ p em, .learn-more-content > hr:last-of-type ~ p strong, .learn-more-content > hr:last-of-type ~ p span, .learn-more-content > hr:last-of-type ~ p a, .educational-content > hr:last-of-type ~ p, .educational-content > hr:last-of-type ~ ul, .educational-content > hr:last-of-type ~ ol, .educational-content > hr:last-of-type ~ ul li, .educational-content > hr:last-of-type ~ ol li, .educational-content > hr:last-of-type ~ p em, .educational-content > hr:last-of-type ~ p strong, .educational-content > hr:last-of-type ~ p span, .educational-content > hr:last-of-type ~ p a { font-size: var(--font-size-prose-sm) !important; } .learn-more-content > hr:last-of-type ~ p, .learn-more-content > hr:last-of-type ~ ul, .learn-more-content > hr:last-of-type ~ ol, .educational-content > hr:last-of-type ~ p, .educational-content > hr:last-of-type ~ ul, .educational-content > hr:last-of-type ~ ol { max-width: 42rem; margin-left: auto; margin-right: auto; color: var(--text-muted); line-height: 1.5; text-align: center; } .learn-more-content > hr:last-of-type ~ p em, .educational-content > hr:last-of-type ~ p em { color: inherit; } /* Learn More Button */ #learn-more-button { border-color: var(--color-primary-light); color: var(--color-primary-light); font-weight: 500; transition: var(--transition-slow); } #learn-more-button:hover { background-color: var(--color-primary-light); color: var(--bg-modal-header); transform: translateY(-2px); box-shadow: 0 4px 12px rgba(102, 179, 255, 0.4); } /* ======================== */ /* MODAL SCROLLBAR */ /* ======================== */ .modal-body::-webkit-scrollbar { width: 10px; } .modal-body::-webkit-scrollbar-track { background: var(--bg-modal-header); } .modal-body::-webkit-scrollbar-thumb { background: var(--border-primary); border-radius: 5px; } .modal-body::-webkit-scrollbar-thumb:hover { background: #555; }
/* 13_home_not_authed.css */
/* ============================= */ /* DYNAMIC FONT SIZE VARIABLES */ /* ============================= */ /* Base viewport width for calculations */ #home-not-authed{ /* Base font sizes that scale with viewport */ --vw-unit: 0.5vw; /* Define base sizes as percentage of viewport width */ --text-scale-xs: calc(1.5 * var(--vw-unit)); --text-scale-sm: calc(2 * var(--vw-unit)); --text-scale-base: calc(2.5 * var(--vw-unit)); --text-scale-md: calc(3 * var(--vw-unit)); --text-scale-lg: calc(4 * var(--vw-unit)); --text-scale-xl: calc(5 * var(--vw-unit)); --text-scale-2xl: calc(6 * var(--vw-unit)); --text-scale-3xl: calc(8 * var(--vw-unit)); --text-scale-hero: calc(10 * var(--vw-unit)); /* Apply min/max constraints */ --text-xs: clamp(10px, var(--text-scale-xs), 18px); --text-sm: clamp(14px, var(--text-scale-sm), 20px); --text-base: clamp(16px, var(--text-scale-base), 24px); --text-md: clamp(16px, var(--text-scale-md), 28px); --text-lg: clamp(20px, var(--text-scale-lg), 32px); --text-xl: clamp(24px, var(--text-scale-xl), 40px); --text-2xl: clamp(30px, var(--text-scale-2xl), 56px); --text-3xl: clamp(40px, var(--text-scale-3xl), 72px); --text-hero: clamp(50px, var(--text-scale-hero), 96px); /* ============================= */ /* PADDING VARIABLES */ /* ============================= */ /* Base padding unit - easily change this to scale all padding */ --padding-base: 40px; /* Padding scale system */ --padding-xs: calc(var(--padding-base) * 0.25); /* 5px */ --padding-sm: calc(var(--padding-base) * 0.5); /* 10px */ --padding-md: calc(var(--padding-base) * 1); /* 20px */ --padding-lg: calc(var(--padding-base) * 1.5); /* 30px */ --padding-xl: calc(var(--padding-base) * 2); /* 40px */ --padding-2xl: calc(var(--padding-base) * 3); /* 60px */ /* Card-specific padding - using md as default */ --card-padding: var(--padding-md); --section-padding: var(--padding-2xl); } /* Override for medium screens */ @media (max-width: 768px) { #home-not-authed{ --text-scale-xs: calc(3.5 * var(--vw-unit)); --text-scale-sm: calc(4 * var(--vw-unit)); --text-scale-base: calc(4.5 * var(--vw-unit)); --text-scale-md: calc(5 * var(--vw-unit)); --text-scale-lg: calc(6 * var(--vw-unit)); --text-scale-xl: calc(7 * var(--vw-unit)); --text-scale-2xl: calc(9 * var(--vw-unit)); --text-scale-3xl: calc(11 * var(--vw-unit)); --text-scale-hero: calc(13 * var(--vw-unit)); /* Adjust padding for tablets */ --padding-base: 30px; } } /* Override for small screens */ @media (max-width: 480px) { #home-not-authed{ --text-scale-xs: calc(4 * var(--vw-unit)); --text-scale-sm: calc(4.5 * var(--vw-unit)); --text-scale-base: calc(5 * var(--vw-unit)); --text-scale-md: calc(5.5 * var(--vw-unit)); --text-scale-lg: calc(6.5 * var(--vw-unit)); --text-scale-xl: calc(8 * var(--vw-unit)); --text-scale-2xl: calc(10 * var(--vw-unit)); --text-scale-3xl: calc(12 * var(--vw-unit)); --text-scale-hero: calc(14 * var(--vw-unit)); /* Adjust padding for mobile */ --padding-base: 20px; } } /* ============================= */ /* RESET AND BASE STYLES */ /* ============================= */ #home-not-authed *{ margin: 0; padding: 0; box-sizing: border-box; } /* Semantic headings (2026-08-21): the hero tagline is an <h1> and the section titles are <h2>, for crawlers and accessibility. They keep the SAME inline font-size the old <div>s had, so this only neutralises the browser's default bold weight / heading line-height — the headings render exactly as before (margins are already zeroed by the universal reset above). Scoped: cannot leak. */ #home-not-authed h1, #home-not-authed h2 { font-weight: inherit; line-height: inherit; } #home-not-authed{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #000; color: #fff; overflow-x: hidden; line-height: 1.5; font-size: var(--text-base) !important; } /* ============================= */ /* CONTAINER AND SECTIONS */ /* ============================= */ #home-not-authed .container{ max-width: 1080px; margin: 0 auto; background: #0a0a0a; padding-top: 44px; /* reserve space for the fixed .top-nav-bar */ } @media (max-width: 768px) { #home-not-authed .container{ padding-top: 40px; } } @media (max-width: 480px) { #home-not-authed .container{ padding-top: 36px; } } /* Large spacing between sections for one-at-a-time viewing */ #home-not-authed .section{ min-height: 100vh; padding: var(--section-padding) var(--padding-xl); display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center; position: relative; background: #0a0a0a; border-bottom: 2px solid #000; } @media (max-width: 768px) { #home-not-authed .section{ padding: var(--section-padding) var(--padding-lg); min-height: 100vh; } } @media (max-width: 480px) { #home-not-authed .section{ padding: var(--section-padding) var(--padding-md); min-height: 100vh; } } /* ============================= */ /* LOGO STYLES */ /* ============================= */ #home-not-authed .logo{ width: 100px; height: 100px; background: #E85D4E; border-radius: 24px; display: flex; align-items: center; justify-content: center; font-weight: bold; color: #fff; margin: 0 auto 40px; font-size: var(--text-3xl) !important; } /* ============================= */ /* TEXT UTILITIES */ /* ============================= */ #home-not-authed .highlight{ color: #E85D4E; } /* Force all inline text to use our sizing */ #home-not-authed div, #home-not-authed span, #home-not-authed p{ font-size: inherit !important; } /* ============================= */ /* STATS GRID */ /* ============================= */ #home-not-authed .stats-grid{ display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 40px; max-width: 400px; } @media (max-width: 480px) { #home-not-authed .stats-grid{ gap: 20px; } } #home-not-authed .stat-item{ background: #1a1a1a; padding: var(--card-padding); border-radius: 20px; text-align: center; } @media (max-width: 480px) { #home-not-authed .stat-item{ padding: var(--card-padding); } } /* ============================= */ /* COMPARISON CARDS */ /* ============================= */ #home-not-authed .comparison-container{ display: flex; gap: 40px; margin: 0 auto; flex-wrap: wrap; justify-content: center; width: 100%; /* Add this */ max-width: 100%; /* Add this */ padding: 0; /* Add this to ensure no hidden padding */ } #home-not-authed .card{ flex: 1; min-width: 300px; padding: var(--padding-lg) var(--card-padding); border-radius: 20px; background: #1a1a1a; text-align: left; } @media (max-width: 368px) { #home-not-authed .card{ min-width: 100%; } } #home-not-authed .card-highlighted{ border: 2px solid #E85D4E; } #home-not-authed .card-title{ margin: 0 0 20px 0; opacity: 0.6; text-transform: uppercase; letter-spacing: 2px; font-size: var(--text-lg) !important; } #home-not-authed .card-highlighted .card-title{ color: #E85D4E; opacity: 1; } /* Feature item with proper positioning */ #home-not-authed .feature-item{ margin: 20px 0; display: flex; align-items: center; gap: 15px; font-size: var(--text-base) !important; position: relative; padding-left: 35px; } /* Base for all ::before elements */ #home-not-authed .feature-item::before{ content: ''; position: absolute; left: 0; width: 20px; height: 20px; } /* X mark for traditional card */ #home-not-authed .card:not(.card-highlighted) .feature-item::before{ content: '×'; color: #E85D4E; font-size: 28px; opacity: 0.5; line-height: 20px; text-align: center; } /* Check mark for highlighted card */ #home-not-authed .card-highlighted .feature-item::before{ content: ''; width: 18px; height: 9px; border-left: 2px solid #4CAF50; border-bottom: 2px solid #4CAF50; transform: rotate(-45deg); position: absolute; left: 4px; top: 5px; opacity: 1; } /* ============================= */ /* TOOLS LIST */ /* ============================= */ #home-not-authed .tools-container{ margin: 20px auto; max-width: 600px; } #home-not-authed .tool-item{ display: flex; align-items: center; gap: 30px; margin: 40px 0; text-align: left; } @media (max-width: 480px) { #home-not-authed .tool-item{ gap: 20px; margin: 30px 0; } } #home-not-authed .tool-icon{ width: 42px; /* was 60px */ height: 42px; /* was 60px */ background: #1a1a1a; border-radius: 10px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; } @media (max-width: 768px) { #home-not-authed .tool-icon{ width: 35px; /* was 50px */ height: 35px; /* was 50px */ } } @media (max-width: 480px) { #home-not-authed .tool-icon{ width: 28px; /* was 40px */ height: 28px; /* was 40px */ } } /* Icon Shapes */ #home-not-authed .icon-circle{ width: 28px; /* was 40px */ height: 28px; /* was 40px */ background: #E85D4E; border-radius: 50%; } #home-not-authed .icon-square{ width: 28px; /* was 40px */ height: 28px; /* was 40px */ background: transparent; border: 2px solid #E85D4E; /* reduced from 3px */ } #home-not-authed .icon-diamond{ width: 25px; /* was 35px */ height: 25px; /* was 35px */ background: #E85D4E; transform: rotate(45deg); } #home-not-authed .icon-triangle{ width: 0; height: 0; border-left: 14px solid transparent; /* was 20px */ border-right: 14px solid transparent; /* was 20px */ border-bottom: 25px solid #E85D4E; /* was 35px */ } #home-not-authed .icon-grid{ display: grid; grid-template-columns: repeat(3, 8px); /* was 12px */ grid-template-rows: repeat(3, 8px); /* was 12px */ gap: 2px; /* was 3px */ } #home-not-authed .grid-cell{ background: #E85D4E; } /* Scale icons for tablets */ @media (max-width: 768px) { #home-not-authed .icon-circle{ width: 25px; height: 25px; } /* was 35px */ #home-not-authed .icon-square{ width: 25px; height: 25px; } /* was 35px */ #home-not-authed .icon-diamond{ width: 21px; height: 21px; } /* was 30px */ #home-not-authed .icon-triangle{ border-left: 12px solid transparent; /* was 17px */ border-right: 12px solid transparent; /* was 17px */ border-bottom: 21px solid #E85D4E; /* was 30px */ } #home-not-authed .icon-grid{ grid-template-columns: repeat(3, 7px); /* was 10px */ grid-template-rows: repeat(3, 7px); /* was 10px */ } } /* Scale icons for mobile */ @media (max-width: 480px) { #home-not-authed .icon-circle{ width: 20px; height: 20px; } /* was 28px */ #home-not-authed .icon-square{ width: 20px; height: 20px; border-width: 2px; } /* was 28px */ #home-not-authed .icon-diamond{ width: 17px; height: 17px; } /* was 24px */ #home-not-authed .icon-triangle{ border-left: 10px solid transparent; /* was 14px */ border-right: 10px solid transparent; /* was 14px */ border-bottom: 17px solid #E85D4E; /* was 24px */ } #home-not-authed .icon-grid{ grid-template-columns: repeat(3, 6px); /* was 8px */ grid-template-rows: repeat(3, 6px); /* was 8px */ gap: 1px; /* was 2px */ } } /* ============================= */ /* CTA BUTTON */ /* ============================= */ #home-not-authed .cta-button{ background: #fff; color: #000; padding: 25px 70px; border-radius: 50px; text-decoration: none; display: inline-block; font-weight: 500; transition: transform 0.2s; border: none; cursor: pointer; margin-top: 40px; font-size: var(--text-md) !important; } @media (max-width: 480px) { #home-not-authed .cta-button{ padding: 20px 50px; } } #home-not-authed .cta-button:hover{ transform: scale(1.05); } /* ============================= */ /* TOP NAV BAR (About / Login) */ /* ============================= */ #home-not-authed .top-nav-bar{ position: fixed; top: 0; left: 0; right: 0; z-index: 100; display: flex; justify-content: space-between; align-items: center; padding: 8px 16px; background: rgba(10, 10, 10, 0.7); -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px); } /* Groups About + Blog on the left so space-between keeps Login / Sign up alone on the right */ #home-not-authed .nav-left-group{ display: flex; align-items: center; gap: 8px; } @media (max-width: 768px) { #home-not-authed .top-nav-bar{ padding: 7px 12px; } } @media (max-width: 480px) { #home-not-authed .top-nav-bar{ padding: 6px 10px; } } /* Mirrors dbc.Button(size='sm') styling used in the auth navbar */ #home-not-authed .nav-btn{ display: inline-block; padding: 0.25rem 0.5rem; border-radius: 0.25rem; font-family: "Lato", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; font-size: 12px; font-weight: 400; text-decoration: none; line-height: 1.5; border: 1px solid transparent; transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, color 0.15s ease-in-out; cursor: pointer; } /* Ghost (text-only) — fills white on hover */ #home-not-authed .nav-btn-about{ background: transparent; color: #f8f9fa; border-color: transparent; } #home-not-authed .nav-btn-about:hover{ background: #f8f9fa; color: #000; border-color: #f8f9fa; } /* Ghost (text-only) — fills success-green on hover */ #home-not-authed .nav-btn-login{ background: transparent; color: #198754; border-color: transparent; } #home-not-authed .nav-btn-login:hover{ background: #198754; color: #fff; border-color: #198754; } /* ============================= */ /* DASHBOARD PREVIEW */ /* ============================= */ #home-not-authed .dashboard-preview{ width: 90%; max-width: 500px; background: #1a1a1a; border-radius: 20px; padding: var(--card-padding); margin: 40px auto 0; } #home-not-authed .chart-area{ height: 150px; background: #0a0a0a; border-radius: 10px; margin-bottom: 30px; position: relative; overflow: hidden; } #home-not-authed .chart-visual{ position: absolute; bottom: 0; left: 0; width: 100%; height: 60%; background: linear-gradient(135deg, #E85D4E 0%, #E85D4E 50%, #4CAF50 100%); clip-path: polygon(0 100%, 0 60%, 30% 70%, 50% 40%, 70% 50%, 100% 20%, 100% 100%); } #home-not-authed .metrics-row{ display: flex; justify-content: space-around; gap: 10px; } #home-not-authed .metric{ text-align: center; } /* ============================= */ /* INTERFACE MOCKUP */ /* ============================= */ #home-not-authed .interface-mockup{ width: 90%; max-width: 600px; background: #0a0a0a; border-radius: 20px; border: 2px solid #222; padding: var(--card-padding); margin: 40px auto 0; } #home-not-authed .chart-container{ display: flex; gap: 20px; padding: var(--padding-xs); background: #050505; border-radius: 10px; margin-bottom: 20px; } #home-not-authed .line-chart{ flex: 1; height: 120px; position: relative; } #home-not-authed .line-path{ stroke: #E85D4E; stroke-width: 3; fill: none; } #home-not-authed .bar-chart{ flex: 1; display: flex; align-items: flex-end; justify-content: space-evenly; gap: 10px; height: 120px; } #home-not-authed .bar{ width: 20%; background: #E85D4E; border-radius: 2px 2px 0 0; } #home-not-authed .bar:nth-child(1){ height: 70%; } #home-not-authed .bar:nth-child(2){ height: 45%; } #home-not-authed .bar:nth-child(3){ height: 85%; background: #4a8de8; } #home-not-authed .bar:nth-child(4){ height: 60%; background: #4ade80; } #home-not-authed .interface-metrics{ display: grid; grid-template-columns: repeat(auto-fit, minmax(50px, 1fr)); gap: 10px; } #home-not-authed .metric-card{ background: #1a1a1a; padding: var(--padding-sm); border-radius: 10px; text-align: center; } /* ============================= */ /* COUNTRY TAGS */ /* ============================= */ #home-not-authed .tags-container{ display: flex; flex-wrap: wrap; gap: 15px; margin: 50px auto; justify-content: center; max-width: 800px; } @media (max-width: 480px) { #home-not-authed .tags-container{ gap: 10px; margin: 30px auto; } } #home-not-authed .tag{ background: #0a0a0a; border: 1px solid #E85D4E; color: #E85D4E; padding: 12px 25px; border-radius: 25px; font-size: var(--text-sm) !important; } @media (max-width: 480px) { #home-not-authed .tag{ padding: 10px 20px; } } #home-not-authed .screener-button{ background: #4ade80; color: #000; padding: 14px 60px; border-radius: 10px; font-weight: 500; border: none; cursor: pointer; margin: 20px auto; font-size: var(--text-md) !important; display: inline-flex; align-items: center; justify-content: center; min-height: 44px; line-height: 1.2; text-decoration: none; } /* ============================= */ /* CORRELATION GRID */ /* ============================= */ #home-not-authed .correlation-grid{ background: #1a1a1a; border-radius: 20px; padding: var(--card-padding); max-width: 600px; margin: 40px auto; } #home-not-authed .correlation-cells{ display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px; margin-bottom: 30px; } #home-not-authed .correlation-cell{ height: 60px; border-radius: 8px; } /* ============================= */ /* PORTFOLIO ANALYSIS */ /* ============================= */ #home-not-authed .portfolio-grid{ display: flex; gap: 40px; max-width: 900px; margin: 0 auto; flex-wrap: wrap; justify-content: center; } #home-not-authed .portfolio-card{ flex: 1; min-width: 300px; background: #1a1a1a; padding: var(--card-padding); border-radius: 20px; } @media (max-width: 768px) { #home-not-authed .portfolio-card{ min-width: 100%; } } #home-not-authed .allocation-item{ margin-bottom: 30px; } #home-not-authed .allocation-header{ display: flex; justify-content: space-between; margin-bottom: 10px; font-size: var(--text-base) !important; } #home-not-authed .allocation-bar{ height: 20px; background: #0a0a0a; border-radius: 10px; overflow: hidden; position: relative; } #home-not-authed .allocation-fill{ height: 100%; position: relative; } #home-not-authed .allocation-marker{ position: absolute; right: -2px; top: 0; width: 4px; height: 100%; background: #fff; } #home-not-authed .allocation-info{ display: flex; justify-content: space-between; margin-top: 5px; color: #999; /* Improved contrast for accessibility */ font-size: var(--text-xs) !important; } #home-not-authed .rebalance-button{ width: 100%; background: #fff; color: #000; padding: 20px; border-radius: 10px; border: none; cursor: pointer; font-size: var(--text-sm) !important; display: inline-flex; align-items: center; justify-content: center; min-height: 44px; line-height: 1.2; text-decoration: none; } #home-not-authed .risk-chart{ height: 250px; position: relative; background: #0a0a0a; border-radius: 10px; overflow: hidden; margin-bottom: 20px; } #home-not-authed .risk-options{ display: flex; justify-content: space-around; margin-top: 30px; } #home-not-authed .risk-option{ text-align: center; } /* ============================= */ /* ANIMATION STYLES - NAMESPACED FOR LANDING PAGE */ /* ============================= */ /* Base animation class */ #home-not-authed .slowio-landing-animate-on-scroll{ opacity: 0; transition: opacity 0.8s cubic-bezier(0.4, 0, 0.2, 1), transform 0.8s cubic-bezier(0.4, 0, 0.2, 1); } /* Fade up animation */ #home-not-authed .slowio-landing-fade-up{ transform: translateY(30px); } #home-not-authed .slowio-landing-fade-up.slowio-landing-animated{ opacity: 1; transform: translateY(0); } /* Fade in animation */ #home-not-authed .slowio-landing-fade-in{ transform: scale(0.95); } #home-not-authed .slowio-landing-fade-in.slowio-landing-animated{ opacity: 1; transform: scale(1); } /* Stagger animations for grids */ #home-not-authed .slowio-landing-stagger{ opacity: 0; transform: translateY(20px); transition: opacity 0.6s cubic-bezier(0.4, 0, 0.2, 1), transform 0.6s cubic-bezier(0.4, 0, 0.2, 1); } #home-not-authed .slowio-landing-stagger.slowio-landing-animated{ opacity: 1; transform: translateY(0); } /* Chart animations */ #home-not-authed .slowio-landing-chart-line{ stroke-dasharray: 1000; stroke-dashoffset: 1000; transition: stroke-dashoffset 1.5s cubic-bezier(0.4, 0, 0.2, 1); } #home-not-authed .slowio-landing-chart-line.slowio-landing-animated{ stroke-dashoffset: 0; } #home-not-authed .slowio-landing-chart-bar{ transform: scaleY(0); transform-origin: bottom; transition: transform 0.8s cubic-bezier(0.4, 0, 0.2, 1); } #home-not-authed .slowio-landing-chart-bar.slowio-landing-animated{ transform: scaleY(1); } /* Progress bar animations */ #home-not-authed .slowio-landing-allocation-fill{ width: 100%; transform: scaleX(0); transform-origin: left; transition: transform 1.2s cubic-bezier(0.4, 0, 0.2, 1); } #home-not-authed .slowio-landing-allocation-fill.slowio-landing-animated{ transform: scaleX(var(--target-scale, 1)); } /* Number counting animation */ #home-not-authed .slowio-landing-count-up{ display: inline-block; } /* Tool icon animations */ #home-not-authed .slowio-landing-tool-icon{ opacity: 0; transform: scale(0.8) rotate(-10deg); transition: opacity 0.6s cubic-bezier(0.4, 0, 0.2, 1), transform 0.6s cubic-bezier(0.4, 0, 0.2, 1); } #home-not-authed .slowio-landing-tool-icon.slowio-landing-animated{ opacity: 1; transform: scale(1) rotate(0); } /* Correlation cell animations */ #home-not-authed .slowio-landing-correlation-cell{ opacity: 0; transform: scale(0); transition: opacity 0.4s cubic-bezier(0.4, 0, 0.2, 1), transform 0.4s cubic-bezier(0.4, 0, 0.2, 1); } #home-not-authed .slowio-landing-correlation-cell.slowio-landing-animated{ opacity: var(--cell-opacity, 1); transform: scale(1); } /* Scatter plot points */ #home-not-authed .slowio-landing-scatter-point{ opacity: 0; transform: scale(0); transition: opacity 0.5s cubic-bezier(0.4, 0, 0.2, 1), transform 0.5s cubic-bezier(0.4, 0, 0.2, 1); } #home-not-authed .slowio-landing-scatter-point.slowio-landing-animated{ opacity: var(--point-opacity, 0.8); transform: scale(1); } /* Network nodes */ #home-not-authed .slowio-landing-network-node{ opacity: 0; transform: scale(0); transition: opacity 0.6s cubic-bezier(0.4, 0, 0.2, 1), transform 0.6s cubic-bezier(0.4, 0, 0.2, 1); } #home-not-authed .slowio-landing-network-node.slowio-landing-animated{ opacity: 1; transform: scale(1); } #home-not-authed .slowio-landing-network-line{ stroke-dasharray: 200; stroke-dashoffset: 200; transition: stroke-dashoffset 0.8s cubic-bezier(0.4, 0, 0.2, 1); } #home-not-authed .slowio-landing-network-line.slowio-landing-animated{ stroke-dashoffset: 0; } /* ============================= */ /* SVG PATH ANIMATIONS */ /* ============================= */ /* GPU-accelerate SVG animations to prevent CLS and improve smoothness */ /* Note: Using will-change instead of translateZ(0) to avoid overriding transform: scale(0) on animated elements like .slowio-landing-risk-point */ #home-not-authed .slowio-landing-risk-point, #home-not-authed .slowio-landing-trend-line, #home-not-authed .slowio-landing-risk-curve, #home-not-authed .slowio-landing-chart-line, #home-not-authed .slowio-landing-chart-bar, #home-not-authed .slowio-landing-allocation-fill, #home-not-authed .slowio-landing-scatter-point, #home-not-authed .slowio-landing-network-line, #home-not-authed .slowio-landing-network-node { will-change: transform, opacity; backface-visibility: hidden; -webkit-backface-visibility: hidden; } /* Risk curve animation (Portfolio Analysis) */ #home-not-authed .slowio-landing-risk-curve{ stroke-dasharray: 1000; stroke-dashoffset: 1000; transition: stroke-dashoffset 1.5s cubic-bezier(0.4, 0, 0.2, 1); } #home-not-authed .slowio-landing-risk-curve.slowio-landing-animated{ stroke-dashoffset: 0; } /* Risk chart points */ #home-not-authed .slowio-landing-risk-point{ opacity: 0; transform: scale(0); transition: opacity 0.5s cubic-bezier(0.4, 0, 0.2, 1), transform 0.5s cubic-bezier(0.4, 0, 0.2, 1); transform-origin: center; } #home-not-authed .slowio-landing-risk-point.slowio-landing-animated{ opacity: 1; transform: scale(1); } /* Dashboard preview chart animation - LEFT TO RIGHT REVEAL */ #home-not-authed .slowio-landing-chart-visual{ /* Use mask for smooth left-to-right reveal */ -webkit-mask-image: linear-gradient(to right, black 0%, black 100%); -webkit-mask-size: 0% 100%; -webkit-mask-repeat: no-repeat; -webkit-mask-position: left center; mask-image: linear-gradient(to right, black 0%, black 100%); mask-size: 0% 100%; mask-repeat: no-repeat; mask-position: left center; transition: -webkit-mask-size 1.5s cubic-bezier(0.4, 0, 0.2, 1), mask-size 1.5s cubic-bezier(0.4, 0, 0.2, 1); } #home-not-authed .slowio-landing-chart-visual.slowio-landing-animated{ -webkit-mask-size: 110% 100%; /* Slightly over 100% to ensure complete fill */ mask-size: 110% 100%; } /* Scatter plot trend line */ #home-not-authed .slowio-landing-trend-line{ stroke-dasharray: 200; stroke-dashoffset: 200; transition: stroke-dashoffset 1s cubic-bezier(0.4, 0, 0.2, 1); } #home-not-authed .slowio-landing-trend-line.slowio-landing-animated{ stroke-dashoffset: 0; } /* Hexagon icon */ #home-not-authed .icon-hexagon{ position: relative; width: 28px; height: 16px; background: #E85D4E; margin: 6px 0; } #home-not-authed .icon-hexagon:before, #home-not-authed .icon-hexagon:after{ content: ""; position: absolute; width: 0; border-left: 14px solid transparent; border-right: 14px solid transparent; } #home-not-authed .icon-hexagon:before{ bottom: 100%; border-bottom: 8px solid #E85D4E; } #home-not-authed .icon-hexagon:after{ top: 100%; border-top: 8px solid #E85D4E; } /* Scale hexagon for tablets */ @media (max-width: 768px) { #home-not-authed .icon-hexagon{ width: 25px; height: 14px; margin: 5.5px 0; } #home-not-authed .icon-hexagon:before, #home-not-authed .icon-hexagon:after{ border-left: 12.5px solid transparent; border-right: 12.5px solid transparent; } #home-not-authed .icon-hexagon:before{ border-bottom: 7px solid #E85D4E; } #home-not-authed .icon-hexagon:after{ border-top: 7px solid #E85D4E; } } /* Scale hexagon for mobile */ @media (max-width: 480px) { #home-not-authed .icon-hexagon{ width: 20px; height: 11px; margin: 4.5px 0; } #home-not-authed .icon-hexagon:before, #home-not-authed .icon-hexagon:after{ border-left: 10px solid transparent; border-right: 10px solid transparent; } #home-not-authed .icon-hexagon:before{ border-bottom: 5.5px solid #E85D4E; } #home-not-authed .icon-hexagon:after{ border-top: 5.5px solid #E85D4E; } } /* ============================= */ /* MINI FOOTER (mirrors about page editorial pattern) */ /* ============================= */ #home-not-authed .mini-footer{ padding: 60px var(--padding-xl); text-align: center; font-family: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, monospace; font-size: var(--text-xs) !important; letter-spacing: 0.2em; color: #fff; background: #000; } #home-not-authed .mini-footer a{ color: #888; text-decoration: none; } #home-not-authed .mini-footer a:hover{ color: #E85D4E; } /* ============================================================= */ /* FOCUS LOOK (switch-over day, docs/_PLAN_focus_redesign.md) */ /* ============================================================= */ /* Only when the page is served in the Focus look: server.py puts data-skin="focus" on <html> (the switch-over flag, or a founder / visitor-preview session). The classic page is untouched. Every word and section stays; what changes: one charcoal background (#191919) with #232323 panels, the Focus visitor top bar (the static page gets its own copy of it, `.top-nav-focus`; inside the Dash app the shell's visitor bar replaces the page's own bar), the system font on the bar, a coral button with white text, the brand mark red (#FF0000, founder 2026-09-26: marks red, interface coral). */ html[data-skin="focus"] body:has(> #home-not-authed), html[data-skin="focus"] #home-not-authed, html[data-skin="focus"] #home-not-authed .container, html[data-skin="focus"] #home-not-authed .section, html[data-skin="focus"] #home-not-authed .mini-footer{ background: #191919 !important; } html[data-skin="focus"] #home-not-authed .section{ border-bottom: 1px solid #2e2e2e; } html[data-skin="focus"] #home-not-authed .stat-item, html[data-skin="focus"] #home-not-authed .card, html[data-skin="focus"] #home-not-authed .tool-icon, html[data-skin="focus"] #home-not-authed .dashboard-preview, html[data-skin="focus"] #home-not-authed .metric-card, html[data-skin="focus"] #home-not-authed .correlation-grid, html[data-skin="focus"] #home-not-authed .portfolio-card{ background: #232323; } html[data-skin="focus"] #home-not-authed .chart-area, html[data-skin="focus"] #home-not-authed .interface-mockup, html[data-skin="focus"] #home-not-authed .chart-container, html[data-skin="focus"] #home-not-authed .tag, html[data-skin="focus"] #home-not-authed .allocation-bar, html[data-skin="focus"] #home-not-authed .risk-chart{ background: #1d1d1d; } html[data-skin="focus"] #home-not-authed .logo{ background: #FF0000; } html[data-skin="focus"] #home-not-authed .cta-button{ background: #E85D4E; color: #fff; border-radius: 10px; font-weight: 600; } html[data-skin="focus"] #home-not-authed .cta-button:hover{ transform: none; background: #d95142; } /* Inside the Dash app the shell's visitor bar is the top bar. */ html[data-skin="focus"] #home-not-authed .top-nav-bar:not(.top-nav-focus){ display: none; } html[data-skin="focus"] main#home-not-authed .container{ padding-top: 64px; } html[data-skin="focus"] div#home-not-authed .container{ padding-top: 0; } html[data-skin="focus"] #home-not-authed .top-nav-focus{ height: 64px; padding: 0 32px; background: #191919; -webkit-backdrop-filter: none; backdrop-filter: none; border-bottom: 1px solid #2e2e2e; } html[data-skin="focus"] #home-not-authed .top-nav-focus, html[data-skin="focus"] #home-not-authed .top-nav-focus *{ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif; } html[data-skin="focus"] #home-not-authed .nav-brand{ display: flex; align-items: center; gap: 10px; color: #f2f2f2; font-weight: 600; font-size: 22px !important; letter-spacing: -0.02em; } html[data-skin="focus"] #home-not-authed .nav-brand img{ width: 26px; height: 26px; border-radius: 7px; } html[data-skin="focus"] #home-not-authed .nav-right-group{ display: flex; align-items: center; gap: 22px; } html[data-skin="focus"] #home-not-authed .top-nav-focus .nav-btn{ padding: 0; border: 0; background: transparent; color: #b8b8b8; font-size: 14px !important; } html[data-skin="focus"] #home-not-authed .top-nav-focus .nav-btn:hover{ color: #f2f2f2; background: transparent; } html[data-skin="focus"] #home-not-authed .top-nav-focus .nav-btn-signup{ background: #E85D4E; color: #fff; padding: 7px 12px; border-radius: 7px; } html[data-skin="focus"] #home-not-authed .top-nav-focus .nav-btn-signup:hover{ background: #d95142; color: #fff; } /* Resources menu: a native <details>, the same items as the app bar's menu */ html[data-skin="focus"] #home-not-authed .nav-res{ position: relative; } html[data-skin="focus"] #home-not-authed .nav-res > summary{ list-style: none; cursor: pointer; display: inline-flex; align-items: center; gap: 5px; } html[data-skin="focus"] #home-not-authed .nav-res > summary::-webkit-details-marker{ display: none; } html[data-skin="focus"] #home-not-authed .nav-res-glyph{ display: none; font-size: 18px !important; line-height: 1; } html[data-skin="focus"] #home-not-authed .nav-res-panel{ position: absolute; right: 0; top: calc(100% + 10px); min-width: 200px; padding: 6px; background: #1f1f1f; border: 1px solid #2e2e2e; border-radius: 10px; box-shadow: 0 12px 32px rgba(0,0,0,.45); display: flex; flex-direction: column; z-index: 101; } html[data-skin="focus"] #home-not-authed .nav-res-item{ display: block; padding: 8px 10px; border-radius: 7px; color: #e6e6e6; text-decoration: none; font-size: 14px !important; white-space: nowrap; } html[data-skin="focus"] #home-not-authed .nav-res-item:hover{ background: #2a2a2a; color: #fff; } html[data-skin="focus"] #home-not-authed .nav-res-sm{ display: none; /* About · Blog · Pricing live in the menu on a phone only */ } @media (max-width: 600px) { html[data-skin="focus"] #home-not-authed .top-nav-focus{ height: 56px; padding: 0 16px; } html[data-skin="focus"] #home-not-authed .nav-res-word, html[data-skin="focus"] #home-not-authed .nav-res-caret{ display: none; } html[data-skin="focus"] #home-not-authed .nav-res-glyph, html[data-skin="focus"] #home-not-authed .nav-res-sm{ display: inline-block; } html[data-skin="focus"] #home-not-authed .nav-res-sm{ display: block; } html[data-skin="focus"] main#home-not-authed .container{ padding-top: 56px; } html[data-skin="focus"] #home-not-authed .nav-right-group{ gap: 14px; } html[data-skin="focus"] #home-not-authed .nav-brand{ font-size: 19px !important; } html[data-skin="focus"] #home-not-authed .top-nav-focus .nav-btn-hide-sm{ display: none; } }
/* 14_financials_ai_modal.css */
/* * Financial AI modal styles (modal_ai_insights). * Scoped to avoid affecting other modals. */ /* Allow nested dropdown menus to escape the modal body */ #modal_ai_insights .modal-body { overflow: visible !important; } /* Cap modal height; let the content scroll when responses are long */ #modal_ai_insights .modal-content { max-height: 90vh; overflow-y: auto; } /* * Cross-validation switch alignment. * * Bootstrap's default `.form-check-input` floats left and adds a small * top margin so a switch-with-label-on-the-right reads correctly inside * a `.form-check` block. We render the switch and its label as separate * children of a flex row (so the right-aligned model count can sit in * the same row), and that float + margin push the switch out of vertical * alignment with the sibling label. Zero them out for our switch only — * mirrors the `cv-switch-compact` pattern from app_ppellm. */ .financials-AI-cv-switch .form-check-input { float: none; margin: 0; } .financials-AI-cv-switch.form-check { /* dbc.Switch wraps the input in a `.form-check` div; remove its default left-padding (designed to make room for the floated input) so the switch sits flush at the start of our flex row. */ padding-left: 0; min-height: 0; margin-bottom: 0; }
/* 15_home_alternatives.css */
/* ============================================================================= Home Page Design - Hover Styles ============================================================================= */ /* ============================================================================= Alternative 2.1: Icon Cards with Category Sections ============================================================================= */ #home-not-authed .alt2-card:hover .alt2-card-inner { border-color: #E85D4E !important; transform: translateY(-4px); box-shadow: 0 8px 24px rgba(232, 93, 78, 0.15); } /* ============================================================================= Alternative 10: Sidebar Layout with Filtering ============================================================================= */ /* Sidebar button hover */ #home-not-authed .alt10-sidebar-btn:hover { background: rgba(232, 93, 78, 0.1) !important; } /* Active sidebar button */ #home-not-authed .alt10-sidebar-btn.active { background: rgba(232, 93, 78, 0.15) !important; } #home-not-authed .alt10-sidebar-btn.active span:last-child { color: #fff !important; } /* Content item hover */ #home-not-authed .alt10-link:hover .alt10-item { background: #1a1a1a !important; transform: translateX(4px); } /* Responsive padding for alt10 container - larger on desktop */ @media (min-width: 768px) { #home-not-authed .alt10-container { padding: 40px 20px !important; } } /* ============================================================================= Onboarding: "Choose where to start" modal — Search-first pill + segmented switch (three doors) (apps/app_homes/function_welcome_follow_card.py, 2026-09-21) ----------------------------------------------------------------------------- Founder pick: direction 02 of the five from the external UX review (the first port used direction 01 by mistake). Scoped under .welcome-follow-card (assets CSS is global). Colours come from the theme tokens in 01_variables.css — the authenticated app is dark — never from literals. Font sizes carry !important on purpose: 02_global_dynamic_fonts.css pins every element to the base token with `body * { … !important }`, and this card is the one place on the home page that needs a display size. Values (phone → ≥992px, where Bootstrap's modal-lg reaches 800px): body padding 20/16/10 → 24/28/12; headline 25/30 → 28/34px, weight 300; ONE enclosure for search + button: margin-top 22px, padding 8px, gap 6px, radius 20 → 36px; search 52px high, text 16 → 18px, no box of its own; button 48 → 52px high, radius 12 → 28px, 224px wide beside the search on desktop; hint + Skip on one row. ============================================================================= */ /* The card is a centred modal (founder, 2026-09-21): the modal frame is the outline, the body carries the design's padding. The dropdown menu opens past the body's bottom edge, so the body must not clip. */ .welcome-follow-card .modal-body { padding: 0; overflow: visible; } .welcome-follow-card .modal-content { overflow: visible; } .welcome-follow-card .wfc { background: transparent; border: 0; padding: 20px 16px 10px; } .welcome-follow-card .wfc-kicker { margin: 0 0 8px 0; color: var(--text-muted); font-size: 11px !important; line-height: 16px; letter-spacing: .8px; text-transform: uppercase; } .welcome-follow-card .wfc-title { margin: 0; color: var(--text-primary); font-weight: 300; font-size: 25px !important; line-height: 30px; letter-spacing: -.3px; } .welcome-follow-card .wfc-desc { margin: 10px 0 0 0; color: var(--text-secondary); font-size: 14px !important; line-height: 21px; } /* Segmented switch (three doors, direction 01 of the 2026-09-21 study): the tabs component (DESIGN.md view-switcher rule) styled as a pill; the tabs carry no panes, so the empty tab-content is hidden. */ .welcome-follow-card .wfc-seg-wrap { margin-top: 18px; text-align: center; } .welcome-follow-card .wfc-seg-wrap .nav-tabs { display: inline-flex; gap: 2px; padding: 3px; border: 1px solid var(--border-primary); border-radius: 999px; background: var(--bg-primary); } .welcome-follow-card .wfc-seg-wrap .nav-tabs .nav-link { padding: 8px 16px; border: 0; border-radius: 999px; background: transparent; box-shadow: none; color: var(--text-muted); font-size: 13px !important; font-weight: 400; line-height: 18px; } .welcome-follow-card .wfc-seg-wrap .nav-tabs .nav-link:hover { color: var(--text-primary); background: transparent; } /* Active segment = the app's own tab colour (07_components.css), not a grey: same blue, same glow, so it reads as one family (founder, 2026-09-21). */ .welcome-follow-card .wfc-seg-wrap .nav-tabs .nav-link.active { background: var(--color-primary); color: var(--text-primary); box-shadow: 0 2px 8px rgba(13, 110, 253, 0.3); } .welcome-follow-card .wfc-seg-wrap .tab-content { display: none; } /* One pill, three searches, each in its own slot: the pill's door class picks the visible slot (dcc.Dropdown puts `className` on its inner element, so the slots are our own wrappers — no positional selector, nothing that a reorder or a dcc upgrade breaks silently). Nothing is re-rendered on a tab change. */ .welcome-follow-card .wfc-shell > .wfc-slot { display: none; min-width: 0; } .welcome-follow-card .wfc-door-stock > .wfc-slot-stock, .welcome-follow-card .wfc-door-etf > .wfc-slot-etf, .welcome-follow-card .wfc-door-screener > .wfc-slot-screener { display: block; } /* Group headers inside the country / sector list (disabled options). */ .welcome-follow-card .wfc-search .Select-option.is-disabled, .welcome-follow-card .wfc-search .VirtualizedSelectDisabledOption { color: var(--text-muted) !important; font-size: 10px !important; letter-spacing: .8px; text-transform: uppercase; } /* The enclosure: search and action share one rounded shape. */ .welcome-follow-card .wfc-shell { display: grid; grid-template-columns: minmax(0, 1fr); gap: 6px; margin-top: 22px; padding: 8px; border: 1px solid var(--border-primary); border-radius: 20px; background: var(--bg-secondary); box-shadow: var(--shadow-default); min-width: 0; } /* The search is the app's dcc.Dropdown (react-select v1): no box of its own inside the enclosure. 16px input also stops iOS Safari zooming. */ .welcome-follow-card .wfc-search { min-width: 0; } .welcome-follow-card .wfc-search .Select-control { height: 52px; padding: 0; } /* Every state in one rule: 04_forms_inputs.css paints the control dark with !important in the focused / open / focused-not-open states at up to (0,4,0) specificity; these selectors outrank each of them. The radius matches the enclosure so that even a stray background keeps the pill's rounded corners. */ .welcome-follow-card .wfc-search .Select-control, .welcome-follow-card .wfc-search .Select.is-focused > .Select-control, .welcome-follow-card .wfc-search .Select.is-open > .Select-control, .welcome-follow-card .wfc-search .Select.is-focused:not(.is-open) > .Select-control { border: 0 !important; border-radius: 12px !important; box-shadow: none !important; background-color: transparent !important; } .welcome-follow-card .wfc-search .Select-placeholder, .welcome-follow-card .wfc-search .Select-value, .welcome-follow-card .wfc-search .Select-value-label, .welcome-follow-card .wfc-search .Select-input, .welcome-follow-card .wfc-search .Select-input input { height: 52px; line-height: 52px; padding-left: 12px; padding-right: 0; font-size: 16px !important; } /* The typed text lives in an auto-sized <input> whose width is measured from the text alone (react-select's AutosizeInput): any padding on the <input> itself pushes the text past its own box and the first letters are clipped. The left inset comes from the .Select-input wrapper only. */ .welcome-follow-card .wfc-search .Select-input input { padding-left: 0; } /* …and that auto-size is BROKEN here: the hidden sizer that measures the text is a plain <div> (no class), so it gets the app's small base font from 02_global_dynamic_fonts.css `body *` while the visible input is 16 / 18px — the input comes out too narrow and long text scrolls off to the left. Ignore the measured inline width: the field takes the whole row (`!important` outranks an inline style). */ .welcome-follow-card .wfc-search .Select-multi-value-wrapper { display: block; } .welcome-follow-card .wfc-search .Select-input { display: block; width: 100%; } .welcome-follow-card .wfc-search .Select-input input { width: 100% !important; box-sizing: border-box; } .welcome-follow-card .wfc-search .Select-multi-value-wrapper, .welcome-follow-card .wfc-search .Select-input { background-color: transparent !important; } .welcome-follow-card .wfc-search .Select-arrow-zone { padding-right: 6px; } .welcome-follow-card .wfc-primary { min-height: 48px; width: 100%; padding: 12px 18px; border-radius: 12px; font-size: 14px !important; line-height: 20px; white-space: nowrap; } /* Hint and Skip on one row under the enclosure. */ .welcome-follow-card .wfc-under { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding-left: 4px; } .welcome-follow-card .wfc-hint { margin: 0; color: var(--text-muted); font-size: 12px !important; line-height: 18px; } .welcome-follow-card .wfc-skip { min-width: 44px; min-height: 44px; padding: 0 8px; color: var(--text-muted); text-decoration: none; font-size: 12px !important; line-height: 18px; } .welcome-follow-card .wfc-skip:hover { color: var(--text-primary); text-decoration: none; } .welcome-follow-card .wfc-feedback:empty { display: none; } .welcome-follow-card .wfc-feedback { margin-top: 8px; padding-left: 4px; color: var(--color-danger-hover); font-size: 13px !important; line-height: 20px; } @media (min-width: 992px) { .welcome-follow-card .wfc { padding: 24px 28px 12px; } .welcome-follow-card .wfc-seg-wrap .nav-tabs .nav-link { padding: 8px 22px; font-size: 14px !important; } .welcome-follow-card .wfc-title { font-size: 28px !important; line-height: 34px; } .welcome-follow-card .wfc-shell { grid-template-columns: minmax(0, 1fr) 224px; align-items: center; border-radius: 36px; } .welcome-follow-card .wfc-search .Select-placeholder, .welcome-follow-card .wfc-search .Select-value, .welcome-follow-card .wfc-search .Select-value-label, .welcome-follow-card .wfc-search .Select-input, .welcome-follow-card .wfc-search .Select-input input { padding-left: 18px; font-size: 18px !important; } .welcome-follow-card .wfc-search .Select-input input { padding-left: 0; } .welcome-follow-card .wfc-search .Select-control, .welcome-follow-card .wfc-search .Select.is-focused > .Select-control, .welcome-follow-card .wfc-search .Select.is-open > .Select-control, .welcome-follow-card .wfc-search .Select.is-focused:not(.is-open) > .Select-control { border-radius: 28px !important; } .welcome-follow-card .wfc-primary { min-height: 52px; border-radius: 28px; } .welcome-follow-card .wfc-under { padding-left: 26px; } .welcome-follow-card .wfc-feedback { padding-left: 26px; } }
/* 16_options_eu.css */
#options_EU-container .options-eu-symbol-row { flex-wrap: nowrap; } #options_EU-container .options-eu-dropdown-col { flex: 1 1 180px; min-width: 0; } #options_EU-container .options-eu-symbol-dropdown { min-width: 180px; width: 100%; } #options_EU-container .options-eu-label-col { white-space: nowrap; } @media (min-width: 768px) { #options_EU-container .options-eu-symbol-dropdown { min-width: 320px; } }
/* 17_options_us.css */
#options_US-root .options-us-mini-card { box-shadow: 0 6px 16px rgba(0, 0, 0, 0.25); } #options_US-root .options-us-tabs .nav { flex-wrap: wrap; gap: 0.35rem; } #options_US-root .options-us-tabs .nav-link { padding: 0.45rem 0.7rem; } #options_US-root .options-us-expiry-toggle { width: 100%; } @media (min-width: 768px) { #options_US-root .options-us-expiry-toggle { width: auto; } } @media (max-width: 576px) { #options_US-root .options-us-tabs .nav-link { font-size: 0.85rem; padding: 0.35rem 0.55rem; } }
/* 18_pricing_public.css */
/* ============================================================================= Public /pricing page Dark-themed card block matching the rest of the app. All selectors scoped under #pricing-public (CSS is global in Dash assets/). NOTE (2026-07): font-size declarations here need `!important` to beat the global `body * { font-size: ... !important }` nuke in 02_global_dynamic_fonts.css — without it they are silently ignored and the whole page renders at the 12px base (which is what happened until 2026-07). The .paywall-offer block at the bottom was deliberately LEFT without !important sizes: the paywall modal is conversion-critical and keeps its current (flattened) look until its new look is visually approved. ========================================================================== */ #pricing-public { padding: 40px 16px 80px 16px; background: var(--bg-primary); color: var(--text-primary); min-height: calc(100vh - 120px); } /* Ensure the shared generate_features_list() bullets and other inherited text render in the dark-theme palette (light-on-dark) within this section. */ #pricing-public, #pricing-public p, #pricing-public span, #pricing-public div, #pricing-public li, #pricing-public h1, #pricing-public h2, #pricing-public h3, #pricing-public h4, #pricing-public h5 { color: var(--text-primary); } #pricing-public .pricing-container { max-width: 1200px; } #pricing-public .pricing-heading { font-size: clamp(28px, 4vw, 44px) !important; font-weight: 700; margin-top: 20px; margin-bottom: 12px; color: var(--text-primary); } #pricing-public .pricing-subheading { font-size: clamp(15px, 1.6vw, 18px) !important; color: var(--text-tertiary); max-width: 680px; margin: 0 auto 48px auto; line-height: 1.5; } #pricing-public .pricing-row { margin-top: 16px; align-items: stretch; } #pricing-public .pricing-col { display: flex; margin-bottom: 24px; } #pricing-public .pricing-card { display: flex; flex-direction: column; width: 100%; border: 1px solid var(--border-primary); border-radius: 12px; background: var(--bg-secondary); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4); transition: transform 0.15s ease, box-shadow 0.15s ease; } #pricing-public .pricing-card:hover { transform: translateY(-3px); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6); } #pricing-public .pricing-card-highlight { border: 2px solid var(--color-primary); box-shadow: 0 4px 16px rgba(13, 110, 253, 0.35); } #pricing-public .pricing-card .card-body { display: flex; flex-direction: column; padding: 28px 24px; } #pricing-public .pricing-tier-title { font-size: 22px !important; font-weight: 700; margin-bottom: 12px; color: var(--text-primary); } #pricing-public .pricing-tier-sub { font-size: 14px !important; color: var(--text-tertiary); margin-bottom: 12px; } #pricing-public .pricing-price-block { text-align: center; margin: 12px 0 8px 0; } #pricing-public .pricing-headline { text-align: center; line-height: 1.1; } #pricing-public .pricing-amount { font-size: 40px !important; font-weight: 700; color: var(--color-primary-light); } #pricing-public .pricing-card:not(.pricing-card-highlight) .pricing-amount { color: var(--text-primary); } #pricing-public .pricing-period { font-size: 16px !important; color: var(--text-tertiary); margin-left: 4px; } #pricing-public .pricing-secondary { font-size: 13px !important; color: var(--text-tertiary); margin-top: 6px; } #pricing-public .pricing-features-list { list-style: none; padding: 0; margin: 8px 0; flex-grow: 1; } /* Icon sizing override: generate_features_list() sets height=30px inline for historical reasons; both the public /pricing and the authed /subscription tier cards look better with tighter 18px bullets. Scoped to the two wrapper ids so the rule can't leak into other pages that might reuse the .pricing-features-list class in the future. */ #pricing-public .pricing-features-list img, #subscription-public .pricing-features-list img { height: 18px !important; width: auto !important; margin-top: 0 !important; } /* Dark-theme invert: source icons are black-line on transparent (designed for light bg). The /pricing marketing page is dark, so invert in-browser to render them white-on-dark — saves shipping a parallel set of *_inverted.webp assets. On /subscription the two paid cards are green (black line-art reads fine there) but the Free card keeps its dark grey background, where the black icons vanish — invert on that card only. */ #pricing-public .pricing-features-list img, #subscription-public #free_card .pricing-features-list img { filter: invert(1); } #pricing-public .pricing-features-list > div { margin-bottom: 6px !important; font-size: 14px !important; } /* The visible bullet label is a <p> inside the row div — the global nuke stamps it directly, so the row-level size above never reaches it. */ #pricing-public .pricing-features-list > div p { font-size: 14px !important; } #pricing-public .pricing-features-list > div > span { margin-right: 12px !important; font-size: 14px !important; } #pricing-public .pricing-cta-wrapper { margin-top: 8px; margin-bottom: 12px; } #pricing-public .pricing-cta-button { min-width: 200px; font-weight: 600; /* Without an authored size the global button rule leaves the CTA at 12px — the smallest text in the card, right under the 40px price. */ font-size: 15px !important; } #pricing-public .pricing-fineprint { font-size: 12px !important; color: var(--text-muted); margin-top: 4px; margin-bottom: 0; } /* Top-up strip under the cards: one quiet sentence, same voice as the subheading (an add-on that works on every plan, not a fourth plan). */ #pricing-public .pricing-topup-strip { font-size: clamp(14px, 1.4vw, 16px) !important; color: var(--text-tertiary); max-width: 680px; margin: 8px auto 0 auto; line-height: 1.5; } /* The global font rules shrink <strong> and <a> below their parent; both must read at the sentence's own size. */ #pricing-public .pricing-topup-strip strong, #pricing-public .pricing-topup-link { font-size: inherit !important; } #pricing-public .pricing-topup-strip strong { color: var(--text-primary); } #pricing-public .pricing-topup-link { text-decoration: underline; } #pricing-public .pricing-disclaimer { font-size: 13px !important; color: var(--text-muted); margin-top: 32px; } /* Mobile: stack cards, tighten padding */ @media (max-width: 768px) { #pricing-public { padding: 24px 10px 60px 10px; } #pricing-public .pricing-heading { margin-bottom: 10px; } #pricing-public .pricing-subheading { margin-bottom: 32px; } #pricing-public .pricing-card .card-body { padding: 22px 18px; } #pricing-public .pricing-amount { font-size: 34px !important; } #pricing-public .pricing-cta-button { min-width: 85%; } } /* /subscription "Top-up" tab: the three pack buttons are the tab's only CTA — prose-sm size (12/13px) and roomier padding instead of the base size every .btn gets from 02_global_dynamic_fonts.css (10–12px by viewport). */ #subscription-public .topup-pack-btn { font-size: var(--font-size-prose-sm) !important; font-weight: 600; padding: 10px 18px; margin: 4px 6px; } /* ============================================================================= Paywall upsell modal (functions/limits/component_paywall_modal.py) ---------------------------------------------------------------------------- The modal reuses the pricing-page primitives (_price_block, generate_features_list, .pricing-* classes) but renders OUTSIDE the #pricing-public / #subscription-public wrappers those rules are scoped to. These class-scoped rules (`.paywall-offer`, set on the ModalBody) re-apply the same look inside the modal. A class — not an id — so several paywall modals can coexist on one page (e.g. the markets page). Dark background, so feature icons ARE inverted (see below). ============================================================================= */ .paywall-offer .pricing-tier-title { font-size: 22px; font-weight: 700; margin-bottom: 12px; color: var(--text-primary); } .paywall-offer .pricing-price-block { text-align: center; margin: 12px 0 8px 0; } .paywall-offer .pricing-headline { text-align: center; line-height: 1.1; } .paywall-offer .pricing-amount { font-size: 40px; font-weight: 700; color: var(--color-primary-light); } .paywall-offer .pricing-period { font-size: 16px; color: var(--text-tertiary); margin-left: 4px; } .paywall-offer .pricing-secondary { font-size: 13px; color: var(--text-tertiary); margin-top: 6px; } .paywall-offer .pricing-features-list { list-style: none; padding: 0; margin: 8px 0; } .paywall-offer .pricing-features-list img { height: 18px !important; width: auto !important; margin-top: 0 !important; /* Modal renders on a dark background — invert the black line-art icons to white (same as the dark /pricing page and the /subscription Free card). */ filter: invert(1); } .paywall-offer .pricing-features-list > div { margin-bottom: 6px !important; font-size: 14px; } .paywall-offer .pricing-features-list > div > span { margin-right: 12px !important; font-size: 14px !important; } .paywall-offer .pricing-cta-wrapper { margin-top: 8px; margin-bottom: 12px; } .paywall-offer .pricing-cta-button { min-width: 200px; font-weight: 600; } .paywall-offer .pricing-fineprint { font-size: 12px; color: var(--text-muted); margin-top: 4px; margin-bottom: 0; }
/* 19_metric_picker.css */
/* ============================================================ */ /* SLOWIO METRIC PICKER (slowio_components.SlowioMetricPicker) */ /* Classes smp-*. The sheet is portalled onto <body> (or into */ /* the surrounding Bootstrap modal), so nothing here relies on a */ /* parent container. Font sizes carry !important on purpose: the */ /* global `body *` rule in 02_global_dynamic_fonts.css would */ /* otherwise shrink the sheet to the dense 10-12px app size. */ /* ============================================================ */ /* ---- the closed field (looks like a form control) ------------ */ .smp { position: relative; display: block; width: 100%; } .smp-field { display: flex; align-items: center; /* value, chips and caret centred in the box (same height as before) */ justify-content: space-between; gap: 8px; width: 100%; min-height: 34px; padding: 5px 10px; line-height: 1.5; font-size: var(--font-size-sm) !important; background-color: var(--bg-primary); color: var(--text-primary); border: 1px solid var(--border-primary); border-radius: var(--radius-md); cursor: pointer; user-select: none; transition: border-color 0.15s ease, box-shadow 0.15s ease; } .smp-field:hover { border-color: var(--border-secondary); } .smp-field:focus-visible { outline: none; border-color: var(--border-focus); box-shadow: 0 0 0 0.2rem var(--border-focus-shadow); } .smp--disabled .smp-field { cursor: default; opacity: 0.6; } .smp-placeholder { color: var(--text-muted); } .smp-value { flex: 1 1 0; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .smp-caret { flex: 0 0 auto; color: var(--text-muted); line-height: 1.5; } /* ---- chips (closed field AND the sheet's selection row) ------- */ .smp-chips { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; flex: 1 1 auto; min-width: 0; } .smp-chip { display: inline-flex; align-items: center; gap: 3px; max-width: 100%; padding: 0 6px; font-size: var(--font-size-sm) !important; line-height: 1.6; border: 1px solid var(--border-primary); border-radius: var(--radius-base); background-color: var(--bg-secondary); color: var(--text-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .smp-chip--more { color: var(--text-muted); background: transparent; } .smp-chip--on { background-color: rgba(13, 110, 253, 0.14); border-color: transparent; } .smp-chip-x { border: 0; background: transparent; padding: 0 0 0 2px; margin: 0; color: var(--text-muted); font-size: 14px !important; line-height: 1; cursor: pointer; } .smp-chip-x:hover { color: var(--text-primary); } /* ---- layer: backdrop + sheet ----------------------------------- */ .smp-layer { position: fixed; inset: 0; z-index: 1090; /* above Bootstrap modals (1055) and their backdrop */ } .smp-backdrop { position: absolute; inset: 0; background: rgba(0, 0, 0, 0.5); touch-action: none; /* a drag on the backdrop must not scroll the page */ cursor: pointer; /* a tap on it closes and applies (also keeps iOS firing the click) */ } .smp-sheet { position: absolute; left: 0; right: 0; /* phones: anchored top AND bottom inside the fixed layer — never a vh height (iOS measures vh on the tallest viewport, so a vh-sized sheet overflows the visible area and hides the header). A strip of the dimmed page stays visible above it (a tap there closes and applies); the component sets `top` (visible top + that strip, ~12 % of the screen) and `bottom` (above the keyboard) from the visual viewport. This `top` is only the first-frame fallback, ~the same 48-120 px strip (svh = the height with the browser bars shown, like innerHeight). */ top: clamp(48px, 12vh, 120px); top: clamp(48px, 12svh, 120px); bottom: 0; display: flex; flex-direction: column; background-color: var(--bg-primary); color: var(--text-primary); border-top: 1px solid var(--border-primary); border-radius: 16px 16px 0 0; box-shadow: 0 -8px 30px rgba(0, 0, 0, 0.35); padding-bottom: env(safe-area-inset-bottom, 0px); /* opacity only: a transform animation moves the hit boxes while a fast thumb is already tapping (and costs more on phones) */ animation: smp-fade 0.15s ease-out; } @keyframes smp-fade { from { opacity: 0; } to { opacity: 1; } } /* Text-bearing descendants: the global `body * { font-size !important }` rule targets them directly, so the parent's size would not reach them. */ .smp-field .smp-placeholder, .smp-field .smp-value, .smp-field .smp-caret, .smp-sheet .smp-row-label, .smp-sheet .smp-row-label mark, .smp-sheet .smp-tile-chosen { font-size: inherit !important; } /* the page font, even where the UA styles buttons/inputs differently */ .smp-field, .smp-row, .smp-tile, .smp-link, .smp-done, .smp-chip-x, .smp-input { font-family: inherit; } @media (prefers-reduced-motion: reduce) { .smp-sheet { animation: none; } } /* tablets / desktop: a centred card (must match DESKTOP_MIN_WIDTH in the JS) */ @media (min-width: 768px) { .smp-sheet { left: 50%; right: auto; top: auto; bottom: 6vh; width: 480px; max-width: 92vw; height: 560px; max-height: 85vh; border: 1px solid var(--border-primary); border-radius: 14px; transform: translateX(-50%); box-shadow: var(--shadow-lg); animation: none; } } /* ---- header ----------------------------------------------------- */ .smp-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 10px 14px; border-bottom: 1px solid var(--border-primary); flex: 0 0 auto; } .smp-title { font-size: 15px !important; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .smp-title-sub { margin-left: 6px; font-size: 12px !important; font-weight: 400; color: var(--text-muted); } .smp-actions { display: flex; align-items: center; gap: 4px; flex: 0 0 auto; } .smp-link { border: 0; background: transparent; padding: 6px 8px; border-radius: var(--radius-md); color: var(--text-muted); font-size: 14px !important; cursor: pointer; } .smp-link:hover { color: var(--text-primary); } .smp-done { border: 0; background-color: var(--color-primary); color: #fff; padding: 6px 12px; border-radius: var(--radius-md); font-size: 14px !important; font-weight: 600; cursor: pointer; } .smp-done:hover { background-color: var(--color-primary-hover); } /* ---- scrollable body ---------------------------------------------- */ .smp-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch; overscroll-behavior: contain; } .smp-row { display: flex; align-items: center; gap: 12px; width: 100%; min-height: 44px; padding: 0 14px; border: 0; border-bottom: 1px solid var(--border-primary); background: transparent; color: var(--text-primary); text-align: left; font-size: 15px !important; cursor: pointer; } .smp-row:hover { background-color: var(--bg-hover); } .smp-row--best { background-color: rgba(13, 110, 253, 0.12); } .smp-row--back .smp-row-label { color: var(--color-primary); } .smp-row-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .smp-row-label mark { background-color: rgba(243, 156, 18, 0.4); color: inherit; border-radius: 2px; padding: 0 1px; } .smp-row-group { flex: 0 0 auto; font-size: 11px !important; color: var(--text-muted); } /* "no data" for this ticker: greyed, listed last, still selectable */ .smp-row--muted .smp-row-label { color: var(--text-muted); } .smp-row-tag { flex: 0 0 auto; font-size: 11px !important; line-height: 1.5; padding: 0 6px; border: 1px solid var(--border-primary); border-radius: 999px; color: var(--text-muted); } /* selection cap reached: unselected rows cannot be added */ .smp-row--capped { opacity: 0.45; cursor: default; } .smp-cap { flex: 0 0 auto; align-self: center; padding-right: 2px; font-size: 12px !important; color: var(--text-muted); font-variant-numeric: tabular-nums; } .smp-cap--full { color: var(--color-warning); font-weight: 600; } .smp-check { flex: 0 0 auto; width: 20px; height: 20px; display: inline-flex; align-items: center; justify-content: center; border: 1.5px solid var(--border-secondary); border-radius: 6px; font-size: 13px !important; color: #fff; } .smp-row--on .smp-check { background-color: var(--color-primary); border-color: var(--color-primary); } .smp-check--radio { border-radius: 50%; } .smp-row--on .smp-check--radio::after { content: ""; width: 8px; height: 8px; border-radius: 50%; background-color: #fff; } .smp-group-head { position: sticky; top: 0; z-index: 1; padding: 6px 14px; background-color: var(--bg-secondary); color: var(--text-muted); font-size: 11px !important; letter-spacing: 0.06em; text-transform: uppercase; border-bottom: 1px solid var(--border-primary); } .smp-empty { padding: 18px 14px; color: var(--text-muted); text-align: center; font-size: 14px !important; } /* family tiles */ .smp-tiles { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; padding: 12px 14px; } .smp-tile { display: flex; align-items: baseline; gap: 6px; min-height: 44px; padding: 8px 12px; text-align: left; background-color: var(--bg-primary); color: var(--text-primary); border: 1px solid var(--border-secondary); border-radius: var(--radius-lg); cursor: pointer; } .smp-tile:hover { border-color: var(--border-focus); background-color: var(--bg-hover); } .smp-tile-name { flex: 0 1 auto; /* the count follows the name, not the tile edge */ min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 14px !important; font-weight: 600; } .smp-tile-count { flex: 0 0 auto; font-size: 12px !important; color: var(--text-muted); font-variant-numeric: tabular-nums; } .smp-tile-count.smp-tile-chosen { color: var(--color-primary); font-weight: 600; } /* ---- selection row + search box (pinned at the bottom) ----------- */ .smp-chips--sheet { flex: 0 0 auto; flex-wrap: nowrap; overflow-x: auto; gap: 6px; padding: 8px 14px 0; border-top: 1px solid var(--border-primary); scrollbar-width: none; } .smp-chips--sheet::-webkit-scrollbar { display: none; } .smp-chips--sheet .smp-chip { flex: 0 0 auto; font-size: 13px !important; } .smp-chips-none { color: var(--text-muted); font-size: 13px !important; padding: 2px 0; } .smp-search { display: flex; align-items: center; gap: 8px; padding: 8px 14px 10px; flex: 0 0 auto; } /* .smp-sheet prefix: outranks the global input[type="text"] size rule */ .smp-sheet .smp-input { flex: 1 1 auto; min-width: 0; height: 44px; padding: 0 12px; font-size: 16px !important; /* ≥16px: no iOS auto-zoom on focus */ background-color: var(--bg-primary); color: var(--text-primary); border: 1px solid var(--border-secondary); border-radius: 10px; -webkit-appearance: none; appearance: none; } .smp-sheet .smp-input::placeholder { color: var(--text-muted); } .smp-sheet .smp-input:focus { outline: none; border-color: var(--border-focus); box-shadow: 0 0 0 0.2rem var(--border-focus-shadow); } .smp-count { flex: 0 0 auto; min-width: 56px; text-align: right; font-size: 12px !important; color: var(--text-muted); font-variant-numeric: tabular-nums; }
/* 20_focus_skin.css */
/* ======================================================================== */ /* FOCUS SKIN — layer A of the app redesign (docs/_PLAN_focus_redesign.md) */ /* File: 20_focus_skin.css */ /* */ /* Loaded ONLY in the `app.focus.<hash>.min.css` bundle, appended AFTER every */ /* other source file, so each rule here overrides the current look without */ /* touching it. The bundle is served per request when the session carries */ /* the founder-only `ui_skin=focus` flag (server.py, /ui/skin/<name>); */ /* everyone else keeps app.main. */ /* */ /* Source of the look: the Codex "Focus Refined" mockups, palette */ /* "Alternative 2 / Slowio charcoal", collapsed to the named roles below */ /* (never the ~200 auto-extracted shades of the mockup's palette.css). */ /* */ /* Scope of layer A = skin only: colours, font, radii, the look of the */ /* Bootstrap components (buttons, cards, tabs, inputs, dropdowns, modals) and */ /* the TYPE SCALE (section 11: the responsive --font-size-* ladder remapped, */ /* heading tokens, dense tables kept). No page-level layout rules; NO chart */ /* colours (those live in functions/function_graphs.py, Python side). */ /* */ /* When the skin becomes the default, its tokens move into 01_variables.css, */ /* the type scale into 02_global_dynamic_fonts.css, the component rules into */ /* 04/07/09 and this file is deleted. */ /* ======================================================================== */ /* ======================== */ /* 1. TOKENS (15 roles) */ /* ======================== */ :root { /* The named roles of the Focus palette */ --focus-page: #191919; /* page background */ --focus-panel: #232323; /* cards, panels, dropdown menus, modals */ --focus-raised: #303030; /* buttons, hovered rows, inputs on panels */ --focus-sidebar: #0f0f0f; /* navbar / future sidebar */ --focus-line: #444444; /* every border */ --focus-line-strong: #6d6d6d; /* hovered / focused neutral borders */ --focus-text: #ffffff; --focus-text-soft: #d0d0d0; /* secondary text */ --focus-text-muted: #b0b0b0; /* labels, captions (was #888: too dim) */ --focus-accent: #e85d4e; /* coral — the ONE accent, replaces brand red */ --focus-accent-rgb: 232, 93, 78; /* the same coral for rgba() tints */ --focus-accent-hover: #f0786b; /* also the readable red for negative TEXT */ --focus-positive: #00bc8c; --focus-negative: #e74c3c; /* fills, badges, health score */ --focus-warning: #f39c12; --focus-blue: #3498db; /* info / second chart series */ /* Remap the app's existing tokens onto the roles above, so every rule in 01..19 that already reads a token follows the skin for free. */ --bg-primary: var(--focus-page); --bg-secondary: var(--focus-panel); --bg-tertiary: var(--focus-sidebar); --bg-dark: var(--focus-sidebar); --bg-hover: rgba(255, 255, 255, 0.06); --bg-modal: var(--focus-panel); --bg-modal-header: var(--focus-panel); --border-primary: var(--focus-line); --border-secondary: var(--focus-line-strong); --border-tertiary: #8a8a8a; --border-focus: var(--focus-accent); --border-focus-shadow: rgba(232, 93, 78, 0.25); --text-primary: var(--focus-text); --text-secondary: var(--focus-text-soft); --text-tertiary: var(--focus-text-soft); --text-muted: var(--focus-text-muted); --text-disabled: #7a7a7a; --color-success: var(--focus-positive); --color-danger: var(--focus-negative); --color-danger-hover: #c44133; --color-warning: var(--focus-warning); --color-info: var(--focus-blue); --color-primary: var(--focus-accent); /* active tab, focus rings */ --color-primary-hover: var(--focus-accent-hover); --color-primary-light: var(--focus-accent-hover); --value-positive: var(--focus-positive); --value-negative: var(--focus-accent-hover); /* readable red text on charcoal */ --table-header-bg: #1c1c1c; --table-border: #3a3a3a; --table-sticky-bg: var(--focus-panel); --table-control-bg: var(--focus-page); --table-control-hover-bg: var(--focus-raised); --shadow-default: 0 1px 2px rgba(0, 0, 0, 0.35); --shadow-hover: 0 6px 18px rgba(0, 0, 0, 0.35); --shadow-lg: 0 25px 100px rgba(0, 0, 0, 0.55); /* Softer corners, as in the mockups (panels 12px, controls 6px) */ --radius-sm: 4px; --radius-base: 6px; --radius-md: 8px; --radius-lg: 10px; --radius-xl: 12px; /* Bootstrap's own variables, so its components follow the skin too */ --bs-body-bg: var(--focus-page); --bs-body-color: var(--focus-text); --bs-secondary-color: var(--focus-text-muted); --bs-tertiary-color: var(--focus-text-muted); --bs-border-color: var(--focus-line); --bs-border-color-translucent: var(--focus-line); --bs-border-radius: var(--radius-base); --bs-border-radius-sm: var(--radius-sm); --bs-border-radius-lg: var(--radius-lg); --bs-border-radius-xl: var(--radius-xl); --bs-link-color: var(--focus-accent-hover); --bs-link-hover-color: var(--focus-accent); --bs-link-color-rgb: 240, 120, 107; --bs-link-hover-color-rgb: 232, 93, 78; --bs-primary: var(--focus-accent); --bs-primary-rgb: 232, 93, 78; --bs-danger: var(--focus-negative); --bs-danger-rgb: 231, 76, 60; --bs-success: var(--focus-positive); --bs-success-rgb: 0, 188, 140; --bs-warning: var(--focus-warning); --bs-warning-rgb: 243, 156, 18; --bs-info: var(--focus-blue); --bs-info-rgb: 52, 152, 219; --bs-secondary: var(--focus-raised); --bs-secondary-rgb: 48, 48, 48; --bs-dark: var(--focus-sidebar); --bs-dark-rgb: 15, 15, 15; --bs-light: var(--focus-raised); --bs-light-rgb: 48, 48, 48; --bs-emphasis-color: var(--focus-text); --bs-emphasis-color-rgb: 255, 255, 255; --bs-secondary-bg: var(--focus-panel); --bs-tertiary-bg: var(--focus-raised); --bs-font-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif; --bs-body-font-family: var(--bs-font-sans-serif); color-scheme: dark; } /* ======================== */ /* 2. FONT */ /* ======================== */ /* The mockup's stack: the system font (San Francisco on Apple, Segoe on Windows). No web font download. The Lato <link> tags are dropped from the page head by server.py while the skin is active. */ body, .dash-table-container, .dash-spreadsheet-container, .dash-spreadsheet-container table, .dash-spreadsheet-container input, .dash-table-container .dash-spreadsheet-container .dash-spreadsheet-inner table, .dash-table-container .dash-spreadsheet-container .dash-spreadsheet-inner td, .dash-table-container .dash-spreadsheet-container .dash-spreadsheet-inner th, .dash-table-container .dash-filter input, .form-control, .btn, .Select, input, select, textarea, button { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif !important; } body { background-color: var(--focus-page); color: var(--focus-text); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } /* Headings: medium weight, slightly tight tracking (sizes untouched here) */ h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6, .text-section-heading { font-weight: 500; letter-spacing: -0.02em; color: var(--focus-text); } .fw-bold { font-weight: 600 !important; } .text-muted { color: var(--focus-text-muted) !important; } /* Bootstrap's .text-light reads --bs-light, which section 1 maps to the RAISED grey — so a "light" heading went dark on dark (the Financials "Recent news & events" / "Portfolio fit & correlation" titles, audit 2026-09-24). Under the skin, light text is the text colour. */ .text-light { color: var(--focus-text) !important; } hr { border-color: var(--focus-line); opacity: 1; } a { color: var(--focus-accent-hover); text-decoration: none; } a:hover { color: var(--focus-accent); } /* Route loading spinner (inline-styled in app_index_string.py) */ ._dash-loading { border-top-color: var(--focus-accent) !important; } /* ======================== */ /* 3. NAVBAR + FOOTER */ /* ======================== */ /* The brand mark stays RED (founder, 2026-09-26: marks red #FF0000, the interface coral). The bar-sized red icon (assets/brand/app_icon_64.webp, scripts/brand_assets_focus.py) replaces the full favicon picture with `content: url()`; the coral copies of the icon are no longer used. */ .navbar img[alt="Slowio logo"] { content: url("/assets/brand/app_icon_64.webp"); border-radius: var(--radius-base); } /* A shade darker than the page and bordered on BOTH edges: the bar is sticky-top on desktop and sticky-BOTTOM on phones (app_navbar.py). */ .navbar, .navbar.bg-dark, .navbar.navbar-dark { background-color: var(--focus-sidebar) !important; border-top: 1px solid var(--focus-line); border-bottom: 1px solid var(--focus-line); --bs-navbar-color: var(--focus-text-muted); --bs-navbar-hover-color: var(--focus-text); --bs-navbar-active-color: var(--focus-text); --bs-navbar-brand-color: var(--focus-text); --bs-navbar-brand-hover-color: var(--focus-text); } .navbar .nav-link { color: var(--focus-text-muted); } .navbar .nav-link:hover, .navbar .nav-link:focus-visible, .navbar .nav-link.active { color: var(--focus-text); } .dropdown-menu { --bs-dropdown-bg: var(--focus-panel); --bs-dropdown-border-color: var(--focus-line); --bs-dropdown-border-radius: var(--radius-lg); --bs-dropdown-color: var(--focus-text); --bs-dropdown-link-color: var(--focus-text-soft); --bs-dropdown-link-hover-color: var(--focus-text); --bs-dropdown-link-hover-bg: var(--focus-raised); --bs-dropdown-link-active-color: var(--focus-text); --bs-dropdown-link-active-bg: var(--focus-raised); --bs-dropdown-divider-bg: var(--focus-line); --bs-dropdown-header-color: var(--focus-text-muted); box-shadow: var(--shadow-hover); } /* ======================== */ /* 4. BUTTONS */ /* ======================== */ /* One accent for the actions that carry the brand (primary + the historic red "danger" buttons the app uses as its brand colour); neutral raised grey for secondary; the semantic hues (success / warning / info) kept but flat. */ .btn { --bs-btn-border-radius: var(--radius-base); --bs-btn-font-weight: 500; box-shadow: none; } .btn-primary, .btn-danger { --bs-btn-color: #ffffff; --bs-btn-bg: var(--focus-accent); --bs-btn-border-color: var(--focus-accent); --bs-btn-hover-color: #ffffff; --bs-btn-hover-bg: var(--focus-accent-hover); --bs-btn-hover-border-color: var(--focus-accent-hover); --bs-btn-focus-shadow-rgb: 232, 93, 78; --bs-btn-active-color: #ffffff; --bs-btn-active-bg: #d44f41; --bs-btn-active-border-color: #d44f41; --bs-btn-disabled-color: #ffffff; --bs-btn-disabled-bg: var(--focus-accent); --bs-btn-disabled-border-color: var(--focus-accent); --bs-btn-font-weight: 500; } .btn-secondary, .btn-light, .btn-dark { --bs-btn-color: var(--focus-text); --bs-btn-bg: var(--focus-raised); --bs-btn-border-color: var(--focus-line); --bs-btn-hover-color: var(--focus-text); --bs-btn-hover-bg: #393939; --bs-btn-hover-border-color: var(--focus-line-strong); --bs-btn-focus-shadow-rgb: 109, 109, 109; --bs-btn-active-color: var(--focus-text); --bs-btn-active-bg: #393939; --bs-btn-active-border-color: var(--focus-line-strong); --bs-btn-disabled-color: var(--focus-text-muted); --bs-btn-disabled-bg: var(--focus-raised); --bs-btn-disabled-border-color: var(--focus-line); } /* The mockups have ONE accent: primary actions coral, every other button a neutral raised grey. The app's "Run …" buttons are color='success' and its helper buttons ("Learn more") color='info' / 'warning' — as BUTTONS they are the page's main action and its secondary actions, not a status, so they take the accent and the neutral look. Badges, alerts and text keep their semantic hues (those rules are not touched here). */ .btn-success { --bs-btn-color: #ffffff; --bs-btn-bg: var(--focus-accent); --bs-btn-border-color: var(--focus-accent); --bs-btn-hover-color: #ffffff; --bs-btn-hover-bg: var(--focus-accent-hover); --bs-btn-hover-border-color: var(--focus-accent-hover); --bs-btn-focus-shadow-rgb: 232, 93, 78; --bs-btn-active-color: #ffffff; --bs-btn-active-bg: #d44f41; --bs-btn-active-border-color: #d44f41; --bs-btn-disabled-color: #ffffff; --bs-btn-disabled-bg: var(--focus-accent); --bs-btn-disabled-border-color: var(--focus-accent); --bs-btn-font-weight: 500; } .btn-warning, .btn-info { --bs-btn-color: var(--focus-text); --bs-btn-bg: var(--focus-raised); --bs-btn-border-color: var(--focus-line); --bs-btn-hover-color: var(--focus-text); --bs-btn-hover-bg: #393939; --bs-btn-hover-border-color: var(--focus-line-strong); --bs-btn-focus-shadow-rgb: 109, 109, 109; --bs-btn-active-color: var(--focus-text); --bs-btn-active-bg: #393939; --bs-btn-active-border-color: var(--focus-line-strong); --bs-btn-disabled-color: var(--focus-text-muted); --bs-btn-disabled-bg: var(--focus-raised); --bs-btn-disabled-border-color: var(--focus-line); } .btn-outline-primary, .btn-outline-danger { --bs-btn-color: var(--focus-accent-hover); --bs-btn-border-color: var(--focus-accent); --bs-btn-hover-color: #ffffff; --bs-btn-hover-bg: var(--focus-accent); --bs-btn-hover-border-color: var(--focus-accent); --bs-btn-active-color: #ffffff; --bs-btn-active-bg: var(--focus-accent); --bs-btn-active-border-color: var(--focus-accent); --bs-btn-focus-shadow-rgb: 232, 93, 78; } .btn-outline-secondary, .btn-outline-light, .btn-outline-dark, .btn-outline-success, .btn-outline-info, .btn-outline-warning { --bs-btn-color: var(--focus-text-soft); --bs-btn-border-color: var(--focus-line); --bs-btn-hover-color: var(--focus-text); --bs-btn-hover-bg: var(--focus-raised); --bs-btn-hover-border-color: var(--focus-line-strong); --bs-btn-active-color: var(--focus-text); --bs-btn-active-bg: var(--focus-raised); --bs-btn-active-border-color: var(--focus-line-strong); } .btn-link { --bs-btn-color: var(--focus-accent-hover); --bs-btn-hover-color: var(--focus-accent); } .btn-close { filter: invert(1) grayscale(100%) brightness(200%); } /* ======================== */ /* 5. CARDS + PANELS */ /* ======================== */ .card { --bs-card-bg: var(--focus-panel); --bs-card-border-color: var(--focus-line); --bs-card-border-radius: var(--radius-xl); --bs-card-inner-border-radius: calc(var(--radius-xl) - 1px); --bs-card-cap-bg: transparent; --bs-card-cap-color: var(--focus-text); --bs-card-color: var(--focus-text); box-shadow: none; } /* Every card header is the panel look (founder, 2026-09-30: "uniformize across the app, like 'How the holdings move together'"): a sentence title at weight 500 on the card's own colour, no band, no rule under it. It retires the classic chart-card band (07_components: .custom-card-shadow > .card-header, a shade darker) for a Focus session; the Focus panels (22_focus_pages section 8, .fpart-panel) already look like this. */ .card .card-header, .custom-card-shadow > .card-header { background-color: transparent; border-bottom: 0; padding: 16px 20px 4px; } .card .card-header .text-section-heading, .card .card-header.text-section-heading, .card .card-header .text-section-heading > span { font-weight: 500; letter-spacing: -0.02em; font-size: var(--font-size-h3) !important; } .card .card-footer { background-color: transparent; border-top: 1px solid var(--focus-line); } .custom-card-shadow, .hover-card { box-shadow: none; } .hover-card:hover { box-shadow: var(--shadow-hover); border-color: var(--focus-line-strong); } .list-group { --bs-list-group-bg: var(--focus-panel); --bs-list-group-color: var(--focus-text); --bs-list-group-border-color: var(--focus-line); --bs-list-group-border-radius: var(--radius-lg); --bs-list-group-action-hover-bg: var(--focus-raised); --bs-list-group-action-hover-color: var(--focus-text); --bs-list-group-active-bg: var(--focus-raised); --bs-list-group-active-border-color: var(--focus-line-strong); } .accordion { --bs-accordion-bg: var(--focus-panel); --bs-accordion-color: var(--focus-text); --bs-accordion-border-color: var(--focus-line); --bs-accordion-border-radius: var(--radius-lg); --bs-accordion-inner-border-radius: calc(var(--radius-lg) - 1px); --bs-accordion-btn-bg: var(--focus-panel); --bs-accordion-btn-color: var(--focus-text); --bs-accordion-active-bg: var(--focus-raised); --bs-accordion-active-color: var(--focus-text); --bs-accordion-btn-focus-border-color: var(--focus-accent); --bs-accordion-btn-focus-box-shadow: 0 0 0 0.2rem var(--border-focus-shadow); } .alert { --bs-alert-border-radius: var(--radius-lg); } /* An "info" alert is a quiet note in the skin's palette, never Bootstrap's bright blue box (an empty watchlist's notice, the capacity-limit inline upsell, the sync tab's notices — audit 2026-09-24). */ .alert-info { --bs-alert-bg: var(--focus-panel); --bs-alert-border-color: var(--focus-line); --bs-alert-color: var(--focus-text-soft); --bs-alert-link-color: var(--focus-accent-hover); } .badge { --bs-badge-border-radius: var(--radius-sm); font-weight: 500; } .progress { --bs-progress-bg: var(--focus-raised); --bs-progress-border-radius: 999px; --bs-progress-bar-bg: var(--focus-accent); } .toast, .app-toast-slot .alert { border-radius: var(--radius-lg); } /* ======================== */ /* 6. TABS */ /* ======================== */ /* Segmented control — the ONE look for every tab bar and every two- or three-way choice in the app (founder, 2026-09-22): a thin bordered track, one pill per option, the active one raised with white text. The layer-A underline tabs were replaced by this. Two selectors carry it: `.nav-tabs` (every dbc.Tabs) and `.seg-control` (the shared builder functions/function_segmented_control.py, a dbc.RadioItems). Change the look here and the whole app follows. */ /* Both edges flush (founder's iPhone, 2026-09-30: the LAST pill left a gap to the frame, the first did not). Three rules keep the frame hugging its pills: `width: fit-content` (an auto width let a column flex parent — the Markets criteria panel — stretch the frame past its pills), every segment `flex-grow: 1` (a bar that wraps fills each row edge to edge instead of ending short), and the RadioItems input at `margin: 0 !important` (04_forms_inputs.css gives every radio 8px !important on the right: the zero-width input carried it inside each segment's box). A tab bar that wraps keeps its LAST row at natural width: the ::after spacer takes that row's free space, so a lone last tab is never stretched across the whole bar (Macro data, 11 tabs at 1440); its negative margin cancels the gap in front of it (a page changes the gap through --seg-gap, never `gap`, or a one-row bar ends short). The option sets of .seg-control have no spacer: they wrap into an even grid (the ETF chart choice, 4 + 4), and a page's own full-width control (the calculators) splits its width between the segments only. */ .nav-tabs { --seg-gap: 2px; display: inline-flex; flex-wrap: wrap; gap: var(--seg-gap); width: fit-content; max-width: 100%; padding: 3px; border: 1px solid var(--focus-line); border-radius: 8px; background-color: transparent; } .nav-tabs > .nav-item, .nav-tabs > .nav-link { flex-grow: 1; } .nav-tabs::after { content: ''; flex: 1000 1 0px; margin-left: calc(-1 * var(--seg-gap)); } .nav-tabs .nav-link { text-align: center; border: 0; border-radius: 6px; margin: 0; background-color: transparent; color: var(--focus-text-muted); padding: 6px 12px; font-weight: 500; line-height: 1.3; transition: color 0.15s ease, background-color 0.15s ease; } .nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus-visible { background-color: transparent; color: var(--focus-text); } .nav-tabs .nav-link.active { background-color: var(--focus-raised); color: var(--focus-text); box-shadow: none; } .welcome-follow-card .wfc-seg-wrap .nav-tabs .nav-link.active { box-shadow: 0 2px 8px rgba(var(--focus-accent-rgb), 0.3); } .seg-control { display: inline-flex; flex-wrap: wrap; gap: 2px; width: fit-content; max-width: 100%; padding: 3px; border: 1px solid var(--focus-line); border-radius: 8px; vertical-align: middle; } .seg-control .form-check { margin: 0; padding: 0; min-height: 0; } .seg-control > * { flex-grow: 1; } .seg-control .seg-input { appearance: none; -webkit-appearance: none; opacity: 0; width: 0; height: 0; margin: 0 !important; padding: 0; border: 0; pointer-events: none; } .seg-control .seg-option { display: block; text-align: center; /* !important: 04_forms_inputs.css gives every radio label (`div[class*="check"] > label`) margin-bottom 8px !important — under the pill it left an 8px strip, so the control's bottom edge sat lower than its top (founder's iPhone, 2026-09-23) */ margin: 0 !important; padding: 5px 11px; border-radius: 6px; color: var(--focus-text-muted); cursor: pointer; line-height: 1.3; white-space: nowrap; font-size: var(--font-size-sm) !important; transition: color 0.15s ease, background-color 0.15s ease; } .seg-control .seg-option:hover { color: var(--focus-text); } .seg-control .seg-on { background-color: var(--focus-raised); color: var(--focus-text); } /* a segmented control whose segments are links (a choice between pages, e.g. the macro outlook editions): same look, no underline */ .seg-control a.seg-option, .seg-control a.seg-option:hover { text-decoration: none; } /* the current page of such a control is plain text, not a link */ .seg-control span.seg-option { cursor: default; } .seg-control .seg-input:focus-visible + .seg-option { outline: 2px solid var(--focus-accent); outline-offset: 1px; } /* A segmented control made of BUTTONS (the ids a callback toggles through `active` / `outline`, e.g. the portfolio performance Daily / Monthly / Yearly): the same pills, drawn from the button variables so the toggled classes (btn-primary / btn-outline-primary) never show through. */ .seg-control > .btn { --bs-btn-color: var(--focus-text-muted); --bs-btn-bg: transparent; --bs-btn-border-color: transparent; --bs-btn-hover-color: var(--focus-text); --bs-btn-hover-bg: transparent; --bs-btn-hover-border-color: transparent; --bs-btn-active-color: var(--focus-text); --bs-btn-active-bg: var(--focus-raised); --bs-btn-active-border-color: transparent; --bs-btn-focus-shadow-rgb: 232, 93, 78; --bs-btn-padding-y: 5px; --bs-btn-padding-x: 11px; --bs-btn-border-radius: 6px; --bs-btn-font-weight: 500; margin: 0; line-height: 1.3; white-space: nowrap; box-shadow: none; font-size: var(--font-size-sm) !important; } /* Phones: a tab bar SCROLLS sideways instead of wrapping into two or three rows (Financials 5 tabs, Portfolio 8, the calculator 8, Macro data 11 — audit 2026-09-24); no scrollbar, the pills keep their geometry, the cut pill at the edge says there is more. From 768px the bar still wraps. */ @media (max-width: 767.98px) { .nav-tabs { flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden; scrollbar-width: none; -webkit-overflow-scrolling: touch; } .nav-tabs::-webkit-scrollbar { display: none; } .nav-tabs .nav-link { flex: none; white-space: nowrap; } } .nav-pills { --bs-nav-pills-link-active-bg: var(--focus-accent); --bs-nav-pills-link-active-color: #ffffff; --bs-nav-pills-border-radius: var(--radius-base); } /* ======================== */ /* 7. INPUTS + SELECTS */ /* ======================== */ .form-control, .form-control:focus, .form-control:valid, input.form-control, .form-select, textarea.form-control { background-color: var(--focus-page); color: var(--focus-text); border: 1px solid var(--focus-line); border-radius: var(--radius-base); box-shadow: none; } .form-control:focus, .form-select:focus { border-color: var(--focus-accent); box-shadow: 0 0 0 0.2rem var(--border-focus-shadow); } .form-control::placeholder { color: var(--focus-text-muted); opacity: 1; } .form-control:disabled, .form-select:disabled { background-color: var(--focus-raised); color: var(--focus-text-muted); } .input-group-text { background-color: var(--focus-raised); color: var(--focus-text-soft); border: 1px solid var(--focus-line); border-radius: var(--radius-base); } /* React-select (dcc.Dropdown) */ .Select-control, .Select.is-open > .Select-control, .Select.is-focused:not(.is-open) > .Select-control { background-color: var(--focus-page); border: 1px solid var(--focus-line); border-radius: var(--radius-base); box-shadow: none; } .Select.is-focused > .Select-control { border-color: var(--focus-accent); box-shadow: 0 0 0 0.2rem var(--border-focus-shadow); } .Select-menu-outer { background-color: var(--focus-panel); border: 1px solid var(--focus-line); border-radius: var(--radius-md); box-shadow: var(--shadow-hover); } .Select-option { background-color: transparent; color: var(--focus-text-soft); } .Select-option:hover, .Select-option.is-focused { background-color: var(--focus-raised); color: var(--focus-text); } .Select-option.is-selected { background-color: var(--focus-raised); color: var(--focus-accent-hover); } .Select-value-label, .Select--single > .Select-control .Select-value { color: var(--focus-text) !important; } .Select-placeholder { color: var(--focus-text-muted) !important; } .Select--multi .Select-value { background-color: var(--focus-raised); border: 1px solid var(--focus-line); border-radius: var(--radius-sm); color: var(--focus-text); } .Select--multi .Select-value-icon { border-right: 1px solid var(--focus-line); } .Select--multi .Select-value-icon:hover { background-color: var(--focus-accent); color: #ffffff; } /* Checkboxes, radios, switches, sliders. Founder, 2026-09-22: the unchecked box / knob was invisible (page-dark on panel-dark) — the box is now the raised grey with a light border, the switch knob is light grey when off and white when on, the track coral when on. */ .form-check-input { background-color: var(--focus-raised); border-color: var(--focus-text-muted); } .form-check-input:checked { background-color: var(--focus-accent); border-color: var(--focus-accent); } .form-switch .form-check-input { background-color: var(--focus-raised); border-color: var(--focus-text-muted); --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23d0d0d0'/%3e%3c/svg%3e"); } .form-switch .form-check-input:checked { background-color: var(--focus-accent); border-color: var(--focus-accent); --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23ffffff'/%3e%3c/svg%3e"); } .form-check-input[type="radio"]:checked { --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23ffffff'/%3e%3c/svg%3e"); } .form-check .form-check-label:not(.text-muted) { color: var(--focus-text-soft); } .form-check-input:focus { border-color: var(--focus-accent); box-shadow: 0 0 0 0.2rem var(--border-focus-shadow); } .form-range::-webkit-slider-thumb { background: var(--focus-accent); } .form-range::-moz-range-thumb { background: var(--focus-accent); } input[type="checkbox"], input[type="radio"], input[type="range"] { accent-color: var(--focus-accent); } /* dcc.Slider / RangeSlider (rc-slider) */ .rc-slider-track { background-color: var(--focus-accent); } .rc-slider-rail { background-color: var(--focus-raised); } .rc-slider-handle { border-color: var(--focus-accent); background-color: var(--focus-page); } .rc-slider-handle:hover, .rc-slider-handle:active, .rc-slider-handle-dragging { border-color: var(--focus-accent-hover) !important; box-shadow: 0 0 0 4px var(--border-focus-shadow) !important; } .rc-slider-dot-active { border-color: var(--focus-accent); } /* ======================== */ /* 8. MODALS */ /* ======================== */ .modal-content { background-color: var(--focus-panel); border: 1px solid var(--focus-line); border-radius: 15px; box-shadow: var(--shadow-lg); } .modal-header { background-color: transparent; border-bottom: 1px solid var(--focus-line); } .modal-body { background-color: transparent; color: var(--focus-text-soft); } .modal-footer { background-color: transparent; border-top: 1px solid var(--focus-line); } .modal-backdrop { --bs-backdrop-opacity: 0.6; } .offcanvas { --bs-offcanvas-bg: var(--focus-panel); --bs-offcanvas-color: var(--focus-text); --bs-offcanvas-border-color: var(--focus-line); } .popover { --bs-popover-bg: var(--focus-panel); --bs-popover-border-color: var(--focus-line); --bs-popover-border-radius: var(--radius-lg); --bs-popover-body-color: var(--focus-text-soft); --bs-popover-header-bg: var(--focus-panel); --bs-popover-header-color: var(--focus-text); } .tooltip { --bs-tooltip-bg: var(--focus-raised); --bs-tooltip-color: var(--focus-text); --bs-tooltip-border-radius: var(--radius-base); } /* ======================== */ /* 9. TABLES */ /* ======================== */ .table-scroll, .datatable-wrap { border-radius: var(--radius-lg); } .table thead th { color: var(--focus-text-muted); font-weight: 500; letter-spacing: 0.02em; } /* A highlighted row (Bootstrap's contextual .table-primary: the bond tool's current price) is the raised grey, not a blue band. */ .table-primary { --bs-table-color: var(--focus-text); --bs-table-bg: var(--focus-raised); --bs-table-border-color: var(--focus-line); --bs-table-striped-bg: var(--focus-raised); --bs-table-striped-color: var(--focus-text); --bs-table-active-bg: var(--focus-raised); --bs-table-active-color: var(--focus-text); --bs-table-hover-bg: #393939; --bs-table-hover-color: var(--focus-text); } .pagination { --bs-pagination-bg: var(--focus-panel); --bs-pagination-color: var(--focus-text-soft); --bs-pagination-border-color: var(--focus-line); --bs-pagination-hover-bg: var(--focus-raised); --bs-pagination-hover-color: var(--focus-text); --bs-pagination-hover-border-color: var(--focus-line-strong); --bs-pagination-active-bg: var(--focus-accent); --bs-pagination-active-border-color: var(--focus-accent); --bs-pagination-active-color: #ffffff; --bs-pagination-disabled-bg: var(--focus-panel); --bs-pagination-disabled-color: var(--text-disabled); --bs-pagination-border-radius: var(--radius-base); } /* ======================== */ /* 10. FOCUS RING */ /* ======================== */ /* Only on controls: a page-wide rule would also paint Bootstrap's modal shell (tabindex=-1) when it takes programmatic focus. Scrollbars are left to the system (a ::-webkit-scrollbar rule turns Safari's overlay bars into 10px layout bars inside every scrollable container). */ a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible, [role="tab"]:focus-visible { outline: 2px solid var(--focus-accent); outline-offset: 2px; } /* React-select's inner text field: the control already shows focus with its coral border (section 7); a ring on the inner <input> rendered as two coral bars beside the typed text (iPhone, 2026-09-22). */ .Select-input > input:focus-visible { outline: none; } /* ======================== */ /* 11. TYPE SCALE */ /* ======================== */ /* Founder decision 2026-09-21 (docs/_PLAN_focus_redesign.md): move toward the mockup's scale — body 13px / 1.5, page titles 30–38px weight 500 tracking -0.035em, h2 21px, h3 17px, eyebrow 9px uppercase tracked — while DATA TABLES keep today's dense 12px and the responsive --font-size-* token system + the global `body * { font-size }` rule stay the enforcement mechanism (02_global_dynamic_fonts.css). How: the ladder below re-declares 02's tokens at its three breakpoints (same min-widths, so the two files never disagree on where a size changes): +2px on phones (the 10px body was the smallest text in the app; the mockup is 13px at every width) and +1px from 544px up. Every rule in 01..19 that reads a token follows for free. Sizes are ONLY ever tokens here — never a raw px on a selector (_tests/test_focus_skin.py). Kept exactly as today, each on a token of its own: - --font-size-table (new) = 02's base ladder 10/11/12 — every grid keeps the density it has now; - --font-size-chart (new) = 02's xs ladder 9/10/10 for the Plotly axis / legend text — a bigger tick font would re-crowd dense axes that were tuned in Python. That frees --font-size-xs (badges, .small, form-text) to rise with the body, so captions stay two steps under it; - --font-size-prose (15/16): the reading size of learn-more modals and .text-readable blocks is already the blog's. --font-size-prose-sm becomes a flat 12px (was 12/13): labels and captions sit one step UNDER the 13px body, as in the mockup (they were = body). */ :root { --font-size-xs: 10px; /* was 9 */ --font-size-sm: 11px; /* was 10 */ --font-size-base: 12px; /* was 10 */ --font-size-md: 13px; /* was 11 */ --font-size-lg: 14px; /* was 12 */ --font-size-xl: 16px; /* was 14 */ --font-size-prose-sm: 12px; --font-size-table: 10px; /* = 02's --font-size-base on phones */ --font-size-chart: 9px; /* = 02's --font-size-xs on phones */ --font-size-title: 30px; /* page title (h1) */ --font-size-stat: 24px; /* a large number in a band of figures (function_focus_parts.stat) */ --font-size-h2: 21px; /* also .modal-title */ --font-size-h3: 17px; /* also .text-section-heading */ --font-size-eyebrow: 9px; --font-size-brand: 20px; /* the shell's wordmark (21_focus_shell.css) */ } @media (min-width: 544px) { :root { --font-size-xs: 11px; --font-size-sm: 11px; --font-size-base: 12px; --font-size-md: 13px; --font-size-lg: 14px; --font-size-xl: 16px; --font-size-prose-sm: 12px; --font-size-table: 11px; --font-size-chart: 10px; --font-size-title: 34px; --font-size-stat: 26px; } } @media (min-width: 768px) { :root { --font-size-xs: 11px; --font-size-sm: 12px; --font-size-base: 13px; /* the mockup's body size */ --font-size-md: 14px; --font-size-lg: 15px; --font-size-xl: 17px; --font-size-prose-sm: 12px; --font-size-table: 12px; /* "data tables keep a dense 12px" */ --font-size-chart: 10px; --font-size-title: 38px; --font-size-stat: 28px; /* the mockup's figures */ --font-size-brand: 22px; } } /* Headings: 02 derives h1/h2/h3 from the xl/lg tokens (24/20/17px on desktop); the mockup's title tier is a step of its own. h4/h5/h6 keep 02's lg/md/base mapping (15/14/13 desktop): one monotone ladder under h3. h2/h3 are flat at every width, as in the mockup (a 21px modal title fits a 390px phone on one line). */ h1, .h1 { font-size: var(--font-size-title) !important; line-height: 1.17; letter-spacing: -0.035em; } h2, .h2 { font-size: var(--font-size-h2) !important; line-height: 1.3; } h3, .h3 { font-size: var(--font-size-h3) !important; line-height: 1.3; } /* Card / section titles (03: the 16px prose token, weight 600) take the h3 tier — the mockup's panel heading (17px, weight 500). Modal titles take the h2 tier so they stay above the section headings inside them. */ .text-section-heading { font-size: var(--font-size-h3) !important; font-weight: 500; } .modal-title { font-size: var(--font-size-h2) !important; font-weight: 500; } /* 03 lifts h5/h6 inside a prose block above its 16px body; h4 (= the lg token, 15px) would now sit 1px under it — same lift. */ .text-readable h4 { font-size: calc(var(--font-size-prose) * 1.1) !important; } /* The page-header eyebrow of the mockups ("RESEARCH", "MONITORING"): a utility for layer B/C — nothing in the app uses it yet. */ .eyebrow { font-size: var(--font-size-eyebrow) !important; font-weight: 600; letter-spacing: 0.14em; line-height: 1.5; text-transform: uppercase; color: var(--focus-text-muted); } /* Plotly text keeps today's size: 02's selectors verbatim, so this later rule wins at equal specificity. Hover labels and annotations stay excluded, as in 02 — their sizes are set in Python. */ .js-plotly-plot .plotly text:not(.hoverlayer text):not(.annotation-text), .js-plotly-plot .legend text, .js-plotly-plot .legendtext, g.legend text.legendtext { font-size: var(--font-size-chart) !important; } /* Data tables keep today's density. Cells: 02's own selectors, verbatim (same specificity, so this later rule wins — a plainer `.table td` LOSES to 02's `.table tbody td`), plus the sticky-header clone (table-sticky-headers.js appends it to <body>, and a DataTable clone carries no `.table` class) and the cells of tables inside prose / learn-more blocks (03 and 09 size those to the body). Descendants: everything inside a cell is pinned too (value spans, sort buttons, DataTable's .dash-cell-value divs, filter inputs) — without it the global `body *` rule would set a cell's span to the 13px body while the cell itself is 12px. Specificity (0,2,1): above 02's .btn / label / .form-control rules, BELOW 02's Plotly text rule (0,4,2) so a chart in a cell keeps its size; it ties 04's 16px phone rule for a dropdown's search input, so a dcc.Dropdown inside a cell would lose that (none exists today, and the viewport lock in server.py masks iOS auto-zoom anyway). Badges are excluded and re-pinned below together with their children, `.small` and `.form-text` captions, which the descendant rule would otherwise catch. */ .table tbody td, .dash-spreadsheet-container td, div.dash-spreadsheet-container .dash-spreadsheet-inner table td, .table thead th, .dash-spreadsheet-container th, div.dash-spreadsheet-container .dash-spreadsheet-inner table th, .sticky-header-wrapper th, .text-readable td, .text-readable th, .learn-more-content td, .learn-more-content th, .educational-content td, .educational-content th, .dash-spreadsheet-container input, :is(.table, .dash-spreadsheet-container, .sticky-header-wrapper, .text-readable, .learn-more-content, .educational-content) :is(td, th) *:not(.badge) { font-size: var(--font-size-table) !important; } :is(.table, .dash-spreadsheet-container, .sticky-header-wrapper, .text-readable, .learn-more-content, .educational-content) :is(td, th) :is(.badge *, .small, .form-text) { font-size: var(--font-size-xs) !important; } /* ======================================== */ /* 12. THE PHONE APPS (switch-over day) */ /* ======================================== */ /* server.py marks <html data-app="native"> for the phone apps that carry the Focus navigation themselves (user agent SlowioApp/2.3 or later, not the Windows shell — functions/function_site_flag.py). On a phone their tab bar and grouped More menu already do what the web top bar does (home, company search, the pages menu), so the web bar steps aside; from 768px (tablets) the sidebar and the bar stay. Older app versions keep the web bar: their More menu still lists the old pages. (The Pricing links are left out for every app version by the server — apps/app_shell, server.py.) */ @media (max-width: 767.98px) { html[data-app="native"] .fshell-top { display: none; } }
/* 21_focus_shell.css */
/* ======================================================================== */ /* FOCUS SHELL — layer B of the app redesign (docs/_PLAN_focus_redesign.md) */ /* File: 21_focus_shell.css */ /* */ /* Loaded ONLY in the app.focus bundle, after 20_focus_skin.css. This file */ /* is the LAYOUT of the new shell (apps/app_shell): the fixed left sidebar, */ /* the top bar, the phone off-canvas, the content offset and the page-header */ /* pattern (`page_frame`). Colours come from the skin's tokens; every size */ /* is a --font-size-* token (never a raw px on a selector). */ /* */ /* The markup exists only for a session whose skin is 'focus', and the */ /* content offset is keyed on the <html data-skin="focus"> attribute that */ /* server.py writes for that session — nothing here can reach another user. */ /* ======================================================================== */ :root { --fshell-top: 64px; --fshell-rail: 216px; --fshell-rail-collapsed: 60px; --fshell-gutter: 32px; } /* ======================== */ /* 1. SIDEBAR */ /* ======================== */ .fshell-side { position: fixed; top: 0; left: 0; bottom: 0; width: var(--fshell-rail); display: flex; flex-direction: column; padding: 14px 10px 10px; background: var(--focus-sidebar); border-right: 1px solid var(--focus-line); overflow: hidden; z-index: 1020; } /* The brand (head) and the foot stay put; only the page list scrolls — 30 pages + their group labels are taller than a 900px screen. */ .fshell-side-head { flex: none; display: flex; align-items: center; padding: 4px 8px 12px; } .fshell-brand { display: flex; align-items: center; gap: 9px; color: var(--focus-text); text-decoration: none; font-weight: 700; letter-spacing: -0.04em; line-height: 1; white-space: nowrap; } .fshell-brand:hover { color: var(--focus-text); } .fshell-brand img { width: 26px; height: 26px; border-radius: 6px; flex: none; } .fshell-brand-word { font-size: var(--font-size-brand) !important; } .fshell-nav { display: flex; flex-direction: column; gap: 1px; } .fshell-side .fshell-nav { flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-x: hidden; scrollbar-width: thin; } .fshell-group { margin: 14px 10px 5px; } .fshell-nav .fshell-group:first-child { margin-top: 2px; } .fshell-link { display: flex; align-items: center; gap: 10px; min-height: 34px; padding: 6px 10px; border-radius: 6px; color: var(--focus-text-muted); text-decoration: none; font-size: var(--font-size-base) !important; white-space: nowrap; position: relative; } .fshell-link img { width: 16px; height: 16px; opacity: 0.7; flex: none; } .fshell-link:hover { background: var(--focus-raised); color: var(--focus-text); } .fshell-link.active { background: rgba(var(--focus-accent-rgb), 0.12); color: var(--focus-accent); } .fshell-link.active img { opacity: 1; } .fshell-link.active::before { content: ''; position: absolute; left: 0; top: 10px; bottom: 10px; width: 2px; background: var(--focus-accent); border-radius: 2px; } /* Second level (the opportunity countries): mounted always, shown while the parent is active (`open`, set by callbacks_shell.py) */ .fshell-sub { display: none; flex-wrap: wrap; gap: 2px 4px; padding: 2px 6px 6px 30px; } .fshell-sub.open { display: flex; } .fshell-sub .fshell-link { min-height: 26px; padding: 3px 8px; font-size: var(--font-size-sm) !important; } .fshell-sub .fshell-link img { display: none; } .fshell-sub .fshell-link.active::before { display: none; } .fshell-side-foot { flex: none; margin-top: auto; padding-top: 8px; border-top: 1px solid var(--focus-line); } .fshell-side-foot .fshell-link { min-height: 30px; padding: 5px 10px; font-size: var(--font-size-sm) !important; } /* The account entry: the last line of the foot, at the size of a page link (the resources above it are the small print). Its link's class is rewritten by the shell callback, so it is styled through this wrapper. */ .fshell-account { margin-top: 6px; } .fshell-side-foot .fshell-account .fshell-link { min-height: 34px; padding: 6px 10px; font-size: var(--font-size-base) !important; } /* Collapsed = icon rail (class set on <html> by 30_focus_shell.js) */ html.fshell-collapsed .fshell-side { width: var(--fshell-rail-collapsed); padding-left: 8px; padding-right: 8px; } html.fshell-collapsed .fshell-side-head { justify-content: center; padding-left: 0; padding-right: 0; } /* the rail keeps the page icons and the account icon; the resources (text links, no icon) go. Scoped to the sidebar: the phone drawer and the top bar keep their labels even when the remembered state is "collapsed" (a window narrowed under 768px after collapsing the rail). */ html.fshell-collapsed .fshell-side .fshell-brand-word, html.fshell-collapsed .fshell-side .fshell-group, html.fshell-collapsed .fshell-side .fshell-sub, html.fshell-collapsed .fshell-side .fshell-side-foot > .fshell-link, html.fshell-collapsed .fshell-side .fshell-link span { display: none; } html.fshell-collapsed .fshell-side .fshell-link, html.fshell-collapsed .fshell-side .fshell-side-foot .fshell-account .fshell-link { justify-content: center; padding: 9px 0; } html.fshell-collapsed .fshell-side .fshell-account { margin-top: 0; } /* ======================== */ /* 2. TOP BAR */ /* ======================== */ /* Three cells (founder, 2026-09-25): start = the brand (phones) + the three shortcuts, centre = the quick-find, end = the performance chips + the menu button. The two side cells share the width equally, so the find box sits in the middle of the bar whatever the sides hold; the breakpoints below keep each side's content inside its half (measured 1024-1440). */ .fshell-top { margin-left: var(--fshell-rail); min-height: var(--fshell-top); display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); align-items: center; gap: 16px; padding: 0 var(--fshell-gutter); background: var(--focus-page); border-bottom: 1px solid var(--focus-line); } html.fshell-collapsed .fshell-top { margin-left: var(--fshell-rail-collapsed); } .fshell-top-start, .fshell-top-end { display: flex; align-items: center; min-width: 0; } .fshell-top-start { gap: 22px; } .fshell-top-end { justify-content: flex-end; gap: 14px; } .fshell-top-center { display: flex; justify-content: center; } .fshell-top .fshell-brand { display: none; } /* The menu button (right end): the rail toggle from 768px, the drawer burger below; one look for both. */ .fshell-burger { display: inline-flex; align-items: center; justify-content: center; flex: none; width: 40px; height: 40px; margin-right: -8px; /* the glyph, not the hit area, lines up with the gutter */ padding: 0; border: 0; border-radius: 8px; background: transparent; color: var(--focus-text-soft); cursor: pointer; } .fshell-top .fshell-drawer-toggle { /* out-specifies .fshell-burger */ display: none; } .fshell-burger:hover, .fshell-burger:focus-visible { background: var(--focus-raised); color: var(--focus-text); } .fshell-top-links { display: flex; align-items: center; gap: 18px; } .fshell-top-links .fshell-link { min-height: 0; padding: 8px 2px; border-radius: 0; background: transparent; } .fshell-top-links .fshell-link:hover { background: transparent; } .fshell-top-links .fshell-link.active { background: transparent; color: var(--focus-text); } .fshell-top-links .fshell-link.active::before { display: none; } .fshell-top-links .fshell-link.active::after { content: ''; position: absolute; left: 2px; right: 2px; bottom: 0; height: 2px; background: var(--focus-accent); border-radius: 2px; } .fshell-chips { display: flex; align-items: center; gap: 14px; white-space: nowrap; } .fshell-chips a { font-size: var(--font-size-sm) !important; text-decoration: none; } /* The chips' colours from the skin's tokens, keyed on the sign class build_perf_chip_rows gives each chip (perf-chip-pos / -neg / -flat; flat = no figure yet, N/A, or exactly 0). The builder also keeps an inline colour for the classic bar, hence !important here. */ .fshell-chips .perf-chip-pos { color: var(--focus-positive) !important; } .fshell-chips .perf-chip-neg { color: var(--focus-negative) !important; } .fshell-chips .perf-chip-flat { color: var(--focus-text-muted) !important; } /* build_perf_chip_rows returns Bootstrap rows (made for the classic bar's card body): undo the grid gutters here, the classic bar is untouched */ .fshell-chips .row { margin: 0; flex-wrap: nowrap; } .fshell-chips .row > * { width: auto; padding: 0; } /* From 992px the find button reads as a search field in the centre of the bar (magnifier, label, the "/" hint at its right end); below it is the 40px magnifier square (section 6). */ .btn.fshell-find { display: flex; align-items: center; gap: 10px; width: clamp(220px, 24vw, 340px); padding: 7px 10px 7px 12px; border: 1px solid var(--focus-line); border-radius: 6px; background: transparent; color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; font-weight: 400; } .btn.fshell-find:hover, .btn.fshell-find:focus-visible { background: var(--focus-raised); border-color: var(--focus-line-strong); color: var(--focus-text); } /* CSS-drawn glyphs (no icon font in the app): a 20px burger and a 16px magnifier, drawn in the current text colour. */ .fshell-glyph-menu { position: relative; display: block; width: 20px; height: 2px; background: currentColor; border-radius: 2px; } .fshell-glyph-menu::before, .fshell-glyph-menu::after { content: ''; position: absolute; left: 0; width: 20px; height: 2px; background: currentColor; border-radius: 2px; } .fshell-glyph-menu::before { top: -6px; } .fshell-glyph-menu::after { top: 6px; } .fshell-glyph-search { position: relative; display: block; width: 14px; height: 14px; border: 2px solid currentColor; border-radius: 50%; box-sizing: border-box; flex: none; } .fshell-glyph-search::after { content: ''; position: absolute; right: -5px; bottom: -6px; width: 2px; height: 7px; background: currentColor; border-radius: 2px; transform: rotate(-45deg); } .fshell-find-label { white-space: nowrap; } .fshell-find kbd { margin-left: auto; padding: 0 5px; border: 1px solid var(--focus-line); border-radius: 3px; background: transparent; color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .btn.fshell-login { white-space: nowrap; } /* The quick-find field (the old navbar's modal, `navbar_modal` = its .modal-dialog) opens with the cursor in it (30_focus_shell.js). On a touch screen it is at least 16px (--font-size-xl: 16px on phones, 17px from 768px), so iOS never zooms into it; the placeholder and the picked name match the typed text. `.Select-input > div` is react-select's hidden sizer: it must measure at the same size, or the field is sized for the smaller text and the first letters scroll out of view. */ @media (max-width: 767.98px), (pointer: coarse) { #navbar_modal .Select-input > input, #navbar_modal .Select-input > div, #navbar_modal .Select-placeholder, #navbar_modal .Select-value-label { font-size: var(--font-size-xl) !important; } } /* The quick-find window opens in the upper part of the screen, not glued to its top edge (founder, 2026-09-29): its top at 15 % of the height, but never so low that the phone keyboard covers the field or the first four results. A keyboard is a FIXED height, not a share of the screen: on an iPhone in Safari (390x664 with the browser bars) the keyboard, its suggestion row and the form bar take ~256px off the bottom of 100svh. The window's own spacing is tighter in Focus (below), so from its top to the fourth result's bottom is ~297px (was ~344; measured at 390 and 1440, both engines). So the top is capped at 100svh - 560px, which lets the 15 % through on an iPhone 14 (~100px, was 8px before 2026-09-29), and never above Bootstrap's own margin (--bs-modal-margin: 0.5rem phones, 1.75rem from 576px). svh = the height with the browser bars shown; the vh line serves older browsers. Where the keyboard RESIZES the page (the Android app's WebView) the window settles at the top margin as the keyboard opens - where it was before. The classic bar's window (same id, app.main bundle) is untouched. */ #navbar_modal { margin-top: max(var(--bs-modal-margin), min(15vh, calc(100vh - 560px))); margin-top: max(var(--bs-modal-margin), min(15svh, calc(100svh - 560px))); } /* Tighter spacing inside the window (founder, 2026-09-29), ~47px freed above the field: a slimmer title bar and body top, and the tabs sit straight above the field (the rule under them, 24 + 1 + 24px, goes). */ #navbar_modal .modal-header { --bs-modal-header-padding-y: 12px; /* the close button's margins follow it */ padding-top: 12px; padding-bottom: 12px; } #navbar_modal .modal-body { padding-top: 12px; } #navbar_modal #nav_search_selection_tabs { margin-bottom: 14px; } #navbar_modal .modal-body > hr:first-of-type { display: none; } /* ======================== */ /* 3. CONTENT OFFSET */ /* ======================== */ /* The page content and the footer (`.app-footer`, layout_footer.py) move right of the rail; the inline 20px margins of #main_page_content are overridden. Keyed on the attribute server.py writes only for a Focus session. The toast keeps centred on the CONTENT, not the viewport. */ html[data-skin="focus"] #main_page_content, html[data-skin="focus"] .app-footer { margin-left: calc(var(--fshell-rail) + var(--fshell-gutter)) !important; margin-right: var(--fshell-gutter) !important; } html[data-skin="focus"] #main_page_content { margin-top: 28px !important; } html[data-skin="focus"].fshell-collapsed #main_page_content, html[data-skin="focus"].fshell-collapsed .app-footer { margin-left: calc(var(--fshell-rail-collapsed) + var(--fshell-gutter)) !important; } html[data-skin="focus"] .app-toast-slot { left: calc(50% + var(--fshell-rail) / 2); } html[data-skin="focus"].fshell-collapsed .app-toast-slot { left: calc(50% + var(--fshell-rail-collapsed) / 2); } /* ======================== */ /* 4. PAGE FRAME */ /* ======================== */ .fpage-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 24px; margin-bottom: 24px; } .fpage-head-text { min-width: 0; } .fpage-title { margin: 10px 0 8px; } .fpage-subtitle { margin: 0; max-width: 620px; color: var(--focus-text-muted); } .fpage-actions { display: flex; align-items: flex-end; gap: 10px; flex-shrink: 0; } .fpage-summary { margin-bottom: 24px; } .fpage-tabs { margin-bottom: 20px; } .fpage-body { min-width: 0; } /* ======================== */ /* 5. DRAWER (phones) */ /* ======================== */ /* Always mounted (so its links keep a fresh active marker); slid out of view until the burger sets `.open` (callbacks_shell.py). Backdrop = the next sibling. Both are hidden from 768px up, where the sidebar shows. */ .fshell-drawer { position: fixed; top: 0; bottom: 0; left: 0; width: 300px; max-width: 86vw; display: flex; flex-direction: column; padding: 10px 12px 20px; background: var(--focus-sidebar); border-right: 1px solid var(--focus-line); overflow: hidden; /* only the page list scrolls: the foot (account) stays in view */ z-index: 1046; /* under Bootstrap's modal backdrop (1050) */ transform: translateX(-102%); transition: transform 0.2s ease; visibility: hidden; } .fshell-drawer.open { transform: none; visibility: visible; } .fshell-backdrop { position: fixed; top: 0; right: 0; bottom: 0; left: 0; display: none; background: rgba(0, 0, 0, 0.55); z-index: 1045; } .fshell-drawer.open ~ .fshell-backdrop { display: block; } .fshell-drawer-head { display: flex; align-items: center; justify-content: space-between; padding: 6px 8px 10px; border-bottom: 1px solid var(--focus-line); margin-bottom: 6px; } .fshell-drawer-title { color: var(--focus-text); font-weight: 500; font-size: var(--font-size-md) !important; } .fshell-drawer-close { border: 0; background: transparent; color: var(--focus-text-muted); font-size: var(--font-size-h3) !important; line-height: 1; padding: 2px 6px; cursor: pointer; } .fshell-drawer-close:hover { color: var(--focus-text); } .fshell-drawer .fshell-nav { flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-x: hidden; } .fshell-drawer .fshell-side-foot { margin-top: 10px; } @media (min-width: 768px) { .fshell-drawer, .fshell-backdrop, .fshell-drawer.open ~ .fshell-backdrop { /* a phone turned sideways with the drawer open */ display: none; } } /* ======================== */ /* 6. BREAKPOINTS */ /* ======================== */ @media (max-width: 1199.98px) { :root { --fshell-rail: 184px; --fshell-gutter: 24px; } /* 992-1199 beside the rail: each half of the bar is ~245-330px, the three shortcuts need ~225 of it at this gap */ .fshell-top-links { gap: 14px; } } /* Tablets (768-991px): beside the 184px rail the full top bar (three links + the chips + the labelled find box, all no-wrap) is wider than the viewport and the whole page scrolled sideways (found at 768 on 2026-09-22). The sidebar already lists the three shortcuts, so the top links go and the find box becomes the 40px magnifier, as on phones. */ @media (max-width: 991.98px) { .fshell-top-links, .fshell-find-label, .fshell-find kbd { display: none; } /* a 40px square, the magnifier centred (the burger's size on phones) */ .btn.fshell-find { width: 40px; height: 40px; padding: 0; justify-content: center; border-radius: 8px; color: var(--focus-text-soft); } /* the magnifier's handle hangs off its ring's bottom-right, so a centred 14px ring puts the drawn lens ~2px right and ~2px low in the square (measured 2026-09-29); pull the drawing back to the centre */ .btn.fshell-find .fshell-glyph-search { transform: translate(-2px, -2px); } /* the page head stacks: beside the rail a title has no room next to its actions (a 300px company picker made "Understand the business." wrap into three lines at 768) */ .fpage-head { display: block; } .fpage-actions { margin-top: 14px; flex-wrap: wrap; } } @media (max-width: 767.98px) { .fshell-side { display: none; } .fshell-top { margin-left: 0 !important; min-height: 56px; gap: 8px; padding: 0 16px; } /* Phones (founder, 2026-09-29): the logo, then the portfolio chips where the wordmark was (as the classic bar showed them: Tot over 7D), the lens in the exact middle, the menu button at the right end. Five columns whose two outer ones are the same width, so the lens sits in the centre whatever the chips hold. The two side cells dissolve (display: contents): the ONE chips element — the pattern id the per-navigation callback refreshes — moves left, no second copy. */ .fshell-top { grid-template-columns: 32px minmax(0, 1fr) auto minmax(0, 1fr) 32px; } .fshell-top-start, .fshell-top-end { display: contents; } .fshell-top .fshell-brand { display: flex; grid-column: 1; grid-row: 1; } .fshell-top .fshell-brand-word { display: none; } .fshell-top-center { grid-column: 3; grid-row: 1; } .fshell-top .fshell-chips { grid-column: 2; grid-row: 1; flex-direction: column; align-items: flex-start; gap: 0; min-width: 0; overflow: hidden; line-height: 1.3; } .fshell-top .fshell-chips .row, .fshell-top .fshell-chips .row > * { max-width: 100%; } .fshell-top .fshell-chips a { display: block; overflow: hidden; text-overflow: ellipsis; } /* Every chip grey (no portfolio figure yet, "N/A" — or both exactly 0): the wordmark instead, as before. Follows the chips' refresh on every navigation. A browser without :has() keeps the grey chips. */ .fshell-top:not(:has(.fshell-chips .perf-chip:not(.perf-chip-flat))) .fshell-brand { grid-column: 1 / 3; } .fshell-top:not(:has(.fshell-chips .perf-chip:not(.perf-chip-flat))) .fshell-brand-word { display: inline; } .fshell-top:not(:has(.fshell-chips .perf-chip:not(.perf-chip-flat))) .fshell-chips { display: none; } /* phones: the menu button opens the drawer (the rail is not shown) */ .fshell-top .fshell-rail-toggle { display: none; } .fshell-top .fshell-drawer-toggle { display: inline-flex; grid-column: 5; grid-row: 1; justify-self: end; } .fpage-head { display: block; } .fpage-actions { margin-top: 14px; flex-wrap: wrap; } html[data-skin="focus"] .app-footer, html[data-skin="focus"].fshell-collapsed .app-footer { margin-left: 16px !important; margin-right: 16px !important; } /* The page's side margin on a phone, HALVED (founder, 2026-10-03): 14px a side instead of 28 — the frame's 16 -> 8 and the page container's 12 -> 6 — so every page gains 28px of width (the holdings table shows a third number column with it). Measured at 375px on nine pages: nothing leaves the screen, no sideways scroll. The footer keeps its 16px. */ html[data-skin="focus"] #main_page_content, html[data-skin="focus"].fshell-collapsed #main_page_content { margin-left: 8px !important; margin-right: 8px !important; } html[data-skin="focus"] #main_page_content .container, html[data-skin="focus"] #main_page_content .container-fluid { padding-left: 6px; padding-right: 6px; } html[data-skin="focus"] .app-toast-slot, html[data-skin="focus"].fshell-collapsed .app-toast-slot { left: 50%; } html[data-skin="focus"] #main_page_content { margin-top: 18px !important; } }
/* 22_focus_pages.css */
/* ======================================================================== */ /* FOCUS PAGES — layer C of the app redesign (docs/_PLAN_focus_redesign.md) */ /* File: 22_focus_pages.css */ /* */ /* Loaded ONLY in the app.focus bundle, after 20 (skin) and 21 (shell). One */ /* section per restructured page, in the order the pages were migrated; */ /* classes are prefixed per page (`fhome-*`). Colours come from the skin's */ /* tokens; every size is a --font-size-* token (never a raw px on a */ /* selector — _tests/test_focus_home.py). Nothing here can reach a user on */ /* the classic UI: the markup exists only for a session on the Focus skin. */ /* ======================================================================== */ /* ======================== */ /* 1. HOME */ /* ======================== */ /* apps/app_homes/layout_home_focus.py — the approved home mockup (_temp/home_alternatives/05-focus-refined.html): the page frame with the coral-bar motif, the START panel, the WITHIN REACH row (the pinned tools), the tool search and the ALL TOOLS grid (two columns, one on phones) with a pin star on every card. */ .fhome .fpage-head { align-items: center; margin-bottom: 28px; } .fhome .fpage-title { margin-top: 0; } /* The mockup's mark: five rounded coral bars, tilted. Decorative. */ .fhome-motif { height: 112px; display: flex; align-items: center; gap: 7px; padding: 8px 10px; transform: rotate(-12deg); } .fhome-motif i { display: block; width: 12px; height: 46px; border-radius: 20px; background: rgba(var(--focus-accent-rgb), 0.28); } .fhome-motif i:nth-child(2), .fhome-motif i:nth-child(4) { height: 71px; background: rgba(var(--focus-accent-rgb), 0.42); } .fhome-motif i:nth-child(3) { height: 96px; background: rgba(var(--focus-accent-rgb), 0.62); } /* START: the main action left, the two side actions stacked right */ .fhome-start { display: grid; grid-template-columns: 1.1fr 1fr; margin-bottom: 28px; border: 1px solid var(--focus-line); border-radius: 12px; background: var(--focus-panel); overflow: hidden; } .fhome-start-main { padding: 26px 28px; } .fhome-start-title { margin: 10px 0 8px; } .fhome-start-text { margin: 0 0 18px; max-width: 320px; color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; } .btn.fhome-start-btn { display: inline-flex; align-items: center; gap: 22px; padding: 11px 18px; font-size: var(--font-size-sm) !important; font-weight: 600; } .fhome-arrow { font-weight: 400; } .fhome-start-side { display: flex; flex-direction: column; border-left: 1px solid var(--focus-line); background: rgba(0, 0, 0, 0.16); } .fhome-side-link { flex: 1; display: flex; align-items: center; gap: 13px; min-width: 0; padding: 22px 24px; color: var(--focus-text); text-decoration: none; } .fhome-side-link + .fhome-side-link { border-top: 1px solid var(--focus-line); } .fhome-side-link:hover { background: var(--focus-raised); color: var(--focus-text); } .fhome-round-icon { width: 36px; height: 36px; flex: none; display: grid; place-items: center; border: 1px solid var(--focus-line); border-radius: 50%; } .fhome-round-icon img { width: 18px; height: 18px; opacity: 0.85; } .fhome-side-text { display: flex; flex-direction: column; min-width: 0; } .fhome-side-text strong { font-weight: 500; letter-spacing: -0.015em; font-size: var(--font-size-md) !important; } .fhome-side-text small { margin-top: 5px; color: var(--focus-text-muted); line-height: 1.5; font-size: var(--font-size-xs) !important; } .fhome-side-link .fhome-arrow { margin-left: auto; color: var(--focus-text-muted); } /* Section heads (WITHIN REACH, ALL TOOLS): a small title + a right-hand link or count. The h2 element keeps its meaning, the size is one step under the page's h2 tier. */ .fhome-section-head { display: flex; align-items: center; justify-content: space-between; gap: 20px; margin: 0 0 14px; } .fhome-section-title { margin: 0; font-weight: 500; font-size: var(--font-size-h3) !important; } .fhome-count { color: var(--focus-text-muted); white-space: nowrap; font-size: var(--font-size-sm) !important; } /* WITHIN REACH: the pinned tools, three per row */ .fhome-reach { margin-bottom: 28px; } .fhome-shortcuts { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; } .fhome-shortcut { display: flex; align-items: center; gap: 12px; min-width: 0; min-height: 53px; padding: 12px 16px; border: 1px solid var(--focus-line); border-radius: 7px; background: var(--focus-panel); color: var(--focus-text); text-decoration: none; } .fhome-shortcut:hover { border-color: var(--focus-line-strong); background: var(--focus-raised); color: var(--focus-text); } .fhome-shortcut img { width: 18px; height: 18px; flex: none; opacity: 0.85; } .fhome-shortcut-name { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .fhome-shortcut small { color: var(--focus-text-muted); white-space: nowrap; font-size: var(--font-size-xs) !important; } .fhome-shortcut b { margin-left: auto; color: var(--focus-text-muted); font-weight: 400; } .fhome-shortcuts .fhome-muted { grid-column: 1 / -1; /* the empty line spans the row */ } .fhome-muted { margin: 0; padding: 10px 0; color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; } /* The tool search: filters the cards below as you type (browser-side) */ .fhome-controls { margin: 0 0 18px; } .form-control.fhome-search { height: 44px; width: 100%; padding: 0 14px; border: 1px solid var(--focus-line); border-radius: 6px; background: var(--focus-panel); color: var(--focus-text); font-size: var(--font-size-md) !important; } .form-control.fhome-search:focus { border-color: var(--focus-accent); box-shadow: none; outline: 0; } .fhome-hidden { display: none !important; } .fhome-empty { margin-top: 22px; padding: 40px 20px; border: 1px dashed var(--focus-line); border-radius: 8px; text-align: center; color: var(--focus-text-muted); } /* ALL TOOLS: grouped as the sidebar, two cards per row */ .fhome-library { padding-top: 22px; border-top: 1px solid var(--focus-line); } .fhome-group { margin: 22px 0 10px; } .fhome-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 11px; } /* A card = the page link + its pin star (a button beside the link, never inside it: a button inside a link is not valid markup) */ .fhome-card { position: relative; min-width: 0; } .fhome-pin { position: absolute; top: 12px; right: 10px; width: 30px; height: 30px; display: grid; place-items: center; border: 0; border-radius: 6px; background: transparent; color: var(--focus-text-muted); cursor: pointer; line-height: 1; font-size: var(--font-size-lg) !important; } .fhome-pin:hover { background: var(--focus-raised); color: var(--focus-text); } .fhome-pin.is-pinned { color: var(--focus-accent); } .fhome-pin:focus-visible { outline: 2px solid var(--focus-accent); outline-offset: 2px; } .fhome-tool { display: flex; gap: 15px; min-width: 0; height: 100%; min-height: 96px; padding: 18px 44px 18px 20px; /* room for the star on the right */ border: 1px solid var(--focus-line); border-radius: 8px; background: var(--focus-panel); color: var(--focus-text); text-decoration: none; transition: background 0.15s, border-color 0.15s; } .fhome-tool:hover { background: var(--focus-raised); border-color: var(--focus-line-strong); color: var(--focus-text); } .fhome-icon-box { width: 36px; height: 36px; flex: none; display: grid; place-items: center; border-radius: 8px; background: var(--focus-raised); } .fhome-icon-box img { width: 22px; height: 22px; opacity: 0.86; } .fhome-tool-copy { display: flex; flex-direction: column; min-width: 0; } .fhome-tool-copy strong { font-weight: 500; letter-spacing: -0.01em; font-size: var(--font-size-md) !important; } .fhome-beta { margin-left: 4px; color: var(--focus-text-muted); font-weight: 400; font-size: var(--font-size-xs) !important; } .fhome-tool-desc { margin-top: 6px; color: var(--focus-text-muted); line-height: 1.55; font-size: var(--font-size-sm) !important; } @media (max-width: 1199.98px) { .fhome-shortcuts { grid-template-columns: repeat(2, minmax(0, 1fr)); } } @media (max-width: 767.98px) { .fhome .fpage-head { margin-bottom: 22px; } .fhome .fpage-actions { display: none; /* the motif only */ } .fhome-start { grid-template-columns: 1fr; margin-bottom: 22px; } .fhome-start-main { padding: 22px; } .fhome-start-side { border-left: 0; border-top: 1px solid var(--focus-line); } .fhome-side-link { padding: 18px 22px; } .fhome-reach { margin-bottom: 22px; } .fhome-shortcuts, .fhome-grid { grid-template-columns: 1fr; } .fhome-shortcut { min-height: 49px; } .fhome-tool { min-height: 0; padding: 16px 44px 16px 16px; gap: 12px; } .fhome-pin { top: 10px; right: 6px; } /* 16px on phones (the xl token): no iOS auto-zoom on focus, even if the viewport lock in server.py is ever relaxed */ .form-control.fhome-search { font-size: var(--font-size-xl) !important; } } /* ======================== */ /* 2. FINANCIALS */ /* ======================== */ /* apps/app_financials/layout_financials.py (the frame: title + picker in the actions slot, the summary, the tab bar, the analysis body) and function_financials_focus_summary.py (layout A, founder 2026-09-22): the summary is ONE grid whose items are the four content callbacks' blocks (their wrappers are `display: contents`); two background items draw the identity panel (header + rings) and the About panel. */ .fin-picker { width: 300px; max-width: 100%; } .fin-picker p { margin: 0 0 6px; text-align: left !important; color: var(--focus-text-muted); font-size: var(--font-size-eyebrow) !important; font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase; } .fin-summary { display: grid; grid-template-columns: 1.1fr 1fr; grid-template-areas: "note note" "identity keyfig" "rings keyfig" "bars bars" "facts pricegraph" "facts2 pricegraph" "compare compare" "ai ai" "holders holders"; gap: 0 16px; align-items: start; margin-bottom: 8px; } .fin-sum-c { display: contents; } .fin-sum-bg { border: 1px solid var(--focus-line); border-radius: 12px; background: var(--focus-panel); align-self: stretch; } .fin-sum-bg-identity { grid-row: identity-start / rings-end; grid-column: identity-start / identity-end; } .fin-sum-bg-about { grid-row: facts-start / holders-end; grid-column: 1 / -1; margin-top: 16px; } .fin-sum-identity { grid-area: identity; padding: 20px 22px 6px; min-width: 0; } .fin-sum-head { display: flex; align-items: center; gap: 14px; } /* the logo box is kept while its callback has not answered (no jump); on the explicit "no logo" answer it goes, with its gap, and the name starts on the rings' left edge */ .fin-sum-logo { flex: 0 0 44px; min-height: 44px; } .fin-sum-logo:has(> .fin-sum-logo-none) { display: none; } .fin-sum-logo img { width: 44px !important; height: 44px !important; margin: 0 !important; border-radius: 10px; object-fit: contain; background: #fff; } .fin-sum-name { min-width: 0; flex: 1 1 auto; } .fin-sum-name h2 { margin: 0 !important; font-size: var(--font-size-h2) !important; font-weight: 500; line-height: 1.2; text-decoration-color: var(--focus-line-strong) !important; } .fin-sum-sub { display: block; margin-top: 3px; color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; } .fin-sum-actions-top { display: flex; gap: 6px; flex: 0 0 auto; } /* Follow (star) and Set an alert (bell): the glyphs are CSS masks (no icon font in this app); the star fills once the ticker is in a watchlist */ .fin-sum-icon { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 30px; padding: 0; border: 1px solid var(--focus-line); border-radius: 8px; background: var(--focus-raised); color: var(--focus-text-soft); cursor: pointer; } .fin-sum-icon:hover, .fin-sum-icon:focus-visible { color: var(--focus-text); border-color: var(--focus-line-strong); } .fin-sum-icon::before { content: ""; width: 16px; height: 16px; background-color: currentColor; -webkit-mask: var(--fin-glyph) center / contain no-repeat; mask: var(--fin-glyph) center / contain no-repeat; } .fin-sum-icon-star { --fin-glyph: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill='none' stroke='black' stroke-width='2' stroke-linejoin='round' d='M12 2.8l2.8 5.8 6.3.9-4.6 4.4 1.1 6.3L12 17.2l-5.6 3 1.1-6.3-4.6-4.4 6.3-.9z'/></svg>"); } .fin-sum-icon-star.is-on { --fin-glyph: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill='black' stroke='black' stroke-width='2' stroke-linejoin='round' d='M12 2.8l2.8 5.8 6.3.9-4.6 4.4 1.1 6.3L12 17.2l-5.6 3 1.1-6.3-4.6-4.4 6.3-.9z'/></svg>"); color: var(--focus-warning); } .fin-sum-icon-bell { --fin-glyph: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' d='M6 16V11a6 6 0 0 1 12 0v5l1.5 2h-15zM10 20.5a2 2 0 0 0 4 0'/></svg>"); } .fin-sum-rings { grid-area: rings; padding: 12px 22px 20px; min-width: 0; } .fin-sum-rings-box { background: transparent !important; box-shadow: none !important; padding: 4px 0 0 !important; } /* the first ring starts on the panel's content edge, the last one ends on it (the name above starts there too) */ .fin-sum-rings-box .row { flex-wrap: nowrap; justify-content: space-between; margin-left: 0; margin-right: 0; } .fin-sum-rings-box .row > .col-3 { flex: 0 0 auto; width: auto; max-width: none; padding-left: 0; padding-right: 0; } .fin-sum-keyfig { grid-area: keyfig; min-width: 0; padding: 18px 20px 12px; border: 1px solid var(--focus-line); border-radius: 12px; background: var(--focus-panel); align-self: stretch; } .fin-sum-switches { display: flex; flex-direction: column; align-items: stretch; gap: 8px; margin-bottom: 14px; } /* the card's title with Follow / Set an alert at its top right */ .fin-sum-keyfig-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; } /* the two choices on ONE line (short labels; the full words are the options' title and screen-reader text) */ .fin-sum-switch-row { display: flex; flex-wrap: wrap; gap: 6px; } .fin-sum-switch-row .seg-control { flex: 0 0 auto; } /* tighter options: both controls fit one line on a 390 px phone */ .fin-sum-switch-row .seg-option { padding-left: 7px !important; padding-right: 7px !important; } /* The six figures are small tiles INSIDE the Key figures card: the ONE exception to the "no card inside a card" rule (founder, 2026-09-29), granted by the founder on 2026-09-30 ("you can keep these 6 small sub-cards, they were actually nice"). Only these six; every other panel stays flat. */ .fin-sum-keyfig .fin-mini { background: var(--focus-raised) !important; box-shadow: none !important; border: 1px solid var(--focus-line); border-radius: 8px !important; } .fin-sum-keyfig .fin-mini b { font-size: var(--font-size-lg) !important; } .fin-sum-bars { grid-area: bars; margin-top: 16px; padding: 18px 20px 6px; border: 1px solid var(--focus-line); border-radius: 12px; background: var(--focus-panel); min-width: 0; } /* the bars fill the panel (proposition A); the title row carries the "Slowio scores, 0–100" note at its right */ .fin-sum-bars-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; flex-wrap: wrap; } .fin-sum-bars .dash-graph, .fin-sum-bars .js-plotly-plot { width: 100%; } .fin-sum-note-right { color: var(--focus-text-muted); white-space: nowrap; font-size: var(--font-size-xs) !important; } .fin-sum-keyfig-title { margin: 0; } .fin-sum-pricegraph .dash-graph, .fin-sum-pricegraph .js-plotly-plot { max-width: 100%; overflow: hidden; } .fin-sum-h { margin: 0 0 10px; font-weight: 500; font-size: var(--font-size-md) !important; } .fin-sum-facts { grid-area: facts; margin-top: 16px; /* the panel background starts 16px down (.fin-sum-bg-about) */ padding: 20px 20px 0; min-width: 0; } .fin-sum-facts .fin-sum-h { margin-bottom: 12px; } .fin-facts { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px 16px; } .fin-sum-facts2 { grid-area: facts2; padding: 12px 20px 16px; } .fin-fact-k { display: block; color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .fin-fact-v { color: var(--focus-text); font-size: var(--font-size-sm) !important; overflow-wrap: anywhere; } .fin-fact-link, .fin-fact-v a { color: var(--focus-text) !important; text-decoration: underline; text-decoration-color: var(--focus-line-strong); text-underline-offset: 3px; } .fin-sum-pricegraph { grid-area: pricegraph; margin-top: 16px; padding: 20px 20px 0 8px; min-width: 0; align-self: center; } /* the secondary buttons' fold (founder's iPhone review, round 3): the page's own fold look (.ffin-fold, 32_focus_fintabs.css), closed by default */ .fin-sum-more { grid-area: compare; margin: 14px 20px 0; padding-bottom: 16px; min-width: 0; } .fin-sum-more[open] { padding-bottom: 8px; } .fin-sum-compare { display: flex; flex-wrap: wrap; gap: 8px; } .fin-sum-compare .btn { white-space: normal; text-align: left; } /* the AI analyst button comes from another callback than the fold: it shows right under the fold's buttons, and only while the fold is open */ .fin-sum-ai { grid-area: ai; padding: 0 20px 20px; display: none; justify-content: flex-start; align-items: flex-start; } .fin-summary:has(.fin-sum-more[open]) .fin-sum-ai { display: flex; } .fin-sum-holders { grid-area: holders; padding: 0 20px 18px; min-width: 0; } .fin-sum-note { grid-area: note; margin-bottom: 12px; } /* A child a callback emits without an area class (a future alert, an Hr) spans a row of its own at the end instead of opening a one-cell gap. */ .fin-sum-c > *:not([class*="fin-sum-"]):not([class*="fin-fact"]) { grid-column: 1 / -1; } /* The three top cards against the window (founder, 2026-10-02): - phones (no side bar): one column; - from 768px, where the side bar appears: name + rings and Key figures share a line, the bars sit below (the facts and the price chart stay stacked until 992px: half of the narrow content is too small for them); - from 1536px (twice that): the bars join the line, so the rings are not spread over a half-page card. */ @media (min-width: 1536px) { .fin-summary { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1.25fr); grid-template-areas: "note note note" "identity keyfig bars" "rings keyfig bars" "facts facts pricegraph" "facts2 facts2 pricegraph" "compare compare compare" "ai ai ai" "holders holders holders"; } .fin-sum-bars { margin-top: 0; align-self: stretch; } } @media (min-width: 768px) and (max-width: 991.98px) { .fin-summary { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); grid-template-areas: "note note" "identity keyfig" "rings keyfig" "bars bars" "facts facts" "facts2 facts2" "pricegraph pricegraph" "compare compare" "ai ai" "holders holders"; } .fin-sum-pricegraph { margin-top: 0; padding: 4px 20px 16px; } /* two half-width cards beside the side bar: tighter insides */ .fin-sum-identity { padding: 16px 16px 6px; } .fin-sum-rings { padding: 10px 16px 16px; } .fin-sum-keyfig { padding: 14px 14px 10px; } /* four 50px rings in a ~216px row: drop their side margins (4 x 58 > 216) */ .fin-sum-rings-box .row .mx-1 { margin-left: 0 !important; margin-right: 0 !important; } } @media (max-width: 767.98px) { .fin-summary { grid-template-columns: 1fr; grid-template-areas: "note" "identity" "rings" "keyfig" "bars" "facts" "facts2" "pricegraph" "compare" "ai" "holders"; } .fin-sum-keyfig { margin-top: 16px; } .fin-sum-pricegraph { margin-top: 0; padding: 4px 20px 16px; } } @media (max-width: 767.98px) { .fin-picker { width: 100%; } .fin-sum-identity, .fin-sum-rings, .fin-sum-facts, .fin-sum-facts2, .fin-sum-ai, .fin-sum-holders, .fin-sum-pricegraph { padding-left: 16px; padding-right: 16px; } .fin-sum-facts { padding-top: 16px; } .fin-sum-more { margin-left: 16px; margin-right: 16px; } /* the fold's line is a 44 px tap target, like the buttons it opens */ .fin-sum-more > summary { min-height: 44px; } .fin-facts { grid-template-columns: repeat(2, minmax(0, 1fr)); } /* the odd one out of a 2-column grid (Sector) takes the whole row */ .fin-facts > .fin-fact:last-child:nth-child(odd) { grid-column: span 2; } /* a 44 px hit area around the 30 px icon buttons (the icon keeps its size) */ .fin-sum-icon::after { content: ""; position: absolute; inset: -7px; } .fin-sum-keyfig { padding: 14px 14px 10px; } .fin-sum-bars { padding: 14px 14px 6px; } /* phones: the compare buttons and the AI button full width, easy to tap */ .fin-sum-compare { flex-direction: column; } .fin-sum-compare > a, .fin-sum-compare .btn, .fin-sum-ai > *, .fin-sum-ai .btn { width: 100%; } .fin-sum-compare .btn, .fin-sum-ai .btn { min-height: 44px; } } /* phones: the ticker is on the subline, so the name drops its bracketed ticker and keeps one line in the common case (desktop unchanged) */ @media (max-width: 575.98px) { .fin-sum-name-tkr { display: none; } } /* ======================== */ /* 3. SCREENING */ /* ======================== */ /* apps/app_screening/function_screening_focus.py — the mockup's screener: a sticky criteria panel on the left (the universe pickers, the thresholds — period included — behind the modal, Run / Save as alert) and the results on the right (the summary, the criteria chips, the score cards, one panel with the three tables as tabs). One column under 992px. */ .fscr .fpage-head { margin-bottom: 20px; } .fscr-layout { display: grid; grid-template-columns: 272px minmax(0, 1fr); gap: 24px; align-items: start; } .fscr-filters { position: sticky; top: 16px; /* the shell's top bar scrolls with the page */ min-width: 0; padding: 20px; border: 1px solid var(--focus-line); border-radius: 12px; background: var(--focus-panel); } .fscr-panel-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 16px; } .fscr-panel-title { margin: 0; font-weight: 500; letter-spacing: -0.02em; font-size: var(--font-size-h3) !important; } .btn.fscr-reset { padding: 0; border: 0; color: var(--focus-accent); text-decoration: none; font-size: var(--font-size-sm) !important; } .btn.fscr-reset:hover, .btn.fscr-reset:focus-visible { color: var(--focus-accent); text-decoration: underline; } .fscr-eyebrow { margin: 0 0 12px; } .fscr-field { margin-bottom: 10px; } .fscr-divider { margin: 18px 0; border-color: var(--focus-line); opacity: 1; } .fscr-hint { margin: 0 0 12px; color: var(--focus-text-muted); line-height: 1.55; font-size: var(--font-size-sm) !important; } .btn.fscr-btn { width: 100%; margin-bottom: 8px; font-size: var(--font-size-sm) !important; } .btn.fscr-run { display: flex; align-items: center; justify-content: space-between; width: 100%; margin: 18px 0 8px; padding: 11px 16px; font-weight: 600; font-size: var(--font-size-sm) !important; } .fscr-arrow { font-weight: 400; } .btn.fscr-learn { white-space: nowrap; } /* the results column */ .fscr-results { min-width: 0; } .fscr-summary { padding: 22px 24px; border: 1px solid var(--focus-line); border-radius: 12px; background: rgba(var(--focus-accent-rgb), 0.06); } .fscr-count { margin: 12px 0 8px; font-weight: 400; line-height: 1; letter-spacing: -0.03em; font-size: var(--font-size-title) !important; } .fscr-count strong { font-weight: 400; font-size: var(--font-size-title) !important; /* the global rule sizes the element itself */ } .fscr-count span { color: var(--focus-text-muted); font-size: var(--font-size-h2) !important; } .fscr-summary p { margin: 0; color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; } .fscr-chips { display: flex; flex-wrap: wrap; gap: 7px; margin: 16px 0 20px; } .fscr-chip { display: inline-flex; align-items: center; padding: 4px 9px; border: 1px solid var(--focus-line); border-radius: 5px; background: var(--focus-raised); color: var(--focus-text); white-space: nowrap; font-size: var(--font-size-xs) !important; } /* the score cards: the classic cards on the panel colour, no shadow */ .fscr-cards { margin: 0 0 20px; } .fscr-cards .card { border: 1px solid var(--focus-line) !important; box-shadow: none !important; } /* The results column is narrower than the classic page (the panel takes 272px): two cards per row until 1400px, the four rings 2x2 under 1200px. */ @media (min-width: 992px) and (max-width: 1399.98px) { .fscr-cards .row > .col-lg-3 { flex: 0 0 50%; max-width: 50%; } } @media (min-width: 768px) and (max-width: 1199.98px) { .fscr-cards .card .col-md-3 { flex: 0 0 50%; max-width: 50%; } } .fscr-cards-hint { margin: 8px 0 0; color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .fscr-panel { min-width: 0; padding: 18px 20px 16px; border: 1px solid var(--focus-line); border-radius: 12px; background: var(--focus-panel); } .fscr-panel .fscr-panel-head { margin-bottom: 14px; } .fscr-tabs { margin-bottom: 14px; } .fscr-note { margin: 14px 0 0; color: var(--focus-text-muted); line-height: 1.6; font-size: var(--font-size-xs) !important; } /* before the first run, a run without a universe, no match */ .fscr-empty { padding: 36px 24px; border: 1px dashed var(--focus-line); border-radius: 12px; text-align: center; } .fscr-empty .fscr-eyebrow { margin-bottom: 8px; } .fscr-empty .fscr-hint { margin: 0 auto; max-width: 420px; } .fscr-empty-title { margin: 0 0 6px; font-weight: 500; font-size: var(--font-size-h3) !important; } @media (max-width: 991.98px) { .fscr-layout { grid-template-columns: 1fr; gap: 20px; } .fscr-filters { position: static; } } @media (max-width: 767.98px) { .fscr-filters { padding: 16px; } .fscr-summary { padding: 18px; } .fscr-panel { padding: 14px 14px 12px; } .fscr-empty { padding: 28px 16px; } } /* ======================== */ /* 4. PORTFOLIOS / WATCHLISTS */ /* ======================== */ /* apps/app_portfolio/function_portfolio_focus.py — restyled, not rebuilt: the frame (the other list + Create / Import as actions, the same tabs in the mockup's order), a selector row (the dropdown slot + the dividends switch), then the classic summary, the tab content and the holdings block as panels. */ .fpf .fpage-head { margin-bottom: 20px; } .fpf .fpage-tabs { margin-bottom: 16px; } .fpf-selector { display: flex; align-items: flex-end; justify-content: space-between; gap: 20px; margin-bottom: 20px; } .fpf-select { flex: 1 1 320px; max-width: 560px; min-width: 0; } .fpf-eyebrow { margin: 0 0 6px; } .fpf-dividends { flex: none; padding-bottom: 6px; } .fpf-dividends .form-check { margin: 0; } .btn.fpf-action { white-space: nowrap; } /* the summary's cards: the panel colour instead of their bg-transparent (the skin already removes the shadow); the inner tiles one step lighter. Scoped to the summary slot so a coloured card in a tab keeps its colour. */ .fpf-body #portfolio_resume_performance .card { border: 1px solid var(--focus-line) !important; border-radius: 12px !important; background: var(--focus-panel) !important; } .fpf-body #portfolio_resume_performance .card .card { border-radius: 8px !important; background: var(--focus-raised) !important; } /* the summary callback closes with a rule; the panels below draw their own */ .fpf-body #portfolio_resume_performance > div > hr { display: none; } /* The four groups of the summary (Performance, Capital, Income & FX, Recent var): a group alone takes the whole row (a fresh watchlist shows only "Recent var"); a tile's number never wraps mid-number. The group columns are the ones holding a card — the half-width tiles inside a group are .col-6 too and keep their width. */ .fpf-body #portfolio_resume_performance .card > .card-body > .row > .col-6:only-child:has(> .card) { flex: 0 0 100%; max-width: 100%; } .fpf-body #portfolio_resume_performance .macro-outlook-tile-value { white-space: nowrap; } /* under 400px one group per row: two side by side left ~80px per tile and "30.27 %" / "7D var. %" wrapped inside the number (audit 2026-09-24) */ @media (max-width: 399.98px) { .fpf-body #portfolio_resume_performance .card > .card-body > .row > .col-6:has(> .card) { flex: 0 0 100%; max-width: 100%; } } /* the tab content and the holdings block sit in one panel */ .fpf-body #portfolio-tabs-content, .fpf-body #_dum_div_portfolio_tabs_content_finished_render_holding_first_callback { padding: 18px 20px 16px; border: 1px solid var(--focus-line); border-radius: 12px; background: var(--focus-panel); min-width: 0; } /* On the Holdings tab the slot holds only the render-ping wrapper (the table lives in the static block below): no empty panel then. */ .fpf-body #portfolio-tabs-content:empty { display: none; } /* two rules on purpose: a browser without :has() drops only this one */ .fpf-body #portfolio-tabs-content:not(:has(> :not([id$="_to_ping_holding_table_update"]))) { display: none; } .fpf-body #_dum_div_portfolio_tabs_content_finished_render_holding_first_callback { margin-top: 16px; /* air under the summary, or under the tab slot when both show */ } .fpf-body #_dum_div_portfolio_tabs_content_finished_render_holding_first_callback > hr { display: none; } /* an empty portfolio / watchlist: a quiet panel with the classic words (function_portfolio_focus.focus_empty_state) */ .fpf-empty { margin: 0 0 16px; padding: 22px 24px; border: 1px dashed var(--focus-line); border-radius: 12px; } .fpf-empty-title { margin: 0 0 4px; font-weight: 500; font-size: var(--font-size-md) !important; } .fpf-empty-text { margin: 0; color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; } /* the performance chart's period pills (the skin's .seg-control > .btn) */ .fpf-period { margin-left: auto; } /* an empty container has no summary card (an empty div in its column): the Performance card takes the whole row instead of the right half */ .fpf-body #portfolio_resume_performance .row > .col-lg-6:has(> div:empty) { display: none; } .fpf-body #portfolio_resume_performance .row > .col-lg-6:has(> div:empty) + .col-lg-6 { flex: 0 0 100%; max-width: 100%; } @media (max-width: 767.98px) { .fpf-selector { flex-direction: column; align-items: stretch; gap: 12px; margin-bottom: 16px; } .fpf-select { flex: none; /* the column must not keep the row's 320px basis as a height */ max-width: none; } .fpf-body #portfolio-tabs-content, .fpf-body #_dum_div_portfolio_tabs_content_finished_render_holding_first_callback { padding: 14px 14px 12px; } } /* ======================== */ /* 7. FOUNDER PAGES */ /* ======================== */ /* The founder pages (Audience, System, Email & content, Data robots — 2026-09-23), built from ONE module: functions/function_founder_page.py. The page's switch as the header action, a band of headline numbers, tabs, then panels (the same dbc.Card as every chart card, so a table beside a chart reads as one family). The classic UI gets the same markup unstyled but usable (founder-only pages). */ /* The band of headline numbers: one panel split into equal cells. The 1px gap over a line-coloured background draws the dividers, so a wrapped row on a phone still has clean lines; flex (not grid) lets the last row's cells grow, so no empty line-coloured cell is ever left. */ .ffounder-kpis { display: flex; flex-wrap: wrap; gap: 1px; margin: 0 0 16px; /* a Bootstrap .row: no side margins here */ border: 1px solid var(--focus-line); border-radius: 12px; background: var(--focus-line); overflow: hidden; } /* 170px basis: a cell never shrinks under it, so a long value wraps between its words instead of clipping (audit 2026-09-24) */ .ffounder-kpi { flex: 1 1 170px; min-width: 0; margin-top: 0; padding: 18px 22px 16px; background: var(--focus-panel); } /* seven cells (the ETF robot): four per row on a laptop — flex alone put six on the first row and the seventh alone, wide, under them; from 1600px the seven fit on one row */ @media (min-width: 768px) and (max-width: 1599.98px) { .ffounder-kpis:has(> :nth-child(7)) > .ffounder-kpi { flex-basis: max(24%, 170px); } } .ffounder-kpi-label, .ffounder-fact .ffounder-kpi-label { color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; } /* a value made of words ("This computer", "7 days ago") wraps between its words; a bare number has no space in it, so it never breaks (a number with its unit, "USD 12", may break between the two) */ .ffounder-kpi-value { margin: 8px 0 6px; font-weight: 400; letter-spacing: -0.04em; line-height: 1.1; font-variant-numeric: tabular-nums; white-space: normal; font-size: var(--font-size-title) !important; } .ffounder-kpi-note { color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .ffounder-kpi-note.up { color: var(--focus-positive); } .ffounder-kpi-note.down { color: var(--focus-accent-hover); /* the skin's readable red for text */ } .ffounder-kpi-note.warn { color: var(--focus-warning); } /* The one question a tab answers, as its first line. */ .ffounder-question { margin: 0 0 16px; max-width: 760px; color: var(--focus-text-soft); line-height: 1.5; font-size: var(--font-size-lg) !important; } .ffounder-panel { margin-top: 0 !important; margin-bottom: 16px !important; min-width: 0; } .ffounder-panel-sub { margin: 0 0 12px; max-width: 760px; color: var(--focus-text-muted); line-height: 1.5; font-size: var(--font-size-sm) !important; } .ffounder-subhead { margin: 14px 0 8px; font-weight: 500; letter-spacing: -0.01em; font-size: var(--font-size-md) !important; } .ffounder-subhead:first-child { margin-top: 0; } .ffounder-footnote, .ffounder-note { margin: 8px 0 16px; color: var(--focus-text-muted); line-height: 1.5; font-size: var(--font-size-xs) !important; } .ffounder-empty { margin: 8px 0 16px; color: var(--focus-text-muted); font-size: var(--font-size-md) !important; } .ffounder-row { margin-bottom: 0; } /* A chart slot with nothing to draw keeps its height. */ .ffounder-empty-chart { display: flex; align-items: center; justify-content: center; margin: 8px 0 16px; border: 1px dashed var(--focus-line); border-radius: 12px; } /* No card inside a card (founder, 2026-09-29): inside a panel the empty slot, the band and the list of folds lose their own frame — the band keeps its dividers, the folds their rules between them. */ .card .ffounder-empty-chart { border: 0; border-radius: 0; } /* the band in a panel: no frame and no fill of its own (the founder panels are page-coloured); the dividers are drawn by each cell's right and bottom edge, clipped at the band's own edges */ .card .ffounder-kpis { gap: 0; border: 0; border-radius: 0; background: transparent; } .card .ffounder-kpi { background: transparent; box-shadow: 1px 1px 0 var(--focus-line); } .card .ffounder-accordion .accordion-item { border-left: 0; border-right: 0; border-radius: 0 !important; background: transparent; } .card .ffounder-accordion .accordion-item:last-of-type { border-bottom: 0; } .card .ffounder-accordion .accordion-button { padding-left: 0; padding-right: 0; border-radius: 0 !important; background: transparent; } .card .ffounder-accordion .accordion-body { padding-left: 0; padding-right: 0; } .ffounder-empty-chart-text { margin: 0; color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; } /* Activity: the view pills, the live window, the three pickers. */ .ffounder-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 16px; margin-bottom: 16px; } .ffounder-filters { display: grid; grid-template-columns: repeat(3, minmax(0, 220px)); gap: 10px; flex: 1 1 520px; } .ffounder-accordion { margin: 4px 0 16px; } /* The person window's facts. */ .ffounder-fact { height: 100%; padding: 12px 14px; border: 1px solid var(--focus-line); border-radius: 10px; background: var(--focus-panel); } .ffounder-fact-value { margin-top: 4px; word-break: break-word; font-size: var(--font-size-md) !important; } /* A "label: value" line (the deploy facts, the job window). */ .ffounder-line { margin: 0 0 6px; line-height: 1.5; word-break: break-word; font-size: var(--font-size-md) !important; } /* A log excerpt: fixed-width text scrolling inside its own box. */ .ffounder-log { max-height: 520px; overflow: auto; margin: 0 0 16px; padding: 12px 14px; border: 1px solid var(--focus-line); border-radius: 10px; background: var(--focus-page); color: var(--focus-text-soft); white-space: pre-wrap; word-break: break-word; font-size: var(--font-size-xs) !important; } .ffounder-select { flex: 1 1 260px; max-width: 460px; } /* A run's progress bar with its percentage beside it (a label inside a narrow fill was clipped), and the robots' number inputs. */ .ffounder-progress-row { display: flex; align-items: center; gap: 12px; } .ffounder-progress-row .ffounder-progress { flex: 1 1 auto; min-width: 0; } .ffounder-progress { height: 12px; } .ffounder-progress-label { flex: none; min-width: 3.5em; text-align: right; font-variant-numeric: tabular-nums; font-size: var(--font-size-sm) !important; } .ffounder-inputgroup { width: auto; flex-wrap: nowrap; } .ffounder-number { max-width: 96px; } /* The email preview: our own email HTML in a sandboxed frame, on white. */ .ffounder-email-frame { width: 100%; height: 900px; border: 1px solid var(--focus-line); border-radius: 10px; background: #fff; } @media (max-width: 575.98px) { .ffounder-inputgroup { width: 100%; flex-wrap: wrap; } } @media (max-width: 767.98px) { .ffounder-kpi { flex-basis: 40%; padding: 14px 16px 12px; } .ffounder-filters { grid-template-columns: minmax(0, 1fr); flex-basis: 100%; } } /* ======================== */ /* 8. SHARED PAGE PARTS */ /* ======================== */ /* functions/function_focus_parts.py — the parts the analysis pages share (Government bonds first, Macro data next, 2026-09-23): the row of labelled controls on top of a tab, the mockup's field (a small label, the bare control, a hint), chips for a multiple choice, panels with a sentence title and one line under it (the classic chart cards, retitled), the band of large numbers, two-column grids, the small note. */ .fpart-controls { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 14px 18px; margin: 0 0 16px; } .fpart-field { flex: 0 1 auto; min-width: 0; } .fpart-field-wide { flex: 1 1 320px; } .fpart-field-label, label.fpart-field-label { display: block; margin: 0 0 5px; color: var(--focus-text-muted); font-weight: 400 !important; letter-spacing: 0; font-size: var(--font-size-xs) !important; } .fpart-field .form-select, .fpart-field .form-control { min-width: 180px; } .fpart-field .dash-dropdown { min-width: 240px; } .fpart-field-hint { display: block; margin-top: 5px; color: var(--focus-text-muted); font-style: normal; font-size: var(--font-size-xs) !important; } .fpart-tag { display: inline-flex; align-items: center; margin-bottom: 6px; padding: 4px 9px; border: 1px solid var(--focus-line); border-radius: 5px; color: var(--focus-text-muted); white-space: nowrap; font-size: var(--font-size-xs) !important; } /* chips: a dbc.Checklist, one bordered pill per option, coral when on */ .fpart-chips { display: flex; flex-wrap: wrap; gap: 6px; } .fpart-chips .form-check, .fpart-chips .form-check-inline { margin: 0; padding: 0; min-height: 0; } .fpart-chips .fpart-chip-input { position: absolute; appearance: none; -webkit-appearance: none; opacity: 0; width: 0; height: 0; margin: 0 !important; /* the vendor inline-check margins */ padding: 0; border: 0; pointer-events: none; } .fpart-chips .fpart-chip { display: inline-block; margin: 0 !important; padding: 5px 11px; border: 1px solid var(--focus-line); border-radius: 999px; color: var(--focus-text-muted); cursor: pointer; line-height: 1.3; white-space: nowrap; font-size: var(--font-size-sm) !important; transition: color 0.15s ease, border-color 0.15s ease, background-color 0.15s ease; } .fpart-chips .fpart-chip:hover { color: var(--focus-text); border-color: var(--focus-line-strong); } .fpart-chips .fpart-chip-on { border-color: var(--focus-accent); background-color: rgba(var(--focus-accent-rgb), 0.12); color: var(--focus-text); } .fpart-chips .fpart-chip-input:focus-visible + .fpart-chip { outline: 2px solid var(--focus-accent); outline-offset: 1px; } /* panels: the classic cards, retitled (function_focus_parts.retitle_card) */ .card.fpart-panel { margin: 0 !important; border: 1px solid var(--focus-line) !important; border-radius: 12px !important; background: var(--focus-panel) !important; box-shadow: none !important; min-width: 0; } .fpart-panel > .card-header { padding: 18px 20px 4px; border-bottom: 0; background: transparent; } .fpart-panel > .card-header .text-section-heading, .fpart-panel > .card-header.text-section-heading, .fpart-panel > .card-header .text-section-heading > span { font-weight: 500; letter-spacing: -0.02em; font-size: var(--font-size-h3) !important; } .fpart-panel > .card-body { padding: 12px 20px 18px; } /* No card inside a card (founder, 2026-09-29). A panel whose job is to hold other panels (a title and a view switch over several cards) is a GROUP: it keeps its head and its content and loses the frame — border, background, padding — so the cards inside sit on the page as siblings and a phone gets their full width back. Add the class to such a panel (never to a panel whose content is not carded, or that content would sit bare on the page). */ .card.fpart-panel.fpart-group { border: 0 !important; border-radius: 0 !important; background: transparent !important; } .card.fpart-panel.fpart-group > .card-header { padding: 8px 0 12px; } .card.fpart-panel.fpart-group > .card-body { padding: 0; } .fpart-sub { display: block; margin: 3px 0 0; color: var(--focus-text-muted); font-weight: 400; letter-spacing: 0; font-size: var(--font-size-xs) !important; } /* labels the classic builders keep inside a panel (series builders) */ .fpart-panel label.fw-bold { font-weight: 400 !important; } /* the band of large numbers: one panel split into cells (the 1px gap over a line-coloured background draws the dividers) */ .fpart-stats { display: flex; flex-wrap: wrap; gap: 1px; border: 1px solid var(--focus-line); border-radius: 12px; background: var(--focus-line); overflow: hidden; } .fpart-stat { flex: 1 1 170px; min-width: 0; padding: 16px 20px 14px; background: var(--focus-panel); } .fpart-stat-label { display: block; color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; } .fpart-stat-value { display: block; margin: 6px 0 4px; font-weight: 400; letter-spacing: -0.04em; line-height: 1.1; white-space: nowrap; font-variant-numeric: tabular-nums; font-size: var(--font-size-stat) !important; } .fpart-stat-value.fpart-pos { color: var(--focus-positive); } .fpart-stat-value.fpart-neg { color: var(--focus-accent-hover); } .fpart-stat-detail { display: block; color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .fpart-two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 16px; align-items: start; } .fpart-two-wide { grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr); } .fpart-note { margin: 12px 0 0; color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .fpart-copy { margin: 0 0 14px; max-width: 560px; color: var(--focus-text-soft); } .fpart-links { display: flex; flex-wrap: wrap; gap: 10px; } .fpart-table-title { margin: 0 0 6px; color: var(--focus-text-muted); font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; font-size: var(--font-size-xs) !important; } .fpart-stub { display: none; } .btn.fpart-learn, .btn.fpart-link-btn { white-space: nowrap; } /* the page's Learn more at the foot of the body (function_focus_parts.learn_more_foot) */ .fpart-learn-foot { display: flex; justify-content: center; margin-top: 28px; padding-top: 18px; border-top: 1px solid var(--focus-line); } /* the shared cross-module tab (bonds, macro data) */ .fpart-cross { display: flex; flex-direction: column; gap: 16px; } @media (max-width: 991.98px) { .fpart-two, .fpart-two-wide { grid-template-columns: minmax(0, 1fr); } } @media (max-width: 767.98px) { .fpart-field, .fpart-field-wide { flex: 1 1 100%; } .fpart-field .form-select, .fpart-field .form-control, .fpart-field .dash-dropdown { min-width: 0; width: 100%; } .fpart-stat { flex: 1 1 45%; padding: 14px 16px 12px; } .fpart-panel > .card-header { padding: 16px 16px 4px; } .fpart-panel > .card-body { padding: 10px 16px 16px; } } /* ---- 8b. Sticky tab bars (founder, 2026-09-30: "when we scroll down, the tab bar sticks at the top, like a table header; a tab and a sub-tab stack") ---- Level 1 = the page's tab bar (page_frame's `.fpage-tabs` slot, every Focus page with tabs). Level 2 = a bar inside a tab that switches whole sections (Portfolios > Performance sub-views, Macro data > Correlations sub-tabs; a new one takes the class `fsticky-sub`); it stacks under level 1. Not level 2: in-chart choices (Bars / Lines, periods, units). - The shell's top bar scrolls away with the page (only the sidebar is fixed), so the first bar sticks one bleed under the viewport's top edge (--fstick-top; the bleed fills the strip above it, so the pills never touch the edge). Were the top bar ever fixed, --fstick-top becomes var(--fshell-top) and nothing else changes. - --fstick-l1 = level 1's height. It varies (from 768px a bar wraps: Macro data = two rows at 1440) and CSS cannot read one element's height into another's `top`, so 30_focus_shell.js writes it on <html> (and data-fstick-stack, the bottom of the lowest bar stuck right now, which the table-header band of table-sticky-headers.js sits under). - Page background, full bleed: the bar paints the page colour under itself and, through a spread shadow clipped to its own row, across the gutters and --fstick-bleed above / below — no layout change, so every page keeps its own spacing around the bar. - Layers: above the tables' sticky columns (05_tables.css, z 1-3), below dcc.Dropdown menus (1000: a page-head picker still opens over the bar), the table-header band (1000, placed under the bars), the sidebar (1020: the bleed runs under it), drawer and modals. - Both levels paint the page colour at rest too (the bleed is never seen then: every bar has >= 8px of free page around it). A bar inside a card paints the card's colour instead (--fstick-bg, set on the card: `.fscr-panel:has(> .fscr-tabs)`, the Screening results). - Every ancestor up to <body> is free of overflow / transform / contain (checked 2026-09-30 in WebKit and Chromium on Portfolios, Stock financials, ETF analysis, Macro data, System, Screening, Subscription; the Screening results card's `overflow: clip` is no scroll container, so it does not count); a bar that scrolls sideways at 390 (skin section 6) keeps doing so: the overflow is on the bar itself, never on an ancestor. */ :root { --fstick-bleed: 8px; --fstick-top: var(--fstick-bleed); --fstick-l1: 0px; /* measured by 30_focus_shell.js */ --fstick-z: 6; } /* the bars: level 1 = the page's slot, the bar of a slot that carries its content (below) and the Screening results' tabs (a page without a page bar); level 2 = the sub-view bars (the next rule). --fstick-bg = the colour a bar paints (the page's, or its card's: below). The :has entry sits in a forgiving :is(): an engine without :has drops only it. */ .fpage-tabs, :is(.fpage-tabs:has(> .tab-content > .tab-pane > :not(:empty)) > .nav-tabs), .fscr-tabs, :is(.fsticky-sub, .fpf-perf-subtabs, [id="macro-correlations-subtabs"]) { position: sticky; top: var(--fstick-top); z-index: var(--fstick-z); background-color: var(--fstick-bg, var(--focus-page)); box-shadow: 0 0 0 100vmax var(--fstick-bg, var(--focus-page)); clip-path: inset(calc(-1 * var(--fstick-bleed)) -100vmax); } /* under level 1 AND its bleed; slides under level 1 at its section's end */ :is(.fsticky-sub, .fpf-perf-subtabs, [id="macro-correlations-subtabs"]) { top: calc(var(--fstick-top) + var(--fstick-l1) + var(--fstick-bleed)); z-index: calc(var(--fstick-z) - 1); } /* a level-2 bar that is a panel HEADER (the Inflation lab's "The context": title + tabs, in a frameless group card): the panel rule above paints the header transparent (its selector out-ranks the :is() list), so the stuck header takes the page colour back here; the containing block is the card, so the header slides away at the section's end like the others (the Portfolios "Explore further" toolbar needs nothing: no rule paints it) */ .fpart-panel > .card-header.fsticky-sub { background-color: var(--fstick-bg, var(--focus-page)); } /* A slot whose tabs carry their content (Subscription builds its four views inside dbc.Tabs) holds the whole page: it stays in the flow and its BAR sticks instead (the bar's containing block is the slot, i.e. the page's content). A label-only slot cannot do the same — its bar's containing block would be the slot itself, one bar high, so the bar would never move — hence the slot is the sticky box there. Label-only tabs render an empty <div> per pane. */ .fpage-tabs:has(> .tab-content > .tab-pane > :not(:empty)) { position: static; background-color: transparent; box-shadow: none; clip-path: none; } /* A bar inside a card (the Screening results panel; `.fscr-panel` is shared with other pages, hence the :has) paints the card's colour, and the card keeps the spread inside its own padding box: `overflow: clip` clips without making the card a scroll container, so the bar still sticks to the viewport (`hidden` would stop it). An engine without `overflow: clip` (Safari < 16) keeps the bar in the flow, spread off: unclipped, it would paint the card colour across the whole page. */ .fscr-panel:has(> .fscr-tabs) { --fstick-bg: var(--focus-panel); overflow: clip; } @supports not (overflow: clip) { .fscr-tabs { position: static; box-shadow: none; clip-path: none; } } /* the stuck strip is the bar itself: the tabs are inline-flex, so a Bootstrap margin on them (the calculators' `mb-4`) sat INSIDE the slot's line box and hung an empty 24px band under the stuck pills; the gap under the bar is the slot's own margin (each page sets it; the calculators' tabs-to-content gap goes 42px -> 18px, like the others) */ .fpage-tabs > .nav-tabs { margin-bottom: 0 !important; } /* a page's sticky side panel stays clear of the stuck tab bar (the panels are sticky from 992px only; under it they are static, uncapped) */ @media (min-width: 992px) { .fpage:has(> .fpage-tabs) :is(.fcal-inputs, .fdcf-side, .fscr-filters, .fmo-aside) { top: calc(var(--fstick-top) + var(--fstick-l1) + 16px); } .fpage:has(> .fpage-tabs) :is(.fcal-inputs, .fdcf-side) { max-height: calc(100vh - var(--fstick-top) - var(--fstick-l1) - 32px); max-height: calc(100dvh - var(--fstick-top) - var(--fstick-l1) - 32px); } } /* ======================== */ /* 9. GOVERNMENT BONDS */ /* ======================== */ /* apps/app_bonds/function_bonds_focus.py — the mockup's bonds page (founder approved the drawing 2026-09-23): the four numbers as the summary band, the six tabs, each tab a row of controls then its panels; the term structure beside the maturity table; the US slope's signal block (the gap in basis points, large); the history's statistics as a band. */ .fbond .fpage-summary { margin-bottom: 20px; } .fbond-tab { display: flex; flex-direction: column; gap: 16px; } .fbond-tab > .fpart-controls { margin-bottom: 0; } .fbond-curve-grid { display: grid; grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); gap: 16px; align-items: start; } .fbond-shape { margin: 0 0 10px; } /* the signal block: the tinted panel of the mockup */ .fbond-signal-slot:empty { display: none; } .fbond-signal { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: 16px 28px; padding: 22px 24px; border: 1px solid var(--focus-line); border-radius: 12px; background: rgba(var(--focus-accent-rgb), 0.07); } .fbond-signal-main { min-width: 0; } .fbond-signal-value { margin: 8px 0 4px; font-weight: 400; letter-spacing: -0.04em; line-height: 1; font-variant-numeric: tabular-nums; font-size: var(--font-size-title) !important; } .fbond-signal-value span { margin-left: 8px; letter-spacing: 0; color: var(--focus-text-muted); font-size: var(--font-size-md) !important; } .fbond-signal-text { margin: 0; color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; } .fbond-signal-line, .fbond-spread-line { display: flex; flex-wrap: wrap; gap: 6px 22px; color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; } .fbond-signal-line b, .fbond-spread-line b { color: var(--focus-text); font-weight: 500; font-variant-numeric: tabular-nums; } .fbond-spread-line { margin-top: 10px; } .fbond-toolbar { margin-top: 10px; } .fbond-toolbar .fpart-controls { margin: 0; } .fbond-toolbar-title { margin: 0 0 14px; font-weight: 500; letter-spacing: -0.02em; font-size: var(--font-size-h2) !important; } /* the series tags of the correlation builder as chips */ .fbond-tab .fpart-panel .badge, .fpart-cross .fpart-panel .badge { padding: 6px 10px; border-radius: 999px; font-weight: 500; font-size: var(--font-size-xs) !important; } @media (max-width: 991.98px) { .fbond-curve-grid { grid-template-columns: minmax(0, 1fr); } } @media (max-width: 767.98px) { .fbond-signal { padding: 18px; } } /* ======================== */ /* 10. MACRO DATA */ /* ======================== */ /* apps/app_macro/function_macro_focus.py — the mockup's macro page (founder approved the drawing 2026-09-23): the controls as one row of fields and the four latest numbers as the band above the tabs; each tab's cards as panels (the classic Bootstrap rows keep the two-column arrangements) and its number cards as bands. Shared parts: section 8. */ .fmac .fpage-summary { margin-bottom: 20px; } .fpart-controls.fmac-controls { align-items: flex-start; /* the labels on one line (the countries field carries a hint) */ } .fmac-controls .form-select { min-width: 110px; } .fmac-eyebrow { margin: 0 0 8px; } .fmac-tab { display: flex; flex-direction: column; gap: 16px; } /* the classic rows of a tab (not the rows inside a panel): panels side by side, even gaps when they stack */ .fmac-tab > div > .row { --bs-gutter-y: 16px; margin-bottom: 0 !important; } .fmac-tab > div > .row > [class*="col"] { margin-bottom: 0 !important; } /* the classic lead sentence and notes under the panels */ .fmac-tab > div > p.text-muted, .fmac-tab small.text-muted { color: var(--focus-text-muted) !important; font-size: var(--font-size-xs) !important; } .fmac-ts-stats { margin-bottom: 16px; } /* the correlation sub-tabs sit like the page tabs, smaller */ .fmac-tab .nav-tabs { margin-bottom: 16px !important; } /* ======================== */ /* 11. MACRO OUTLOOK */ /* ======================== */ /* apps/app_macro_outlook/function_macro_outlook_focus.py — both editions (founder approved the drawing 2026-09-23): the edition switch in the header, the opening panel with the bottom line, the six model figures as a band, then the report as a reading panel with its charts in place and an "In this report" panel beside it (sticky from 1200px), the sources folded, the disclaimer. */ .fmo-hero { margin: 0 0 18px; padding: 22px 26px; border: 1px solid var(--focus-line); border-radius: 12px; background: rgba(var(--focus-accent-rgb), 0.06); } .fmo-hero-kicker { margin: 14px 0 4px; color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; } .fmo-hero-text { margin: 0 0 12px; max-width: 880px; line-height: 1.45; letter-spacing: -0.01em; font-size: var(--font-size-h3) !important; } .fmo-hero-intro, .fmo-hero-meta { margin: 0 0 4px; max-width: 820px; color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; } .fmo-eyebrow { margin: 0 0 8px; } /* six figures: one row on a wide screen, two rows of three in between, three rows of two on a phone (the shared phone rule) — never one alone */ .fmo-figures .fpart-stat { flex: 1 1 30%; } @media (min-width: 1400px) { .fmo-figures .fpart-stat { flex: 1 1 0; } } .fmo-figures .fpart-stat-detail { overflow-wrap: anywhere; } /* the model writes these values: a long one wraps instead of being cut */ .fmo-figures .fpart-stat-value { white-space: normal; overflow-wrap: anywhere; } .fmo-grid { display: grid; grid-template-columns: minmax(0, 1fr) 260px; gap: 16px; align-items: start; margin-top: 16px; } .fmo-report { min-width: 0; padding: 22px 26px; border: 1px solid var(--focus-line); border-radius: 12px; background: var(--focus-panel); } .fmo-report-title { margin: 0 0 10px; } .fmo-section { scroll-margin-top: 16px; } .fmo-aside { position: sticky; top: 16px; display: flex; flex-direction: column; gap: 16px; } .fmo-aside-panel { padding: 16px 18px; border: 1px solid var(--focus-line); border-radius: 12px; background: var(--focus-panel); } .fmo-aside-title { margin: 0 0 8px; font-weight: 500; letter-spacing: -0.01em; font-size: var(--font-size-md) !important; } .fmo-toc { margin: 0; padding: 0; list-style: none; } .fmo-toc li { margin: 0 0 6px; } .fmo-toc a, .fmo-links a { color: var(--focus-accent); text-decoration: none; font-size: var(--font-size-sm) !important; } .fmo-toc a:hover, .fmo-links a:hover { text-decoration: underline; } .fmo-links { display: grid; gap: 6px; margin-top: 8px; } .fmo-sources { margin-top: 16px; } /* below 1200px (tablets, and laptops with the sidebar open) the report takes the full width, so its charts keep their wide layout; the contents list comes BEFORE the report, the context links after it */ @media (max-width: 1199.98px) { .fmo-grid { grid-template-columns: minmax(0, 1fr); } .fmo-aside { display: contents; } .fmo-aside-contents { order: -1; } } @media (max-width: 767.98px) { .fmo-hero, .fmo-report { padding: 16px; } .fmo-figures .fpart-stat { flex: 1 1 45%; } }
/* 23_focus_calculators.css */
/* ======================================================================== */ /* Focus redesign, layer C: the calculators (growth tools + DCF). */ /* Moved out of 22_focus_pages.css on 2026-09-23 so the calculator session */ /* and the other layer-C sessions never edit the same file. Focus bundle */ /* only (combine_css_files.py appends it after 22). Sizes = tokens. */ /* ======================================================================== */ /* ======================== */ /* 5. GROWTH CALCULATOR */ /* ======================== */ /* apps/app_calculator/function_calculator_focus.py — the mockup's lab: the intro strip, then per tool the inputs in a panel on the left that stays in view (the mockup's fields: label with the unit beside it, the control, a hint line; the model note at the foot), the outcome on the right: the tool's result tiles drawn as the mockup's result block (the first as the answer, large; the others as the breakdown line), then its charts in panels with the mockup's titles. One column under 992px. */ .fcal .fpage-tabs { margin-bottom: 18px; } .fcal-tag, .fdcf-tag { display: inline-flex; align-items: center; padding: 4px 9px; border: 1px solid var(--focus-line); border-radius: 5px; background: var(--focus-raised); color: var(--focus-text-muted); white-space: nowrap; font-size: var(--font-size-xs) !important; } .fcal-intro { display: flex; align-items: center; gap: 24px; padding: 22px 26px; border: 1px solid var(--focus-line); border-radius: 12px; background: var(--focus-panel); } .fcal-intro-mark { font-family: Georgia, "Times New Roman", serif; font-style: italic; line-height: 1; color: var(--focus-text-muted); font-size: var(--font-size-title) !important; } .fcal-intro-text { margin: 0; line-height: 1.6; color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; } .fcal-intro-text strong { color: var(--focus-text); font-weight: 400; } .fcal-intro-link { margin-left: auto; max-width: 220px; text-align: right; color: var(--focus-accent); text-decoration: none; font-size: var(--font-size-sm) !important; } .fcal-intro-link:hover { text-decoration: underline; color: var(--focus-accent); } .fcal-lab { display: grid; grid-template-columns: 320px minmax(0, 1fr); gap: 22px; align-items: start; } .fcal-inputs { position: sticky; top: 16px; max-height: calc(100vh - 32px); max-height: calc(100dvh - 32px); overflow-y: auto; min-width: 0; padding: 22px; border: 1px solid var(--focus-line); border-radius: 12px; background: var(--focus-panel); } .fcal-eyebrow { margin: 0 0 8px; } .btn.fcal-learn, .btn.fdcf-learn { white-space: nowrap; } .fcal-sentence { margin: 0 0 18px; font-weight: 500; line-height: 1.3; letter-spacing: -0.02em; font-size: var(--font-size-h2) !important; } /* the parameter card(s) lose their card look inside the panel */ .fcal-fields .card { border: 0 !important; border-radius: 0 !important; background: transparent !important; box-shadow: none !important; margin: 0 !important; } .fcal-fields .card + .card { margin-top: 14px !important; padding-top: 14px; border-top: 1px solid var(--focus-line) !important; } .fcal-fields .card-header { padding: 0 0 8px; border: 0; background: transparent; color: var(--focus-text-muted); font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; font-size: var(--font-size-xs) !important; } .fcal-fields .card-body { padding: 0; } /* one field per line in the panel */ .fcal-fields .row > [class*="col"] { flex: 0 0 100%; max-width: 100%; } .fcal-fields .mb-3:has(> .fcal-field) { margin-bottom: 0 !important; /* the field carries its own spacing */ } /* the mockup's field: label + unit on one line, the control, a hint */ .fcal-field { margin-bottom: 14px; } .fcal-field-label { display: flex; justify-content: space-between; align-items: center; gap: 10px; margin: 0 0 5px; color: var(--focus-text); font-size: var(--font-size-sm) !important; } .fcal-unit { color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .fcal-field .form-control, .fcal-field .form-select, .fcal-control { width: 100%; min-height: 41px; font-variant-numeric: tabular-nums; font-size: var(--font-size-md) !important; } .fcal-field-hint { display: block; margin-top: 5px; color: var(--focus-text-muted); font-style: normal; font-size: var(--font-size-xs) !important; } /* labels the builders keep (switches, radios, selects with their own rows) */ .fcal-fields .form-label { margin-bottom: 4px; font-size: var(--font-size-sm) !important; } .fcal-fields .form-text { font-size: var(--font-size-xs) !important; } /* a caution under a field (the Kelly leverage line) is a quiet hint, not Bootstrap's orange text — the panel's palette has one accent */ .fcal-fields .form-text.text-warning { color: var(--focus-text-muted) !important; } .fcal-fields .btn-primary, .fcal-fields .btn-success { width: 100%; } /* the comparison switch sits right under the values switch (the drawing: the two switches together above Reset); the builder's rule and spacing around it are dropped */ .fcal-compare { margin-top: 0; } .fcal-compare hr { display: none; } .fcal-compare .mt-4 { margin-top: 0 !important; } .fcal-compare .form-switch { margin-bottom: 0; } /* the growth tool's "Costs and inflation" sub-heading over the fee, the cost and inflation (arrange_growth_fields) */ .fcal-subhead { margin: 4px 0 10px; padding-top: 14px; border-top: 1px solid var(--focus-line); } /* the Zoom row of a chart that is static under Focus (the position range bars): hidden, the switch stays in the tree as a State */ .fcal-zoom-hidden { display: none !important; } /* the breakeven sentence under its chart: a plain line, not green bold */ .fcal-breakeven-line { margin: 8px 0 4px; color: var(--focus-text); font-size: var(--font-size-sm) !important; } /* the reset link above the note */ .btn.fcal-reset { display: inline-block; margin: 6px 0 4px; padding: 0; border: 0; color: var(--focus-accent); text-decoration: none; font-size: var(--font-size-sm) !important; } .btn.fcal-reset:hover, .btn.fcal-reset:focus-visible { color: var(--focus-accent); text-decoration: underline; } /* the model note at the foot of the panel (the classic disclaimer alert) */ .fcal-note { margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--focus-line); color: var(--focus-text-muted); line-height: 1.7; font-size: var(--font-size-xs) !important; } .fcal-note strong { color: var(--focus-text); font-weight: 500; } .fcal-outcome { min-width: 0; } /* the charts and the other cards: panels with the mockup's titles */ .fcal-outcome .card.custom-card-shadow { box-shadow: none !important; border: 1px solid var(--focus-line) !important; border-radius: 12px !important; background: var(--focus-panel) !important; } .fcal-outcome .card-header { background: transparent; border-bottom: 0; padding-bottom: 0; } .fcal-outcome .card-header .text-section-heading { font-weight: 500; letter-spacing: -0.02em; font-size: var(--font-size-h3) !important; } .fcal-chart-sub { display: block; margin-top: 3px; color: var(--focus-text-muted); font-weight: 400; letter-spacing: 0; font-size: var(--font-size-xs) !important; } @media (max-width: 991.98px) { .fcal-lab { grid-template-columns: 1fr; gap: 18px; } .fcal-inputs { position: static; max-height: none; overflow: visible; } /* one column, the answer first (founder, 2026-09-24 "yes", go 2026-09-26; the DCF's technique, 9ec7de05): the tool's result block, then the inputs panel, then the rest of the outcome in its order. The outcome column dissolves (display: contents) so its blocks and the panel share one flex column; no id or callback changes. The first outcome block is the result slot on every tool (function_calculator_focus.OUTCOME_ORDER). */ .fcal-lab { display: flex; flex-direction: column; align-items: stretch; /* the base grid says start: here every block takes the full width */ gap: 0; } .fcal-outcome { display: contents; } .fcal-outcome > * { order: 3; min-width: 0; /* a wide table scrolls in its panel, never widens the page */ max-width: 100%; } .fcal-outcome > :first-child { order: 1; } .fcal-inputs { order: 2; margin-bottom: 16px; } /* four tools' result slots carry Bootstrap's mt-4: under the tab bar every answer starts at the same height */ .fcal-outcome > :first-child [id$="-results"].mt-4 { margin-top: 0 !important; } /* the bond tool's link to the bonds page keeps its own width */ .fcal-outcome > .fcal-link-button { align-self: flex-start; } /* Kelly allocation: its result slot also carries "How the pieces fit" (a tall panel of figures and text). Only the answer moves up; the panel stays after the inputs, so the inputs sit right under the answer. The slot's loader dissolves to let the two pieces take their own places; its spinner box is hidden (the formula is instant, and a spinner without its loader's frame would cover the whole lab). A message the callback returns instead (invalid inputs) keeps its box and leads, like the answer. */ .fcal-outcome > :first-child:has(#calc-kelly-results), .fcal-outcome > :first-child > :has(> #calc-kelly-results), .fcal-outcome #calc-kelly-results, .fcal-outcome #calc-kelly-results > div:has(> .flab-result) { display: contents; } .fcal-outcome > :first-child:has(#calc-kelly-results) > :not(:has(#calc-kelly-results)) { display: none !important; /* the loader sets display: flex inline while loading */ } .fcal-outcome #calc-kelly-results > * { order: 1; } .fcal-outcome #calc-kelly-results > div > * { order: 3; min-width: 0; max-width: 100%; } .fcal-outcome #calc-kelly-results > div > .flab-result { order: 1; } } @media (max-width: 767.98px) { .fcal-inputs { padding: 16px; } .fcal-intro { flex-wrap: wrap; gap: 14px; padding: 18px; } .fcal-intro-link { margin-left: 0; max-width: none; text-align: left; } } /* ======================== */ /* 6. DCF CALCULATOR */ /* ======================== */ /* apps/app_resources/discount_calc/function_dcf_focus.py — the mockup's DCF: the assumptions panel on the left (scenarios, two fields, the rate sliders, the terminal method, the four growth-curve points as slider pairs), the outcome on the right (the result section, the year table, the wide bars, the growth path and the build-up, the Monte Carlo distribution and figures, the sensitivity grid). */ .fdcf .fpage-head { margin-bottom: 20px; } .fdcf-layout { display: grid; grid-template-columns: 340px minmax(0, 1fr); gap: 24px; align-items: start; } /* the left column: two cards (inputs + path forward, terminal value), sticky together */ .fdcf-side { position: sticky; top: 16px; display: flex; flex-direction: column; gap: 16px; max-height: calc(100vh - 32px); max-height: calc(100dvh - 32px); overflow-y: auto; min-width: 0; } .fdcf-inputs { min-width: 0; padding: 22px; border: 1px solid var(--focus-line); border-radius: 12px; background: var(--focus-panel); } .flab-eyebrow { margin: 0 0 8px; } .fdcf-sentence { margin: 0 0 10px; font-weight: 500; line-height: 1.3; letter-spacing: -0.02em; font-size: var(--font-size-h2) !important; } .fdcf-inputs .fdcf-tag { margin-bottom: 16px; } /* the three scenarios as one row of pills */ .fdcf-inputs .btn-group-vertical { flex-direction: row; gap: 6px; width: 100%; margin-bottom: 18px !important; } .fdcf-inputs .btn-group-vertical > .btn { flex: 1 1 0; margin-top: 0; border-radius: 6px !important; padding: 9px 4px; font-size: var(--font-size-xs) !important; } /* the two number fields side by side: label rows, boxes and hints on SHARED rows (subgrid), so a label that wraps in the 340px panel with the sidebar open ("Last year free cash flow") never pushes its box below the other one's (audit 2026-09-24); a browser without subgrid gets two plain columns, bottoms aligned */ .fdcf-two { display: grid; grid-template-columns: 1fr 1fr; grid-template-rows: auto auto auto; gap: 0 12px; align-items: end; } .fdcf-two > .fdcf-field { display: grid; grid-template-rows: subgrid; grid-row: span 3; } .fdcf-field-label { display: flex; justify-content: space-between; align-items: flex-start; gap: 8px; margin: 0 0 5px; color: var(--focus-text); font-size: var(--font-size-sm) !important; } .fdcf-field-label small { flex: none; white-space: nowrap; color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .fdcf-field .form-control { min-height: 41px; font-variant-numeric: tabular-nums; font-size: var(--font-size-md) !important; } .fdcf-field-hint { display: block; margin-top: 5px; color: var(--focus-text-muted); font-style: normal; font-size: var(--font-size-xs) !important; } .fdcf-divider { margin: 18px 0; border-color: var(--focus-line); opacity: 1; } .fdcf-hint { margin: 0 0 12px; color: var(--focus-text-muted); line-height: 1.5; font-size: var(--font-size-sm) !important; } /* the terminal method select (the classic builder's row) */ .fdcf-inputs .row > [class*="col"] { flex: 0 0 100%; max-width: 100%; } .fdcf-inputs .form-label { margin-bottom: 5px; font-size: var(--font-size-sm) !important; } .fdcf-inputs .form-select { min-height: 41px; } .fdcf-inputs .form-text { font-size: var(--font-size-xs) !important; } /* SHARED with the calculators (flab-*, apps/app_calculator/function_focus_lab.py): the range field, the result block, the meta line and the panels below. */ /* the mockup's range field: label, the slider with its value above the handle, the bounds under it */ .flab-range { margin: 10px 0 4px; } .flab-range-label { color: var(--focus-text); font-size: var(--font-size-sm) !important; } .flab-slider { margin: 14px 0 0; padding: 8px 6px 0 !important; /* dcc.Slider sets padding: 25px inline */ } .flab-slider .rc-slider { height: 12px; padding: 4px 0; } .flab-slider .rc-slider-rail { height: 4px; border-radius: 2px; background: var(--focus-raised); } .flab-slider .rc-slider-track { height: 4px; border-radius: 2px; background: var(--focus-accent); } .flab-slider .rc-slider-handle { width: 16px; height: 16px; margin-top: -6px; border: 2px solid var(--focus-accent); background: var(--focus-panel); opacity: 1; box-shadow: none; } .flab-slider .rc-slider-handle:active, .flab-slider .rc-slider-handle:focus { box-shadow: 0 0 0 4px rgba(var(--focus-accent-rgb), 0.25); } .flab-slider .rc-slider-tooltip-inner { min-width: 0; height: auto; padding: 0 2px; border-radius: 0; background: transparent; box-shadow: none; color: var(--focus-accent); font-weight: 600; font-variant-numeric: tabular-nums; font-size: var(--font-size-sm) !important; } .flab-slider .rc-slider-tooltip-arrow { display: none; } .flab-range-bounds { display: flex; justify-content: space-between; color: var(--focus-text-muted); font-size: var(--font-size-eyebrow) !important; } /* each point: its year and its growth side by side, so the four points fit a laptop screen inside the sticky panel */ .fdcf-point-block { display: grid; grid-template-columns: 1fr 1fr; gap: 0 14px; padding: 10px 12px 6px; margin-bottom: 8px; border: 1px solid var(--focus-line); border-radius: 8px; background: var(--focus-raised); } .fdcf-point-block .fdcf-point { grid-column: 1 / -1; } .fdcf-point-block .flab-range { margin: 2px 0 0; } .fdcf-point-block .flab-slider { margin-top: 12px; } .fdcf-point { color: var(--focus-text-muted); font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; font-size: var(--font-size-eyebrow) !important; } /* the outcome column in the mockup's order: the result, the bars, the sensitivity grid, then the year table and the other charts. The valuation callback writes the result AND the table into one slot, so that slot is `display: contents` and the column orders its items. */ .fdcf-results { display: flex; flex-direction: column; min-width: 0; } .fdcf-out { min-width: 0; } .fdcf-results > #div_discounted_calculation { display: contents; } .fdcf-results > #div_discounted_calculation > div > .fdcf-result { order: 1; } .fdcf-results > .fdcf-chart { order: 2; } .fdcf-results > #sensitivity_table { order: 3; } .fdcf-results > #div_discounted_calculation > div > .fdcf-table-panel { order: 4; } .fdcf-results > .row { order: 5; } .fdcf-results > #div_discounted_calculation > div { display: contents; } .flab-result { margin-bottom: 16px; padding: 26px 28px 22px; border: 1px solid var(--focus-line); border-radius: 12px; background: rgba(var(--focus-accent-rgb), 0.06); } .flab-result-label { display: block; margin-top: 16px; color: var(--focus-text-muted); font-size: var(--font-size-md) !important; } .flab-result-value { display: block; margin: 6px 0 12px; font-weight: 400; letter-spacing: -0.05em; line-height: 1.1; font-variant-numeric: tabular-nums; font-size: var(--font-size-title) !important; } .flab-result-text { margin: 0; max-width: 480px; color: var(--focus-text-muted); line-height: 1.7; font-size: var(--font-size-sm) !important; } .fdcf-breakdown { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 18px; margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--focus-line); } .fdcf-breakdown span { display: block; margin-bottom: 6px; color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .fdcf-breakdown b { font-weight: 400; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; font-size: var(--font-size-h3) !important; } .flab-meta { display: flex; flex-wrap: wrap; gap: 8px 24px; margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--focus-line); color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .flab-meta b { color: var(--focus-text); font-weight: 500; } .flab-panel { margin-bottom: 16px; padding: 20px 22px 16px; border: 1px solid var(--focus-line); border-radius: 12px; background: var(--focus-panel); min-width: 0; } .flab-panel-title { margin: 0 0 4px; font-weight: 500; letter-spacing: -0.02em; font-size: var(--font-size-h3) !important; } .flab-panel-sub { margin: 0 0 14px; color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .fdcf-chart { margin-bottom: 16px; } /* the chart cards and the Monte Carlo figures: panels */ .fdcf-results .card { border: 1px solid var(--focus-line) !important; border-radius: 12px !important; background: var(--focus-panel) !important; box-shadow: none !important; } .fdcf-results .card-header { background: transparent; border-bottom: 0; } .fdcf-results .card-header h5, .fdcf-results .card-header .text-section-heading { font-weight: 500; letter-spacing: -0.02em; font-size: var(--font-size-h3) !important; } .fdcf-hidden { display: none; } /* the Monte Carlo waits for the first change of an input: until then its graph slot holds a quiet note (function_dcf_focus.MC_PENDING_NOTE) across the whole row — the empty figures column steps aside; the first result replaces the note and the row returns to its two columns */ .fdcf-mc-pending { margin: 0; padding: 18px 22px; border: 1px dashed var(--focus-line); border-radius: 12px; color: var(--focus-text-muted); line-height: 1.6; font-size: var(--font-size-sm) !important; } .fdcf-results > .row:has(.fdcf-mc-pending) > [class*="col"]:first-child { flex: 0 0 100%; max-width: 100%; } .fdcf-results > .row:has(.fdcf-mc-pending) > [class*="col"]:not(:first-child) { display: none; } /* while the Monte Carlo runs (the callback's running class on the graph slot, function_dcf_focus.MC_RUNNING_CLASS): the note reads "Computing 1,000 possible values…"; on a later change the previous result dims */ .fdcf-mc-busy { display: none; } .fdcf-mc-running .fdcf-mc-idle { display: none; } .fdcf-mc-running .fdcf-mc-busy { display: inline; } .fdcf-results > .row > [class*="col"] { transition: opacity 0.2s ease; } .fdcf-results > .row:has(.fdcf-mc-running):not(:has(.fdcf-mc-pending)) > [class*="col"] { opacity: 0.45; } @media (max-width: 991.98px) { /* one column in the founder's order (2026-09-23): the result first, the inputs card, "The next N years", the terminal card, the rest */ .fdcf-layout { display: flex; flex-direction: column; align-items: stretch; /* every card the full width (the inputs card was narrower than the result) */ gap: 0; } .fdcf-side, .fdcf-results { display: contents; } .fdcf-results > #div_discounted_calculation > div > .fdcf-result { order: 1; } .fdcf-side > .fdcf-card-inputs { order: 2; } .fdcf-results > .fdcf-chart { order: 3; } .fdcf-side > .fdcf-card-terminal { order: 4; } .fdcf-results > #sensitivity_table { order: 5; } .fdcf-results > #div_discounted_calculation > div > .fdcf-table-panel { order: 6; } .fdcf-results > .row { order: 7; } .fdcf-side > .fdcf-inputs { margin-bottom: 16px; } .fdcf-breakdown { grid-template-columns: 1fr; gap: 12px; } } @media (max-width: 767.98px) { .fdcf-inputs { padding: 16px; } .fdcf-two { grid-template-columns: 1fr; } .flab-result { padding: 20px 18px 16px; } } /* ======================== */ /* 7. LAB PARTS (shared) */ /* ======================== */ /* apps/app_calculator/function_focus_lab.py — the parts both lab pages share (the renamed flab-* rules of section 6 plus these). */ /* the range value: the slider's own tooltip, pinned at the right end of the label row (follows the handle while dragging, no callback) */ .flab-range { position: relative; } .flab-range-label { padding-right: 72px; } .flab-slider .rc-slider-tooltip { left: auto !important; right: -6px !important; top: -33px !important; transform: none !important; padding: 0; } .flab-slider .rc-slider-disabled .rc-slider-track, .flab-slider.rc-slider-disabled .rc-slider-track { background: var(--focus-line); } .flab-slider .rc-slider-disabled .rc-slider-tooltip-inner { color: var(--focus-text-muted); } .flab-range-hint { display: block; margin-top: 4px; color: var(--focus-text-muted); font-style: normal; font-size: var(--font-size-xs) !important; } .flab-result-note { margin: 8px 0 0; max-width: 520px; color: var(--focus-text-muted); line-height: 1.6; font-size: var(--font-size-xs) !important; } .flab-result-muted { background: var(--focus-panel); } .flab-result-muted .flab-result-value { color: var(--focus-text-muted); } .flab-panel-note { margin: 14px 0 0; color: var(--focus-text-muted); line-height: 1.7; font-size: var(--font-size-xs) !important; } .flab-panel p.flab-panel-copy { margin: 0 0 10px; color: var(--focus-text); line-height: 1.7; font-size: var(--font-size-sm) !important; } /* label / value pairs, two columns on wide screens */ .flab-facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(230px, 100%), 1fr)); /* never wider than its column (the DCF's Monte Carlo figures at 1024px) */ gap: 0 28px; margin: 0; } .flab-fact { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; padding: 9px 0; border-bottom: 1px solid var(--focus-line); min-width: 0; } .flab-fact dt { color: var(--focus-text-muted); font-weight: 400; font-size: var(--font-size-sm) !important; } .flab-fact dd { margin: 0; color: var(--focus-text); text-align: right; font-variant-numeric: tabular-nums; font-size: var(--font-size-sm) !important; } /* the DCF: title + tag on one line, the net-debt note, the breakdown's share line, the value-per-share tiles */ .fdcf-panel-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 14px; } .fdcf-panel-head .fdcf-sentence { margin: 0; } .fdcf-inputs .seg-control { margin: 4px 0 6px; } .fdcf-note { margin: 16px 0 0; font-size: var(--font-size-xs) !important; } .fdcf-breakdown small { display: block; margin-top: 4px; color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .fdcf-grid { display: grid; grid-template-columns: minmax(96px, auto) repeat(5, minmax(0, 1fr)); gap: 6px; } .fdcf-grid-corner, .fdcf-grid-head { display: flex; align-items: center; justify-content: center; padding: 6px 4px; color: var(--focus-text-muted); font-variant-numeric: tabular-nums; font-size: var(--font-size-xs) !important; } .fdcf-grid-corner, .fdcf-grid-row { justify-content: flex-start; } .fdcf-grid-cell { padding: 12px 4px; border: 1px solid var(--focus-line); border-radius: 8px; background: var(--focus-raised); text-align: center; font-variant-numeric: tabular-nums; font-size: var(--font-size-sm) !important; } .fdcf-grid-cell.is-current { border-color: var(--focus-accent); background: rgba(var(--focus-accent-rgb), 0.14); color: var(--focus-text); font-weight: 600; } .fdcf-grid-cell.is-na { color: var(--focus-text-muted); } @media (max-width: 767.98px) { .flab-facts { grid-template-columns: 1fr; } /* the first column narrow (the corner label wraps), the five values fit */ .fdcf-grid { grid-template-columns: 44px repeat(5, minmax(0, 1fr)); gap: 3px; } .fdcf-grid-corner { white-space: normal; line-height: 1.2; padding: 2px 0; font-size: var(--font-size-eyebrow) !important; } .fdcf-grid-head { padding: 4px 0; font-size: var(--font-size-eyebrow) !important; } .fdcf-grid-cell { padding: 9px 0; letter-spacing: -0.02em; white-space: nowrap; overflow: hidden; font-size: var(--font-size-xs) !important; } } @media (max-width: 400px) { .fdcf-grid-cell { font-size: var(--font-size-eyebrow) !important; } .fdcf-grid-cell.is-current { font-weight: 400; /* the border and the tint mark it; bold clips at 320px */ } } /* the calculators' outcome parts (function_calculator_focus.py) */ .fcal-scenarios > [class*="col"] > .flab-result { height: calc(100% - 16px); } .flab-subhead { margin: 22px 0 4px; font-weight: 500; letter-spacing: -0.01em; font-size: var(--font-size-md) !important; } .flab-list { margin: 0 0 8px; padding-left: 18px; color: var(--focus-text); line-height: 1.7; font-size: var(--font-size-sm) !important; } .flab-list li + li { margin-top: 6px; } .fcal-link-button { display: inline-flex; align-items: center; gap: 6px; margin: 4px 0 16px; padding: 10px 16px; border: 1px solid var(--focus-line); border-radius: 8px; color: var(--focus-text); text-decoration: none; font-size: var(--font-size-sm) !important; } .fcal-link-button:hover, .fcal-link-button:focus-visible { border-color: var(--focus-accent); color: var(--focus-accent); } /* the tool's intro line (the classic info note above the inputs) */ .fcal-lead { margin: -4px 0 14px; color: var(--focus-text-muted); line-height: 1.6; font-size: var(--font-size-xs) !important; } .fcal-lead strong { color: var(--focus-text); font-weight: 500; } /* a segmented choice fills the panel width, its help text under it */ .fcal-fields .seg-control, .fdcf-inputs .seg-control { display: flex; flex-wrap: nowrap; width: 100%; } .fcal-fields .seg-control > *, .fdcf-inputs .seg-control > * { flex: 1 1 0; min-width: 0; text-align: center; } .fcal-fields .seg-control .seg-option, .fdcf-inputs .seg-control .seg-option { display: block; padding-left: 6px; padding-right: 6px; } .fcal-fields .seg-control + .form-text { display: block; margin-top: 6px; }
/* 24_focus_portfolio.css */
/* 24_focus_portfolio.css */ /* ======================================================================== */ /* The Focus PORTFOLIOS / WATCHLISTS page — layer C, the full mockup */ /* (founder's choice 2026-09-24 from the drawing). Appended to the Focus */ /* bundle only (combine_css_files.py), after 22_focus_pages.css (section 4 */ /* there keeps the frame rules of the first restyle). Classes fpf-*, every */ /* size a --font-size-* token. apps/app_portfolio/function_portfolio_focus.py */ /* ======================================================================== */ /* ---- tab title ---- */ .fpf-tab-title { font-size: var(--font-size-h2) !important; font-weight: 500; letter-spacing: -0.02em; margin: 0; } .fpf-tab-sub { color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; margin: 2px 0 0; } .fpf-panel-title { font-size: var(--font-size-h3) !important; font-weight: 500; letter-spacing: -0.02em; } .fpf-panel-title .fpart-sub { display: block; color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; font-weight: 400; letter-spacing: 0; margin-top: 2px; } .fpf-panel { border: 1px solid var(--focus-line); border-radius: 12px; background: var(--focus-panel); padding: 18px 20px; min-width: 0; } .fpf-two { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; margin-top: 16px; } .fpf-two.fpf-one { grid-template-columns: minmax(0, 1fr); } /* ---- the page's order (founder, 2026-09-30): title → the first card (banner + the list fold) → the chart card (the other numbers folded in) → tabs → tab content / holdings → Learn More ---- */ /* one callback fills the summary slot (banner + panels); the frame is a flex column, the wrappers between it and the pieces are `display: contents`, and `order` places the pieces */ .fpf .fpage { display: flex; flex-direction: column; position: relative; } .fpf .fpage-body, .fpf .fpf-body, .fpf .fpf-summary-load, .fpf .fpf-summary-load-inner, .fpf #portfolio_resume_performance, .fpf .fpf-summary { display: contents; } .fpf .fpage > *, .fpf .fpf-body > * { order: 10; } .fpf .fpage-head { order: 0; } .fpf .fpf-banner { order: 1; } .fpf .fpf-selector { order: 2; } .fpf .fpf-summary-row { order: 3; } .fpf .fpage-tabs { order: 4; } .fpf .fpf-learn-end { order: 99; } .fpf .fpf-summary-load-inner > .dash-loading, .fpf .fpf-summary-load-inner > [class*="loading"] { position: absolute; /* the spinner needs a box: the frame is its containing block now */ left: 0; right: 0; top: 40vh; } /* ---- the banner (the summary callback under Focus) ---- */ .fpf-summary { margin-bottom: 16px; } .fpf-banner { display: grid; grid-template-columns: 1.3fr 1.1fr 1fr 1fr; gap: 18px 28px; align-items: center; border: 1px solid var(--focus-line); border-radius: 12px; background: var(--focus-panel); padding: 20px 24px; margin-bottom: 16px; } .fpf-banner-name { font-size: var(--font-size-h2) !important; font-weight: 500; letter-spacing: -0.02em; margin: 0 0 2px; } .fpf-banner-line { color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; margin: 0; } .fpf-banner-label, .fpf-card-label, .fpf-score-label { display: block; color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; } .fpf-banner-value { display: inline-block; font-size: var(--font-size-title) !important; /* the headline value at the page-title size (the drawing) */ font-weight: 400; letter-spacing: -0.04em; line-height: 1.05; margin: 4px 0 2px; font-variant-numeric: tabular-nums; } .fpf-banner-unit { color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; } .fpf-banner-ret { font-size: var(--font-size-md) !important; /* the return line under the value, one step up */ font-weight: 500; } .fpf-banner-big > .btn.fpf-metric-btn + .btn.fpf-metric-btn { display: block; /* the return line always UNDER the value, as drawn */ margin-top: 2px; } .fpf-banner-sub { color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .fpf-banner-mini { border-left: 1px solid var(--focus-line); padding-left: 18px; } /* the watchlist band: name + five short cells (no money) */ .fpf-banner.fpf-banner-wl { grid-template-columns: 1.4fr repeat(5, minmax(0, 1fr)); gap: 14px 18px; } .fpf-banner-wl .fpf-banner-mini { padding-left: 14px; } .fpf-add { font-size: var(--font-size-sm) !important; white-space: nowrap; } .fpf-banner-big > .btn.fpf-metric-btn, .fpf-banner-mini > .btn.fpf-metric-btn, .fpf-banner-mini > .fpf-banner-sub { display: block; /* the value, then the line under it */ } .fpf-banner-mini-value { display: inline-block; font-size: var(--font-size-h2) !important; /* Total P/L / Last year: one step larger than before (the mockup) */ font-weight: 400; letter-spacing: -0.03em; margin: 4px 0 2px; font-variant-numeric: tabular-nums; } .fpf-banner-small { font-size: var(--font-size-sm) !important; font-weight: 500; } .fpf-banner-subrow { display: flex; flex-wrap: wrap; gap: 4px 12px; } /* every number is the classic metric button (its modal): drawn as text */ .btn.fpf-metric-btn { display: inline; padding: 0; margin: 0; border: 0; background: transparent; color: inherit; font: inherit; text-align: left; box-shadow: none; cursor: pointer; } .btn.fpf-metric-btn:hover, .btn.fpf-metric-btn:focus-visible { background: transparent; color: inherit; text-decoration: underline; text-decoration-color: var(--focus-accent); } .fpf-summary-row { display: grid; grid-template-columns: 1.6fr 1fr; gap: 16px; align-items: start; } .fpf-summary-row > .fpf-panel { margin: 0; } .fpf-panel-head { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: flex-start; gap: 10px 16px; margin-bottom: 8px; } .fpf-graph-controls { margin: 0; gap: 8px 12px; } .fpf-graph-controls .fpart-field { min-width: 0; } .fpf-graph-controls .form-select { min-width: 110px; } .fpf-graph-foot { display: flex; justify-content: space-between; align-items: center; gap: 12px; margin-top: 4px; font-size: var(--font-size-xs) !important; } .fpf-dl { display: grid; margin: 6px 0 0; } .fpf-dl-row { display: flex; justify-content: space-between; gap: 14px; padding: 6px 0; border-bottom: 1px solid var(--focus-line); font-size: var(--font-size-sm) !important; } .fpf-dl-row:last-child { border-bottom: 0; } .fpf-dl-row dt { color: var(--focus-text-muted); font-weight: 400; } .fpf-dl-row dd { margin: 0; font-variant-numeric: tabular-nums; } .fpf .fpart-pos { color: var(--focus-positive); } .fpf .fpart-neg { color: var(--focus-accent-hover); /* the readable red for TEXT (22 does the same) */ } /* ---- corporate-action panel (the CA banner under Focus) ---- */ .fpf-ca { margin-bottom: 16px; } .fpf-ca-rows { display: grid; margin-top: 10px; } .fpf-ca-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px 18px; align-items: center; padding: 10px 0; border-top: 1px solid var(--focus-line); } .fpf-ca-cell { min-width: 0; } .fpf-ca-text { margin: 0; font-size: var(--font-size-sm) !important; } .fpf-ca-hint { display: block; color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; margin-top: 2px; } .fpf-ca-actions { display: flex; flex-wrap: wrap; gap: 6px; justify-content: flex-end; } .fpf-ca-actions .btn { margin: 0 !important; } .fpf-ca-note { display: block; margin-top: 10px; } /* ---- holdings toolbar (the static block under Focus) ---- */ .fpf-toolbar { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: flex-end; gap: 12px 16px; margin-bottom: 14px; } .fpf-toolbar-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px; } .fpf-find { width: 190px; } /* the view choice = the app's segmented control (its pattern id type is fpf-view) */ .fpf-view, .fpf-toolbar-controls .seg-control { margin: 0; } /* the classic "Edit table" row lost its switch; keep its buttons right-aligned */ .fpf-body #_dum_div_portfolio_tabs_content_finished_render_holding_first_callback > .fpf-toolbar + .row { justify-content: flex-end; } /* ---- the Compact holdings table (the mockup's: a company tile, tall rows, 8 columns that fit) ---- */ .fpf-body #portfolio_table_holdings_div table td, .fpf-body #portfolio_table_holdings_div table th { padding-top: 12px; padding-bottom: 12px; vertical-align: middle; } .fpf-body #portfolio_table_holdings_div table td.tbl-company-col { min-width: 220px; max-width: 300px; } .fpf-body #portfolio_table_holdings_div .tbl-company-name { white-space: normal; font-size: var(--font-size-base) !important; color: var(--focus-text); font-weight: 500; } .fpf-body #portfolio_table_holdings_div .tbl-company-sub { color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .fpf-body #portfolio_table_holdings_div .tbl-sub { color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .fpf-body #portfolio_table_holdings_div table td:not(.tbl-company-col) { white-space: nowrap; } /* ---- concentration bars, scores, cards ---- */ .fpf-bars { display: grid; gap: 10px; margin-top: 8px; } .fpf-bar-head { display: flex; justify-content: space-between; gap: 12px; font-size: var(--font-size-sm) !important; margin-bottom: 4px; } .fpf-bar-track { height: 4px; border-radius: 2px; background: var(--focus-line); overflow: hidden; } .fpf-bar-track i { display: block; height: 4px; border-radius: 2px; } .fpf-scores { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; margin-top: 8px; } .fpf-score-value { display: block; font-size: var(--font-size-h3) !important; font-weight: 400; letter-spacing: -0.02em; } .fpf-score-value small { color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .fpf-score-track { display: block; height: 3px; border-radius: 2px; background: var(--focus-line); margin-top: 6px; overflow: hidden; } .fpf-score-track b { display: block; height: 3px; border-radius: 2px; } .fpf-link { display: inline-block; margin-top: 8px; color: var(--focus-accent); font-size: var(--font-size-sm) !important; text-decoration: none; } .fpf-cards { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; } .fpf-card { border: 1px solid var(--focus-line); border-radius: 12px; background: var(--focus-raised); padding: 16px 18px; min-width: 0; } .fpf-card-head { display: flex; align-items: center; gap: 10px; } .fpf-card-name { display: block; font-weight: 500; } .fpf-card-sub { display: block; color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .fpf-card-price-row { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; margin: 12px 0 6px; } .fpf-card-price { font-size: var(--font-size-h3) !important; font-weight: 400; font-variant-numeric: tabular-nums; } .fpf-card-price small { color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .fpf-card-foot { margin-top: 12px; font-size: var(--font-size-sm) !important; display: flex; justify-content: space-between; align-items: center; gap: 8px; } .fpf-card-remove.btn { font-size: var(--font-size-sm) !important; color: var(--focus-text-muted); padding: 0; text-decoration: none; } .fpf-card-remove.btn:hover { color: var(--focus-negative); } /* ---- classes carried by the Python tree (structure hooks) ---- */ .fpf-banner-head, .fpf-banner-big { min-width: 0; } .fpf-graph-panel, .fpf-others { min-width: 0; } .fpf-bar, .fpf-score { min-width: 0; } /* ---- step 7: research notes on the watchlist cards (function_research_notes.py) ---- */ .fpf-card-note { margin-top: 12px; } .fpf-card-note .fpf-card-label { display: block; margin-bottom: 4px; } .fpf-note.form-control { width: 100%; min-height: 66px; resize: vertical; font-size: var(--font-size-sm) !important; line-height: 1.4; padding: 6px 8px; } .fpf-card-note.fpf-note-editing .fpf-note-status { visibility: hidden; /* the old "Saved." while a new text is typed (a class, never React's text) */ } .fpf-note-status { display: block; min-height: 1.2em; margin-top: 3px; font-size: var(--font-size-eyebrow) !important; color: var(--focus-text-muted); } /* ---- ONE level of cards (founder's phone review, 2026-09-29: no card inside a card) ---- The tab slot and the static holdings block lose the panel chrome of the first restyle (22, section 4): their pieces — panels, bands, the holdings table, the watchlist cards — are the cards now, side by side on the page. A classic chart card or a band that sits INSIDE a panel draws no second frame. */ .fpf-body #portfolio-tabs-content, .fpf-body #_dum_div_portfolio_tabs_content_finished_render_holding_first_callback { padding: 0; border: 0; border-radius: 0; background: none; } .fpf-body #portfolio_table_holdings_div .table-scroll { border: 1px solid var(--focus-line); border-radius: 12px; background: var(--focus-panel); } .fpf-body .fpf-panel .card { border: 0 !important; border-radius: 0 !important; background: transparent !important; box-shadow: none !important; } .fpf-body .fpf-panel .card > .card-header, .fpf-body .fpf-panel .card > .card-body { padding-left: 0; padding-right: 0; } .fpf-body .fpf-panel .card > .card-header { background: transparent !important; } /* edit mode's "Delete selected list" under the table card, not on its edge */ .fpf-body #portfolio_holdings_bottoms_components_for_table_edits:not(:empty) { margin-top: 14px; } .fpf-body .fpf-panel .fpart-stats { border: 0; border-radius: 0; } /* ---- a staged copy waiting (leaving the Edit view keeps it; Save or Discard end it) ---- */ .fpf-staged { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; margin: 0 0 10px; } .fpf-staged-text { color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; } .fpf-discard { font-size: var(--font-size-sm) !important; } /* ---- the Company column on a phone: long names wrap (two lines at most), the column takes at most 36 % of the screen (45 % less a fifth, founder 2026-10-03). With the page's halved side margin (21_focus_shell.css) 1M %, 1Y % AND Return % fit a 375px phone — measured on the founder's combined portfolio: Return % ends 7px inside the frame. (28 % did it without the margin change but cut the names too short.) Wider screens keep 220-300px ---- */ @media (max-width: 575.98px) { .fpf-body #portfolio_table_holdings_div table td.tbl-company-col { min-width: 0; max-width: none; } .fpf-body #portfolio_table_holdings_div .tbl-company { max-width: 36vw; } .fpf-body #portfolio_table_holdings_div .tbl-company-name { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; overflow-wrap: anywhere; } .fpf-body #portfolio_table_holdings_div .tbl-company-sub { display: block; overflow: hidden; text-overflow: ellipsis; } /* the holdings view choice's four options (… | Edit) on one line at 390px */ .fpf-toolbar-controls .seg-control .seg-option { padding-left: 8px; padding-right: 8px; } } /* ---- narrow screens ---- */ @media (max-width: 991.98px) { .fpf-banner, .fpf-banner.fpf-banner-wl { grid-template-columns: 1fr 1fr; } .fpf-banner-wl .fpf-banner-head { grid-column: 1 / -1; } .fpf-summary-row, .fpf-two { grid-template-columns: minmax(0, 1fr); } .fpf-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); } } @media (max-width: 575.98px) { .fpf-ca-row { grid-template-columns: minmax(0, 1fr); } .fpf-ca-actions { justify-content: flex-start; } .fpf-banner, .fpf-banner.fpf-banner-wl { grid-template-columns: minmax(0, 1fr); gap: 14px; } .fpf-banner-mini, .fpf-banner-wl .fpf-banner-mini { border-left: 0; padding-left: 0; border-top: 1px solid var(--focus-line); padding-top: 12px; } .fpf-cards { grid-template-columns: minmax(0, 1fr); } .fpf-scores { grid-template-columns: repeat(2, minmax(0, 1fr)); } .fpf-find { width: 100%; } } /* ======================================================================== */ /* Founder's review round 3 (2026-09-30): ONE gap, the first card with its */ /* list fold, two columns on a phone, the other numbers folded in the chart */ /* card, Learn More at the end. */ /* ======================================================================== */ /* ---- ONE gap between cards, page-wide — the rule every tab inherits. Every block of the page body (the first card, the chart card, the tab bar, and inside every tab each .card — the Focus panels are .card.fpart-panel —, .fpf-panel, band .fpart-stats, pair .fpf-two / .fpart-two, the watchlist cards, an empty-state panel, the holdings table) carries ONLY a bottom margin of var(--spacing-lg) (16px, the app's spacing token; alias --fpf-gap); nothing else adds vertical space between cards. In block flow the margin of a card at the end of a wrapper collapses through it (no double gap); a block inside a block adds none at its end; the items of a pair use the grid's gap. A tab built from these classes needs no spacing rule of its own. ---- */ .fpf { --fpf-gap: var(--spacing-lg); /* the page's one gap, for a tab's own stylesheet (33 / 34) */ } .fpf .fpf-summary-row, .fpf .fpage-tabs, .fpf-body :is(.card, .fpf-panel, .fpart-stats, .fpf-two, .fpart-two, .fpf-cards, .fpf-empty) { margin-top: 0; margin-bottom: var(--spacing-lg); } .fpf-body :is(.card, .fpf-panel, .fpart-stats) :is(.card, .fpf-panel, .fpart-stats, .fpf-two, .fpart-two, .fpf-empty):last-child, .fpf-body :is(.fpf-two, .fpart-two, .fpf-cards, .fpf-summary-row) > * { margin-bottom: 0; } .fpf :is(.fpf-two, .fpart-two, .fpf-cards, .fpf-summary-row) { gap: var(--spacing-lg); } /* the retitled classic cards (.card.fpart-panel) get `margin: 0 !important` from 22 (section 8): the page's gap needs the same weight for them, on every tab, with the same exceptions */ .fpf-body .card.fpart-panel { margin-top: 0 !important; margin-bottom: var(--spacing-lg) !important; } .fpf-body :is(.card, .fpf-panel, .fpart-stats) .card.fpart-panel:last-child, .fpf-body :is(.fpf-two, .fpart-two, .fpf-cards, .fpf-summary-row) > .card.fpart-panel { margin-bottom: 0 !important; } .fpf-body #portfolio_table_holdings_div .table-scroll { margin-bottom: var(--spacing-lg); } .fpf-body #portfolio_table_holdings_div .table-scroll > table { margin-bottom: 0; /* the table card ends with its last row (Bootstrap's 1rem inside the frame) */ } .fpf-body #_dum_div_portfolio_tabs_content_finished_render_holding_first_callback { margin-top: 0; /* the tab bar's (or the tab slot's) bottom gap is the space */ } .fpf-body #portfolio-tabs-content { margin-top: 0 !important; /* the classic slot's mt-3 (!important): the tab bar's gap is the space */ margin-bottom: 0; } /* ---- the first card: the banner, then (same card) the list fold (round 4: the dropdown, the dividends choice and Create / Import all inside it — the card ends with the fold line) ---- */ .fpf-banner { margin-bottom: var(--spacing-lg); } .fpf-selector { display: block; /* one fold line across the card (22's first restyle made it a flex row) */ margin: 0 0 var(--spacing-lg); padding: 0 24px 14px; border: 1px solid var(--focus-line); border-radius: 12px; background: var(--focus-panel); } /* when the banner is there, the two read as ONE card (the banner is the callback's, the fold is static: they are neighbours in the flex order, never nested) */ .fpf .fpage:has(.fpf-banner) .fpf-banner { margin-bottom: 0; border-bottom: 0; border-bottom-left-radius: 0; border-bottom-right-radius: 0; padding-bottom: 4px; } .fpf .fpage:has(.fpf-banner) .fpf-selector { border-top: 0; border-top-left-radius: 0; border-top-right-radius: 0; } .fpf-select-fold.ffin-fold { margin-top: 14px; padding-top: 10px; border-top: 1px solid var(--focus-line); } /* no banner (loading, no list yet): the fold is a card of its own — no line under its top edge */ .fpf .fpage:not(:has(.fpf-banner)) .fpf-selector > .fpf-select-fold { border-top: 0; margin-top: 4px; } .fpf-select-fold > summary { min-height: 32px; } .fpf-select-fold .fpf-select { max-width: 560px; } /* under the dropdown: the dividends choice, Create / Import on the right (they wrap on a phone) */ .fpf-fold-actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px 16px; margin-top: 12px; } .fpf-dividends .seg-control { margin: 0; } /* ---- the chart card: Period and Compare to side by side; the other numbers folded in ---- */ .fpf-others-fold .fpf-dl { margin-top: 2px; } .fpf-fold-sub { margin: 0 0 4px; color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .fpf-summary-row { grid-template-columns: minmax(0, 1fr); } /* ---- the header (Codex astra's proposal): the other list as a small link on the eyebrow's line; no actions column (Create / Import lives in the list fold since round 4) ---- */ .fpf .fpage-head { display: block; } .fpf .fpage-actions:empty { display: none; } .fpf .fpage-eyebrow { display: flex; align-items: center; justify-content: space-between; gap: 12px; } .fpf-eyebrow-text { min-width: 0; } .fpf .fpf-peer { flex: none; display: inline-flex; align-items: center; gap: 5px; min-height: 32px; color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; letter-spacing: 0; text-transform: none; text-decoration: none; white-space: nowrap; border-radius: 4px; } .fpf .fpf-peer:hover { color: var(--focus-accent); } .btn.fpf-create { display: inline-flex; align-items: center; gap: 7px; min-height: 34px; white-space: nowrap; font-size: var(--font-size-sm) !important; } .fpf .fpf-peer:focus-visible, .fpf-create:focus-visible { outline: 2px solid var(--focus-accent); outline-offset: 3px; } .fpf-pen { display: block; flex: none; width: 15px; height: 15px; background-color: currentColor; -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round' d='m15 5 4 4M4 20l4.5-1L20 7.5a2.83 2.83 0 0 0-4-4L4.5 15Z'/></svg>") center / contain no-repeat; mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round' d='m15 5 4 4M4 20l4.5-1L20 7.5a2.83 2.83 0 0 0-4-4L4.5 15Z'/></svg>") center / contain no-repeat; } /* ---- the loading shape of the first card and the chart card (round 4): the summary slot's initial content until the summary callback answers — grey bars, one small spinner; the list fold joins it as the bottom of the same card (it carries .fpf-banner) ---- */ .fpf-banner-skel { position: relative; /* its natural height is the banner's, within a few px (measured 390 / 1440) */ } .fpf-skel { display: block; height: 12px; margin: 6px 0; border-radius: 4px; background: var(--focus-raised); animation: fpf-skel-pulse 1.4s ease-in-out infinite; } .fpf-skel-name { width: 55%; height: 22px; } .fpf-skel-line { width: 75%; } .fpf-skel-label { width: 40%; } .fpf-skel-value { width: 65%; height: 34px; } .fpf-skel-mini { width: 50%; height: 24px; } .fpf-skel-card { min-height: 425px; /* the chart card's height (measured 1440 / 390) */ animation: fpf-skel-pulse 1.4s ease-in-out infinite; } .fpf-skel-spin { position: absolute; top: 16px; right: 18px; width: 18px; height: 18px; border: 2px solid var(--focus-line); border-top-color: var(--focus-accent); border-radius: 50%; animation: fpf-skel-spin 0.8s linear infinite; } @keyframes fpf-skel-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.45; } } @keyframes fpf-skel-spin { to { transform: rotate(360deg); } } @media (prefers-reduced-motion: reduce) { .fpf-skel, .fpf-skel-card, .fpf-skel-spin { animation: none; } } /* ---- Learn More at the very end, centred ---- */ .fpf-learn-end { display: flex; justify-content: center; margin: 8px 0 var(--spacing-lg); } @media (max-width: 575.98px) { /* the banner: Total P/L beside Last year (the watchlist's short cells two by two) */ .fpf-banner, .fpf-banner.fpf-banner-wl { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 0; } .fpf-banner-head, .fpf-banner-big, .fpf-banner-wl .fpf-banner-head { grid-column: 1 / -1; } .fpf-banner-mini, .fpf-banner-wl .fpf-banner-mini { align-self: stretch; /* the two cells of a row share their top line and height */ padding-right: 10px; } .fpf-banner-mini:nth-child(even of .fpf-banner-mini) { border-left: 1px solid var(--focus-line); padding-left: 14px; padding-right: 0; } .fpf-banner-mini-value { font-size: var(--font-size-h3) !important; } .fpf-banner .fpf-banner-sub { overflow-wrap: anywhere; } .fpf-selector { padding: 0 16px 14px; } /* Create / Import: a 40px touch target on a phone */ .btn.fpf-create { min-height: 40px; } .fpf-skel-card { min-height: 480px; } .fpf-banner-skel.fpf-banner-wl { min-height: 322px; /* the watchlist band stacks five cells on a phone */ } /* the header link: a 40px touch target on a phone */ .fpf .fpf-peer { min-height: 40px; } /* Period | Compare to: two columns */ .fpf-graph-controls { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); width: 100%; } .fpf-graph-controls .form-select { min-width: 0; width: 100%; } }
/* 25_focus_etf.css */
/* ============================================================================= 25_focus_etf.css — the ETF pages under the Focus skin (ETF screening, ETF alternatives, ETF compare, Opportunities) and the table cells their tables share (functions/function_tables.py company_cell / score_badge). Own file so parallel sessions never share one (22 = the other layer-C pages, 24 = the Portfolio, 26 = the "Analyse any list" page). Focus bundle only; every size a --font-size-* token; page classes fetf-* / fopp-*, cell classes tbl-*. ============================================================================= */ /* ----- 1. Table cells: company tile + score badge --------------------------- The builder's two cells (docs/_howto_make_table.md). The tile = the name (linked) with "TICKER · sector" under it; its sub line carries `.small` so the skin's cell caption rule sizes it (xs); everything else in a cell keeps the table size. (The two-letter tinted mark went on the founder's phone review, 2026-09-29.) Colours: the rings' colours for the badges (value green, health red, efficiency orange, global blue). Moves to 05_tables.css on default day. */ .tbl-company { display: flex; flex-direction: column; min-width: 0; line-height: 1.3; } .tbl-company-name { color: var(--focus-text); text-decoration: none; font-weight: 500; } a.tbl-company-name:hover { color: var(--focus-accent); } .tbl-company-sub { color: var(--focus-text-muted); white-space: nowrap; } .tbl-badge { display: inline-block; min-width: 46px; padding: 1px 7px; border: 1px solid var(--focus-line); border-radius: 5px; text-align: center; font-variant-numeric: tabular-nums; white-space: nowrap; } .tbl-badge-value { color: #00bc8c; background: rgba(0, 188, 140, 0.12); border-color: rgba(0, 188, 140, 0.35); } .tbl-badge-health { color: #f0786b; background: rgba(231, 76, 60, 0.12); border-color: rgba(231, 76, 60, 0.35); } .tbl-badge-efficiency { color: #f39c12; background: rgba(243, 156, 18, 0.12); border-color: rgba(243, 156, 18, 0.35); } .tbl-badge-global { color: #5dade2; background: rgba(52, 152, 219, 0.12); border-color: rgba(52, 152, 219, 0.35); } .tbl-badge-na { color: var(--focus-text-muted); } /* ----- 2. ETF screening (apps/app_etf_screening/function_etf_screening_focus.py) The Stock screening look (22_focus_pages.css `fscr-*`: the sticky panel, the summary, the chips, the cards, the panel, the empty states) + what the ETF page adds: the unit beside a field label, the two assets fields side by side, the table panel with the compare bar in its head (hidden until a run has a table), the count line. */ .fetf .fetf-filters .fpart-field { display: block; margin-bottom: 10px; } .fetf .fetf-filters label.fpart-field-label { display: flex; justify-content: space-between; gap: 8px; } .fetf-unit { color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .fetf .fetf-filters .form-control { width: 100%; min-width: 0; } .fetf-two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 8px; } .fetf-two .fpart-field-hint { white-space: normal; } /* the table panel: hidden while its slot holds the idle marker (before a run, no match, an error, the plan limit — the first output says why) */ .fetf-table-panel { margin-top: 4px; } .fetf-table-panel:has(.fetf-idle) { display: none; } .fetf-table-head { align-items: flex-start; flex-wrap: wrap; } .fetf-panel-sub { margin: 4px 0 0; color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } /* the compare bar is always in view on the Focus page (the classic callback shows it only once a box is ticked; its button stays disabled until two or three are) — the drawing's "Compare selected · N selected" in the head */ .fetf-compare-bar { display: block !important; margin: 0 !important; white-space: nowrap; } .fetf-compare-bar .btn { margin-right: 8px !important; } .fetf-count { margin: 0 0 8px; color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .fetf-cards .card { background: var(--focus-panel) !important; } .fetf-modal .card { border: 1px solid var(--focus-line); box-shadow: none; } /* the compare window: the radar beside the side-by-side table on wide screens */ @media (min-width: 1200px) { .fetf-compare-modal .modal-body div:has(> #etf-scr-compare-radar-div) { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr); gap: 16px; align-items: start; } } /* a long chip (a category name, a keyword) never widens the column */ .fetf .fscr-chip { max-width: 100%; overflow: hidden; text-overflow: ellipsis; } /* ----- 3. ETF compare + ETF alternatives (apps/app_etf_compare/function_etf_compare_focus.py) One classic page, two routes: the switch of links in the header, the compare picker, one card per fund (the radar's series colours), the tabs; the alternatives panel reuses the screener's sticky panel (section 2). */ .fcmp .fpage-actions { align-items: center; flex-wrap: wrap; } .fcmp-picker { margin: 0 0 16px; padding: 16px 20px; border: 1px solid var(--focus-line); border-radius: 12px; background: var(--focus-panel); } /* the button sits BESIDE the field at every width, anchored at its top: the focused multi-select grows downward (in WebKit past its own box, over what lies below it), so a button under the field — or a bottom-aligned one that moves as the field grows and shrinks — lost the click (seen in WebKit) */ .fcmp-picker-row { display: flex; flex-wrap: nowrap; align-items: flex-start; gap: 10px 14px; } .fcmp-picker-row .fpart-field-wide { flex: 1 1 auto; min-width: 0; } .fcmp-picker-row .btn.fcmp-run { flex: none; } .btn.fcmp-run { margin-top: 21px; /* the field label's line */ display: inline-flex; align-items: center; gap: 12px; padding: 8px 16px; font-weight: 600; font-size: var(--font-size-sm) !important; } .fcmp-hint { margin: 8px 0 0; } .fcmp-cards { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; } .fcmp-card { min-width: 0; padding: 14px 16px; border: 1px solid var(--focus-line); border-top-width: 3px; border-radius: 12px; background: var(--focus-panel); } .fcmp-card.fcmp-series-0 { border-top-color: var(--focus-accent); } .fcmp-card.fcmp-series-1 { border-top-color: var(--focus-blue); } /* series 2 = the chart palette's third colour (FOCUS_SERIES_COLORS; no skin token) */ .fcmp-card.fcmp-series-2 { border-top-color: #c9ac72; } .fcmp-bar-fill.fcmp-series-0 { background: var(--focus-accent); } .fcmp-bar-fill.fcmp-series-1 { background: var(--focus-blue); } .fcmp-bar-fill.fcmp-series-2 { background: #c9ac72; } .fcmp-card-name { font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: var(--font-size-md) !important; } .fcmp-card-sub { color: var(--focus-text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: var(--font-size-xs) !important; } .fcmp-card-big { margin: 10px 0 0; line-height: 1.1; letter-spacing: -0.04em; font-variant-numeric: tabular-nums; } .fcmp-card-big span { font-size: var(--font-size-stat) !important; } .fcmp-card-big small { color: var(--focus-text-muted); letter-spacing: 0; font-size: var(--font-size-sm) !important; } .fcmp-card-line { margin-top: 6px; color: var(--focus-text-soft); font-size: var(--font-size-sm) !important; } .fcmp-card-line b { font-weight: 500; color: var(--focus-text); } .fcmp-bars { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 6px 14px; margin-top: 12px; } .fcmp-bar { display: grid; grid-template-columns: 1fr auto; row-gap: 3px; color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .fcmp-bar b { font-weight: 500; color: var(--focus-text); } .fcmp-bar i { grid-column: 1 / 3; display: block; height: 4px; border-radius: 2px; background: var(--table-border); overflow: hidden; } .fcmp-bar em { display: block; height: 100%; } .fcmp-tabs { margin: 18px 0 14px; } /* the band of the fund you know (the shared stats band, a wider first cell) */ .fcmp-band { margin: 0 0 12px; } .fcmp-band .fpart-stat { flex: 1 1 110px; } .fcmp-band .fcmp-fund { flex: 2 1 240px; } .fcmp-fund-name { display: block; margin: 4px 0 2px; font-weight: 500; line-height: 1.3; font-size: var(--font-size-md) !important; } .fcmp-alt-filters .fpart-chips { margin-bottom: 4px; } @media (max-width: 991.98px) { .fcmp-cards { grid-template-columns: minmax(0, 1fr); } } /* ----- 4. Opportunities (apps/app_opportunities/function_opportunities_focus.py) The Stock screening results column at full width (the screen is fixed, so no criteria panel): the markets as a segmented switch of links, the summary with "Run your own screen →", the chips, the four score cards, ONE table panel (company tiles, score badges), ONE chart. */ .fopp-markets { margin: 0 0 16px; } .fopp-summary-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; } .fopp-summary-row > div { min-width: 0; } .btn.fopp-run-own { flex: none; white-space: nowrap; font-size: var(--font-size-sm) !important; } /* full width: the classic four cards a row from 992px (the screener's 2-a-row rule is for its narrower results column) */ @media (min-width: 992px) and (max-width: 1399.98px) { .fopp-cards .row > .col-lg-3 { flex: 0 0 25%; max-width: 25%; } } .fopp-panel { margin: 0 0 20px; } .fopp-panel-sub { margin: 4px 0 0; color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } /* The first column is the frozen one (05_tables.css): on a phone it is capped at a third of the screen, so the tile lets its lines wrap there; wider, it gets room for a long name on one or two lines. */ @media (min-width: 768px) { .fopp-table td:first-child { min-width: 220px; } } @media (max-width: 767px) { .fopp-table .tbl-company-sub { white-space: normal; } } .fopp-chart { margin: 0 0 8px; } @media (max-width: 575.98px) { .fopp-summary-row { flex-direction: column; } }
/* 26_focus_list.css */
/* ============================================================================= 26_focus_list.css — the "Analyse any list" page (/Returns, /Correlation, /Optimization) under the Focus skin. Own file so parallel sessions never share one (22 = the other layer-C pages, 24 = the Portfolio). Focus bundle only; every size a --font-size-* token; classes flist-*. ============================================================================= */ /* ----- 1. The selector panel ----------------------------------------------- */ .flist .flist-selector { border: 1px solid var(--focus-line); border-radius: 12px; background: var(--focus-panel); padding: 16px 18px 6px; margin: 0 0 14px; } .flist .flist-row > [class*="col-"] { min-width: 0; } .flist .flist-selector .fpart-field .dash-dropdown, .flist .flist-selector .fpart-field .form-select { width: 100%; min-width: 0; } .flist .flist-count { color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; padding-bottom: 8px; } .flist .flist-count-over { color: var(--focus-accent); } .flist .flist-run { margin-bottom: 8px; } .flist .flist-status { color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; padding: 2px 0 8px; } /* ----- 2. The view switch and the control rows ---------------------------- */ .flist .flist-views { margin: 0 0 14px; } .flist .flist-controls { margin: 0 0 14px; } .flist .flist-controls .fpart-field .dash-dropdown { min-width: 300px; } .flist .flist-controls .fpart-field .form-select { width: auto; min-width: 160px; } /* ----- 3. Bands, panels, charts ------------------------------------------- */ .flist .flist-band { margin: 0 0 14px; } .flist .flist-view > .card, .flist .flist-mc > .card { margin-bottom: 14px; } .flist .flist-two > [class*="col-"] > .card { height: 100%; } .flist .flist-sub-heading { margin: 4px 0 10px; font-weight: 500; font-size: var(--font-size-h3) !important; } .flist .flist-mc-block { margin-top: 6px; } .flist .flist-mc-actions { display: flex; flex-direction: column; gap: 4px; align-self: flex-end; } .flist .flist-mc-run { align-self: flex-start; } .flist .flist-mc-hint { color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } /* ----- 4. Empty states and the disclaimer ----------------------------------- */ .flist .flist-empty { border: 1px dashed var(--focus-line-strong, var(--focus-line)); border-radius: 12px; padding: 28px 22px; margin: 0 0 14px; text-align: center; } /* no card inside a card (founder, 2026-09-29): inside a panel (the Monte Carlo add-on) the empty state is the panel's own text, not a dashed box */ .flist .card .flist-empty { border: 0; border-radius: 0; padding: 20px 0 8px; } .flist .flist-empty-title { margin: 0 0 6px; font-weight: 500; font-size: var(--font-size-h3) !important; } .flist .flist-empty-text { margin: 0 auto; max-width: 560px; color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; } .flist .flist-disclaimer { border: 1px solid var(--focus-line); border-radius: 8px; padding: 8px 12px; margin: 0 0 14px; color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } /* ----- 5. Phones ------------------------------------------------------------ */ @media (max-width: 767px) { .flist .flist-controls .fpart-field .dash-dropdown { min-width: 0; width: 100%; } .flist .flist-selector { padding: 12px 12px 4px; } }
/* 27_focus_alerts.css */
/* ============================================================================= 27_focus_alerts.css — the Alerts page under the Focus skin and the three plain-value table cell looks it introduced in the shared builder (functions/function_tables.py build_table: repeat_blank / sub_cols / pill_cols). Own file so parallel sessions never share one (22 = the other layer-C pages, 24 = the Portfolio, 25 = the ETF pages, 26 = the "Analyse any list" page). Focus bundle only; every size a --font-size-* token; page classes falr-*, cell classes tbl-*. ============================================================================= */ /* ----- 1. Table cells: blanked group cell, muted second line, pill ---------- The builder emits the classes; the look lives here until the skin becomes the default, when the rules move to 05_tables.css. The second line carries `.small`, so the skin's cell caption rule sizes it (xs). */ .table .tbl-sub { display: block; margin-top: 2px; color: var(--focus-text-muted); font-weight: 400; white-space: normal; } .table .tbl-pill { display: inline-block; padding: 1px 8px; border-radius: 999px; border: 1px solid rgba(var(--focus-accent-rgb), 0.55); background: rgba(var(--focus-accent-rgb), 0.14); color: var(--focus-text); white-space: nowrap; } /* a row followed by a row of the same group: no rule between the two (Bootstrap draws each row's rule as its cells' bottom border) */ .table tbody tr:has(+ tr.tbl-row-cont) > td { border-bottom-color: transparent; } /* ----- 2. The Alerts page (falr-*) ------------------------------------------- apps/app_alerts/function_alerts_focus.py: the band (the shared fpart-stats), then ONE panel — head (title, line, the kind filter, the Edit switch), the value rules (render_alert_table's slot) and the screening alerts (render_screening_alerts_table's slot). */ .falr-panel { padding: 16px 18px 18px; border: 1px solid var(--focus-line); border-radius: 12px; background: var(--focus-panel); } .falr-head { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: 12px 18px; margin-bottom: 12px; } .falr-title { margin: 0; font-weight: 500; letter-spacing: -0.02em; font-size: var(--font-size-h3) !important; } .falr-sub { margin: 2px 0 0; color: var(--focus-text-muted); font-size: var(--font-size-prose-sm) !important; } .falr-tools { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 16px; } .falr-filter { min-width: 0; max-width: 100%; } .falr-tools .falr-edit { margin: 0; white-space: nowrap; } .falr-tools .falr-edit .form-check { margin-bottom: 0; } /* a block the filter leaves out renders empty and takes no room */ .falr-values-off, .falr-scr-off { display: none; } .falr-table td { vertical-align: top; } .falr-note { margin: 10px 0 0; color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .falr-empty { margin: 4px 0 0; padding: 14px 16px; border: 1px dashed var(--focus-line); border-radius: 8px; color: var(--focus-text-muted); } .falr-empty-link { white-space: nowrap; } .falr-scr { margin-top: 20px; } .falr-values-slot:has(> .falr-values-off) + .falr-scr-slot .falr-scr { margin-top: 0; } .falr-scr-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; padding-bottom: 6px; border-bottom: 1px solid var(--focus-line); } .falr-scr-title { font-weight: 500; } .falr-scr-line { color: var(--focus-text-muted); font-size: var(--font-size-prose-sm) !important; } .falr-scr-actions .btn { margin-left: 4px; } /* the two forms: one field per line, the rule being edited in bold */ .falr-form .fpart-field { display: block; margin: 0 0 14px; } .falr-form .fpart-field-hint { display: block; margin-top: 4px; } .falr-ruleinfo { margin: 0 0 12px; font-weight: 600; } /* the band: two by two below 992px (four cells in the narrower column otherwise wrap 3 + 1) */ @media (max-width: 991.98px) { .falr .fpart-stat { flex: 1 1 45%; } } /* phones and tablets (below 992px the column beside the sidebar is too narrow for seven columns): each rule becomes a block of three lines — ticker + company, the rule + when it is checked, then Latest · Gap · Status (cells counted from the END, so the Edit mode's two leading button cells change nothing). No card inside a card (founder, 2026-09-29): the blocks are rows of the panel, a rule between them, not boxes inside it. */ @media (max-width: 991.98px) { .falr-values .falr-table thead { display: none; } .falr-values .falr-table, .falr-values .falr-table tbody { display: block; width: 100%; } .falr-values .falr-table tr { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 10px; margin: 0; padding: 10px 0; border: 0; border-bottom: 1px solid var(--focus-line); } .falr-values .falr-table tr:has(+ tr.tbl-row-cont) { border-bottom: 0; } .falr-values .falr-table tr.tbl-row-cont { border-top: 1px dashed var(--focus-line); } /* the builder's sticky control / first columns (05_tables.css, with !important) and their seam mask make no sense in a block */ .falr-values .falr-table td { display: block; flex: 0 0 auto; padding: 0; border: 0; position: static !important; left: auto; white-space: normal; background: transparent !important; box-shadow: none; } .falr-values .falr-table td::after { content: none; } /* Edit mode: ✎ and X close the block (after line 3), the company keeps line 1 */ .falr-values .falr-table td.edit-col, .falr-values .falr-table td.remove-col { order: 4; width: auto; max-width: none; } .falr-values .falr-table td.tbl-cell-repeat { display: none; } .falr-values .falr-table td:nth-last-child(6) { /* the company: rest of line 1 */ flex: 1 1 55%; min-width: 0; } /* line 3 always starts with Latest: a full-width break (a flex item drawn by ::after, ordered between the first cells and the last three) */ .falr-values .falr-table tr::after { content: ''; flex: 0 0 100%; order: 2; } .falr-values .falr-table td:nth-last-child(-n+3) { order: 3; } .falr-values .falr-table td:nth-last-child(4) .tbl-sub, .falr-values .falr-table td:nth-last-child(3) .tbl-sub, .falr-values .falr-table td:nth-last-child(2) .tbl-sub { display: inline; margin-left: 6px; } .falr-values .falr-table td:nth-last-child(3)::before { content: 'Latest '; color: var(--focus-text-muted); } .falr-values .falr-table td:nth-last-child(2)::before { content: 'Gap '; color: var(--focus-text-muted); } }
/* 28_focus_tools.css */
/* 28_focus_tools.css — Strategy testing, Strategy optimization, ML prediction, Fair value (Focus layer C, group 5). Focus bundle only (combine_css_files.py skin_files); one section per page, classes prefixed per page, every size a --font-size-* token with !important. */ /* ======================== */ /* 1. THE TOOL PARTS */ /* ======================== */ /* apps/app_shell/function_focus_tools.py — the frame the four tool pages share: the Stock screening panel (fscr-*, 22 section 3) holds numbered sections, the results column opens with the calculators' answer block (flab-*, 23). */ .ftool .fpage-head { margin-bottom: 20px; } .ftool-layout { grid-template-columns: 300px minmax(0, 1fr); } /* The tool panels are taller than a screen: a sticky panel would hide Run below the fold while the results scroll, and an inner scroll would clip the dropdown menus (react-select opens inside its container) — so the panel scrolls with the page, as on the classic page. */ .ftool-panel { position: static; } .ftool-reset { color: var(--focus-accent); text-decoration: none; font-size: var(--font-size-sm) !important; } .ftool-reset:hover, .ftool-reset:focus-visible { color: var(--focus-accent); text-decoration: underline; } .ftool-section { padding: 14px 0 6px; border-top: 1px solid var(--focus-line); } .ftool-section-title { display: flex; align-items: baseline; gap: 8px; margin-bottom: 10px; font-weight: 600; font-size: var(--font-size-sm) !important; } .ftool-num { color: var(--focus-accent); letter-spacing: 0.14em; font-weight: 600; font-size: var(--font-size-eyebrow) !important; } .ftool-hint { margin: 4px 0 8px; } .ftool-panel .fpart-field { margin-bottom: 10px; } .ftool-panel .fpart-field .form-control, .ftool-panel .fpart-field .form-select, .ftool-panel .fpart-field .dash-dropdown { width: 100%; min-width: 0; max-width: 100%; } .ftool-two { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; } .ftool-universe { margin-bottom: 4px; } .ftool-panel .seg-control { margin-bottom: 10px; } /* no flex-wrap here: on a phone the bar scrolls sideways like every tab bar (skin section 6); from 768px the skin already wraps it */ .ftool-panel .nav-tabs { margin-bottom: 10px; } /* the classic tab bodies centre their pickers at 8/12 or 6/12: full width here */ .ftool-panel .tab-content .row { margin: 0; } .ftool-panel .tab-content .row > [class*="col-"] { flex: 0 0 100%; width: 100%; max-width: 100%; padding: 0; } .ftool-panel .tab-content label { display: block; margin-bottom: 4px; } .ftool-panel .tab-content p { margin: 0 0 8px; color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; } /* one criterion row: the metric on a line, then operator · value · remove */ .ftool-criteria .row { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: 0 0 10px; } .ftool-criteria .row > [class*="col-"] { margin: 0 !important; padding: 0; } .ftool-criteria .row > [class*="col-"]:nth-child(1) { flex: 0 0 100%; width: 100%; max-width: 100%; } .ftool-criteria .row > [class*="col-"]:not(:first-child) { flex: 1 1 0; width: auto; max-width: none; min-width: 0; } .ftool-criteria .row > [class*="col-"]:has(> .btn) { flex: 0 0 auto; } .ftool-criteria .row > [class*="col-"]:has(> .btn) .btn { margin: 0 !important; } /* a stacked row's field names itself (function_focus_tools.row_field): the remove button then sits on the inputs' line, not on the labels' (!important: the classic row carries Bootstrap's align-items-center utility) */ .ftool-criteria .row:has(.ftool-mini) { align-items: flex-end !important; } .ftool-mini { display: block; margin: 0 0 2px; color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .ftool-criteria .form-control, .ftool-criteria .form-select { width: 100%; min-width: 0; } .btn.ftool-btn { margin: 0 0 8px; font-size: var(--font-size-sm) !important; } .ftool-more-wrap .ftool-hint { margin-top: 0; } .ftool-more { padding-top: 6px; } .ftool-foot { padding-top: 12px; border-top: 1px solid var(--focus-line); } .ftool-summary { margin: 0 0 4px; } .ftool-foot .btn.ftool-btn { width: 100%; } .ftool-foot .progress { margin-top: 8px; } /* no card inside a card (founder, 2026-09-29): inside the panel a notice is a quiet line with its colour bar, not a box */ .ftool-panel .alert { margin: 0 0 12px; padding: 2px 0 2px 12px; border: 0; border-left: 3px solid var(--focus-blue); border-radius: 0; background: transparent; color: var(--focus-text-soft); font-size: var(--font-size-sm) !important; } .ftool-panel .alert-success { border-left-color: var(--focus-positive); } .ftool-panel .alert-warning { border-left-color: var(--focus-warning); } .ftool-panel .alert .btn { margin-top: 8px; } .ftool-alert { margin: 0 0 12px; font-size: var(--font-size-sm) !important; } /* the results column */ .ftool-status:empty { display: none; } .ftool-status .alert { margin-bottom: 14px; border: 1px solid var(--focus-line); border-left: 3px solid var(--focus-line-strong); background: var(--focus-panel); color: var(--focus-text-soft); font-size: var(--font-size-sm) !important; } .ftool-status .alert-success { border-left-color: var(--focus-positive); } .ftool-status .alert-warning { border-left-color: var(--focus-warning); } .ftool-status .alert-danger { border-left-color: var(--focus-negative); } .ftool-status .alert .btn-close { filter: invert(1) grayscale(1); } .ftool-sum { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 12px; margin: 0 0 10px; } .ftool-sum-title { font-weight: 500; letter-spacing: -0.02em; font-size: var(--font-size-h3) !important; } .ftool-sum-detail { color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; } .ftool-chips { margin: 0 0 16px; } .ftool-unit { margin-left: 6px; color: var(--focus-text-muted); letter-spacing: 0; font-size: var(--font-size-h3) !important; } .ftool-results .flab-result-text { max-width: 640px; } .ftool-results .fpart-stats { margin-bottom: 16px; } .ftool-band .fpart-stat-value.text-danger { color: var(--focus-accent-hover) !important; } .ftool-grid2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; margin-bottom: 16px; } .ftool-grid2 > .card, .ftool-grid2 > div > .card { margin-bottom: 0 !important; height: 100%; } .ftool-results > .card, .ftool-results .fst-results > .card { margin-bottom: 16px; } .ftool-table { margin-top: 12px; } .ftool-quiet { margin: 0 0 16px; padding: 16px 20px; border: 1px solid var(--focus-line); border-radius: 12px; } .ftool-quiet-title { margin: 0 0 8px; font-weight: 500; font-size: var(--font-size-md) !important; } .ftool-bullets { margin: 0; padding-left: 18px; color: var(--focus-text-muted); line-height: 1.6; font-size: var(--font-size-sm) !important; } .ftool-disclaimer { margin: 22px 0 8px; color: var(--focus-text-muted); text-align: center; font-size: var(--font-size-xs) !important; } .ftool-figs { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 16px; margin-top: 12px; } .ftool-fig-label { display: block; color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; } .ftool-fig-value { display: block; margin: 6px 0 4px; font-weight: 400; letter-spacing: -0.04em; line-height: 1.1; font-variant-numeric: tabular-nums; font-size: var(--font-size-stat) !important; } .ftool-fig-main .ftool-fig-value { font-size: var(--font-size-title) !important; } .ftool-fig-detail { display: block; color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } @media (max-width: 1199.98px) { .ftool-grid2 { grid-template-columns: minmax(0, 1fr); } } @media (max-width: 991.98px) { .ftool-layout { grid-template-columns: minmax(0, 1fr); } } /* ======================== */ /* 2. STRATEGY TESTING */ /* ======================== */ /* apps/app_strategy_testing/function_strategy_testing_focus.py — the rule on the left, the answer first on the right (the rule's CAGR, the capital, the benchmark), the band, the charts, every trade. */ .fst-results .fscr-chips { margin-top: 0; } .fst-results .flab-result { margin-bottom: 16px; } /* ======================== */ /* 3. STRATEGY OPTIMIZATION */ /* ======================== */ /* apps/app_strategy_optimization/function_strategy_optimization_focus.py — the search on the left, the best rule and its result on the unseen dates first. */ .fso-answer .ftool-figs { padding-top: 14px; border-top: 1px solid var(--focus-line); } .fso-rule { margin: 10px 0 0; font-weight: 500; letter-spacing: -0.01em; font-size: var(--font-size-h3) !important; } .fso-warn { padding-left: 10px; border-left: 2px solid var(--focus-warning); } .fso-apply { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 12px; } .fso-apply-select { width: auto; min-width: 150px; } .fso-apply-feedback .alert { margin: 10px 0 0; font-size: var(--font-size-sm) !important; } /* narrow screens: the tables scroll sideways instead of stacking a word per line */ .fso-split td:first-child { min-width: 150px; } .fso-rules td[data-dash-column="rule"], .fso-rules th[data-dash-column="rule"] { min-width: 220px; } /* the five best rules fit the panel: the headers may wrap, "Feasible" stays narrow */ .fso-rules .dash-spreadsheet-inner th { white-space: normal !important; } .fso-rules th[data-dash-column="feasible"], .fso-rules td[data-dash-column="feasible"] { width: 64px; } .fso-results > .card { margin-bottom: 16px; } .fso-run-line { margin: 0 0 6px; color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } /* ======================== */ /* 4. ML PREDICTION */ /* ======================== */ /* apps/app_ml_prediction/function_ml_prediction_focus.py — four tabs, each answer first: Train (the panel + the last run), What it learned (the skill numbers and the limit), Valuation gap (the read-out, the sections as panels), Backtest (the screen against its pond). */ .fml-tabs + .tab-content { padding-top: 16px; } .fml-tab > .card, .fml-tab > div > .card { margin-bottom: 16px; } /* the (i) of a panel section / the backtest answer, at the right end of its line */ .fml-info { margin-left: auto; } .fml-answer:empty, .fml-status:empty { display: none; } .fml-answer > .flab-result, .fml-status > .flab-result, .fml-learned { margin-bottom: 16px; } .fml-limit { padding-left: 10px; border-left: 2px solid var(--focus-line-strong); } .fml-answer-wrap { position: relative; } /* no backtest read yet: nothing for its (i) to explain */ .fml-answer-wrap:has(> .fml-answer:empty) { display: none; } .fml-answer-info { position: absolute; top: 14px; right: 14px; z-index: 1; } .fml-controls { margin-bottom: 12px; } .fml-bt-fields .fpart-field .form-select { min-width: 150px; } /* a select as wide as its longest option never pushes past a phone screen (WebKit counts the clipped option text as overflow unless the select hides it) */ .fml-controls .fpart-field, .fml-controls .fpart-field .form-select { max-width: 100%; } .fml-controls .fpart-field .form-select { overflow: hidden; text-overflow: ellipsis; } .fml-bt-run { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px 16px; } .fml-apply-wrap { align-self: flex-end; } .btn.fml-apply { font-size: var(--font-size-sm) !important; } .fml-side { flex: 0 1 auto; min-width: 0; padding: 0; } .fml-side label { display: block; margin: 0 0 5px; color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .fml-side-note, .fml-note { margin: 8px 0 0; color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; } .fml-under-chart { margin-top: 10px; } .fml-readout-answer { margin-bottom: 12px; } .fml-rest { margin-top: 4px; } .fml-details { margin: 0 0 16px; padding: 10px 14px; border: 1px solid var(--focus-line); border-radius: 10px; background: var(--focus-panel); } .fml-fold { cursor: pointer; color: var(--focus-text-soft); font-size: var(--font-size-sm) !important; } .fml-details[open] .fml-fold { margin-bottom: 10px; } .fml .ftool-universe { margin-bottom: 12px; } /* every notice on the page as a quiet line (the classic bright alerts) */ .fml .alert { border: 1px solid var(--focus-line); border-left: 3px solid var(--focus-line-strong); background: var(--focus-panel); color: var(--focus-text-soft); font-size: var(--font-size-sm) !important; } .fml .alert-success { border-left-color: var(--focus-positive); } .fml .alert-warning { border-left-color: var(--focus-warning); } .fml .alert-danger { border-left-color: var(--focus-negative); } /* no card inside a card (founder, 2026-09-29): section 1 of Valuation gap is a group (fpart-group, 22 §8) — its controls in a head-less panel, each chart a card of its own; a notice inside a card is a quiet line, not a box */ .fml-stack { display: flex; flex-direction: column; gap: 16px; } .card.fml-sub > .card-body { padding-top: 18px; } .fml-span { grid-column: 1 / -1; } .fml .card:not(.fpart-group) .alert { padding: 2px 0 2px 12px; border: 0; border-left: 3px solid var(--focus-line-strong); border-radius: 0; background: transparent; } .fml .card:not(.fpart-group) .alert-success { border-left-color: var(--focus-positive); } .fml .card:not(.fpart-group) .alert-warning { border-left-color: var(--focus-warning); } .fml .card:not(.fpart-group) .alert-danger { border-left-color: var(--focus-negative); } /* ======================== */ /* 5. FAIR VALUE */ /* ======================== */ /* apps/app_fairvalue/function_fairvalue_focus.py — founder-only: the picker in the header, the model's range beside today's price first, the methods side by side; the screener's filters and the backtest's controls as one row. */ .ffv .fpage-actions { align-items: center; flex-wrap: wrap; } .ffv-picker { width: 300px; max-width: 100%; } .ffv-ticker { width: 100%; } .btn.ffv-run { font-size: var(--font-size-sm) !important; } .ffv-beta { margin: 0 0 8px; } .ffv-tabs + .tab-content { padding-top: 16px; } .ffv-tab > .card { margin-bottom: 16px; } .ffv-intro { display: flex; align-items: flex-start; gap: 12px; margin: 0 0 12px; } .ffv-intro .fscr-hint { margin: 0; } .ffv-info { margin-left: auto; } .ffv-controls { margin-bottom: 12px; } .ffv-controls .fpart-field, .ffv-controls .fpart-field .form-select { max-width: 100%; } .ffv-controls .fpart-field .form-select { min-width: 150px; overflow: hidden; text-overflow: ellipsis; } .ffv-run-wrap { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px; align-self: flex-end; } .ffv-bt-run .form-check { margin: 0; } .ffv-results > .card, .ffv-results > .fpart-panel { margin-bottom: 16px; } .ffv-answer { margin-bottom: 16px; } .ffv-band { margin-bottom: 16px; } .ffv-band-select { margin-bottom: 8px; } .ffv-band-select .row { margin: 0; } /* phones: the table scrolls sideways instead of stacking the method's words */ .ffv-methods td:first-child { min-width: 220px; } .ffv-warn { padding-left: 10px; border-left: 2px solid var(--focus-warning); } .ffv-echo { margin: 0 0 10px; color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } /* the "where it came from" views side by side (stacked on a phone) */ .ffv-trio { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; margin-bottom: 16px; } .ffv-trio > .card { margin-bottom: 0 !important; height: 100%; } @media (max-width: 991.98px) { .ffv-trio { grid-template-columns: minmax(0, 1fr); } } .ffv .alert { border: 1px solid var(--focus-line); border-left: 3px solid var(--focus-line-strong); background: var(--focus-panel); color: var(--focus-text-soft); font-size: var(--font-size-sm) !important; } .ffv .alert-warning { border-left-color: var(--focus-warning); } /* no card inside a card (founder, 2026-09-29): inside a panel a notice is a quiet line with its colour bar, not a box */ .ffv .card .alert { padding: 2px 0 2px 12px; border: 0; border-left: 3px solid var(--focus-line-strong); border-radius: 0; background: transparent; } .ffv .card .alert-warning { border-left-color: var(--focus-warning); }
/* 29_focus_group7.css */
/* 29_focus_group7.css — Options EU, Inflation lab, Books, Account (Focus layer C, group 7). Focus bundle only (combine_css_files.py skin_files); one section per page, classes prefixed per page, every size a --font-size-* token with !important. */ /* ----- 1. Allowance bars (fbars-*) ------------------------------------------- functions/function_allowance_bars.py — ONE shared builder (the Account page; the Subscription page reuses it). A fill and one plain word per state, never the count (founder, 2026-09-25). */ .fbars { display: flex; flex-direction: column; gap: 10px; } .fbars-section { margin-top: 6px; color: var(--focus-text-muted); font-size: var(--font-size-eyebrow) !important; letter-spacing: 0.14em; text-transform: uppercase; font-weight: 600; } .fbars-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px 22px; } @media (max-width: 991.98px) { .fbars-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } } @media (max-width: 575.98px) { .fbars-grid { grid-template-columns: minmax(0, 1fr); } } .fbars-row { min-width: 0; } .fbars-top { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; } .fbars-name { color: var(--focus-text); font-size: var(--font-size-sm) !important; min-width: 0; overflow-wrap: anywhere; } .fbars-word { color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; white-space: nowrap; } .fbars-track { display: block; height: 4px; margin-top: 6px; border-radius: 2px; background: var(--focus-line); overflow: hidden; } .fbars-fill { display: block; height: 100%; border-radius: 2px; background: var(--focus-positive); } .fbars-state-half .fbars-fill { background: var(--focus-blue); } .fbars-state-almost .fbars-fill { background: var(--focus-warning); } .fbars-state-almost .fbars-word { color: var(--focus-warning); } .fbars-state-out .fbars-fill { background: var(--focus-accent); } .fbars-state-out .fbars-word { color: var(--focus-accent-hover); } .fbars-state-none .fbars-name, .fbars-state-none .fbars-word { color: var(--focus-text-muted); } .fbars-details > summary { cursor: pointer; color: var(--focus-accent); font-size: var(--font-size-sm) !important; margin: 4px 0 10px; } .fbars-details[open] > summary { margin-bottom: 14px; } .fbars-note { color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; margin: 4px 0 0; } /* ----- 2. Options EU (foeu-*) ------------------------------------------------- apps/app_options_EU/function_options_focus.py — the group-7 drawing, option (b): notice + one row of fields + the band on top; the chain, open interest, trading, put/call, top strikes and implied volatility on the page; the four other views behind "Deeper views" (the classic tab bar). Panels and fields are the shared fpart-* parts (22 section 8). */ .foeu .fpage-summary { display: flex; flex-direction: column; gap: 14px; } .foeu-notice:empty { display: none; } .foeu-notice .alert { margin: 0 !important; border: 1px solid rgba(243, 156, 18, 0.55) !important; border-radius: 10px; background: rgba(243, 156, 18, 0.10) !important; color: var(--focus-text) !important; font-size: var(--font-size-sm) !important; } .fpart-controls.foeu-controls { margin: 0; align-items: flex-start; } .foeu-field-underlying { flex: 1 1 360px; } .foeu-field-expiry { flex: 1 1 280px; } .foeu-underlying-row { display: flex; gap: 6px; align-items: center; } .foeu-underlying-row .dash-dropdown { flex: 1 1 auto; min-width: 0 !important; } .btn.foeu-refresh { margin: 0 !important; flex: none; } .foeu-field-expiry .dash-dropdown { min-width: 0 !important; width: 100%; } .foeu-selected:empty { display: none; } .foeu-date-chip { align-self: center; margin: 18px 0 0 !important; padding: 5px 11px; border: 1px solid rgba(var(--focus-accent-rgb), 0.55); border-radius: 999px; color: var(--focus-text) !important; background: rgba(var(--focus-accent-rgb), 0.10); white-space: nowrap; font-size: var(--font-size-xs) !important; } .foeu-band:empty { display: none; } .foeu-main, .foeu-iv { display: flex; flex-direction: column; gap: 16px; } .foeu .fpage-body { display: flex; flex-direction: column; gap: 16px; } .foeu-two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 16px; align-items: start; } .foeu-panel-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 10px 14px; flex-wrap: wrap; } .foeu-panel-title { flex: 1 1 240px; min-width: 0; } .foeu-panel-right { margin-left: auto; flex: none; display: flex; align-items: center; gap: 8px; max-width: 100%; } .foeu-info-row { display: flex; justify-content: flex-end; min-height: 0; } .foeu-info-row:empty { display: none; } .foeu-section-eyebrow { margin: 8px 0 0; } .fpart-controls.foeu-iv-controls { margin: 0; align-items: flex-start; } .foeu-field .form-control { min-width: 110px !important; width: 130px; } .foeu-compute-wrap { align-self: flex-end; padding-bottom: 20px; } .btn.foeu-compute { margin: 0 !important; white-space: nowrap; } .foeu-status { display: block; color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .foeu-status:empty { display: none; } .foeu-status .text-info, .foeu-status .text-success, .foeu-status .text-muted { color: var(--focus-text-muted) !important; } .foeu-status .text-warning { color: var(--focus-warning) !important; } /* the chain: calls | strike | puts */ .foeu-chain-table td, .foeu-chain-table th { text-align: right; white-space: nowrap; } .foeu-chain-table td:nth-child(5), .foeu-chain-table th:nth-child(5) { text-align: center; background: rgba(255, 255, 255, 0.03); } .foeu-strike { font-weight: 600; } .foeu-spot { color: var(--focus-accent); border-bottom: 1px solid var(--focus-accent); } .foeu-top .table td, .foeu-top .table th { text-align: right; } /* deeper views: the classic tab bar in the panel head */ .foeu-deeper .foeu-panel-right { flex: 1 1 auto; justify-content: flex-end; } /* no forced wrap: on a phone the bar scrolls sideways like every other tab bar (skin §6), so the last tab stays flush with the frame (founder's round 3, 2026-09-30) */ .foeu-deeper-tabs.nav-tabs { margin: 0 !important; } .foeu-deeper-view { display: flex; flex-direction: column; gap: 16px; } .foeu-deeper-view > div { display: flex; flex-direction: column; gap: 16px; } .foeu-deeper-view .row { row-gap: 16px; } .foeu .card.fpart-panel .card { box-shadow: none !important; } /* no card inside a card (founder, 2026-09-29): "Behind the numbers" and "Deeper views" are groups (fpart-group, 22 §8); a notice inside a card is a quiet line with its colour bar, not a box (the Download views' data status) */ .foeu .card:not(.fpart-group) .alert { padding: 2px 0 2px 12px; border: 0; border-left: 3px solid var(--focus-line-strong); border-radius: 0; background: transparent; color: var(--focus-text-soft); } .foeu .card:not(.fpart-group) .alert-warning { border-left-color: var(--focus-warning); } .foeu .card:not(.fpart-group) .alert-success { border-left-color: var(--focus-positive); } .foeu .card:not(.fpart-group) .alert-danger { border-left-color: var(--focus-negative); } @media (max-width: 991.98px) { .foeu-two { grid-template-columns: minmax(0, 1fr); } } @media (max-width: 767.98px) { .foeu-date-chip { margin-top: 0 !important; } .foeu-compute-wrap { padding-bottom: 0; } .foeu-field .form-control { width: 100%; } .foeu-panel-right { margin-left: 0; } } /* ----- 3. Inflation lab (finf-*) --------------------------------------------- apps/app_inflation/function_inflation_focus.py — option (b): the answer first (the band of five yields and the percentile line), the lab ("The view" in the calculator's lab look, flab-* in 23, beside the result), then "The context" (the classic tabs without the calculator). Focus sessions only. */ .finf .fpage-summary { display: flex; flex-direction: column; gap: 10px; } .finf-line { margin: 0; color: var(--focus-text-soft); font-size: var(--font-size-sm) !important; } .finf .fpage-body { display: flex; flex-direction: column; gap: 18px; } .finf-lab { display: grid; grid-template-columns: 300px minmax(0, 1fr); gap: 16px; align-items: start; } .finf-view-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 10px; margin-bottom: 8px; } .finf-view .fpart-field-label { margin-top: 4px; } .finf-gap { margin-top: 14px !important; } .finf-mode { flex-wrap: wrap; } /* "Three scenarios": the mode callback disables the view slider; the three scenarios replace it (the drawing), so a disabled view slider is hidden */ .finf-forecast:has(.rc-slider-disabled) { display: none; } .finf-scenarios { margin-top: 10px; } .finf-scen { margin: 10px 0 0; padding-top: 8px; border-top: 1px solid var(--focus-line); } .finf-scen-eyebrow { margin: 0 0 4px; } .finf-outcome { min-width: 0; container-type: inline-size; } .finf-result { display: flex; flex-direction: column; gap: 16px; } .finf-two { display: grid; grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr); gap: 16px; align-items: start; } .finf-one { display: grid; gap: 16px; } /* the scenarios and the sensitivity side by side only when the result column leaves each tile room for a four-digit number */ @container (max-width: 760px) { .finf-two { grid-template-columns: minmax(0, 1fr); } } .finf-tiles { display: grid; grid-template-columns: 76px repeat(5, minmax(0, 1fr)); gap: 3px; font-variant-numeric: tabular-nums; } .finf-tile { padding: 8px 2px; border-radius: 4px; background: var(--focus-raised); text-align: center; color: var(--focus-text-soft); font-size: var(--font-size-xs) !important; } .finf-tile-head { background: transparent; color: var(--focus-text-muted); } .finf-tile-pos { color: var(--focus-positive); } .finf-tile-neg { color: var(--focus-accent-hover); } .finf-tile-mid { outline: 1px solid var(--focus-accent); } .finf-context-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 10px 14px; flex-wrap: wrap; } .finf-context-title { flex: 1 1 240px; min-width: 0; } .finf-context-right { margin-left: auto; flex: none; max-width: 100%; } /* no flex-wrap: on a phone the bar scrolls sideways like every tab bar (skin section 6) */ .finf-context-tabs.nav-tabs { margin: 0 !important; } .finf-tab { display: flex; flex-direction: column; gap: 16px; } .finf-tab .row { row-gap: 16px; } .finf-tab .card.fpart-panel .card-body .card { box-shadow: none !important; } /* a bare classic table's own panel (_bare_tables_to_panels): spaced like its siblings */ .finf-table-panel:not(:first-child) { margin-top: 16px !important; } .finf-table-panel .table.mt-3 { margin-top: 0 !important; } .finf-foot { margin-top: 0; } @media (max-width: 991.98px) { .finf-lab, .finf-two { grid-template-columns: minmax(0, 1fr); } } @media (max-width: 575.98px) { .finf-tiles { grid-template-columns: 58px repeat(5, minmax(0, 1fr)); } .finf-context-right { margin-left: 0; } } /* ----- 4. Books (fbook-*) ----------------------------------------------------- apps/app_resources/books/function_books_focus.py — option (b): three shelves, a cover, the short title, the authors, one line on why the book matters, the publisher's description behind "Description", the bookshop link. */ .fbook .fpage-body { display: flex; flex-direction: column; gap: 18px; } .fbook-shelves { display: flex; flex-direction: column; gap: 22px; } .fbook-shelf-name { margin: 0 0 10px; } .fbook-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; align-items: start; } .fbook-card { display: grid; grid-template-columns: 76px minmax(0, 1fr); gap: 14px; padding: 14px; border: 1px solid var(--focus-line); border-radius: 12px; background: var(--focus-panel); min-width: 0; } .fbook-cover { width: 76px; height: auto; border-radius: 3px; display: block; } .fbook-text { min-width: 0; } .fbook-title { margin: 0; color: var(--focus-text); font-weight: 500; line-height: 1.3; font-size: var(--font-size-md) !important; } .fbook-author { margin: 3px 0 6px; color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .fbook-why { margin: 0 0 8px; color: var(--focus-text-soft); font-size: var(--font-size-sm) !important; } .fbook-details > summary { cursor: pointer; color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .fbook-desc { margin: 6px 0 0; color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .fbook-link { display: inline-block; margin-top: 8px; color: var(--focus-accent); text-decoration: none; font-size: var(--font-size-xs) !important; } .fbook-link:hover { color: var(--focus-accent-hover); } .fbook-disclosure { margin-top: 0; } @media (max-width: 1199.98px) { .fbook-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } } @media (max-width: 767.98px) { .fbook-grid { grid-template-columns: minmax(0, 1fr); } } /* ----- 5. Account (facct-*) --------------------------------------------------- apps/app_userpage/function_userpage_focus.py — option (b): the plan card and "The essentials" (the subscription mockup's look), the allowances (fbars-*), the email switches as one list, security, preferences, the data section. */ .facct .fpage-body { display: flex; flex-direction: column; gap: 16px; } .facct-top { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); gap: 16px; align-items: stretch; } .facct-two { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 16px; align-items: start; } .facct-stack { display: flex; flex-direction: column; gap: 16px; } .facct-plan { padding: 20px 22px; border: 1px solid var(--focus-line); border-radius: 12px; background: var(--focus-raised); min-width: 0; } .facct-plan-top { display: flex; justify-content: space-between; align-items: center; gap: 10px; } .facct-plan-name { margin: 16px 0 4px; font-weight: 500; letter-spacing: -0.04em; font-size: var(--font-size-title) !important; } .facct-plan-line { margin: 0 0 14px; color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; } .facct-plan-warn { margin-top: -8px; color: var(--focus-warning); } .btn.facct-plan-btn { margin: 0 !important; } .facct-tag { display: inline-block; padding: 2px 8px; border: 1px solid var(--focus-line); border-radius: 5px; color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .facct-tag-ok { color: var(--focus-positive); border-color: rgba(0, 188, 140, 0.45); } .facct-tag-warn { color: var(--focus-warning); border-color: rgba(243, 156, 18, 0.5); } .facct-status { display: flex; justify-content: flex-end; align-items: center; gap: 8px; flex-wrap: wrap; } .facct-dl { margin: 0; } .facct-dl-row { display: flex; justify-content: space-between; align-items: center; gap: 16px; padding: 9px 0; border-bottom: 1px solid var(--focus-line); } .facct-dl-row:last-child { border-bottom: 0; } .facct-dl dt { margin: 0; color: var(--focus-text-muted); font-weight: 400; font-size: var(--font-size-sm) !important; } .facct-dl dd { margin: 0; text-align: right; word-break: break-word; overflow-wrap: anywhere; font-size: var(--font-size-sm) !important; } .facct-panel-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 10px 14px; flex-wrap: wrap; } .facct-panel-title { flex: 1 1 220px; min-width: 0; } .facct-panel-right { margin-left: auto; flex: none; } .facct-rows { display: flex; flex-direction: column; } .facct-row { display: flex; justify-content: space-between; align-items: center; gap: 16px; padding: 10px 0; border-bottom: 1px solid var(--focus-line); } .facct-rows > .facct-row:last-child, .facct-rows > div:last-child > .facct-row { border-bottom: 0; } .facct-row-text { min-width: 0; } .facct-row-label, .facct-switch-label { display: block; margin: 0; color: var(--focus-text); font-weight: 500; font-size: var(--font-size-sm) !important; } .facct-row-detail { display: block; margin-top: 2px; color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .facct-row-control { flex: none; } .facct-row-control .form-check { margin: 0; min-height: 0; } .facct-row[hidden] { display: none !important; } /* the pause row: one stronger line above it (drawn over the previous row's own line) */ .facct-row-pause > .facct-row { margin-top: -1px; border-top: 1px solid var(--focus-line-strong); } /* "Pause all emails" on: the switches are disabled, their words dim with them */ .facct-row:has(.form-check-input:disabled) .facct-row-text { opacity: 0.55; } .facct-saved { display: block; margin-top: 8px; font-size: var(--font-size-xs) !important; } .facct-danger .btn { white-space: nowrap; } @media (max-width: 991.98px) { .facct-top, .facct-two { grid-template-columns: minmax(0, 1fr); } }
/* 30_focus_research.css */
/* 30_focus_research.css — Comparisons, ETF analysis, Markets (Focus layer C, groups 4 + 6). Focus bundle only (combine_css_files.py skin_files); one section per page, classes prefixed per page, every size a --font-size-* token with !important. */ /* ----- 0. Shared by the three pages: a quiet header button (Learn more, "Find similar funds", a link drawn as one) */ .fres-head-btn, .btn.fres-head-btn { display: inline-flex; align-items: center; padding: 5px 12px; border: 1px solid var(--focus-line-strong); border-radius: 6px; background: var(--focus-panel); color: var(--focus-text); text-decoration: none; white-space: nowrap; font-size: var(--font-size-sm) !important; } .fres-head-btn:hover, .btn.fres-head-btn:hover { border-color: var(--focus-accent); background: var(--focus-raised); color: var(--focus-text); } /* ----- 1. Comparisons (apps/app_financials/function_comparisons_focus.py) — the full mockup A Focus session on /Comparisons only: the Financials page and the classic UI never carry these classes. The analysis region is a flex column; the callback's body is laid out in it through `display: contents` + `order`, so the static chart-options row (a layout piece) sits between the preset bar and the six charts. */ .fcpr-share { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; border: 1px solid var(--focus-line-strong); border-radius: 6px; color: var(--focus-text-soft); font-size: var(--font-size-sm) !important; } .fcpr-share-label { font-size: var(--font-size-sm) !important; } .fcpr-share-copy { display: inline-flex; color: var(--focus-text-soft); cursor: pointer; } .fcpr-picker-panel { margin: 0 0 12px; padding: 14px 18px; border: 1px solid var(--focus-line); border-radius: 12px; background: var(--focus-panel); } .fcpr-picker-eyebrow { margin: 0 0 8px; } .fcpr-picker #financials_dropdown_prompt { display: none; } .fcpr-region { display: flex; flex-direction: column; gap: 0; } .fcpr-body, .fcpr-body-inner { display: contents; } .fcpr-o10 { order: 10; } .fcpr-o20 { order: 20; } .fcpr-o30 { order: 30; } .fcpr-o40 { order: 40; } .fcpr-o50 { order: 50; } .fcpr-o60 { order: 60; } .fcpr-options { order: 70; } .fcpr-o80 { order: 80; } .fcpr-o90 { order: 90; } .fcpr-top { margin: 0 0 12px; } .fcpr-peers-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; } .fcpr-peers-label { color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; } .fcpr-peers { display: flex; flex-wrap: wrap; gap: 8px; } .fcpr-peers .form-check { margin: 0; padding: 0; } .fcpr-peer-input { position: absolute; opacity: 0; pointer-events: none; } .fcpr-peer { display: inline-flex; align-items: baseline; gap: 6px; padding: 5px 10px; border: 1px dashed var(--focus-line-strong); border-radius: 8px; color: var(--focus-text-soft); cursor: pointer; font-size: var(--font-size-sm) !important; } .fcpr-peer::before { content: '+'; color: var(--focus-text-muted); } .fcpr-peer:hover, .fcpr-peer-input:focus-visible + .fcpr-peer { border-color: var(--focus-accent); color: var(--focus-text); } .fcpr-peer-on { border-style: solid; border-color: var(--focus-accent); background: rgba(var(--focus-accent-rgb), 0.14); } .fcpr-chip-name { font-size: var(--font-size-sm) !important; } .fcpr-chip-tkr { margin-left: 4px; color: var(--focus-text-muted); letter-spacing: 0.04em; font-size: var(--font-size-xs) !important; } .fcpr-cards-wrap { margin: 0 0 14px; } .fcpr-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 12px; } .fcpr-card { min-width: 0; padding: 14px 16px; border: 1px solid var(--focus-line); border-top: 3px solid var(--focus-line-strong); border-radius: 12px; background: var(--focus-panel); cursor: pointer; } .fcpr-card:hover, .fcpr-card:focus-visible { background: var(--focus-raised); } .fcpr-card-label { margin-top: 12px; color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .fcpr-card-big { line-height: 1.1; letter-spacing: -0.04em; font-variant-numeric: tabular-nums; font-size: var(--font-size-stat) !important; } .fcpr-card-big small { color: var(--focus-text-muted); letter-spacing: 0; font-size: var(--font-size-md) !important; } .fcpr-bars { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; margin-top: 10px; } .fcpr-bar { display: grid; min-width: 0; color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .fcpr-bar-label { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .fcpr-bar-value { font-weight: 400; font-variant-numeric: tabular-nums; font-size: var(--font-size-h3) !important; } .fcpr-bar-value small { color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .fcpr-bar-top::after { content: ' ●'; vertical-align: top; color: var(--focus-text-soft); font-size: var(--font-size-xs) !important; } .fcpr-bar i { display: block; height: 3px; margin-top: 3px; border-radius: 2px; background: var(--table-border); overflow: hidden; } .fcpr-bar em { display: block; height: 100%; } .fcpr-card-line { margin-top: 10px; color: var(--focus-text-soft); font-size: var(--font-size-xs) !important; } .fcpr-card-cell { display: flex; flex-direction: column; gap: 4px; } .fcpr-trap { color: var(--focus-warning); text-align: center; font-size: var(--font-size-xs) !important; } .fcpr-hint, .fcpr-note, .fcpr-options-hint { margin: 8px 0 0; color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; } .fcpr-warn { color: var(--focus-warning); } /* the Div panels (history, side by side, yearly figures) carry the card frame themselves: one padding, the content full width inside it */ .fcpr-panel { margin: 0 0 14px; padding: 16px 18px; border: 1px solid var(--focus-line); border-radius: 12px; background: var(--focus-panel); min-width: 0; } .fcpr-panel-head { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: 10px 16px; } .fcpr-panel .dash-graph, .fcpr-panel .table-scroll, .fcpr-panel .fcpr-every, .fcpr-panel .fcpr-tables-btn-row { margin: 10px 0 0; } .fcpr-hist-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px; } .fcpr-switch .form-check-label { font-size: var(--font-size-sm) !important; } .fcpr-history, .fcpr-price { margin: 0 0 14px; } /* no card inside a card (founder rule, 2026-09-29): the full-table fold is a ruled section of the Side by side panel, not a boxed panel inside it */ .fcpr-every { padding: 10px 0 0; border-top: 1px solid var(--focus-line); } .fcpr-every summary { color: var(--focus-text-soft); cursor: pointer; font-size: var(--font-size-sm) !important; } .fcpr-every[open] summary { margin-bottom: 8px; } .fcpr-charts-head { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: 10px 16px; margin: 8px 0 10px; } .fcpr-charts-head .nav-tabs { margin-bottom: 0 !important; } .fcpr-charts-head > div:last-child { margin: 0 !important; } .fcpr-charts-head > * { min-width: 0; max-width: 100%; } .fcpr-options { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 18px; margin: 0 0 12px; padding: 10px 14px; border: 1px solid var(--focus-line); border-radius: 10px; background: var(--focus-panel); } .fcpr-options-head { display: flex; flex-direction: column; gap: 2px; flex: 1 1 220px; } .fcpr-options-head .fcpr-options-hint { margin: 0; } .fcpr-options-switches { display: flex; flex-wrap: wrap; gap: 6px 16px; } .fcpr-run, .fcpr-tables-btn { padding: 6px 14px; border: 1px solid var(--focus-accent); border-radius: 6px; background: var(--focus-accent); color: var(--focus-text); font-weight: 600; font-size: var(--font-size-sm) !important; } .fcpr-run:hover, .fcpr-tables-btn:hover { background: var(--focus-accent-hover); } .fcpr-graphs { margin: 0 0 6px; } @media (max-width: 767.98px) { .fcpr-bars { grid-template-columns: repeat(2, minmax(0, 1fr)); } .fcpr-picker-panel, .fcpr-panel { padding: 12px 14px; } } /* the six charts: the two pickers side by side under the plot, Bars / Lines + Zoom below */ .fcpr-graphs .card-body { display: grid; grid-template-columns: minmax(0, 3fr) minmax(0, 2fr); gap: 8px 10px; align-items: start; } .fcpr-graphs .card-body > :first-child, .fcpr-graphs .card-body > :last-child { grid-column: 1 / -1; } @media (max-width: 575.98px) { .fcpr-graphs .card-body { grid-template-columns: minmax(0, 1fr); } } /* the history chart's own card sits inside the panel: one frame */ .fcpr-history .fcpr-panel .card { border: 0; background: transparent; box-shadow: none; } /* ----- 2. ETF analysis (apps/app_etf/function_etf_focus.py) — the full mockup The header carries the fund picker; a banner and a band of four sit over six tabs (+ Bonds for a fund with bonds: the banner's marker shows the tab through :has()). Market structure, Price & trend and Category comparison are hidden tabs reached from "Fund details › Additional analysis"; while one is open the Fund details tab reads as the active one. */ .fetf-picker { display: flex; flex-direction: column; gap: 4px; min-width: 260px; flex: 1 1 300px; max-width: 440px; } .fetf-picker .fetf-picker-dd { margin: 0; } .fetf-summary { display: flex; flex-direction: column; gap: 12px; } .fetf-banner { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: 10px 16px; padding: 16px 20px; border: 1px solid var(--focus-line); border-radius: 12px; background: var(--focus-panel); } .fetf-banner-text { min-width: 0; flex: 1 1 320px; } .fetf-name { margin: 4px 0 4px; font-weight: 500; letter-spacing: -0.02em; font-size: var(--font-size-h3) !important; } .fetf-identity { margin: 0; color: var(--focus-text-soft); font-size: var(--font-size-sm) !important; } .fetf-list-tag { padding: 4px 10px; border: 1px solid var(--focus-positive); border-radius: 999px; color: var(--focus-positive); white-space: nowrap; font-size: var(--font-size-xs) !important; } .fetf-list-tag.fetf-list-partial { border-color: var(--focus-warning); color: var(--focus-warning); } /* the tabs: Bonds only for a fund with bonds; the three views are never shown in the bar */ .fetf-tabs .fetf-tab-hidden, .fetf-tabs .fetf-tab-bonds { display: none; } .fetf-page:has(.fetf-bond-fund) .fetf-tabs .fetf-tab-bonds { display: list-item; } .fetf-page:has(.fetf-subview) .fetf-tabs .fetf-tab-details > .nav-link { background-color: var(--focus-raised); color: var(--focus-text); } .fetf-tab { display: flex; flex-direction: column; gap: 16px; min-width: 0; } /* the wrapper a requirement callback shows / hides with an inline display: spaced by margins */ .fetf-tab-inner { min-width: 0; } .fetf-tab-inner > * + * { margin-top: 16px; } .fetf-band .fpart-stat:has(#efficiency-score-value:empty) { display: none; } /* a value a second callback writes arrives as a coloured span: the band's size, one tone */ .fetf-band .fpart-stat-value * { color: inherit !important; font-size: var(--font-size-stat) !important; } .fetf-band #max-drawdown-value, .fetf-band #max-drawdown-risk { color: var(--focus-accent-hover); } /* no card inside a card (founder, 2026-09-29): a band inside a panel (Concentration, the cost of holding) keeps its dividers, not its frame */ .card .fetf-band { border: 0; border-radius: 0; } /* a value that is a word (the payment frequency): it wraps, one size down */ .fetf-band .fpart-stat-value.fetf-word-value { white-space: normal; overflow-wrap: anywhere; font-size: var(--font-size-h3) !important; } /* the lists of bars */ .fetf-bars { display: grid; gap: 10px; } .fetf-bar { min-width: 0; } .fetf-bar-head { display: flex; justify-content: space-between; gap: 12px; margin-bottom: 4px; font-size: var(--font-size-sm) !important; } .fetf-bar-head strong { font-weight: 500; font-variant-numeric: tabular-nums; white-space: nowrap; } .fetf-bar-head small { margin-left: 8px; color: var(--focus-text-muted); font-weight: 400; font-size: var(--font-size-xs) !important; } .fetf-bar-track { position: relative; height: 4px; border-radius: 2px; background: var(--focus-line); overflow: hidden; } .fetf-bar-track i, .fetf-bar-track i.fetf-bar-fill { display: block; height: 4px; border-radius: 2px; background: var(--focus-accent); } .fetf-bar-track-cat { margin-top: 2px; background: transparent; } .fetf-bar-track i.fetf-bar-cat { background: var(--focus-line-strong); } .fetf-bar-signed i { position: absolute; top: 0; } .fetf-bar-signed i.fetf-bar-pos { left: 50%; background: var(--focus-blue); } .fetf-bar-signed i.fetf-bar-neg { right: 50%; background: var(--focus-accent); } /* "Behind the numbers" / "More views": one card at a time */ .fetf-behind { display: flex; flex-direction: column; gap: 12px; min-width: 0; } .fetf-behind-head { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: 10px 16px; } .fetf-behind-controls { margin: 0; } .fetf-pair-ctl, .fetf-views-ctl { display: flex; flex-wrap: wrap; gap: 8px 12px; margin: 0 0 10px; } .fetf-plain { min-width: 0; } .fetf-plain .card { border: 0; background: transparent; box-shadow: none; } .fetf-trading, .fetf-trading > div { display: flex; flex-direction: column; gap: 16px; } .fetf-curve { margin-top: 14px; } /* the company tiles need their width: the table scrolls sideways on phones instead of squeezing */ .fetf-holdings-table { margin: 0; min-width: 560px; } .fetf-compare { margin-top: 14px; } .fetf-compare-link { display: inline-flex; } /* Fund details */ .fetf-dl { display: grid; grid-template-columns: minmax(120px, max-content) minmax(0, 1fr); gap: 8px 16px; margin: 0; font-size: var(--font-size-sm) !important; } .fetf-dl dt { color: var(--focus-text-muted); font-weight: 400; } .fetf-dl dd { margin: 0; min-width: 0; overflow-wrap: anywhere; } .fetf-dl dd a { color: var(--focus-text-soft); } .fetf-goto-list { display: flex; flex-direction: column; gap: 12px; margin-top: 14px; } .fetf-goto-row { display: flex; flex-direction: column; gap: 2px; } .fetf-goto, .fetf-inline-link { align-self: flex-start; padding: 0; border: 0; background: none; color: var(--focus-text); text-align: left; text-decoration: underline; text-decoration-color: var(--focus-line-strong); text-underline-offset: 3px; cursor: pointer; font-size: var(--font-size-md) !important; } .fetf-inline-link { font-size: inherit !important; } .fetf-goto:hover, .fetf-inline-link:hover { text-decoration-color: var(--focus-accent); } /* the sub-views: where they sit, and the choice between the three */ .fetf-subview { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 16px; } .fetf-sub-crumb { display: flex; align-items: baseline; gap: 4px; } .fetf-back { color: var(--focus-text-soft); font-size: var(--font-size-sm) !important; } .fetf-notice .card { margin: 0; } @media (max-width: 767.98px) { .fetf-picker { min-width: 0; max-width: none; flex-basis: 100%; } .fetf-banner { padding: 14px 16px; } .fetf-dl { grid-template-columns: minmax(0, 1fr); gap: 2px 0; } .fetf-dl dd { margin-bottom: 8px; } } /* ----- 3. Markets (apps/app_markets/function_markets_focus.py) — the full mockup The free overview (bars, a history line, a reference table) over "Compare chosen groups": the criteria panel beside the results column (the Stock screening pattern). The cards reuse the Comparisons section's fcpr-* rules, the bars the ETF section's fetf-bar* rules. */ .fmk-page .fpage-body { display: flex; flex-direction: column; gap: 24px; } .fmk-overview, .fmk-view { display: flex; flex-direction: column; gap: 16px; min-width: 0; } .fmk-intro { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: 12px 24px; padding: 18px 20px; border: 1px solid var(--focus-line); border-radius: 12px; background: var(--focus-panel); } .fmk-intro-title { margin: 6px 0 0; font-weight: 500; letter-spacing: -0.03em; line-height: 1.15; font-size: var(--font-size-h2) !important; } .fmk-intro-title span { color: var(--focus-text-muted); font-size: var(--font-size-h2) !important; } .fmk-intro-count { display: flex; flex-direction: column; align-items: flex-end; text-align: right; } .fmk-intro-count strong { font-weight: 400; letter-spacing: -0.04em; line-height: 1.1; font-size: var(--font-size-stat) !important; } .fmk-intro-count small { color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .fmk-panel-tools { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 8px; margin: 0 0 12px; } .fmk-hist-pick { max-width: 260px; } .fmk-row-link { padding: 0; border: 0; background: none; color: var(--focus-text); text-align: left; text-decoration: underline; text-decoration-color: var(--focus-line-strong); text-underline-offset: 3px; cursor: pointer; font-size: var(--font-size-table) !important; } .fmk-row-link:hover { text-decoration-color: var(--focus-accent); } .fmk-ref-table { margin: 0; } /* the company tiles need their width: the table scrolls sideways on phones instead of squeezing */ .fmk-largest-table { margin: 0; min-width: 640px; } .fmk-compare { display: flex; flex-direction: column; gap: 12px; } .fmk-section-head { display: flex; flex-direction: column; gap: 2px; } .fmk-layout { display: grid; grid-template-columns: minmax(280px, 340px) minmax(0, 1fr); gap: 16px; align-items: start; } .fmk-crit { display: flex; flex-direction: column; gap: 10px; padding: 16px 18px; min-width: 0; border: 1px solid var(--focus-line); border-radius: 12px; background: var(--focus-panel); } .fmk-crit .fpart-field { flex: 0 0 auto; } .fmk-crit hr { margin: 6px 0; border-color: var(--focus-line); opacity: 1; } .fmk-crit-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; } .fmk-reset { color: var(--focus-text-soft); font-size: var(--font-size-sm) !important; } .fmk-step { margin: 0; } .fmk-kind-tabs { margin: 0 0 8px !important; } .fmk-group-pick { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; } .btn.fmk-narrow { align-self: flex-start; padding: 0; color: var(--focus-text-soft); text-decoration: none; font-size: var(--font-size-sm) !important; } .btn.fmk-narrow:hover { color: var(--focus-text); } .fmk-subset p { margin: 8px 0 4px; color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .fmk-switches { display: flex; flex-direction: column; gap: 6px; } .fmk-switch .form-check-label { font-size: var(--font-size-sm) !important; } .btn.fmk-run, .fmk-run { width: 100%; margin-top: 6px; padding: 8px 14px; border: 1px solid var(--focus-accent); border-radius: 8px; background: var(--focus-accent); color: var(--focus-text); font-weight: 600; font-size: var(--font-size-md) !important; } .btn.fmk-run:hover { background: var(--focus-accent-hover); border-color: var(--focus-accent-hover); } .fmk-results, .fmk-results-slot { min-width: 0; } .fmk-empty { display: flex; flex-direction: column; gap: 6px; padding: 28px 24px; border: 1px dashed var(--focus-line-strong); border-radius: 12px; color: var(--focus-text-soft); } .fmk-body { display: flex; flex-direction: column; gap: 16px; min-width: 0; } .fmk-cards { grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); } .fcpr-card.fmk-card { cursor: default; } .fcpr-card.fmk-card-company { border-top-color: var(--focus-text-soft); } .fmk-charts-head { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: 10px 16px; } .fmk-charts-head .nav-tabs { margin-bottom: 0 !important; } .fmk-charts-head > * { min-width: 0; max-width: 100%; } @media (max-width: 991.98px) { .fmk-layout { grid-template-columns: minmax(0, 1fr); } } @media (max-width: 575.98px) { .fmk-intro { padding: 14px 16px; } .fmk-intro-count { align-items: flex-start; text-align: left; } }
/* 31_focus_account.css */
/* 31_focus_account.css — Pricing, Subscription, Checkout, Billing, Log in, Sign up, Password reset (Focus layer C, group 8). Focus bundle only (combine_css_files.py skin_files); one section per page, classes prefixed per page, every size a --font-size-* token with !important. */ /* ======================================================================== */ /* 1. VISITOR BAR (signed-out Focus sessions: layout_shell.build_focus_ */ /* visitor_bar) — the wordmark left, Pricing · Resources · Log in · */ /* Sign up right; no sidebar, so the page is not offset for one. */ /* ======================================================================== */ html[data-skin="focus"]:has(.fvisit-bar) #main_page_content, html[data-skin="focus"]:has(.fvisit-bar) .app-footer { margin-left: auto !important; margin-right: auto !important; max-width: 1240px; padding-left: var(--fshell-gutter); padding-right: var(--fshell-gutter); } html[data-skin="focus"]:has(.fvisit-bar) .app-toast-slot { left: 50%; } .fvisit-bar { display: flex; align-items: center; justify-content: space-between; gap: 16px; min-height: var(--fshell-top); padding: 0 var(--fshell-gutter); background: var(--focus-page); border-bottom: 1px solid var(--focus-line); } .fvisit-nav { display: flex; align-items: center; gap: 18px; min-width: 0; } .fvisit-nav .fvisit-link, .fvisit-res .fvisit-link.btn { color: var(--focus-text-muted); text-decoration: none; font-size: var(--font-size-md) !important; /* = the landing bar's 14px on desktop */ padding: 4px 0; border: 0; background: none; box-shadow: none; } .fvisit-nav .fvisit-link:hover, .fvisit-nav .fvisit-link:focus-visible, .fvisit-res .fvisit-link.btn:hover { color: var(--focus-text); } .fvisit-nav .fvisit-link.active { color: var(--focus-accent); } .fvisit-res .dropdown-menu { background: var(--focus-panel); border: 1px solid var(--focus-line); } .fvisit-res .dropdown-item { color: var(--focus-text); font-size: var(--font-size-sm) !important; } .fvisit-res .dropdown-item:hover, .fvisit-res .dropdown-item:focus { background: var(--focus-raised); color: var(--focus-text); } .fvisit-res-glyph { display: none; } /* Sign in with Google under the entry page's email step (function_auth_entry.py) */ .fauth-or { display: flex; align-items: center; gap: 10px; color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; } .fauth-or::before, .fauth-or::after { content: ''; flex: 1 1 auto; border-top: 1px solid var(--focus-line); } .fauth .fauth-google.btn { display: flex; align-items: center; justify-content: center; gap: 10px; border-color: var(--focus-line); color: var(--focus-text); background: var(--focus-panel); } .fauth .fauth-google.btn:hover { background: var(--focus-page); border-color: var(--focus-text-muted); color: var(--focus-text); } .fauth-google-mark { width: 18px; height: 18px; flex: none; } .fauth-apple-mark { filter: invert(1); /* the black Apple mark on the dark button */ } .fvisit-signup.btn { /* the ONE coral button; same size and corners as the landing page's */ font-size: var(--font-size-md) !important; padding: 7px 12px; border-radius: 7px; white-space: nowrap; } @media (max-width: 767.98px) { html[data-skin="focus"]:has(.fvisit-bar) #main_page_content, html[data-skin="focus"]:has(.fvisit-bar) .app-footer { padding-left: 0; padding-right: 0; margin-left: 16px !important; margin-right: 16px !important; } /* a visitor gets the same halved side margin as a signed-in page (21_focus_shell.css, the phone block) */ html[data-skin="focus"]:has(.fvisit-bar) #main_page_content { margin-left: 8px !important; margin-right: 8px !important; } .fvisit-bar { padding: 0 16px; gap: 10px; } .fvisit-nav { gap: 12px; } /* About, Blog and Pricing live in the Resources menu on a phone; Resources shows as ☰ */ .fvisit-nav .fvisit-pricing, .fvisit-nav .fvisit-hide-sm, .fvisit-res-word { display: none; } .fvisit-res-glyph { display: inline-block; } /* the ☰ alone, as on the landing page's bar: no caret beside it */ .fvisit-res .dropdown-toggle::after { display: none; } } /* ======================================================================== */ /* 2. PRICING (design A) — apps/app_pricing/function_pricing_focus.py. */ /* The plan cards (fpr-card) are the ONE builder the Subscription page's */ /* "Change plan" tab reuses. */ /* ======================================================================== */ .fpr { max-width: 1100px; margin: 0 auto; padding-bottom: 40px; } .fpr .eyebrow { display: block; color: var(--focus-text-muted); } .fpr-head { text-align: center; margin: 12px auto 28px; max-width: 720px; } .fpr-title { font-size: var(--font-size-title) !important; font-weight: 500; letter-spacing: -0.035em; margin: 10px 0 8px; } .fpr-subtitle { color: var(--focus-text-muted); font-size: var(--font-size-md) !important; margin: 0; } .fpr-toolbar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px 20px; margin-bottom: 16px; } .fpr-switch { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px; } .fpr-save, .fpr-ccy-note, .fpr-ccy-note > div { color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } /* a beta tool in the toolkit list (the registry's beta flag) */ .fpr-beta { margin-left: 6px; padding: 0 6px; border: 1px solid var(--focus-line); border-radius: 999px; color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; vertical-align: 1px; } .fpr-ccy-note { text-align: right; } /* the pill's label wrapper (the switch's CSS hook) takes the pill's size, not the global body size */ .fpr-period .fpr-q-label { font-size: var(--font-size-sm) !important; } /* the Monthly / Every 3 months switch: both prices are in the page. Needs :has() (Safari 15.4+, Firefox 121+); without it the monthly prices stay, which is still true — the switch then does nothing. */ .fpr-when-q { display: none; } .fpr:has(.fpr-period .seg-on .fpr-q-label) .fpr-when-q { display: block; } .fpr:has(.fpr-period .seg-on .fpr-q-label) .fpr-when-m { display: none; } .fpr-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; align-items: stretch; } .fpr-card { display: flex; flex-direction: column; background: var(--focus-panel); border: 1px solid var(--focus-line); border-radius: 12px; padding: 20px 20px 16px; min-width: 0; } .fpr-card.fpr-featured { border-color: rgba(var(--focus-accent-rgb), 0.7); } .fpr-card-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: 20px; } .fpr-tag { border: 1px solid var(--focus-line-strong); border-radius: 5px; padding: 1px 7px; color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .fpr-card-title { font-size: var(--font-size-h3) !important; font-weight: 500; margin: 4px 0 14px; } .fpr-amount-row { display: flex; align-items: baseline; gap: 6px; } .fpr-ccy { color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .fpr-amount { font-size: var(--font-size-title) !important; font-weight: 400; letter-spacing: -0.04em; line-height: 1.05; } .fpr-per { color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; margin-top: 4px; } .fpr-rule { border: 0; border-top: 1px solid var(--focus-line); margin: 16px 0; opacity: 1; } .fpr-usage-head { font-size: var(--font-size-h2) !important; font-weight: 500; letter-spacing: -0.02em; } .fpr-usage-sub { color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; margin: 2px 0 10px; } .fpr-facts { list-style: none; padding: 0; margin: 0 0 16px; } .fpr-facts li { position: relative; padding: 3px 0 3px 20px; font-size: var(--font-size-sm) !important; } .fpr-facts li::before { content: "✓"; position: absolute; left: 0; color: var(--focus-positive); } .fpr-cta { margin-top: auto; } .fpr-cta .btn, .fpr-btn.btn { width: 100%; font-size: var(--font-size-sm) !important; } .fpr-fine { color: var(--focus-text-muted); text-align: center; font-size: var(--font-size-xs) !important; margin: 10px 0 0; } .fpr-note { color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .fpr-under-cards { margin: 12px 0 0; max-width: 820px; } .fpr-section, .fpr-topup, .fpr-faq { margin-top: 48px; } .fpr-section-head { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: 10px 24px; margin-bottom: 14px; } .fpr-h2 { font-size: var(--font-size-h2) !important; font-weight: 500; letter-spacing: -0.02em; margin: 6px 0 0; } .fpr-h3 { font-size: var(--font-size-h3) !important; font-weight: 500; margin: 6px 0 8px; } .fpr-aside { color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; max-width: 380px; margin: 0; } .fpr-tools { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 18px 24px; background: var(--focus-panel); border: 1px solid var(--focus-line); border-radius: 12px; padding: 20px; } .fpr-tools-group { font-size: var(--font-size-md) !important; font-weight: 500; margin: 0 0 10px; } .fpr-tools ul { list-style: none; padding: 0; margin: 0; } .fpr-tools li { color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; padding: 3px 0; } .fpr-topup { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); gap: 24px; background: var(--focus-panel); border: 1px solid var(--focus-line); border-radius: 12px; padding: 22px; } .fpr-body { color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; margin: 0 0 14px; } .fpr-link { color: var(--focus-accent); text-decoration: none; font-size: var(--font-size-sm) !important; } .fpr-topup-btn.btn { font-size: var(--font-size-sm) !important; } /* no card inside a card (founder, 2026-09-29): the three packs are a ruled row of the top-up card, not a framed box inside it */ .fpr-packs { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); border-top: 1px solid var(--focus-line-strong); border-bottom: 1px solid var(--focus-line-strong); margin-bottom: 10px; } .fpr-pack { text-align: center; padding: 12px 6px; } .fpr-pack + .fpr-pack { border-left: 1px solid var(--focus-line-strong); } .fpr-pack-pay, .fpr-pack-credit { color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .fpr-pack-amount { font-size: var(--font-size-h3) !important; font-weight: 500; margin: 2px 0; } .fpr-faq { display: grid; grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.6fr); gap: 24px; } .fpr-qa { border-top: 1px solid var(--focus-line); padding: 14px 0; } .fpr-q { font-size: var(--font-size-md) !important; font-weight: 500; margin: 0 0 6px; } .fpr-a { color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; margin: 0; } .fpr-disclaimer { color: var(--focus-text-muted); text-align: center; font-size: var(--font-size-xs) !important; margin: 36px 0 0; } @media (max-width: 991.98px) { .fpr-grid { grid-template-columns: minmax(0, 1fr); } .fpr-tools { grid-template-columns: repeat(2, minmax(0, 1fr)); } .fpr-topup, .fpr-faq { grid-template-columns: minmax(0, 1fr); } } @media (max-width: 575.98px) { .fpr-ccy-note { text-align: left; } .fpr-tools { grid-template-columns: minmax(0, 1fr); padding: 16px; } .fpr-topup { padding: 16px; } } /* ======================================================================== */ /* 3. LOG IN · SIGN UP · PASSWORD RESET (design c) — apps/app_auth/ */ /* function_auth_focus.py. The form column first in the DOM (first on */ /* phones), the story beside it from 992px. A form panel whose inputs */ /* the callback replaced (the success answers) is a completion panel. */ /* ======================================================================== */ .fauth { display: grid; grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr); gap: 24px; max-width: 1040px; margin: 40px auto 48px; align-items: stretch; } .fauth.fauth-single { grid-template-columns: minmax(0, 460px); justify-content: center; } .fauth-form-col { order: 2; min-width: 0; display: flex; flex-direction: column; } .fauth-single .fauth-form-col { order: 0; } /* the reset form is short: it keeps its own height beside the story */ .fauth-reset .fauth-form-col { align-self: start; } /* the form panel fills the column's height (beside the story); on sign-up it sits inside dcc.Loading (parent_className fauth-loading > content div) */ .fauth-form-col > .fauth-form, .fauth-loading, .fauth-loading > div:first-child, .fauth-loading .fauth-form { flex: 1 1 auto; } .fauth-loading, .fauth-loading > div:first-child { display: flex; flex-direction: column; } /* the story panel */ .fauth-story { order: 1; display: flex; flex-direction: column; justify-content: space-between; gap: 20px; min-width: 0; padding: 36px 36px 28px; border: 1px solid var(--focus-line); border-radius: 12px; background: radial-gradient(120% 90% at 0% 100%, rgba(var(--focus-accent-rgb), 0.12), transparent 60%), var(--focus-panel); } .fauth-story-title, .fauth-story-title span { font-size: var(--font-size-title) !important; } .fauth-story-title { margin: 12px 0 0; font-weight: 500; line-height: 1.15; color: var(--focus-text); } .fauth-story-second { color: var(--focus-text-muted); } .fauth-story-text { max-width: 420px; margin: 0; font-size: var(--font-size-md) !important; line-height: 1.6; color: var(--focus-text-muted); } .fauth-bars { display: flex; align-items: flex-end; gap: 8px; height: 96px; } .fauth-bar { flex: 1 1 0; max-width: 36px; border-radius: 4px 4px 0 0; background: rgba(var(--focus-accent-rgb), 0.35); } .fauth-bar:nth-child(1) { height: 22%; } .fauth-bar:nth-child(2) { height: 34%; } .fauth-bar:nth-child(3) { height: 30%; } .fauth-bar:nth-child(4) { height: 48%; } .fauth-bar:nth-child(5) { height: 58%; } .fauth-bar:nth-child(6) { height: 74%; } .fauth-bar:nth-child(7) { height: 100%; background: var(--focus-accent); } .fauth-story-words { margin: 0; font-size: var(--font-size-sm) !important; color: var(--focus-text-muted); } /* the form panel */ .fauth-form { display: flex; flex-direction: column; gap: 14px; padding: 28px 28px 24px; border: 1px solid var(--focus-line); border-radius: 12px; background: var(--focus-panel); } .fauth-head { margin-bottom: 4px; } .fauth-title { margin: 8px 0 4px; font-size: var(--font-size-h2) !important; font-weight: 500; color: var(--focus-text); } .fauth-line { margin: 0; font-size: var(--font-size-sm) !important; color: var(--focus-text-muted); } .fauth-field { display: flex; flex-direction: column; gap: 6px; } .fauth-label { margin: 0; font-size: var(--font-size-sm) !important; font-weight: 500; color: var(--focus-text); } .fauth-hint { font-size: var(--font-size-xs) !important; line-height: 1.5; color: var(--focus-text-muted); } .fauth .fauth-input { width: 100%; margin: 0; } .fauth-pw { position: relative; } .fauth-pw:has(.fauth-show) .fauth-input { padding-right: 64px; } .fauth-show { position: absolute; top: 50%; right: 6px; transform: translateY(-50%); padding: 3px 10px; border: 1px solid var(--focus-line); border-radius: 6px; background: var(--focus-raised); color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; line-height: 1.4; cursor: pointer; } .fauth-show:hover, .fauth-show:focus-visible { color: var(--focus-text); border-color: var(--focus-line-strong); } .fauth-row { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 8px 16px; } .fauth-remember .form-check { margin: 0; min-height: 0; } /* the shared block carries inline margins tuned for the classic sizes */ .fauth-terms .row { flex-wrap: nowrap; align-items: center; margin: 0; --bs-gutter-x: 0; } .fauth-terms .row > * { margin: 0 !important; padding: 0; } .fauth-terms .row > :first-child { flex: none; width: auto; margin-right: 0 !important; } /* the switch's own label is empty (the words are the next column): without it the words line up with "Remember me" */ .fauth-terms .form-check-label:empty { display: none; } .fauth-terms .form-check { margin: 0; min-height: 0; } .fauth-remember, .fauth-remember .form-check-label { margin: 0; font-size: var(--font-size-sm) !important; color: var(--focus-text-muted); } .fauth-link { font-size: var(--font-size-sm) !important; color: var(--focus-accent); text-decoration: none; } .fauth-link:hover { color: var(--focus-text); text-decoration: underline; } /* the terms switch (apps/app_footer/divs_policies.py: inline grey styles) */ .fauth-terms, .fauth-terms p, .fauth-terms a { font-size: var(--font-size-sm) !important; } .fauth-terms p { color: var(--focus-text-muted) !important; } .fauth-terms a { color: var(--focus-text) !important; } .fauth-terms a:hover { color: var(--focus-accent) !important; } .fauth .fauth-submit { width: 100%; margin-top: 2px; font-size: var(--font-size-md) !important; } .fauth-alert:empty { display: none; } .fauth-alert .alert { margin: 0; font-size: var(--font-size-sm) !important; } .fauth-alt { margin: 4px 0 0; text-align: center; font-size: var(--font-size-sm) !important; color: var(--focus-text-muted); } .fauth-alt-out { margin-top: 14px; } /* the completion panel: the callback's answer replaced the form (no input left); after a sign-up the account is signed in, so "Already have an account?" goes */ /* (:has() cannot hold another :has(), hence the two separate tests) */ .fauth-form-col:has(.fauth-loading):not(:has(.fauth-loading input)) .fauth-alt-out { display: none; } .fauth-form:not(:has(input)) { justify-content: center; gap: 8px; min-height: 240px; text-align: left; } .fauth-form:not(:has(input)) p { margin: 0; text-align: left !important; font-size: var(--font-size-md) !important; color: var(--focus-text-muted); } .fauth-form:not(:has(input)) > div > p:first-child, .fauth-form:not(:has(input)) > p:first-child { font-size: var(--font-size-h3) !important; color: var(--focus-text); } .fauth-form:not(:has(input)) .d-grid { margin-top: 12px !important; } @media (max-width: 991.98px) { .fauth { grid-template-columns: minmax(0, 1fr); max-width: 520px; margin-top: 24px; } .fauth-form-col { order: 0; } .fauth-story { order: 1; padding: 24px; gap: 10px; } /* the story shortened under the form: the headline and the three words */ .fauth-story-text, .fauth-bars { display: none; } } /* phones: 16px fields (no iOS zoom on focus) and a larger Show target */ @media (max-width: 767.98px) { .fauth .fauth-input { font-size: var(--font-size-xl) !important; } .fauth-show { min-height: 30px; padding: 4px 12px; } } @media (max-width: 575.98px) { .fauth { margin-top: 16px; } .fauth-form { padding: 20px 16px; } } /* ======================================================================== */ /* 4. SUBSCRIPTION (design c) + Checkout (b) + Billing (b) — apps/ */ /* app_payments/function_subscription_focus.py. Overview · Usage · */ /* Change plan (the /pricing cards, .fpr-*) · Top-up. */ /* ======================================================================== */ .fsub .fsub-tab-body { display: flex; flex-direction: column; gap: 16px; padding-top: 16px; } .fsub-top { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr); gap: 16px; align-items: stretch; } .fsub-panel { min-width: 0; padding: 18px 20px; border: 1px solid var(--focus-line); border-radius: 12px; background: var(--focus-panel); } .fsub-panel-head { margin-bottom: 10px; } .fsub-panel-title { margin: 0; font-size: var(--font-size-h3) !important; font-weight: 500; color: var(--focus-text); } .fsub-panel-sub { margin: 4px 0 0; font-size: var(--font-size-sm) !important; color: var(--focus-text-muted); } .fsub-note { margin: 10px 0 0; font-size: var(--font-size-sm) !important; line-height: 1.55; color: var(--focus-text-muted); } /* the plan card */ .fsub-plan { display: flex; flex-direction: column; min-width: 0; padding: 20px 22px; border: 1px solid var(--focus-line); border-radius: 12px; background: var(--focus-raised); } .fsub-plan-top { display: flex; justify-content: space-between; align-items: center; gap: 10px; } .fsub-plan-name { margin: 14px 0 6px; font-size: var(--font-size-title) !important; font-weight: 500; letter-spacing: -0.04em; color: var(--focus-text); } .fsub-plan-price { margin-bottom: 6px; } .fsub-plan-line { margin: 0 0 16px; font-size: var(--font-size-sm) !important; color: var(--focus-text-muted); } .fsub-plan-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: auto; } .fsub-tag { display: inline-block; padding: 2px 8px; border: 1px solid rgba(0, 188, 140, 0.45); border-radius: 5px; color: var(--focus-positive); font-size: var(--font-size-xs) !important; } /* the essentials / billing lists */ .fsub-dl { margin: 0; } .fsub-dl-row { display: flex; justify-content: space-between; align-items: center; gap: 16px; padding: 9px 0; border-bottom: 1px solid var(--focus-line); } .fsub-dl-row:last-child { border-bottom: 0; } .fsub-dl dt { margin: 0; font-weight: 400; font-size: var(--font-size-sm) !important; color: var(--focus-text-muted); } .fsub-dl dd { margin: 0; text-align: right; overflow-wrap: anywhere; font-size: var(--font-size-sm) !important; color: var(--focus-text); } .fsub-control { border-color: rgba(var(--focus-accent-rgb), 0.35); } /* messages: the status line and the answer after Stripe (hidden while empty) */ .fsub-messages { display: flex; flex-direction: column; gap: 8px; } .fsub-status:empty, .fsub-result:empty, .fsub-hidden { display: none; } /* the status line of the subscribe / top-up / billing buttons: a bar at the foot of the screen while it has words, so it is seen next to the button */ .fsub-status { margin: 0; font-size: var(--font-size-sm) !important; color: var(--focus-text); } .fsub-status:not(:empty) { position: fixed; left: 50%; bottom: 16px; z-index: 1040; transform: translateX(-50%); width: max-content; max-width: calc(100vw - 32px); padding: 10px 16px; border: 1px solid var(--focus-line); border-radius: 10px; background: var(--focus-raised); box-shadow: 0 6px 24px rgba(0, 0, 0, 0.45); } /* no answer yet: no gap under the page header */ .fsub .fpage-summary:not(:has(.fsub-result:not(:empty))):not(:has(.fsub-status:not(:empty))) { margin: 0; } .fsub-result { padding: 16px 20px; border: 1px solid rgba(var(--focus-accent-rgb), 0.45); border-radius: 12px; background: var(--focus-panel); font-size: var(--font-size-md) !important; color: var(--focus-text); } .fsub-result * { font-size: var(--font-size-md) !important; } .fsub-spinner { text-align: center; } .fsub-verifying { margin-top: 10px; font-size: var(--font-size-sm) !important; color: var(--focus-text-muted); } /* Change plan: the /pricing cards with the interval pair and the summary box */ .fsub-or { margin-top: 2px; } .fsub-interval { display: flex; gap: 6px; margin: 12px 0 2px; } .fsub-interval .btn { flex: 1 1 0; font-size: var(--font-size-sm) !important; } .fsub-summary:empty, .fpr-cta:has(.btn:disabled) .fsub-summary { display: none; } .fsub-sum { margin-bottom: 12px; padding: 12px 14px; border: 1px solid var(--focus-line); border-radius: 10px; background: var(--focus-page); } .fsub-sum-head { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; margin: 6px 0 4px; } .fsub-sum-plan { font-size: var(--font-size-md) !important; font-weight: 500; color: var(--focus-text); } .fsub-sum-rhythm { font-size: var(--font-size-sm) !important; color: var(--focus-text-muted); } .fsub-sum .fsub-note { margin-top: 6px; font-size: var(--font-size-xs) !important; } .fpr-card.fsub-current { border-color: rgba(0, 188, 140, 0.55); } .fsub-free-text:empty { display: none; } .fsub-under-cards { margin: 0; } /* Top-up */ .fsub-balance { margin: 0; font-size: var(--font-size-h3) !important; color: var(--focus-text); } .fsub-packs { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; } .fsub .fsub-pack { min-height: 64px; white-space: normal; font-size: var(--font-size-sm) !important; } @media (max-width: 991.98px) { .fsub-top { grid-template-columns: minmax(0, 1fr); } } @media (max-width: 575.98px) { .fsub-packs { grid-template-columns: minmax(0, 1fr); } .fsub-panel { padding: 16px; } }
/* 32_focus_fintabs.css */
/* 32 = the Financials tab contents in the Focus look (Codex astra version 3, founder pick 2026-09-26). Focus bundle only; classes ffin-*; every size a --font-size-* token, every colour a token. Builder: apps/app_financials/function_financials_focus_tabs.py (the classic callbacks' blocks rearranged, every classic id once). Guards: _tests/test_focus_fintabs.py. */ /* ---------------------------------------------------------------- 1. the region: head, status line, cards, Run + Settings */ .ffin-region { display: flex; flex-direction: column; gap: 0; } /* the analysis slot and the body are transparent to the layout (display: contents), so their pieces are the region's flex items: the Run + Settings row and the settings panel sit right after the six chart cards, before the flow diagrams and the tables (founder's iPhone review, 2026-09-29). Anything else a callback writes in the slot (a notice, a paywall) keeps the cards' place. */ .ffin-region > #div_pagecontent_financials_analysis, .ffin-region .ffin-body { display: contents; } .ffin-region > .ffin-head { order: 0; } .ffin-region > .ffin-market-line { order: 1; } .ffin-region > #div_pagecontent_financials_analysis > *, .ffin-region .ffin-body > * { order: 2; } .ffin-region > .ffin-actions-row { order: 3; } .ffin-region > #financial_setting_collapse_content { order: 4; } .ffin-region .ffin-body > .ffin-flows { order: 5; } .ffin-region .ffin-body > .ffin-tables { order: 6; } .ffin-actions-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 18px 0 0; } .ffin-head { display: flex; align-items: center; justify-content: space-between; gap: 16px 24px; flex-wrap: wrap; margin: 4px 0 20px; } .ffin-head-text { min-width: 0; } .ffin-title { font-size: var(--font-size-h2) !important; font-weight: 500; letter-spacing: -0.02em; line-height: 1.3; margin: 0; color: var(--focus-text); } .ffin-sub { font-size: var(--font-size-prose-sm) !important; color: var(--focus-text-muted); margin: 6px 0 0; } /* the tab's own head: the CSS shows the active tab's title (no callback) */ .ffin-t-rat { display: none; } #main_page_content:has(#financials-main-tabs .nav-item:nth-child(2) > .nav-link.active) .ffin-region .ffin-t-rat { display: block; } #main_page_content:has(#financials-main-tabs .nav-item:nth-child(2) > .nav-link.active) .ffin-region .ffin-t-fin { display: none; } /* Ratios ends after its ratio cards (Codex's tab): the flow diagrams and the data tables stay on the Financials tab (the same body serves both tabs) */ #main_page_content:has(#financials-main-tabs .nav-item:nth-child(2) > .nav-link.active) .ffin-body > .ffin-flows, #main_page_content:has(#financials-main-tabs .nav-item:nth-child(2) > .nav-link.active) .ffin-body > .ffin-tables { display: none !important; } /* Settings holds the market switches, which apply on the Ratios tab only (the classic rule): on the Financials tab the button, its panel and the status line stay out of sight */ .ffin-region:has(#wrapper_financials_averages_graph[style*="none"]) #financials_button_settings_graphs, .ffin-region:has(#wrapper_financials_averages_graph[style*="none"]) #financial_setting_collapse_content, .ffin-region:has(#wrapper_financials_averages_graph[style*="none"]) .ffin-market-line { display: none !important; } .ffin-run.btn, .ffin-run { background: var(--focus-accent) !important; border: 1px solid var(--focus-accent) !important; color: var(--focus-text) !important; border-radius: 7px; padding: 9px 14px; font-size: var(--font-size-sm) !important; font-weight: 500; white-space: nowrap; line-height: 1.4; } .ffin-run span { margin-left: 8px; font-size: var(--font-size-sm) !important; } /* the analysis tab's Run is the small one of its row (the portfolio-fit Run keeps its size) */ .ffin-actions-row .ffin-run { padding: 6px 12px; font-size: var(--font-size-xs) !important; } .ffin-actions-row .ffin-run span { margin-left: 6px; font-size: var(--font-size-xs) !important; } .ffin-settings-btn.btn, .ffin-settings-btn { background: var(--focus-raised) !important; border: 1px solid var(--focus-line) !important; color: var(--focus-text) !important; border-radius: 7px; padding: 9px 14px; font-size: var(--font-size-sm) !important; white-space: nowrap; line-height: 1.4; } .ffin-settings-glyph { margin-right: 8px; color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; } /* the analysis tab's Settings is the small one of its row (Portfolio-fit's Force refresh keeps its size) */ .ffin-actions-row .ffin-settings-btn { padding: 6px 12px; font-size: var(--font-size-xs) !important; } .ffin-actions-row .ffin-settings-glyph { margin-right: 6px; font-size: var(--font-size-xs) !important; } /* the settings panel (the three market switches, Ratios tab) and the status line under it */ .ffin-market { background: var(--focus-raised); border: 1px solid var(--focus-line); border-radius: 10px; padding: 16px; margin: 12px 0 0; } .ffin-market .ffin-sub { margin: 4px 0 12px; } .ffin-switches { display: flex; flex-wrap: wrap; gap: 12px 28px; } .ffin-switch .form-check-label, .ffin-switch label { font-size: var(--font-size-sm) !important; color: var(--focus-text-soft); } /* the panel is the raised colour: an off switch's track would vanish into it */ .ffin-market .form-switch .form-check-input:not(:checked) { background-color: var(--focus-line) !important; border-color: var(--focus-line-strong) !important; } .ffin-market-line { display: flex; flex-wrap: wrap; gap: 6px 14px; padding: 4px 0 18px; } .ffin-market-part { font-size: var(--font-size-xs) !important; color: var(--focus-text-muted); } .ffin-market-part:first-child { color: var(--focus-text-soft); } .ffin-market-part:not(:last-child)::after { content: "\00B7"; margin-left: 14px; color: var(--focus-text-muted); } .ffin-market-period::before { content: attr(data-period); } /* ---------------------------------------------------------------- 2. the six chart cards */ .ffin-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; align-items: start; } @media (max-width: 991px) { .ffin-grid { grid-template-columns: minmax(0, 1fr); } } .ffin-card { background: var(--focus-panel); border: 1px solid var(--focus-line); border-radius: 12px; padding: 20px 22px; min-width: 0; } .ffin-card-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 14px; } .ffin-card-title { font-size: var(--font-size-h3) !important; font-weight: 500; line-height: 1.4; margin: 0; color: var(--focus-text); } .ffin-card-title > span { display: block; font-size: var(--font-size-h3) !important; font-weight: 500; color: var(--focus-text); } .ffin-card-title > span > span, .ffin-card-title .fpart-sub, .ffin-card-head .fpart-sub { display: block; margin-top: 4px; font-size: var(--font-size-prose-sm) !important; font-weight: 400; color: var(--focus-text-muted); } .ffin-card-title small { display: block; margin-top: 4px; font-size: var(--font-size-prose-sm) !important; font-weight: 400; color: var(--focus-text-muted); } /* the headline figures (written by the graph callback, read from the chart's own series) */ .ffin-latest { display: flex; align-items: center; justify-content: space-between; gap: 10px; min-height: 64px; margin-bottom: 8px; } .ffin-eyebrow { display: block; font-size: var(--font-size-xs) !important; letter-spacing: 0.08em; text-transform: uppercase; color: var(--focus-text-muted); margin-bottom: 4px; } .ffin-big { font-size: var(--font-size-stat) !important; font-weight: 500; line-height: 1.2; letter-spacing: -0.025em; font-variant-numeric: tabular-nums; color: var(--focus-text); } .ffin-change { display: flex; flex-direction: column; text-align: right; } .ffin-second { font-size: var(--font-size-lg) !important; font-weight: 500; color: var(--focus-text); font-variant-numeric: tabular-nums; } .ffin-second.ffin-pos { color: var(--focus-positive); } .ffin-second.ffin-neg { color: var(--focus-accent-hover); } .ffin-second-cap { font-size: var(--font-size-xs) !important; color: var(--focus-text-muted); } .ffin-missing { display: inline-block; margin-top: 4px; border: 1px solid var(--focus-line); border-radius: 5px; padding: 1px 7px; font-size: var(--font-size-xs) !important; color: var(--focus-text-soft); } .ffin-zero-note { margin: -2px 0 8px; } .ffin-graph { position: relative; } /* Bars / Lines: inside the fold, at the left of the Zoom switch */ .ffin-card-controls { display: flex; align-items: center; min-height: 30px; margin-right: auto; } /* the fold: "Chart options & source values" */ .ffin-fold { margin-top: 14px; border-top: 1px solid var(--focus-line); padding-top: 10px; } .ffin-fold > summary { list-style: none; cursor: pointer; display: flex; align-items: center; font-size: var(--font-size-sm) !important; color: var(--focus-text-soft); } .ffin-fold > summary::-webkit-details-marker { display: none; } .ffin-fold > summary::before { content: "+"; margin-right: 8px; color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; } .ffin-fold[open] > summary::before { content: "\2212"; } .ffin-fold-arrow { margin-left: auto; color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; } .ffin-fold-inner { padding-top: 12px; } .ffin-opt-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; margin: 4px 0 12px; } @media (max-width: 575px) { .ffin-opt-grid { grid-template-columns: minmax(0, 1fr); } } .ffin-field { display: flex; flex-direction: column; gap: 6px; min-width: 0; } .ffin-field-label { font-size: var(--font-size-xs) !important; color: var(--focus-text-muted); } .ffin-hint { font-size: var(--font-size-xs) !important; color: var(--focus-text-muted); } .ffin-picker { margin: 0 !important; } .ffin-opt-row { display: flex; justify-content: flex-end; align-items: center; gap: 12px; flex-wrap: wrap; } .ffin-region .ffin-body > .ffin-flows { margin-top: 30px; } .ffin-fold-body:empty { display: none; } .ffin-fold-body { margin-top: 12px; } .ffin-note { font-size: var(--font-size-xs) !important; line-height: 1.6; color: var(--focus-text-muted); margin: 10px 0 0; } .ffin-values { margin-top: 12px; } .ffin-values .table-scroll { max-height: 320px; } /* ---------------------------------------------------------------- 3. the flow diagrams */ .ffin-flows { display: flex; flex-direction: column; gap: 18px; margin-top: 30px; } .ffin-head-sub { margin: 0; } .ffin-flow-graph { max-width: 100%; overflow-x: auto; } .ffin-flow-graph .dash-graph, .ffin-flow-graph .js-plotly-plot { min-width: 640px; } .ffin-year { margin-top: 12px; } .ffin-year .rc-slider { margin: 10px 0 18px; } .ffin-year .mt-2 { margin-top: 0 !important; } /* the classic spacing class: the margin above does it */ /* ---------------------------------------------------------------- 4. the yearly tables */ .ffin-tables { margin-top: 18px; } .ffin-generate-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; } .ffin-generate.btn, .ffin-generate { display: inline-flex; align-items: center; gap: 12px; background: var(--focus-accent) !important; border: 1px solid var(--focus-accent) !important; color: var(--focus-text) !important; border-radius: 7px; padding: 9px 14px; font-size: var(--font-size-sm) !important; font-weight: 500; } .ffin-meter { border-left: 1px solid var(--focus-line-strong); padding-left: 12px; font-size: var(--font-size-xs) !important; font-weight: 400; } .ffin-datasets { margin-top: 8px; } .ffin-dataset { margin-top: 20px; } .ffin-dataset > summary { list-style: none; cursor: pointer; display: flex; justify-content: space-between; align-items: baseline; gap: 10px; font-size: var(--font-size-lg) !important; color: var(--focus-text); } .ffin-dataset > summary::-webkit-details-marker { display: none; } .ffin-dataset > summary::before { content: "+"; margin-right: 8px; color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; } .ffin-dataset[open] > summary::before { content: "\2212"; } .ffin-muted { margin-left: auto; font-size: var(--font-size-xs) !important; color: var(--focus-text-muted); } /* no card inside a card (founder rule, 2026-09-29): the tables sit flat in their card, a rule above */ .ffin-dataset .table-scroll { max-height: 520px; margin-top: 10px; border: 0 !important; border-top: 1px solid var(--focus-line) !important; border-radius: 0 !important; } /* ---------------------------------------------------------------- 5. phones */ @media (max-width: 575px) { .ffin-card { padding: 16px; } .ffin-head { margin-bottom: 16px; } .ffin-pfit-toolbar .ffin-run { flex: 1; } } /* ---------------------------------------------------------------- 6. value tables: numbers to the right */ .ffin-values td:not(:first-child), .ffin-values th:not(:first-child) { text-align: right; } /* ---------------------------------------------------------------- 7. the News tab (step 3) */ .ffin-news { padding-top: 4px; } .ffin-news-coverage { display: flex; flex-direction: row; align-items: center; justify-content: space-between; gap: 12px 24px; border-top: 1px solid var(--focus-line); border-bottom: 1px solid var(--focus-line); padding: 16px 0; margin: 0 0 22px; } .ffin-news-window { display: block; font-size: var(--font-size-lg) !important; font-weight: 500; color: var(--focus-text); } .ffin-news-coverage-note { display: block; margin-top: 6px; font-size: var(--font-size-xs) !important; color: var(--focus-text-muted); max-width: 760px; } .ffin-news-toggle { flex: 0 0 auto; } @media (max-width: 575px) { .ffin-news-coverage { flex-direction: column; align-items: stretch; } .ffin-news-toggle { align-self: flex-end; } } .ffin-news-toggle .form-check-label { font-size: var(--font-size-sm) !important; color: var(--focus-text-soft); } .ffin-news-research { display: flex; flex-wrap: wrap; gap: 12px 24px; margin: -6px 0 16px; } .ffin-news-research small { font-size: var(--font-size-xs) !important; color: var(--focus-text-muted); max-width: 420px; } .ffin-news-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; align-items: stretch; } @media (max-width: 991px) { .ffin-news-grid { grid-template-columns: minmax(0, 1fr); } } .ffin-news-card { display: flex; flex-direction: column; } .ffin-news-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; } .ffin-news-top .stock-news-read-btn { border: 1px solid var(--focus-line) !important; border-radius: 5px; width: 26px; height: 26px; display: inline-flex; align-items: center; justify-content: center; color: var(--focus-text-muted) !important; text-decoration: none; } .ffin-news-top .stock-news-read-glyph { font-size: var(--font-size-sm) !important; } .ffin-news-marked-read { opacity: 0.6; } .ffin-news-tags { display: flex; flex-wrap: wrap; gap: 10px 14px; margin: 12px 0; } .ffin-news-tag { display: inline-flex; align-items: center; gap: 6px; font-size: var(--font-size-xs) !important; color: var(--focus-text-muted); } .ffin-dot { display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: var(--focus-text-muted); flex: none; } .ffin-dot-action { background: var(--focus-accent); } .ffin-news-title { font-size: var(--font-size-h3) !important; font-weight: 600; line-height: 1.4; margin: 0; color: var(--focus-text); } .ffin-news-source { font-size: var(--font-size-xs) !important; color: var(--focus-text-muted); margin: 8px 0 14px; } .ffin-news-summary { font-size: var(--font-size-base) !important; line-height: 1.75; color: var(--focus-text-soft); margin: 0; } .ffin-news-context { border-left: 2px solid var(--focus-line); padding: 2px 0 2px 12px; margin: 14px 0; } .ffin-news-context p { font-size: var(--font-size-sm) !important; color: var(--focus-text-soft); margin: 4px 0 0; } /* no card inside a card: the read classification is a ruled section of the story card, not a raised box */ .ffin-news-read { border-top: 1px solid var(--focus-line); padding: 10px 0 0; margin-top: auto; } .ffin-news-read > summary { list-style: none; cursor: pointer; display: flex; align-items: center; font-size: var(--font-size-sm) !important; color: var(--focus-text); } .ffin-news-read > summary::-webkit-details-marker { display: none; } .ffin-news-read > summary::before { content: "+"; margin-right: 8px; color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; } .ffin-news-read[open] > summary::before { content: "\2212"; } .ffin-news-read-cap { margin-left: auto; font-size: var(--font-size-xs) !important; color: var(--focus-text-muted); } .ffin-news-read p { font-size: var(--font-size-sm) !important; color: var(--focus-text-soft); margin: 8px 0 0; } .ffin-news-link { display: inline-block; margin-top: 16px; font-size: var(--font-size-sm) !important; color: var(--focus-text-soft); text-decoration: none; } .ffin-news-link:hover { color: var(--focus-text); } .ffin-news-calendar { margin-top: 18px; } .ffin-news-disclaimer { display: block; margin-top: 22px; } /* ---------------------------------------------------------------- 8. Portfolio & correlation (step 4) */ .ffin-pfit { padding-top: 4px; } .ffin-pfit-toolbar { display: flex; align-items: flex-end; gap: 10px; flex-wrap: wrap; margin: 0 0 8px; } .ffin-pfit-select { flex: 1; min-width: 240px; } .ffin-pfit-status { font-size: var(--font-size-xs) !important; color: var(--focus-text-muted); margin: 6px 0 16px; } .ffin-pfit-status::before { content: ""; display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: var(--focus-positive); margin-right: 8px; vertical-align: middle; } .ffin-pfit-status:empty { display: none; } .ffin-pfit-views { margin: 0 0 16px; } .ffin-pfit .tab-content > .tab-pane > div > .ffin-card, .ffin-pfit .ffin-grid + .ffin-card { margin-top: 18px; } .ffin-pfit .ffin-grid { margin-top: 18px; } .ffin-pfit-slider { max-width: 420px; margin-bottom: 8px; } .ffin-pfit-rolling { display: grid; grid-template-columns: minmax(220px, 300px) minmax(0, 1fr); gap: 18px; align-items: start; } @media (max-width: 991px) { .ffin-pfit-rolling { grid-template-columns: minmax(0, 1fr); } } .ffin-pfit-controls { display: flex; flex-direction: column; gap: 8px; } .ffin-pfit-bench-row { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); gap: 14px; } @media (max-width: 767px) { .ffin-pfit-bench-row { grid-template-columns: minmax(0, 1fr); } } .ffin-pfit-whatif { margin-bottom: 12px; } .ffin-pfit-alloc { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); gap: 16px; align-items: end; margin: 6px 0 12px; } .ffin-pfit-weight .ffin-big small { font-size: var(--font-size-md) !important; color: var(--focus-text-muted); } .ffin-pfit-track { height: 4px; background: var(--focus-line); border-radius: 2px; overflow: hidden; margin-top: 8px; } .ffin-pfit-track i { display: block; height: 100%; } .ffin-pfit-bar-now { background: var(--focus-accent); } .ffin-pfit-bar-opt { background: var(--focus-blue); } .ffin-pfit-arrow { color: var(--focus-text-muted); font-size: var(--font-size-lg) !important; padding-bottom: 14px; } .ffin-pfit-sentence { font-size: var(--font-size-base) !important; color: var(--focus-text-soft); margin: 0 0 12px; } .ffin-pfit-empty { padding: 12px 0; } .ffin-pfit .ffin-card + .ffin-card, .ffin-pfit .ffin-grid + .ffin-card, .ffin-pfit .ffin-card + .ffin-grid { margin-top: 18px; } .ffin-pfit .ffin-grid > .ffin-card { margin-top: 0; } /* side-by-side panels share their top edge */ /* the classic callbacks' status boxes (dbc.Alert) as plain lines inside the panels */ .ffin-pfit .alert { background: transparent !important; border: 0 !important; color: var(--focus-text-soft) !important; padding: 0 !important; margin: 0 0 12px !important; font-size: var(--font-size-base) !important; } /* ---------------------------------------------------------------- 9. Value checks (step 5) */ .ffin-checks-section { padding-top: 4px; } .ffin-checks-list { display: flex; flex-direction: column; } .ffin-checks-row { display: grid; grid-template-columns: 26px minmax(0, 1fr) auto; gap: 14px; align-items: center; padding: 14px 0; border-top: 1px solid var(--focus-line); } .ffin-checks-mark { width: 24px; height: 24px; border-radius: 50%; border: 1px solid var(--focus-line-strong); display: inline-flex; align-items: center; justify-content: center; font-size: var(--font-size-sm) !important; color: var(--focus-text-muted); } .ffin-checks-mark.ffin-checks-ok { color: var(--focus-positive); border-color: var(--focus-positive); } .ffin-checks-mark.ffin-checks-flag { color: var(--focus-accent-hover); border-color: var(--focus-accent-hover); } .ffin-checks-label { font-size: var(--font-size-md) !important; font-weight: 600; color: var(--focus-text); } .ffin-checks-detail { font-size: var(--font-size-sm) !important; color: var(--focus-text-muted); margin-top: 2px; } .ffin-checks-side { display: flex; align-items: center; gap: 14px; justify-content: flex-end; flex-wrap: wrap; } .ffin-checks-status { font-size: var(--font-size-xs) !important; color: var(--focus-text-muted); white-space: nowrap; } .ffin-checks-status.ffin-checks-ok { color: var(--focus-positive); } .ffin-checks-status.ffin-checks-flag { color: var(--focus-accent-hover); } .ffin-checks-news { background: none; border: 0; padding: 0; font-size: var(--font-size-xs) !important; color: var(--focus-text-soft); cursor: pointer; white-space: nowrap; } .ffin-checks-news:hover { color: var(--focus-text); } /* no card inside a card: the profit-growth line is a ruled section of the checks card */ .ffin-checks-growth { border-top: 1px solid var(--focus-line); padding: 14px 0 0; margin-top: 8px; } .ffin-checks-growth p { font-size: var(--font-size-md) !important; font-weight: 600; color: var(--focus-text); margin: 4px 0 0; } .ffin-checks .ffin-card-head .btn { flex: none; } @media (max-width: 575px) { .ffin-checks-row { grid-template-columns: 26px minmax(0, 1fr); } .ffin-checks-side { grid-column: 2; justify-content: flex-start; } }
/* 33_focus_portfolio_tabs.css */
/* 33_focus_portfolio_tabs.css */ /* ======================================================================== */ /* The Focus PORTFOLIOS page, Allocations / Financials / Metrics tabs */ /* (function_portfolio_focus_tabs.py). Split out of 24_focus_portfolio.css on */ /* 2026-09-30 so the tabs can be built in isolation; the frame, banner and */ /* holdings rules stay in 24. Focus bundle only. Sizes = --font-size-* tokens. */ /* ======================================================================== */ /* ---- step 3: Allocations, Financials, Metrics (function_portfolio_focus_tabs.py) ---- */ .fpf-show { min-width: 0; } /* the 'show one of' control = the app's segmented control (its pattern id type is fpf-showctl) */ .fpf-showctl, .fpf-toolbar-controls .fpf-showctl { margin: 0; } .fpf-alloc-groupby { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; } .fpf-alloc-label { color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; } .fpf-alloc-group { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--fpf-gap); align-items: start; margin-bottom: var(--fpf-gap); /* a family is a block of the page: the one gap below it */ } /* the page's one gap (24_focus_portfolio.css): a card inside a family's grid cell (behind its dcc.Loading) or inside the grouping cell adds no space of its own; the grid's gap is the space */ .fpf-alloc-group :is(.card, .fpf-panel, .fpart-stats), .fpf-alloc-cell :is(.card, .fpf-panel) { margin-bottom: 0; } .fpf-alloc-group > div { min-width: 0; } .fpf-fin-band, .fpf-met-band { margin-bottom: 16px; } .fpf-fin .fpf-panel { margin-bottom: 16px; } .fpf-three { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; margin-top: 16px; } .fpf-met .fpf-two { margin-top: 0; } .fpf-met .fpf-two + .fpf-three, .fpf-met .fpf-three + .fpf-two { margin-top: 16px; } .fpf-met-list .list-group-item { padding: 6px 0; background: transparent; border-color: var(--focus-line); } .fpf-met-list .btn { font-size: var(--font-size-sm) !important; } .fpf-met-plain { font-size: var(--font-size-sm) !important; border-color: var(--focus-line); } .fpf-met-copy { color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; margin: 0 0 8px; } @media (max-width: 991.98px) { .fpf-alloc-group, .fpf-three { grid-template-columns: minmax(0, 1fr); } } /* ---- Allocations, review round 3 (2026-09-30): the ETF page's bar lists (functions.function_focus_parts.bar_row, `fetf-bar*` in 30_focus_research.css) instead of the pies and the rainbow bars. Coral = a measured group, blue (.fpf-bar-part) = the part of the portfolio the view does not break down, grey (.fpf-bar-rest) = unknown / "Other". A group opens onto its holdings: a native <details>, no callback. ---- */ .fpf-alloc-cell { min-width: 0; } .fpf-alloc-span { grid-column: 1 / -1; min-width: 0; } .fpf-grp > summary { display: block; list-style: none; cursor: pointer; border-radius: 4px; } .fpf-grp > summary::-webkit-details-marker { display: none; } .fpf-grp > summary .fetf-bar-head > span:first-child::before { content: ''; display: inline-block; width: 0; height: 0; margin: 0 8px 1px 1px; border-top: 4px solid transparent; border-bottom: 4px solid transparent; border-left: 5px solid var(--focus-text-muted); transition: transform 0.15s ease; } .fpf-grp[open] > summary .fetf-bar-head > span:first-child::before { transform: rotate(90deg); } .fpf-grp > summary:hover .fetf-bar-head > span:first-child { color: var(--focus-accent); } .fpf-grp > summary:focus-visible { outline: 1px solid var(--focus-accent); outline-offset: 3px; } /* a group without holdings inside lines up with the ones that have the arrow */ .fpf-grp-list:not(.fpf-grp-flat) .fpf-grp-plain .fetf-bar-head > span:first-child { padding-left: 14px; } .fpf-alloc-panel > .fpf-panel-title { margin-bottom: 12px !important; /* over the heading's Bootstrap mb-0: title → first bar, inside the card */ } /* a section placeholder keeps the classic reserve (140–450px) only while it loads: a bar list shorter than that would leave a hole under it once the families stack on a phone */ .fpf-alloc-group [id^="portfolio-advanced-"]:not(:empty), .fpf-alloc #portfolio-advanced-concentration-cards:not(:empty) { min-height: 0 !important; } .fpf-grp-inside { margin: 8px 0 4px 3px; padding-left: 11px; border-left: 1px solid var(--focus-line); gap: 8px; } .fpf-grp-inside { display: grid; } /* a holding inside a group: name + value, the bar drawn from --w on a grey track */ .fpf-pos { position: relative; display: flex; justify-content: space-between; gap: 12px; padding-bottom: 7px; color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .fpf-pos > span:last-child { font-variant-numeric: tabular-nums; white-space: nowrap; } .fpf-pos small { margin-left: 8px; font-size: var(--font-size-xs) !important; } .fpf-pos::before, .fpf-pos::after { content: ''; position: absolute; left: 0; bottom: 0; height: 3px; border-radius: 2px; } .fpf-pos::before { right: 0; background: var(--focus-line); } .fpf-pos::after { width: var(--w, 0); background: var(--focus-accent); opacity: 0.6; } .fpf-bar-rest .fetf-bar-head > span { color: var(--focus-text-muted); } .fpf-bar-rest .fetf-bar-track i { background: var(--focus-line-strong); } .fpf-bar-part .fetf-bar-track i { background: var(--focus-blue); } .fpf-alloc-panel .fpart-note { margin: 10px 0 0; } .fpf-rank-more { margin-top: 10px; } /* review (Codex, 2026-09-30): a card keeps its own height beside a longer one; "Other" reads as clickable */ .fpf-alloc > .fpf-two { align-items: start; } .fpf-grp-other > summary .fetf-bar-head > span:first-child { color: var(--focus-text); } @media (max-width: 575.98px) { .fpf-grp > summary { padding: 3px 0; /* a taller tap target on phones */ } } /* ---- round 4 (2026-09-30): the "More views" card — the ETF page's eight holdings charts (functions/function_graphs_holdings.py), grouped as "Group positions by" says. The two segmented controls sit in the ETF page's .fetf-views-ctl row; one figure below. ---- */ .fpf-allocviz-out { min-height: 350px; /* the smallest figure: no jump while the chosen one is drawn */ } .fpf-allocviz-out > div > .fpart-note:first-child { margin: 0 0 6px; }
/* 34_focus_portfolio_perf.css */
/* 34_focus_portfolio_perf.css */ /* ======================================================================== */ /* The Focus PORTFOLIOS page, Performance tab (function_performance_focus.py) */ /* — Overview, Returns, Rolling, Factor. Split out of 24_focus_portfolio.css */ /* on 2026-09-30 so the tab can be built in isolation; the frame, banner and */ /* holdings rules stay in 24. Focus bundle only. Sizes = --font-size-* tokens. */ /* ======================================================================== */ /* ---- the Performance tab (function_performance_focus.py) ---- */ /* the date range, its presets and the two invested switches: one card, LAST in the tab */ .fpf-perf-controls { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: 10px 18px; } .fpf-perf .fpf-perf-controls .fpf-perf-range { margin: 0; /* inside the card: no page gap under the date row */ align-items: flex-end; } .fpf-perf-presets { margin: 0; } .fpf-perf-presets .btn { margin: 0 !important; } /* the selected preset: the classic callback drops its `outline` (no .active) */ .fpf-perf-presets > .btn.btn-primary:not(.btn-outline-primary) { background: var(--focus-raised) !important; color: var(--focus-text) !important; border-color: transparent !important; } .btn.fpf-perf-apply { margin: 0; } .fpf-perf-classic-heading { display: none; /* the Focus sentence heading replaces the classic section title */ } .fpf-perf-apply-slot { align-self: flex-end; } .fpf-perf-switches { display: flex; flex-wrap: wrap; gap: 6px 16px; padding-bottom: 6px; } .fpf-perf-switches .form-check { margin: 0; } .fpf-perf-deeper { margin: 22px 0 10px; } .fpf-perf-view-head { margin: 6px 0 12px; } .fpf-perf-subtabs { margin-bottom: 12px; } .fpf-perf-footer { margin-top: 14px; } table.fpf-perf-periods { font-variant-numeric: tabular-nums; } .fpf-perf-note.alert { background: transparent; border: 1px solid var(--focus-line); color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; } /* ---- ONE rhythm (founder, 2026-09-30: 'the spacing between the cards is not always uniform, like in the Financials module'): every card, band, row of cards, note and control row of the tab is spaced by the page's one gap (--fpf-gap, 24_focus_portfolio.css) as a bottom margin and nothing else — the view pass (`_one_rhythm`) drops the classic mb-N / mt-N margins around them. 22 zeroes a panel's margin with !important, so the panels need the scoped !important here; a card inside a row of cards leaves the space to its row (the gutter stacks them on a phone). ---- */ .fpf-perf .card.fpart-panel, .fpf-perf .fpf-perf-built { margin-top: 0 !important; margin-bottom: var(--fpf-gap) !important; } .fpf-perf .fpart-controls, .fpf-perf .fpf-perf-note.alert { margin-top: 0; margin-bottom: var(--fpf-gap); } /* a row keeps its gutter's negative top margin (its columns' own top margin cancels it) */ .fpf-perf .fpf-perf-row { margin-bottom: var(--fpf-gap); } .fpf-perf .fpf-perf-row > * > .card.fpart-panel, .fpf-perf .card .card.fpart-panel { margin-bottom: 0 !important; } /* ---- Overview: the value chart's card holds the Daily / Monthly / Yearly fold at its foot ---- */ /* the wrapper draws the card (the panel look of 22, section 8); the chart card inside it draws no second frame (no card inside a card) */ .fpf-perf-built { border: 1px solid var(--focus-line); border-radius: 12px; background: var(--focus-panel); min-width: 0; } .fpf-perf .fpf-perf-built .card.fpart-panel { margin-bottom: 0 !important; border: 0 !important; border-radius: 0 !important; background: transparent !important; } .fpf-perf-built .card.fpart-panel > .card-body { padding-bottom: 0; } .fpf-perf-built > .fpf-perf-fold { margin: 14px 20px 0; padding-bottom: 16px; } .fpf-perf-fold .seg-control { margin: 0; } /* ---- the views: sections in cards, the classic rules gone ---- */ .fpf-perf-hr { display: none; } .fpf-perf-section .fpf-perf-subhead { margin: 0 0 8px; font-weight: 500; font-size: var(--font-size-base) !important; } @media (max-width: 767.98px) { /* 'Best' over 'Worst' periods on a phone: the page's gap between the two tables */ .fpf-perf-section .row > [class*="col"] + [class*="col"] { margin-top: var(--fpf-gap); } } /* ---- Returns: the benchmark picker full width, the metric beside it on a wide screen ---- */ .fpf-perf-returns-controls .fpart-field-wide .dash-dropdown { width: 100%; } .fpf-perf .fpf-perf-returns-controls { row-gap: var(--fpf-gap); /* a phone stacks the two fields: the page's gap between them */ } @media (max-width: 767.98px) { .fpf-perf-controls { flex-direction: column; align-items: stretch; } .fpf-perf-built > .fpf-perf-fold { margin: 12px 16px 0; padding-bottom: 14px; } } /* ---- the "Date range and options" card (last in the tab) ---- */ .fpf-perf-range-card .fpf-perf-switches { padding-bottom: 0; } .fpf-perf-band .fpart-stat { min-width: 0; } /* a table in a section card starts right under the card's head (the classic table carries mt-3) */ .fpf-perf-section table.table { margin-top: 0 !important; margin-bottom: 0; }
/* 35_focus_portfolio_corr_opt.css */
/* 35_focus_portfolio_corr_opt.css */ /* ======================================================================== */ /* The Focus PORTFOLIOS page, Correlations / Rebalance & Optimization / News */ /* tabs and the target weights (function_correlation_focus.py, */ /* function_optimization_focus.py, function_news_focus.py, function_target_weights.py). */ /* Split out of 24_focus_portfolio.css on 2026-09-30 (round 4) so the tabs can */ /* be built in isolation. Focus bundle only. Sizes = --font-size-* tokens. */ /* ======================================================================== */ /* ---- step 4: Correlations, Rebalance, News (function_correlation_focus.py, ---- */ /* ---- function_optimization_focus.py, function_news_focus.py) -------------- */ .fpf-corr-controls, .fpf-opt-controls, .fpf-news-filters { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 10px 18px; margin: 6px 0 12px; } .fpf-corr-controls .fpart-field, .fpf-opt-controls .fpart-field, .fpf-news-filters .fpart-field { min-width: 150px; max-width: 100%; } .fpf-corr-controls .fpart-field-wide, .fpf-opt-controls .fpart-field-wide { flex: 1 1 320px; } /* a period slider became the segmented control: the pills sit on the field's baseline */ .fpf-corr-controls .seg-control { margin: 0; } /* the heatmap card's controls sit between its header and its body (a direct child of the card): the card's side padding; the network card's control is inside the body already */ .fpf-corr .card > .fpf-corr-controls { padding: 0 16px; } .fpf-corr-sort { display: flex; gap: 8px; align-items: flex-end; } .fpf-corr-sort .btn { font-size: var(--font-size-sm) !important; } .fpf-net-map { min-height: 200px; } .fpf-opt-targets { min-height: 60px; } .fpf-news-band { margin-bottom: 12px; } /* "How the holdings connect" is a static card (function_correlation_focus.network_panel): the threshold control sits under its header, the map slot under the control */ .fpf-corr-net > .card-body { display: flex; flex-direction: column; } .fpf-corr-net .fpf-corr-controls { margin-top: 0; } .fpf-corr-dropdown { min-width: 260px; } .fpf-corr-foot { margin: 18px 0 6px; } .fpf-corr-foot .btn { font-size: var(--font-size-sm) !important; } /* the optimiser: objective chips + the four fields in ONE row */ .fpf-opt-objectives { display: flex; flex-direction: column; gap: 4px; flex: 1 1 360px; } .fpf-opt-objectives .btn { font-size: var(--font-size-sm) !important; border-radius: 999px; } .fpf-opt-objectives .d-flex { gap: 0 2px; } .fpf-opt-status { font-size: var(--font-size-sm) !important; color: var(--focus-text-muted); margin-bottom: 8px; } .fpf-opt-switch { align-self: flex-end; padding-bottom: 6px; } .fpf-opt-band .fpart-stat-value { font-size: var(--font-size-h3) !important; } .fpf-opt-arrow { white-space: nowrap; } /* the news cards: the mockup's numbered stories */ .fpf-news-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; } .fpf-news-card { position: relative; border: 1px solid var(--focus-line); border-radius: 10px; padding: 14px 16px 12px; background: var(--focus-panel); display: flex; flex-direction: column; gap: 6px; } .fpf-news-card.fpf-news-read { opacity: 0.6; } .fpf-news-head { display: flex; justify-content: space-between; align-items: center; gap: 8px; } .fpf-news-head .btn { font-size: var(--font-size-sm) !important; } .fpf-news-number { position: absolute; right: 16px; bottom: 10px; font-size: var(--font-size-h2) !important; color: var(--focus-text-muted); opacity: 0.35; font-weight: 700; pointer-events: none; } .fpf-news-title { font-size: var(--font-size-h3) !important; margin: 0; line-height: 1.3; } .fpf-news-title a { color: inherit; text-decoration: none; } .fpf-news-company { font-size: var(--font-size-sm) !important; color: var(--focus-text-muted); margin: 0; } .fpf-news-foot { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; margin-top: auto; padding-right: 36px; font-size: var(--font-size-sm) !important; } .fpf-news-toggle { align-self: flex-end; padding-bottom: 6px; } .fpf-news-lines { margin: 10px 0; } @media (max-width: 767.98px) { .fpf-news-grid { grid-template-columns: minmax(0, 1fr); } .fpf-corr-dropdown { min-width: 100%; } } .fpf-corr, .fpf-opt, .fpf-news { display: flex; flex-direction: column; gap: 4px; } .fpf-corr-views, .fpf-opt-views { margin-top: 18px; } .fpf-corr-views .seg-control, .fpf-opt-views .seg-control { margin: 0; } .fpf-news-cards { margin-bottom: 8px; } .fpf-news-eyebrow .stock-news-dot { margin-right: 4px; } .fpf-news-eyebrow .small { font-size: var(--font-size-eyebrow) !important; letter-spacing: 0.08em; text-transform: uppercase; } /* ---- step 6: the founder's target weights (function_optimization_focus.py + function_target_weights.py) ---- */ .fpf-target-actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 10px 0 6px; } .fpf-target-actions .btn { font-size: var(--font-size-sm) !important; } .fpf-target-status { min-height: 1.4em; font-size: var(--font-size-sm) !important; color: var(--focus-text-muted); } .fpf-target-line { display: block; margin-top: 6px; font-size: var(--font-size-sm) !important; color: var(--focus-text-muted); } .fpf-target-input.form-control { width: 96px; margin-left: auto; text-align: right; font-size: var(--font-size-md) !important; padding: 2px 8px; } .fpf-opt-targets table td:last-child, .fpf-opt-targets table th:last-child { text-align: right; } .fpf-opt-targets .table-scroll { max-height: 420px; overflow-y: auto; } .fpf-target-diff .table-scroll { max-height: 360px; overflow-y: auto; margin-bottom: 12px; } /* ---- round 4 (founder, iPhone 2026-09-30) ------------------------------------ */ /* "How the holdings connect": ONE card holds the full map (wider screens) and the phone map + the strongest pairs (function_correlation_focus.phone_network); the screen width picks one. */ .fpf-net-phone { display: none; } @media (max-width: 767.98px) { .fpf-corr-net .fpf-net-full { display: none !important; } .fpf-corr-net .fpf-net-phone { display: block; } } .fpf-net-scope { margin: 4px 0 12px; color: var(--focus-text-muted); font-size: var(--font-size-sm) !important; } .fpf-net-pairs { display: flex; flex-direction: column; gap: 10px; margin-bottom: 16px; } /* Rebalance: the objective + the four selects in the "Optimisation settings" panel */ .fpf-opt-settings .fpf-opt-controls { margin: 12px 0 0; } /* the objective chips on their own line, the four selects share the next one (two by two on a phone) */ .fpf-opt-settings .fpf-opt-objectives { flex: 1 1 100%; } .fpf-opt-settings .fpf-opt-controls .fpart-field { flex: 1 1 130px; min-width: 0; } /* a grid pulled straight under a panel title (the metrics table) gets the title's gap */ .fpf-opt .fpf-panel-title + .dash-table-container, .fpf-corr .fpf-panel-title + .dash-table-container { margin-top: 12px; }
/* 36_focus_macro_additions.css */
/* ============================================================== */ /* 36. MACRO — the pulse band, the Monthly tab, the projections */ /* ============================================================== */ /* apps/app_macro/macro_monthly.py (founder, 2026-09-30, chosen from the drawings macro_A/B/C): the pulse band above the tabs (the band of large numbers with a sparkline in each cell), the Monthly tab's period control and chart grid, the projections switch of the time-series tab. Focus bundle only; the classic look uses Bootstrap classes. Sizes are tokens. To be merged into 22_focus_pages.css section 10 (fmac-*) by the lead. */ .fmacx-pulse .fmacx-pulse-cell { display: flex; flex-direction: column; } .fmacx-spark { margin-top: auto; /* the sparklines line up across the cells */ padding-top: 10px; } .fmacx-spark-cap { margin-top: 2px; color: var(--focus-text-muted) !important; font-size: var(--font-size-xs) !important; font-variant-numeric: tabular-nums; } .fmacx-spark-none { margin-top: auto; padding-top: 10px; color: var(--focus-text-muted) !important; font-size: var(--font-size-xs) !important; } .fmacx-pulse-note { margin: 8px 0 0; } .fpart-controls.fmacx-controls { align-items: flex-end; margin: 0 0 12px; } /* the Monthly tab's and the projections' chart rows (not the rows inside a chart card): even gaps */ .fmacx-body > .row, .fmacx-body > div > .row { --bs-gutter-y: 16px; margin-bottom: 0 !important; } .fmacx-body > .row + .row, .fmacx-body > div > .row + .row { margin-top: 8px; } .fmacx-body > .row > [class*="col"] { margin-bottom: 0 !important; } .fmacx-body { margin-bottom: 16px; }
/* 37_focus_bonds_additions.css */
/* ==================================================================== */ /* 37. GOVERNMENT BONDS — the Yield curves additions (2026-09-30) */ /* ==================================================================== */ /* apps/app_bonds/function_bonds_focus.py (`fbondx-*`): the founder's choice from the mockups bonds_A / bonds_B / bonds_C — A. the euro-area AAA curve (overlay on the term structure + gap panel), B. how the curve moved (presets, the chart's Play button, a date slider), C. the 2-year yield beside the central bank rate (five small charts and a "How to read it" card). Focus bundle only; override-only on top of 22_focus_pages.css section 9 (`fbond-*`) and the shared parts (`fpart-*`). Sizes are tokens. */ /* ---- B. How the curve moved ---------------------------------------- */ .fbondx-presets { margin: 2px 0 6px; } .fbondx-presets .seg-control { flex-wrap: wrap; } /* the date slider sits in the figure's bottom band, right of the Play button that Plotly draws there (one row, as in the mockup) */ .fbondx-history-grid .bonds-history-controls { position: relative; display: flex; align-items: center; gap: 14px; margin: -52px 0 6px 92px !important; min-height: 40px; } .fbondx-history-grid .bonds-history-slider { flex: 1 1 auto; min-width: 0; } .fbondx-history-grid .bonds-history-slider .rc-slider { margin: 10px 0 18px; } .fbondx-history-grid .bonds-history-slider .rc-slider-track { background-color: var(--focus-blue); } .fbondx-history-grid .bonds-history-slider .rc-slider-handle { border-color: var(--focus-blue); } .fbondx-history-grid .bonds-history-slider .rc-slider-dot { display: none; } .fbondx-history-grid .bonds-history-slider .rc-slider-mark-text { color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .fbondx-history-grid .bonds-history-date { flex: 0 0 auto; margin-bottom: 18px; color: var(--focus-text); font-variant-numeric: tabular-nums; white-space: nowrap; font-size: var(--font-size-sm) !important; } /* ---- A. The gap panel ---------------------------------------------- */ .fbondx-gap .card-body > .text-muted { color: var(--focus-text-muted) !important; font-size: var(--font-size-xs) !important; } /* ---- C. The 2-year yield and the central bank rate ------------------ */ .fbondx-policy { display: flex; flex-direction: column; gap: 6px; margin-top: 12px; } .fbondx-eyebrow { margin: 0; } .fbondx-section-title { margin: 0; font-weight: 500; letter-spacing: -0.02em; font-size: var(--font-size-h2) !important; } .fbondx-section-sub { margin: 0; } .fbondx-key { display: flex; flex-wrap: wrap; gap: 6px 22px; margin: 4px 0 10px; color: var(--focus-text-soft); font-size: var(--font-size-sm) !important; } .fbondx-key span { display: inline-flex; align-items: center; gap: 8px; } .fbondx-key-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--focus-accent); } .fbondx-key-step { width: 16px; height: 2px; background: var(--focus-blue); } .fbondx-policy-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; } .fbondx-policy-grid > .card { margin: 0 !important; border: 1px solid var(--focus-line) !important; border-radius: 12px !important; background: var(--focus-panel) !important; box-shadow: none !important; min-width: 0; } .fbondx-policy-grid > .card > .card-header { padding: 14px 16px 0; border-bottom: 0; background: transparent; } .fbondx-policy-grid > .card > .card-header.text-section-heading, .fbondx-policy-grid > .card > .card-header .text-section-heading { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 2px 12px; font-weight: 500; font-size: var(--font-size-md) !important; } .fbondx-policy-gap { color: var(--focus-text-muted); font-weight: 400; font-size: var(--font-size-sm) !important; } .fbondx-policy-grid > .card > .card-body { padding: 8px 12px 12px; } .fbondx-howto .card-body { display: flex; flex-direction: column; justify-content: center; padding: 20px 18px !important; } .fbondx-howto p { margin: 0 0 10px; color: var(--focus-text-soft); font-size: var(--font-size-sm) !important; } .fbondx-howto-title { margin: 0 0 10px; font-weight: 500; font-size: var(--font-size-md) !important; } @media (max-width: 991.98px) { .fbondx-policy-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } } @media (max-width: 767.98px) { .fbondx-policy-grid { grid-template-columns: minmax(0, 1fr); } /* a phone: the Play button keeps the figure's bottom band, the slider gets the full width under it */ .fbondx-history-grid .bonds-history-controls { margin: 2px 0 6px 0 !important; } }
/* 38_focus_sovereign_debt.css */
/* ==================================================================== */ /* 38. SOVEREIGN DEBT — the /sovereign-debt page (2026-10-10) */ /* ==================================================================== */ /* apps/app_sovereign_debt/ (`fsov-*`): the maturity wall, the scenario lab, who buys the debt and the long US view. Plan: docs/_PLAN_sovereign_debt.md. Focus bundle only; on top of the page frame (`fpage-*`, 21_focus_shell.css), the shared parts (`fpart-*`, 22_focus_pages.css section 8) and the lab parts (`flab-*`, 23_focus_calculators.css). Every size is a token. One section per tab. */ /* ---- 0. Page head: unit and country ---------------------------------- */ .fsov-head-controls .seg-control { flex-wrap: wrap; } /* the data-quality dot inside a country pill */ .fsov-head-controls .seg-option { display: inline-flex; align-items: center; justify-content: center; } /* ---- 0b. Shared parts (function_sovdebt_parts.py) --------------------- */ /* The page's colours. Series: a tab's root sets --fsov-bonds / -bills / -pick / -muted inline from the chart palette (function_sovdebt_charts.page_colors), so an HTML bar and a chart bar are the same colour by construction; the values here only stand in when a tab does not set them. Heat grid: from "almost the panel" to the blue mixed with the text colour — a light blue on a dark theme, a deep blue on a light one, with no second set of values to keep. */ .fsov { --fsov-bonds: var(--focus-blue); --fsov-bills: var(--focus-warning); --fsov-pick: var(--focus-accent); --fsov-muted: var(--focus-line-strong); --fsov-third: var(--focus-text-muted); /* plain tokens for a browser without color-mix(); the mixes are below */ --fsov-heat-lo: var(--focus-raised); --fsov-heat-hi: var(--focus-blue); --fsov-heat-first: 150px; --fsov-heat-cell: 58px; } @supports (background: color-mix(in srgb, red, blue)) { .fsov { --fsov-heat-lo: color-mix(in srgb, var(--focus-blue) 10%, var(--focus-panel)); --fsov-heat-hi: color-mix(in srgb, var(--focus-blue) 60%, var(--focus-text)); } } .fsov-tab { display: flex; flex-direction: column; gap: 20px; min-width: 0; } .fsov-section, .fsov-stack { display: flex; flex-direction: column; gap: 16px; min-width: 0; } /* the head of a group of cards that has no frame of its own */ .fsov-head { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: 8px 16px; margin-top: 8px; } .fsov-head-title { margin: 2px 0 0; font-weight: 500; letter-spacing: -0.02em; font-size: var(--font-size-h2) !important; } .fsov-text { max-width: 82ch; margin: 0 0 8px; color: var(--focus-text-soft); line-height: 1.5; font-size: var(--font-size-md) !important; } .fsov-text:last-child { margin-bottom: 0; } /* A two-line axis label ("Q4" over "'26") is drawn as <tspan>s, which the app's global text-size rule would set to the body size: keep them at the chart size of their own label. */ .fsov .js-plotly-plot .plotly .xtick tspan, .fsov .js-plotly-plot .plotly .ytick tspan { font-size: var(--font-size-chart) !important; } /* Text drawn ON a chart of this page as an annotation (an event label beside its dotted line, a corner caption, the label of the highest bar): the small caption size, on every tab. The app's global text rule would otherwise set an annotation — and each line of a two-line one, a <tspan> — to the body size, larger than the axis labels beside it; a size set from Python does not survive that rule. */ .fsov .js-plotly-plot .plotly .annotation-text, .fsov .js-plotly-plot .plotly .annotation-text tspan { font-size: var(--font-size-xs) !important; } /* A dashed line of the page's line builder (function_sovdebt_charts.lines) names itself in its trace id; the dash pattern is held here, because the app's chart animation overwrites the one Plotly put on the line. */ .fsov .js-plotly-plot .scatterlayer [class*="tracefsov-dashed"] .js-line { stroke-dasharray: 9px 9px !important; } /* data quality: full = official, amber = partly estimated, ring = incomplete */ .fsov-q { display: inline-block; flex: none; width: 8px; height: 8px; margin-right: 6px; border-radius: 50%; } .fsov-q-official { background: var(--focus-positive); } .fsov-q-partly_estimated { background: var(--focus-warning); } .fsov-q-incomplete { border: 1.5px solid var(--focus-accent-hover); } .fsov-badge { display: inline-flex; flex-wrap: wrap; align-items: center; padding: 4px 10px; border: 1px solid var(--focus-line); border-radius: 999px; color: var(--focus-text-soft); font-size: var(--font-size-xs) !important; } .fsov-badge-date { color: var(--focus-text-muted); white-space: pre; font-size: var(--font-size-xs) !important; } /* a "select this country" button: a plain button, the look comes from where it sits */ .fsov-pick { -webkit-appearance: none; appearance: none; margin: 0; padding: 0; border: 0; border-radius: 6px; background: transparent; color: inherit; font-family: inherit; text-align: left; cursor: pointer; } .fsov-pick:focus-visible { outline: 2px solid var(--focus-accent); outline-offset: 1px; } /* role colours: boxes, lines and bar segments all draw in currentColor */ .fsov-c-bonds { color: var(--fsov-bonds); } .fsov-c-bills { color: var(--fsov-bills); } .fsov-c-pick { color: var(--fsov-pick); } .fsov-c-muted { color: var(--fsov-muted); } .fsov-c-third { color: var(--fsov-third); } .fsov-legend { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 16px; margin: 0 0 10px; } .fsov-lg-item, .fsov-lg-extra { display: inline-flex; align-items: center; color: var(--focus-text-soft); font-size: var(--font-size-xs) !important; } .fsov-lg-extra { color: var(--focus-text-muted); } .fsov-sw { display: inline-block; flex: none; width: 12px; height: 12px; margin-right: 6px; border-radius: 3px; background: currentColor; } .fsov-sw.fsov-hatch, .fsov-sw.fsov-lump { border: 1px solid currentColor; background: repeating-linear-gradient(45deg, currentColor 0 1.5px, transparent 1.5px 4px); } /* the two ends of a dumbbell: where a value starts (a ring), where it ends (a dot) */ .fsov-sw.fsov-dot, .fsov-sw.fsov-ring { border-radius: 50%; } .fsov-sw.fsov-ring { border: 2px solid currentColor; background: transparent; } /* hatched the other way: several years in one bar */ .fsov-sw.fsov-lump { background: repeating-linear-gradient(-45deg, currentColor 0 1.5px, transparent 1.5px 4px); } .fsov-lg-line { display: inline-block; flex: none; width: 18px; height: 0; margin-right: 6px; border-top: 2px solid currentColor; } .fsov-lg-line.fsov-dash { border-top-style: dashed; } /* label / value pairs: rows of a hover card (the default), one flowing line where there is no hover (set by the part that holds them) */ .fsov-kvs { display: grid; gap: 3px; } .fsov-kv { display: flex; justify-content: space-between; gap: 18px; white-space: nowrap; } .fsov-kv-k { color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .fsov-kv b { font-weight: 500; font-variant-numeric: tabular-nums; font-size: var(--font-size-xs) !important; } /* the heat grid: rounded cells (the correlation matrix's look), every row its own grid on ONE column template, the first cell sticky while the box scrolls */ .fsov-heat-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; padding: 2px 0 6px; } .fsov-heat { display: flex; flex-direction: column; gap: 3px; min-width: 820px; } .fsov-heat-row { display: grid; grid-template-columns: var(--fsov-heat-cols); gap: 3px; align-items: stretch; } .fsov-heat-head { align-items: end; } .fsov-heat-corner, .fsov-heat-col { padding: 0 4px 4px; color: var(--focus-text-muted); text-align: center; white-space: nowrap; font-size: var(--font-size-xs) !important; } .fsov-heat-corner, .fsov-heat-name { position: sticky; left: 0; z-index: 1; background: var(--focus-panel); text-align: left; } .fsov-heat-name { display: flex; align-items: center; min-width: 0; padding-right: 6px; } .fsov-heat-pick { display: block; max-width: 100%; padding: 4px 6px 4px 2px; overflow: hidden; color: var(--focus-text-soft); text-overflow: ellipsis; white-space: nowrap; font-size: var(--font-size-sm) !important; } .fsov-heat-pick .fsov-q { vertical-align: 1px; } .fsov-heat-pick:hover { color: var(--focus-text); } .fsov-heat-pick.fsov-sel { color: var(--focus-text); font-weight: 600; box-shadow: inset 0 -2px 0 var(--fsov-pick); border-radius: 0; } .fsov-heat-cell { display: flex; align-items: center; justify-content: center; min-height: 30px; padding: 5px 4px; border-radius: 5px; white-space: nowrap; font-variant-numeric: tabular-nums; font-size: var(--font-size-table) !important; } /* the shade = the cell's own --fsov-heat (0% .. 100%) between the two tokens. A browser without color-mix() shows every cell in the one plain colour, figures in the text colour: readable, unshaded. Only where the mix exists does a strong cell turn light and take dark figures. */ .fsov-heat-value { background: var(--fsov-heat-lo); color: var(--focus-text); } .fsov-heat-strong { font-weight: 500; } @supports (background: color-mix(in srgb, red, blue)) { .fsov-heat-value { background: color-mix(in oklab, var(--fsov-heat-hi) var(--fsov-heat, 0%), var(--fsov-heat-lo)); } .fsov-heat-strong { color: var(--focus-page); } } .fsov-heat-tail { border: 1px dashed var(--focus-line); color: var(--focus-text-soft); } .fsov-heat-none { background-image: repeating-linear-gradient(45deg, var(--focus-line) 0 1px, transparent 1px 6px); color: var(--focus-text-muted); opacity: 0.7; } .fsov-scale { display: flex; align-items: center; justify-content: flex-end; gap: 8px; margin: 0 0 8px; } .fsov-scale span { color: var(--focus-text-muted); font-size: var(--font-size-xs) !important; } .fsov-scale-bar { display: block; width: 140px; height: 8px; border-radius: 4px; background: linear-gradient(90deg, var(--fsov-heat-lo), var(--fsov-heat-hi)); } @media (max-width: 767.98px) { .fsov { --fsov-heat-first: 116px; --fsov-heat-cell: 54px; } .fsov-tab { gap: 16px; } .fsov-heat { min-width: 760px; } .fsov-lg-item { white-space: normal; } } /* ---- 1. Maturity wall ------------------------------------------------- */ /* the ranking: one button per country (name, bar, value), the split and the shares in a card on hover — or on a second line where there is no hover */ .fsov-rank { display: flex; flex-direction: column; gap: 2px; } .fsov-rank-row { position: relative; display: grid; grid-template-columns: 132px minmax(0, 1fr) 84px; align-items: center; gap: 0 12px; width: 100%; padding: 6px 8px; } .fsov-rank-row:hover { background: var(--bg-hover); } .fsov-rank-row.fsov-sel { background: var(--focus-raised); box-shadow: inset 2px 0 0 var(--fsov-pick); } .fsov-rank-name { overflow: hidden; color: var(--focus-text-soft); text-overflow: ellipsis; white-space: nowrap; font-size: var(--font-size-md) !important; } .fsov-sel .fsov-rank-name { color: var(--focus-text); font-weight: 600; } .fsov-rank-bar { display: flex; gap: 2px; height: 14px; } .fsov-seg { display: block; height: 100%; background: currentColor; } .fsov-seg:first-child { border-radius: 2px 0 0 2px; } .fsov-seg:last-child { border-radius: 0 4px 4px 0; } .fsov-seg:only-child { border-radius: 2px 4px 4px 2px; } .fsov-rank-value { text-align: right; font-weight: 600; font-variant-numeric: tabular-nums; font-size: var(--font-size-md) !important; } .fsov-rank-none, .fsov-rank-na { color: var(--focus-text-muted); font-weight: 400; font-size: var(--font-size-sm) !important; } .fsov-rank-none { font-style: italic; } .fsov-rank-more { display: none; position: absolute; z-index: 6; top: calc(100% - 2px); left: 152px; min-width: min(230px, calc(100% - 160px)); max-width: min(340px, calc(100% - 160px)); padding: 10px 12px; border: 1px solid var(--focus-line); border-radius: 8px; background: var(--focus-sidebar); box-shadow: var(--shadow-hover); pointer-events: none; } .fsov-rank-row:hover .fsov-rank-more, .fsov-rank-row:focus-visible .fsov-rank-more { display: block; } .fsov-rank-why { display: block; margin-top: 6px; color: var(--focus-text-muted); white-space: normal; font-size: var(--font-size-xs) !important; } /* four figures in a band: two by two while the rail leaves the page too narrow for four in a row (the shared band would put three and one) */ @media (max-width: 991.98px) { .fsov-tab .fpart-stat { flex: 1 1 45%; } } /* a long amount ("MX$14.9tn") in a half-width cell of a small phone: one size down, never cut */ @media (max-width: 399.98px) { .fsov-tab .fpart-stat-value { letter-spacing: -0.03em; font-size: var(--font-size-h2) !important; } } /* the two charts of a country, side by side (one column under 992px: fpart-two) */ .fsov-charts { align-items: stretch; } .fsov-charts > .card { height: 100%; } /* no hover (a touch screen) or a narrow window: the card becomes the row's second line, so the split and the shares are always readable */ @media (hover: none), (max-width: 767.98px) { .fsov-rank-row { grid-template-columns: 104px minmax(0, 1fr) 72px; gap: 2px 10px; padding: 7px 6px; } .fsov-rank-more, .fsov-rank-row:hover .fsov-rank-more { display: block; position: static; grid-column: 1 / -1; min-width: 0; max-width: none; padding: 0; border: 0; background: transparent; box-shadow: none; } .fsov-rank-more .fsov-kvs { display: flex; flex-wrap: wrap; gap: 0 12px; } .fsov-rank-more .fsov-kv { gap: 4px; } /* the total is already at the end of the row */ .fsov-rank-more .fsov-kv:first-child { display: none; } } /* ---- 2. Scenarios (the lab) ------------------------------------------- */ /* function_sovdebt_tab_scenarios.py: the house lab parts (`flab-*`, 23_focus_calculators.css) in the lab's two columns — the controls at the left, sticky under the page's tab bar so the sliders stay in reach while the charts scroll; the answer and the charts at the right. One column under 992px: the controls, then the answer, then the charts. */ .fsov-lab { display: grid; grid-template-columns: 320px minmax(0, 1fr); gap: 20px; align-items: start; } .fsov-lab-controls { --fsov-lab-top: calc(var(--fstick-top) + var(--fstick-l1) + var(--fstick-bleed) + 8px); position: sticky; top: var(--fsov-lab-top); max-height: calc(100vh - var(--fsov-lab-top) - 16px); max-height: calc(100dvh - var(--fsov-lab-top) - 16px); margin-bottom: 0; overflow-y: auto; } /* The app's chart animation (assets/custom_js/plotly_animation_scroll_v2.js) hides a graph and draws its lines in again over a second, on every figure it receives. In the lab a slider release sends five figures: they must change at once. So here a line is never held back (its dash offset stays 0, no draw-in), and a point is never faded in. */ .fsov-lab .js-plotly-plot .lines .js-line { stroke-dashoffset: 0 !important; animation: none !important; } .fsov-lab .js-plotly-plot .scatterlayer .point { opacity: 1 !important; transition: none !important; } /* A slot that is off screen (hidden but laid out: layout_sovereign_debt. HIDDEN_SLOT) carries this class. A slider's always-shown value label sets its own `visibility: visible`, which would show through and stay exposed to a screen reader: hidden here by the class. */ .fsov-off .rc-slider-tooltip { visibility: hidden !important; } /* a two-way choice between two sliders: the slider's own label and hint look */ .fsov-lab-choice { margin: 14px 0 8px; } .fsov-lab-choice .seg-control { margin-top: 6px; } /* the panel's head: the title and, always in sight, "Reset to baseline" */ .fsov-lab-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; } .fsov-lab-reset { flex: none; white-space: nowrap; } /* a hint a callback rewrites sits in a span of its own: same size as a fixed hint */ .fsov-lab-controls .flab-range-hint span { font-size: var(--font-size-xs) !important; } .fsov-lab-outcome, .fsov-lab-result { display: flex; flex-direction: column; gap: 16px; min-width: 0; } .fsov-lab-result .flab-result, .fsov-lab-facts { margin-bottom: 0; } /* the computed sentence is the answer here, not a caption: a reading size and the full line (the house block caps a caption at 480px) */ .fsov-lab-result .flab-result-text { max-width: 82ch; color: var(--focus-text-soft); line-height: 1.6; font-size: var(--font-size-md) !important; } .fsov-lab-result .flab-result-text b { color: var(--focus-text); font-weight: 600; font-size: var(--font-size-md) !important; } .fsov-lab-facts { padding-top: 8px; padding-bottom: 8px; } .fsov-lab-charts { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 16px; min-width: 0; } .fsov-lab-wide { grid-column: 1 / -1; } /* the list of every country: name, two dots on one scale, the value */ .fsov-db { display: flex; flex-direction: column; gap: 2px; } .fsov-db-row { display: grid; grid-template-columns: 132px minmax(0, 1fr) 104px; align-items: center; gap: 0 14px; width: 100%; padding: 6px 8px; } /* the scale above the rows */ .fsov-db-head { padding-top: 0; padding-bottom: 0; } button.fsov-db-row:hover { background: var(--bg-hover); } button.fsov-db-row.fsov-sel { background: var(--focus-raised); box-shadow: inset 2px 0 0 var(--fsov-pick); } .fsov-db-name { overflow: hidden; color: var(--focus-text-soft); text-overflow: ellipsis; white-space: nowrap; font-size: var(--font-size-md) !important; } .fsov-sel .fsov-db-name { color: var(--focus-text); font-weight: 600; } .fsov-db-axis { position: relative; height: 16px; } .fsov-db-axis span { position: absolute; top: 0; transform: translateX(-50%); color: var(--focus-text-muted); white-space: nowrap; font-size: var(--font-size-xs) !important; } /* the track's grid lines repeat every tick (--fsov-db-step, set per list) */ .fsov-db-track { position: relative; height: 18px; border-right: 1px solid var(--focus-line); background-image: linear-gradient(to right, var(--focus-line) 1px, transparent 1px); background-size: var(--fsov-db-step, 25%) 100%; } .fsov-db-line { position: absolute; top: 50%; height: 2px; margin-top: -1px; background: var(--fsov-muted); } .fsov-db-dot { position: absolute; top: 50%; box-sizing: border-box; border-radius: 50%; } .fsov-db-start { width: 11px; height: 11px; margin: -5.5px 0 0 -5.5px; border: 2px solid var(--fsov-muted); background: var(--focus-panel); } .fsov-db-end { width: 13px; height: 13px; margin: -6.5px 0 0 -6.5px; border: 2px solid var(--focus-panel); background: var(--fsov-pick); } .fsov-db-value { text-align: right; font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; font-size: var(--font-size-md) !important; } .fsov-db-value small { color: var(--focus-text-muted); font-weight: 400; font-size: var(--font-size-xs) !important; } /* Beside the charts (992px and up) the panel is as tall as the window lets it be and scrolls inside: so that all seven controls fit a laptop, a hint shows two lines (its full text is its tooltip) and the gaps are tighter; and where the panel still runs past the window, its bottom edge fades out — the cue that there is more below (macOS hides the scrollbar). The fade is the panel's own last 20px: at rest, or with everything in view, it lies over empty panel and shows nothing. */ @media (min-width: 992px) { .fsov-lab-controls { padding-bottom: 0; } .fsov-lab-controls::after { content: ''; display: block; position: sticky; bottom: 0; z-index: 1; height: 20px; margin: 0 -22px; background: linear-gradient(to bottom, transparent, var(--focus-panel) 85%); pointer-events: none; } .fsov-lab-controls .flab-range { margin: 6px 0 2px; } .fsov-lab-controls .fsov-lab-choice { margin: 10px 0 6px; } .fsov-lab-controls .flab-range-hint { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden; } } /* a short window: the sliders' end labels go too (the value is always shown) */ @media (min-width: 992px) and (max-height: 920px) { .fsov-lab-controls .flab-range-bounds { display: none; } } /* a 13-inch laptop's window: one line of each hint (the tooltip has the rest) */ @media (min-width: 992px) and (max-height: 840px) { .fsov-lab-controls .flab-range-hint { -webkit-line-clamp: 1; line-clamp: 1; } } /* the rail leaves the two chart columns too narrow under 1280px */ @media (max-width: 1279.98px) { .fsov-lab-charts { grid-template-columns: minmax(0, 1fr); } } @media (max-width: 991.98px) { .fsov-lab { grid-template-columns: minmax(0, 1fr); } .fsov-lab-controls { position: static; max-height: none; overflow-y: visible; } } @media (max-width: 575.98px) { .fsov-db-row { grid-template-columns: 92px minmax(0, 1fr) 88px; gap: 0 10px; padding: 6px 4px; } } /* ---- 3. Who buys & backstop ------------------------------------------- */ /* function_sovdebt_tab_buyers.py: three sections on the shared parts of 0b (tab, section, stack, head, text, pick). Here: the three explanations side by side, the flag dots, and the three tables (the builder's tables with wrapping headers, right-aligned figures, long-text columns). */ /* more air between the tab's three sections than between a section's cards */ .fsov-buyers > .fsov-section + .fsov-section { margin-top: 20px; } /* three short explanations: plain text on the page, no frame */ .fsov-why { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px 28px; } .fsov-why-title { margin: 0 0 6px; font-weight: 500; letter-spacing: -0.01em; font-size: var(--font-size-lg) !important; } /* "How the scores are built": the same reading text as the page's paragraphs */ .fsov-how p { margin: 0 0 10px; color: var(--focus-text-soft); line-height: 1.5; font-size: var(--font-size-md) !important; } .fsov-how p:last-child { margin-bottom: 0; color: var(--focus-text-muted); } /* a flag beside a value: full = past the threshold, a ring = close to it (the shape says it too, not only the colour) */ .fsov-flag { display: inline-block; box-sizing: border-box; width: 8px; height: 8px; margin-left: 6px; border-radius: 50%; } .fsov-flag-hit { background: var(--focus-negative); } .fsov-flag-near { border: 2px solid var(--focus-warning); } /* in a sentence (the note under the table) the dot comes first */ .fpart-note .fsov-flag { margin: 0 4px 0 0; } /* The three tables. The builder keeps a header on one line; ten columns of long headers would then be twice as wide as their figures, so here a header wraps. Specificity: one class above 05_tables.css' own cell rules. */ .table.fsov-watch > thead > tr > th { white-space: normal; vertical-align: bottom; line-height: 1.25; } .table.fsov-watch > tbody > tr > td { padding-top: 7px; padding-bottom: 7px; vertical-align: top; } .table.fsov-watch .fsov-cell-num { text-align: right; font-variant-numeric: tabular-nums; } /* a figure stays on one line; its header may wrap (rule above) */ .table.fsov-watch td.fsov-cell-num { white-space: nowrap; } /* a figure without a flag keeps the dot's room (8px + its 6px gap), so the figures of a flagged column end on one line */ .table.fsov-watch > * > tr > .fsov-cell-room { padding-right: 20px; } /* long text: wide enough to read in a few lines, the table scrolls sideways inside its own box when the screen is narrower */ .table.fsov-watch .fsov-cell-wide { min-width: 200px; } /* three long-text columns in the scorecard: a little narrower, so the whole table fits a desktop panel without scrolling */ .table.fsov-scorecard .fsov-cell-wide { min-width: 190px; } /* the parts of a score: small digits on one line */ .table .fsov-score-parts { white-space: nowrap; letter-spacing: 0.04em; font-variant-numeric: tabular-nums; } /* a country name that selects the country: reads as a name, behaves as a button; the selected one takes the selection colour */ .fsov-watch .fsov-pick { font-weight: 500; text-decoration: underline dotted var(--focus-line-strong); text-underline-offset: 3px; } .fsov-watch .fsov-pick:hover { text-decoration-color: currentColor; } .fsov-watch .fsov-pick.fsov-sel { color: var(--focus-accent-hover); text-decoration: none; } /* The first column (the country, the date) stays in place while a table scrolls sideways — the shared builder's rule for every `.table-scroll` (05_tables.css section 3: sticky, with the opaque panel colour behind it, so the columns sliding under it do not show through). The one thing to add here: in the SELECTED country's row that frozen cell takes the row's own highlight, still opaque, instead of the plain panel colour. */ .table.fsov-watch > tbody > tr.table-primary > td:first-child { background-color: var(--focus-raised); } @media (max-width: 991.98px) { .fsov-why { grid-template-columns: minmax(0, 1fr); } } @media (max-width: 767.98px) { .table.fsov-watch .fsov-cell-wide, .table.fsov-scorecard .fsov-cell-wide { min-width: 180px; } } /* the band of buyer figures: long labels, so on a wide screen wider cells than the default (a phone keeps the shared two-per-row layout) */ @media (min-width: 768px) { .fsov-buyers-stats .fpart-stat { flex-basis: 230px; } } /* ---- 4. US long view & money ------------------------------------------ */
