From f2fe9a77949ae53ee4bfe1c22c0e948f80632902 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 2 Jul 2026 01:57:40 +0000 Subject: [PATCH] Anonymize Quantinno/QFAF branding for public build with hidden reveal toggle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Public-facing default now shows generic labels — "Quantinno" -> Manager, "QFAF" -> Fund, "Quantinno Fundamental Arbitrage Fund" -> Fundamental Arbitrage Fund. Bare-noun mapping lets a preceding article flow through naturally ("the QFAF" -> "the Fund") while standalone labels stay clean ("QFAF Value" -> "Fund Value"). - src/branding.ts: transform (brandText) + global localStorage-backed reveal store (useSyncExternalStore, default off) so every subscribed component updates live with no page reload. - src/hooks/useSecretReveal.ts + WorkspaceTab: five rapid clicks on the brand flip real names on/off, with a brief confirmation. No affordance — hidden from public users. - getPopupContent() is the single chokepoint for all 55 popup strings; other user-visible strings across components/formulas/Excel/chart legend route through brandText(). - Only display strings changed; data-model fields, CSS classes, and CSV/Excel serialization keys keep the literal qfaf/quantinno spelling, so no new CalculatorInputs field and CSV/Excel round-trips are unaffected. - Documented as D-029 in docs/DECISIONS.md. Build, 419 tests, lint (0 errors), and Prettier all pass; browser-verified default anonymized state and the secret reveal toggle. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01S9oJbhvHXD2pJqwobUyxno --- docs/DECISIONS.md | 24 ++++ src/AdvancedMode/QfafTestByYear.tsx | 23 ++-- src/AdvancedMode/SettingsPanel.tsx | 10 +- src/AdvancedMode/StrategyComparison.tsx | 8 +- src/App.tsx | 4 +- src/Calculator.tsx | 8 +- src/InfoPopup.tsx | 3 + src/ResultsTable.tsx | 29 +++-- src/WealthChart.tsx | 3 + src/branding.ts | 121 ++++++++++++++++++ src/components/DisclaimerFooter.tsx | 18 ++- src/components/Formulas/ProjectionFormula.tsx | 7 +- .../Formulas/QfafMechanicsFormula.tsx | 11 +- src/components/Formulas/QfafSizingFormula.tsx | 20 +-- .../Formulas/Section461lFormula.tsx | 11 +- .../Formulas/StrategyRatesFormula.tsx | 5 +- src/components/Formulas/TaxAlphaFormula.tsx | 11 +- src/components/MeetingMode/MeetingMode.tsx | 41 ++++-- src/components/OnboardingTour.tsx | 6 +- src/components/ScenarioComparisonPanel.tsx | 13 +- src/components/SizingSummary.tsx | 16 ++- src/components/StickyHeader.tsx | 4 +- src/components/StrategySelectionInputs.tsx | 88 ++++++++----- src/components/TaxRatesDisplay.tsx | 6 +- src/constants/storageKeys.ts | 6 + src/hooks/useSecretReveal.ts | 40 ++++++ src/pages/QfafTestPage.tsx | 9 +- src/popupContent.ts | 15 ++- src/utils/excelExport.ts | 15 ++- src/workspace/WorkspaceTab.tsx | 55 +++++--- src/workspace/workspace.css | 9 ++ 31 files changed, 493 insertions(+), 146 deletions(-) create mode 100644 src/branding.ts create mode 100644 src/hooks/useSecretReveal.ts diff --git a/docs/DECISIONS.md b/docs/DECISIONS.md index 4cdf788..4e4c927 100644 --- a/docs/DECISIONS.md +++ b/docs/DECISIONS.md @@ -506,6 +506,30 @@ popups gain per-leg deleverage attribution; the conflict chips become target sel No change to single-strategy mode, to D-016/D-017 attribution, or to the sensitivity grid. +### D-029 — Public anonymization of manager/fund names + hidden reveal toggle +**Date:** 2026-07-02 +**Decision (owner):** For the general-public build, the specific manager brand +**"Quantinno"** and its fund **"QFAF" / "Quantinno Fundamental Arbitrage Fund"** (D-007) +are replaced with generic labels. Anonymized is the **DEFAULT**; the owner can reveal the +real names for internal use via a hidden trigger. +**Mapping (anonymized):** `Quantinno` → **Manager**, `QFAF` → **Fund**, full fund name → +**Fundamental Arbitrage Fund** (the "Quantinno" prefix and any `(QFAF)` gloss are dropped). +The bare noun forms are intentional so a preceding article flows through naturally ("the +QFAF" → "the Fund"; "Quantinno alpha" → "Manager alpha") while standalone labels stay +clean ("QFAF Value" → "Fund Value", not "the Fund Value"). +**Hidden toggle:** **five rapid clicks on the workspace brand** (`.wx-brand`) flip real +names on/off, with a brief "Internal names on / Anonymized" confirmation. No cursor, +tooltip, or ARIA affordance — undiscoverable to public users. +**Status: IMPLEMENTED (2026-07-02).** New `src/branding.ts` holds the transform + +a global `localStorage`-backed reveal store (`useSyncExternalStore`, key +`taxCalc:reveal-brand`, default off) so every subscribed component updates live — no page +reload, in-progress inputs preserved. All user-visible strings route through `brandText()`; +the 55 popup strings flow through the single `getPopupContent()` chokepoint. Only display +strings changed — data-model field names, CSS classes, and CSV/Excel serialization keys +keep the literal `qfaf`/`quantinno` spelling, so no CalculatorInputs field was added and +CSV/Excel round-trips are unaffected. **Standing requirement:** new user-visible +manager/fund text must be wrapped in `brandText()` (or come from `getPopupContent`). + --- ## Pending decision queue (next batches) diff --git a/src/AdvancedMode/QfafTestByYear.tsx b/src/AdvancedMode/QfafTestByYear.tsx index b243131..a739a07 100644 --- a/src/AdvancedMode/QfafTestByYear.tsx +++ b/src/AdvancedMode/QfafTestByYear.tsx @@ -23,6 +23,7 @@ import { HIST_ORD_LOSS_AVG, } from '../qfafTestData'; import './QfafTestByYear.css'; +import { brandText, useBrandRevealed } from '../branding'; interface QfafTestByYearProps { filingStatus: FilingStatus; @@ -144,6 +145,7 @@ const ROW_DEFINITIONS: RowDef[] = [ ]; export function QfafTestByYear({ filingStatus }: QfafTestByYearProps) { + useBrandRevealed(); const [state, dispatch] = useReducer(reducer, null, () => ({ assumptions: { ...DEFAULT_ASSUMPTIONS }, })); @@ -201,7 +203,7 @@ export function QfafTestByYear({ filingStatus }: QfafTestByYearProps) {

Assumptions: Adjust the cells in orange

- +
$
{rowDef.key === 'dealsCollateralValue' ? `Deals Collateral Value (${getStrategyLabel(state.assumptions.coreStrategyId)})` - : rowDef.label} + : brandText(rowDef.label)} {results.map(result => ( @@ -370,7 +373,7 @@ export function QfafTestByYear({ filingStatus }: QfafTestByYearProps) { {/* Historical Performance Section */}
-

Historical QFAF Performance

+

{brandText('Historical QFAF Performance')}

{/* Annual Returns + Breakdown */} @@ -482,16 +485,16 @@ export function QfafTestByYear({ filingStatus }: QfafTestByYearProps) {

Calculation Notes

  • - QFAF Subscription: Sized based on initial investment with Year 1 - adjustment factor (1.049×), then decays ~7.7% annually. + {brandText('QFAF Subscription:')} Sized based on initial investment + with Year 1 adjustment factor (1.049×), then decays ~7.7% annually.
  • - Deals Collateral: Calculated so total ST losses = QFAF ST gains. - Combines Overlay (growing at {(QUANTINNO_ALPHA_RATE * 100).toFixed(2)}%) + Core - collateral. + Deals Collateral: Calculated so{' '} + {brandText('total ST losses = QFAF ST gains')}. Combines Overlay (growing at{' '} + {(QUANTINNO_ALPHA_RATE * 100).toFixed(2)}%) + Core collateral.
  • - Ordinary Losses: QFAF generates{' '} + Ordinary Losses: {brandText('QFAF generates')}{' '} {(state.assumptions.qfafGenerationRate * 100).toFixed(0)}% of subscription as ordinary losses.
  • diff --git a/src/AdvancedMode/SettingsPanel.tsx b/src/AdvancedMode/SettingsPanel.tsx index 55696c5..0bb5a54 100644 --- a/src/AdvancedMode/SettingsPanel.tsx +++ b/src/AdvancedMode/SettingsPanel.tsx @@ -6,6 +6,7 @@ import { formatPercent as formatPercentBase, } from '../utils/formatters'; import './SettingsPanel.css'; +import { brandText, useBrandRevealed } from '../branding'; interface SettingsPanelProps { settings: AdvancedSettings; @@ -17,6 +18,7 @@ interface SettingsPanelProps { const formatPercent = (value: number) => formatPercentBase(value, 1); export function SettingsPanel({ settings, onChange, onReset }: SettingsPanelProps) { + useBrandRevealed(); const handleChange = (path: string, value: number | boolean) => { const keys = path.split('.'); if (keys.length === 1) { @@ -70,14 +72,16 @@ export function SettingsPanel({ settings, onChange, onReset }: SettingsPanelProp
    {/* QFAF Mechanics */}
    -

    QFAF Mechanics

    +

    {brandText('QFAF Mechanics')}

    - QFAF Growth + {brandText('QFAF Growth')} + + + {brandText('Whether QFAF appreciates with market returns')} - Whether QFAF appreciates with market returns
    )} diff --git a/src/App.tsx b/src/App.tsx index 50dec15..c9449da 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,6 +4,7 @@ import { QfafTestPage } from './pages/QfafTestPage'; import { WorkspaceTab } from './workspace/WorkspaceTab'; import { ThemeToggle } from './components/ThemeToggle'; import { ErrorBoundary } from './components/ErrorBoundary'; +import { brandText, useBrandRevealed } from './branding'; type View = 'calculator' | 'workspace' | 'qfaf-test'; @@ -19,6 +20,7 @@ function getInitialView(): View { } export function App() { + useBrandRevealed(); const [activeView, setActiveView] = useState(getInitialView); // Allow dev access to QFAF Test via Ctrl+Shift+Q @@ -51,7 +53,7 @@ export function App() { {activeView === 'qfaf-test' && ( )} diff --git a/src/Calculator.tsx b/src/Calculator.tsx index 18ad532..ca0fbbc 100644 --- a/src/Calculator.tsx +++ b/src/Calculator.tsx @@ -51,6 +51,7 @@ import { SensitivityAnalysis } from './AdvancedMode/SensitivityAnalysis'; import { ScenarioAnalysis } from './AdvancedMode/ScenarioAnalysis'; import { StrategyComparison } from './AdvancedMode/StrategyComparison'; import { MeetingMode } from './components/MeetingMode/MeetingMode'; +import { brandText, useBrandRevealed } from './branding'; // Generate default year overrides for 10 years const generateDefaultOverrides = (baseIncome: number): YearOverride[] => { @@ -68,6 +69,7 @@ interface CalculatorProps { } export function Calculator({ isActive = true }: CalculatorProps) { + useBrandRevealed(); const [inputs, setInputs] = useState(DEFAULTS); const advancedMode = useAdvancedMode(); const { isExpanded } = useScrollHeader('scroll-sentinel'); @@ -414,9 +416,11 @@ export function Calculator({ isActive = true }: CalculatorProps) { Meeting Mode

    Tax Optimization Calculator

    -

    QFAF + Collateral Strategy

    +

    {brandText('QFAF + Collateral Strategy')}

    - Model tax-loss harvesting strategies with QFAF overlays and collateral optimization. + {brandText( + 'Model tax-loss harvesting strategies with QFAF overlays and collateral optimization.' + )}

    This tool is in active development. Feedback and suggestions are welcome!{' '} diff --git a/src/InfoPopup.tsx b/src/InfoPopup.tsx index 9240bb1..0a3fbbb 100644 --- a/src/InfoPopup.tsx +++ b/src/InfoPopup.tsx @@ -1,6 +1,7 @@ import { useState } from 'react'; import { createPortal } from 'react-dom'; import { getPopupContent } from './popupContent'; +import { useBrandRevealed } from './branding'; interface InfoPopupProps { title: string; @@ -114,6 +115,7 @@ interface FieldInfoPopupProps { } export function FieldInfoPopup({ contentKey, currentValue, breakdown }: FieldInfoPopupProps) { + useBrandRevealed(); // re-render when the brand-reveal toggle flips const content = getPopupContent(contentKey); if (!content) return null; @@ -134,6 +136,7 @@ interface InfoTextProps { export function InfoText({ contentKey, children, currentValue, breakdown }: InfoTextProps) { const [isOpen, setIsOpen] = useState(false); + useBrandRevealed(); // re-render when the brand-reveal toggle flips const content = getPopupContent(contentKey); // If no content, just render the children without popup functionality diff --git a/src/ResultsTable.tsx b/src/ResultsTable.tsx index e941144..4199ddc 100644 --- a/src/ResultsTable.tsx +++ b/src/ResultsTable.tsx @@ -8,6 +8,7 @@ import { BreakdownGranularity, } from './calculations/lossBreakdown'; import { getEffectiveStLossRate } from './calculations/helpers'; +import { brandText, useBrandRevealed } from './branding'; type ViewMode = 'combined' | 'qfaf-only' | 'collateral-only'; @@ -65,6 +66,7 @@ export function ResultsTable({ strategyId, ltGainRate, }: ResultsTableProps) { + useBrandRevealed(); const partialFirstYearMonths = startMonth > 1 ? 13 - startMonth : null; const [expandPortfolio, setExpandPortfolio] = useState(false); const [expandCapital, setExpandCapital] = useState(false); @@ -222,14 +224,18 @@ export function ResultsTable({ @@ -287,7 +293,7 @@ export function ResultsTable({ ) : viewMode === 'qfaf-only' ? ( - QFAF Value + {brandText('QFAF Value')} ) : viewMode === 'collateral-only' ? ( @@ -306,7 +312,7 @@ export function ResultsTable({ Collateral - QFAF + {brandText('QFAF')} Cash Out @@ -486,7 +492,7 @@ export function ResultsTable({ {viewMode === 'qfaf-only' - ? 'QFAF Benefit' + ? brandText('QFAF Benefit') : viewMode === 'collateral-only' ? 'Coll. Benefit' : 'Savings'} @@ -967,7 +973,9 @@ export function ResultsTable({ Period Deployment Year ST Losses (Collateral) - {qfafEnabled && Ord. Losses (QFAF)} + {qfafEnabled && ( + {brandText('Ord. Losses (QFAF)')} + )} @@ -1094,6 +1102,7 @@ function TransposedTable({ qfafEnabled: boolean; startMonth: number; }) { + useBrandRevealed(); const showQfaf = viewMode === 'combined' || viewMode === 'qfaf-only'; const showCollateral = viewMode === 'combined' || viewMode === 'collateral-only'; const partialMonths = startMonth > 1 ? 13 - startMonth : null; @@ -1144,7 +1153,7 @@ function TransposedTable({ ...(showQfaf && qfafEnabled ? [ { - label: 'QFAF', + label: brandText('QFAF'), contentKey: 'col-qfaf-value', cell: (y: YearResult) => money(y.qfafValue), }, @@ -1181,7 +1190,7 @@ function TransposedTable({ ...(qfafEnabled && viewMode === 'combined' ? [ { - label: 'QFAF ST Gains', + label: brandText('QFAF ST Gains'), contentKey: 'col-st-gains', cell: (y: YearResult) => (active(y) ? money(y.stGainsGenerated) : '—'), }, @@ -1383,7 +1392,7 @@ function TransposedTable({ { label: viewMode === 'qfaf-only' - ? 'QFAF Benefit' + ? brandText('QFAF Benefit') : viewMode === 'collateral-only' ? 'Coll. Benefit' : 'Net Savings', diff --git a/src/WealthChart.tsx b/src/WealthChart.tsx index 9750306..e7276e6 100644 --- a/src/WealthChart.tsx +++ b/src/WealthChart.tsx @@ -13,6 +13,7 @@ import { import { YearResult } from './types'; import { formatCurrency, formatCurrencyAbbreviated } from './utils/formatters'; import { useDarkMode } from './hooks/useDarkMode'; +import { brandText, useBrandRevealed } from './branding'; interface WealthChartProps { data: YearResult[]; @@ -117,6 +118,7 @@ export const PortfolioValueChart = React.memo(function PortfolioValueChart({ startMonth, }: WealthChartProps) { const { isDark } = useDarkMode(); + useBrandRevealed(); // re-render legend/tooltip when the brand-reveal toggle flips // Memoize chart data transformation with confidence bands (007) const chartData = useMemo( @@ -243,6 +245,7 @@ export const PortfolioValueChart = React.memo(function PortfolioValueChart({
@@ -57,7 +61,11 @@ export function DisclaimerFooter() {

Suitability

    -
  • QFAF strategies are designed for Qualified Purchasers ($5M+ investments)
  • +
  • + {brandText( + 'QFAF strategies are designed for Qualified Purchasers ($5M+ investments)' + )} +
  • Not suitable for investors who cannot tolerate significant volatility
  • This calculator is for educational purposes only
  • Consult qualified tax, legal, and investment advisors before investing
  • diff --git a/src/components/Formulas/ProjectionFormula.tsx b/src/components/Formulas/ProjectionFormula.tsx index 084ce58..b5eee3c 100644 --- a/src/components/Formulas/ProjectionFormula.tsx +++ b/src/components/Formulas/ProjectionFormula.tsx @@ -1,3 +1,5 @@ +import { brandText, useBrandRevealed } from '../../branding'; + interface ProjectionFormulaProps { qfafMultiplier?: number; projectionYears?: number; @@ -7,12 +9,13 @@ export function ProjectionFormula({ qfafMultiplier = 1.5, projectionYears = 10, }: ProjectionFormulaProps) { + useBrandRevealed(); const pct = (qfafMultiplier * 100).toFixed(0); return (

    {projectionYears}-Year Projection Assumptions

    -        {`Portfolio Growth: 7% annual return (conservative)
    +        {brandText(`Portfolio Growth: 7% annual return (conservative)
     
     Each Year:
       QFAF Value(n+1) = QFAF Value(n) × 1.07
    @@ -22,7 +25,7 @@ Tax Events (annual, based on year-start values):
       QFAF ST Gains = QFAF Value × ${pct}%
       QFAF Ordinary Losses = QFAF Value × ${pct}%
       Collateral ST Losses = Collateral Value × ST Loss Rate
    -  Collateral LT Gains = Collateral Value × LT Gain Rate`}
    +  Collateral LT Gains = Collateral Value × LT Gain Rate`)}
           

    Tax Savings Calculation

    diff --git a/src/components/Formulas/QfafMechanicsFormula.tsx b/src/components/Formulas/QfafMechanicsFormula.tsx index 3d9cbbd..d6d4025 100644 --- a/src/components/Formulas/QfafMechanicsFormula.tsx +++ b/src/components/Formulas/QfafMechanicsFormula.tsx @@ -1,21 +1,24 @@ +import { brandText, useBrandRevealed } from '../../branding'; + interface QfafMechanicsFormulaProps { qfafMultiplier?: number; } export function QfafMechanicsFormula({ qfafMultiplier = 1.5 }: QfafMechanicsFormulaProps) { + useBrandRevealed(); const pct = (qfafMultiplier * 100).toFixed(0); return (
    -

    QFAF Mechanics

    -

    QFAF is a K-1 partnership hedge fund with fixed 250/250 leverage:

    +

    {brandText('QFAF Mechanics')}

    +

    {brandText('QFAF is a K-1 partnership hedge fund with fixed 250/250 leverage:')}

    -        {`QFAF Annual Tax Events (per $1 invested):
    +        {brandText(`QFAF Annual Tax Events (per $1 invested):
       • ST Capital Gains: ${pct}% of market value
       • Ordinary Losses: ${pct}% of market value
     
     These are generated through swap contracts that produce:
       • Short-term gains (taxed at ordinary rates if unmatched)
    -  • Ordinary losses (can offset W-2 income)`}
    +  • Ordinary losses (can offset W-2 income)`)}
           

    Why Match ST Gains?

    diff --git a/src/components/Formulas/QfafSizingFormula.tsx b/src/components/Formulas/QfafSizingFormula.tsx index d3f5070..ad31834 100644 --- a/src/components/Formulas/QfafSizingFormula.tsx +++ b/src/components/Formulas/QfafSizingFormula.tsx @@ -1,19 +1,23 @@ +import { brandText, useBrandRevealed } from '../../branding'; + interface QfafSizingFormulaProps { qfafMultiplier?: number; } export function QfafSizingFormula({ qfafMultiplier = 1.5 }: QfafSizingFormulaProps) { + useBrandRevealed(); const pct = (qfafMultiplier * 100).toFixed(0); const decimal = qfafMultiplier.toFixed(2); return (
    -

    QFAF Auto-Sizing Formula

    +

    {brandText('QFAF Auto-Sizing Formula')}

    - QFAF is sized so its ST gains match the collateral's average ST losses over the sizing - window: + {brandText( + "QFAF is sized so its ST gains match the collateral's average ST losses over the sizing window:" + )}

    -        {`QFAF Value = (Collateral × Avg ST Loss Rate) / ${pct}%
    +        {brandText(`QFAF Value = (Collateral × Avg ST Loss Rate) / ${pct}%
     
     Where:
       • Collateral = Your investment amount
    @@ -30,14 +34,14 @@ Example ($10M Core 145/45, Yrs 1–10 avg):
     
     Year 1 only ($10M Core 145/45):
       ST Losses = $10M × 28.5% = $2.85M
    -  QFAF Value = $2.85M / ${decimal} = $${Math.round(2850000 / qfafMultiplier).toLocaleString()}`}
    +  QFAF Value = $2.85M / ${decimal} = $${Math.round(2850000 / qfafMultiplier).toLocaleString()}`)}
           

    Why This Sizing?

    - QFAF generates short-term gains that would be taxed at ordinary rates (~40.8%). By matching - these with collateral ST losses, you convert them to long-term treatment (~23.8%), saving - ~17% in taxes. + {brandText( + 'QFAF generates short-term gains that would be taxed at ordinary rates (~40.8%). By matching these with collateral ST losses, you convert them to long-term treatment (~23.8%), saving ~17% in taxes.' + )}

    ); diff --git a/src/components/Formulas/Section461lFormula.tsx b/src/components/Formulas/Section461lFormula.tsx index d3a0da9..f02ebfa 100644 --- a/src/components/Formulas/Section461lFormula.tsx +++ b/src/components/Formulas/Section461lFormula.tsx @@ -1,8 +1,11 @@ +import { brandText, useBrandRevealed } from '../../branding'; + interface Section461lFormulaProps { qfafMultiplier?: number; } export function Section461lFormula({ qfafMultiplier = 1.5 }: Section461lFormulaProps) { + useBrandRevealed(); const pct = (qfafMultiplier * 100).toFixed(0); const exampleLosses = Math.round(866667 * qfafMultiplier).toLocaleString(); return ( @@ -10,21 +13,21 @@ export function Section461lFormula({ qfafMultiplier = 1.5 }: Section461lFormulaP

    Section 461(l) Excess Business Loss Limitation

    Limits how much ordinary loss can offset W-2/ordinary income each year:

    -        {`2026 Limits (per Rev. Proc. 2025-32):
    +        {brandText(`2026 Limits (per Rev. Proc. 2025-32):
       • MFJ: $512,000
       • Single/MFS/HOH: $256,000
     
     Usable Ordinary Loss = min(QFAF Ordinary Losses, §461(l) Limit, Taxable Income)
    -Excess to NOL = QFAF Ordinary Losses - Usable Ordinary Loss`}
    +Excess to NOL = QFAF Ordinary Losses - Usable Ordinary Loss`)}
           

    Example ($10M Core 145/45, MFJ)

    -        {`QFAF Ordinary Losses = $866,667 × ${pct}% = $${exampleLosses}
    +        {brandText(`QFAF Ordinary Losses = $866,667 × ${pct}% = $${exampleLosses}
     §461(l) Limit (MFJ) = $512,000
     
     Usable This Year = min($${exampleLosses}, $512,000, Taxable Income)
    -Excess → NOL = Ordinary Losses - Usable Amount`}
    +Excess → NOL = Ordinary Losses - Usable Amount`)}
           

    NOL Carryforward

    diff --git a/src/components/Formulas/StrategyRatesFormula.tsx b/src/components/Formulas/StrategyRatesFormula.tsx index 3db3398..29c9c4d 100644 --- a/src/components/Formulas/StrategyRatesFormula.tsx +++ b/src/components/Formulas/StrategyRatesFormula.tsx @@ -1,7 +1,10 @@ +import { brandText, useBrandRevealed } from '../../branding'; + export function StrategyRatesFormula() { + useBrandRevealed(); return (
    -

    Quantinno Beta 1 Strategy Rates

    +

    {brandText('Quantinno Beta 1 Strategy Rates')}

    Each strategy has fixed annual ST loss and LT gain rates:

    Core Strategies (Cash Funded)
    diff --git a/src/components/Formulas/TaxAlphaFormula.tsx b/src/components/Formulas/TaxAlphaFormula.tsx index e2290ab..cfdc9d0 100644 --- a/src/components/Formulas/TaxAlphaFormula.tsx +++ b/src/components/Formulas/TaxAlphaFormula.tsx @@ -1,10 +1,13 @@ +import { brandText, useBrandRevealed } from '../../branding'; + export function TaxAlphaFormula() { + useBrandRevealed(); return (

    Annual Tax Alpha Calculation

    Tax alpha comes from two components:

    -        {`Tax Alpha = Ordinary Loss Benefit
    +        {brandText(`Tax Alpha = Ordinary Loss Benefit
               - LT Gain Cost
     
     Where:
    @@ -12,12 +15,12 @@ Where:
       LT Gain Cost = Collateral LT Gains × LT Rate
     
     Note: QFAF short-term gains are offset by collateral
    -short-term losses (by design), so they wash to zero.`}
    +short-term losses (by design), so they wash to zero.`)}
           

    Example ($10M Core 145/45, MFJ, CA)

    -        {`Annual Tax Events:
    +        {brandText(`Annual Tax Events:
       QFAF ST Gains: $1,300,000 (offset by collateral ST losses)
       QFAF Ordinary Losses: $1,300,000 (capped at $512K for MFJ)
       Collateral ST Losses: $1,300,000 (offsets QFAF ST gains)
    @@ -29,7 +32,7 @@ Tax Alpha Components:
       ─────────────────────────────────────────
       Net Tax Alpha: $139,876/year
     
    -As % of Total: $139,876 / $10,866,667 = 1.29%`}
    +As % of Total: $139,876 / $10,866,667 = 1.29%`)}
           
    ); diff --git a/src/components/MeetingMode/MeetingMode.tsx b/src/components/MeetingMode/MeetingMode.tsx index 5a78405..2e30722 100644 --- a/src/components/MeetingMode/MeetingMode.tsx +++ b/src/components/MeetingMode/MeetingMode.tsx @@ -13,6 +13,7 @@ import { formatWithCommas, parseFormattedNumber } from '../../utils/formatters'; import { getEffectiveView } from '../../utils/effectiveAllocation'; import { ExitTaxAnalysis } from '../../calculations/exitTax'; import { computeEdiInsights, computeStepUpComparison } from '../../calculations/ediInsights'; +import { brandText, useBrandRevealed } from '../../branding'; // Design tokens from EDI Calc Meeting Mode.html const M = { @@ -251,6 +252,7 @@ function Rail({ onPinScenario: () => void; hasPin: boolean; }) { + useBrandRevealed(); const projYears = advancedSettings.projectionYears; const clampYear = (raw: string) => Math.max(1, Math.min(projYears, parseInt(raw, 10) || 1)); const effectiveView = useMemo(() => getEffectiveView(inputs), [inputs]); @@ -469,7 +471,7 @@ function Rail({ letterSpacing: 0.4, }} > - QFAF + {brandText('QFAF')}
    - QFAF overlay + {brandText('QFAF overlay')} {( [ - ['QFAF value', fmtCurrency(results.sizing.qfafValue), '#38bdf8'], + [brandText('QFAF value'), fmtCurrency(results.sizing.qfafValue), '#38bdf8'], ['Total exposure', fmtCurrency(results.sizing.totalExposure), '#60a5fa'], ['Combined ST', fmtPercent(taxRates.combinedStRate), '#fb7185'], ['Ordinary (deductions)', fmtPercent(taxRates.combinedOrdinaryRate), '#34d399'], @@ -1903,6 +1905,7 @@ function PrintPageHeader({ } function PrintPageFooter() { + useBrandRevealed(); return (
    ); } @@ -1961,6 +1965,7 @@ function MechanicsView({ currentStrategy: Strategy | undefined; filingLabel: string; }) { + useBrandRevealed(); const strategyLabel = currentStrategy?.name ?? 'Overlay strategy'; const totalStLossesHarvested = visibleYears.reduce((s, y) => s + y.stLossesHarvested, 0); // EDI mode (QFAF off): no QFAF, no ordinary losses, no NOL — the story is @@ -1973,7 +1978,9 @@ function MechanicsView({ n: 1, title: 'Harvest losses systematically', tone: M.accent, - body: `The long/short ${strategyLabel} overlay realizes short-term losses on constituent positions while maintaining benchmark-like exposure. No QFAF is used in this mode, so there are no ordinary-loss deductions or NOL.`, + body: brandText( + `The long/short ${strategyLabel} overlay realizes short-term losses on constituent positions while maintaining benchmark-like exposure. No QFAF is used in this mode, so there are no ordinary-loss deductions or NOL.` + ), metric: fmtCurrency(totalStLossesHarvested), metricLabel: 'ST losses harvested', }, @@ -2005,11 +2012,13 @@ function MechanicsView({ : [ { n: 1, - title: 'Establish QFAF', + title: brandText('Establish QFAF'), tone: M.accent, - body: 'A Quantinno Fundamental Arbitrage Fund (QFAF) overlay would be structured against collateral, designed to give exposure without a taxable sale of the underlying. Tax treatment depends on fund qualification and current IRS guidance.', + body: brandText( + 'A Quantinno Fundamental Arbitrage Fund (QFAF) overlay would be structured against collateral, designed to give exposure without a taxable sale of the underlying. Tax treatment depends on fund qualification and current IRS guidance.' + ), metric: fmtCurrency(qfafValue), - metricLabel: 'QFAF value', + metricLabel: brandText('QFAF value'), }, { n: 2, @@ -2060,7 +2069,9 @@ function MechanicsView({ : { heading: 'Why the losses are ordinary (§475(f))', body: - 'The QFAF is modeled as a limited partnership engaged in high-turnover arbitrage trading ' + + brandText( + 'The QFAF is modeled as a limited partnership engaged in high-turnover arbitrage trading ' + ) + 'that has made a mark-to-market election under IRC §475(f). Under that election the ' + "fund's trading losses are ordinary in character and generally non-passive, which is why " + 'they can offset W-2 and other ordinary income. This treatment is a working draft pending ' + @@ -2367,6 +2378,7 @@ export function MeetingMode({ whatIfs, onUpdateWhatIfs, }: MeetingModeProps) { + useBrandRevealed(); const [level, setLevel] = useState('high'); const [railCollapsed, setRailCollapsed] = useState(false); const [showDecomp, setShowDecomp] = useState(true); @@ -2958,7 +2970,8 @@ export function MeetingMode({ ) : ( <> - Enhanced direct indexing with the QFAF overlay generates an estimated{' '} + Enhanced direct indexing with the {brandText('QFAF overlay')} generates an + estimated{' '} {fmtCurrency(totalTaxSavings)} of net tax savings through {endYear}, driven by ordinary loss deductions, NOL usage, and capital loss carryforwards. diff --git a/src/components/OnboardingTour.tsx b/src/components/OnboardingTour.tsx index cb236f0..94a10e7 100644 --- a/src/components/OnboardingTour.tsx +++ b/src/components/OnboardingTour.tsx @@ -1,5 +1,6 @@ import { useState, useEffect, useCallback, useRef } from 'react'; import { STORAGE_KEYS } from '../constants/storageKeys'; +import { brandText, useBrandRevealed } from '../branding'; const TOUR_KEY = STORAGE_KEYS.TOUR_COMPLETED; @@ -43,6 +44,7 @@ const STEPS: TourStep[] = [ ]; export function OnboardingTour() { + useBrandRevealed(); const [active, setActive] = useState(false); const [step, setStep] = useState(0); const [position, setPosition] = useState<{ top: number; left: number; width: number } | null>( @@ -119,9 +121,9 @@ export function OnboardingTour() { {step + 1} / {STEPS.length} -

    {currentStep.title}

    +

    {brandText(currentStep.title)}

    -

    {currentStep.description}

    +

    {brandText(currentStep.description)}

    - + 175%
    - Historical: min 131%, max 158%, avg 142% — QFAF MV as % of collateral + {brandText( + 'Historical: min 131%, max 158%, avg 142% — QFAF MV as % of collateral' + )}
    @@ -936,14 +953,14 @@ export function StrategySelectionInputs({ )} - Years averaged for QFAF sizing — Default: 5 (capped by duration) + {brandText('Years averaged for QFAF sizing — Default: 5 (capped by duration)')}
    - + 10 years
    - Years QFAF runs before breakeven unwind — default: 5 + {brandText('Years QFAF runs before breakeven unwind — default: 5')}
    @@ -999,7 +1016,9 @@ export function StrategySelectionInputs({ 10%
    - Reduces auto-sized QFAF for conservative sizing + + {brandText('Reduces auto-sized QFAF for conservative sizing')} +
@@ -1007,10 +1026,11 @@ export function StrategySelectionInputs({ {advancedSettings.growthEnabled && (
)} diff --git a/src/components/TaxRatesDisplay.tsx b/src/components/TaxRatesDisplay.tsx index 073115d..6284041 100644 --- a/src/components/TaxRatesDisplay.tsx +++ b/src/components/TaxRatesDisplay.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { InfoPopup, InfoText, TaxRatesFormula } from '../InfoPopup'; import { CollapsibleSection } from './CollapsibleSection'; import { useValueFlash } from '../hooks/useValueFlash'; +import { brandText, useBrandRevealed } from '../branding'; /** * Props for the TaxRatesDisplay component. @@ -31,6 +32,7 @@ export const TaxRatesDisplay = React.memo(function TaxRatesDisplay({ combinedStRate, combinedLtRate, }: TaxRatesDisplayProps) { + useBrandRevealed(); const formatRate = (rate: number) => `${(rate * 100).toFixed(1)}%`; // Flash on rate changes @@ -51,7 +53,9 @@ export const TaxRatesDisplay = React.memo(function TaxRatesDisplay({ } - guidance="These marginal rates determine the value of each tax event. Higher ordinary rates increase the value of QFAF ordinary loss deductions." + guidance={brandText( + 'These marginal rates determine the value of each tax event. Higher ordinary rates increase the value of QFAF ordinary loss deductions.' + )} className="tax-rates-section" >
diff --git a/src/constants/storageKeys.ts b/src/constants/storageKeys.ts index d52a4e3..354e433 100644 --- a/src/constants/storageKeys.ts +++ b/src/constants/storageKeys.ts @@ -27,4 +27,10 @@ export const STORAGE_KEYS = { PINNED_EDI_SCENARIO: 'taxCalc:pinned-edi-scenario', /** Workspace scenario display name (D-027 top context bar) */ SCENARIO_NAME: 'taxCalc:scenario-name', + /** + * Reveal real manager/fund branding (Quantinno / QFAF) instead of the + * anonymized public labels. Off by default; toggled via a hidden click + * sequence on the app title for internal use only. + */ + REVEAL_BRAND: 'taxCalc:reveal-brand', } as const; diff --git a/src/hooks/useSecretReveal.ts b/src/hooks/useSecretReveal.ts new file mode 100644 index 0000000..545b025 --- /dev/null +++ b/src/hooks/useSecretReveal.ts @@ -0,0 +1,40 @@ +import { useCallback, useRef, useState } from 'react'; +import { toggleBrandRevealed } from '../branding'; + +/** Rapid clicks required (within WINDOW_MS of each other) to flip the toggle. */ +const REQUIRED_CLICKS = 5; +const WINDOW_MS = 1500; +const FLASH_MS = 1800; + +/** + * Hidden reveal toggle: N rapid clicks on the bound element flip real-name + * branding on/off. Deliberately undiscoverable — no cursor, tooltip, or ARIA + * affordance — so public users never trip it. Returns an `onClick` to spread + * onto the target element plus a transient `flash` (true = revealed, false = + * anonymized, null = idle) for brief confirmation feedback. + */ +export function useSecretReveal() { + const count = useRef(0); + const last = useRef(0); + const timer = useRef | undefined>(undefined); + const [flash, setFlash] = useState(null); + + const onClick = useCallback(() => { + const now = Date.now(); + if (now - last.current > WINDOW_MS) { + count.current = 0; + } + last.current = now; + count.current += 1; + + if (count.current >= REQUIRED_CLICKS) { + count.current = 0; + const revealed = toggleBrandRevealed(); + setFlash(revealed); + clearTimeout(timer.current); + timer.current = setTimeout(() => setFlash(null), FLASH_MS); + } + }, []); + + return { onClick, flash }; +} diff --git a/src/pages/QfafTestPage.tsx b/src/pages/QfafTestPage.tsx index 0b2ffab..b124240 100644 --- a/src/pages/QfafTestPage.tsx +++ b/src/pages/QfafTestPage.tsx @@ -1,8 +1,10 @@ import { useState } from 'react'; import { QfafTestByYear } from '../AdvancedMode/QfafTestByYear'; import { FILING_STATUSES, FilingStatus } from '../types'; +import { brandText, useBrandRevealed } from '../branding'; export function QfafTestPage() { + useBrandRevealed(); const [filingStatus, setFilingStatus] = useState('mfj'); const [dismissedDisclosure, setDismissedDisclosure] = useState(false); @@ -32,10 +34,11 @@ export function QfafTestPage() { )}
-

QFAF Test (By Year)

+

{brandText('QFAF Test (By Year)')}

- Model QFAF economics year-by-year with custom cash infusions, tax rates, and §461(l) - carryforward projections. + {brandText( + 'Model QFAF economics year-by-year with custom cash infusions, tax rates, and §461(l) carryforward projections.' + )}

diff --git a/src/popupContent.ts b/src/popupContent.ts index e2ade72..a47a922 100644 --- a/src/popupContent.ts +++ b/src/popupContent.ts @@ -1,5 +1,6 @@ // Popup content definitions for all calculated fields // Each popup includes: definition, formula (if applicable), and impact explanation +import { brandText } from './branding'; export interface PopupContent { title: string; @@ -926,7 +927,17 @@ export const POPUP_CONTENT: Record = { }, }; -// Helper function to get popup content by key +// Helper function to get popup content by key. +// Routes every string field through brandText so manager/fund names are +// anonymized in the public build (reactive; components reading this must +// subscribe via useBrandRevealed / useBrandText to re-render on toggle). export function getPopupContent(key: string): PopupContent | undefined { - return POPUP_CONTENT[key]; + const content = POPUP_CONTENT[key]; + if (!content) return undefined; + return { + title: brandText(content.title), + definition: brandText(content.definition), + formula: content.formula === undefined ? undefined : brandText(content.formula), + impact: brandText(content.impact), + }; } diff --git a/src/utils/excelExport.ts b/src/utils/excelExport.ts index c8232aa..4598018 100644 --- a/src/utils/excelExport.ts +++ b/src/utils/excelExport.ts @@ -3,6 +3,7 @@ import type { AdvancedSettings } from '../types'; import type { ExitTaxAnalysis } from '../calculations/exitTax'; import { computeEdiInsights, computeStepUpComparison } from '../calculations/ediInsights'; import { getEffectiveView } from './effectiveAllocation'; +import { brandText } from '../branding'; interface ExportData { inputs: CalculatorInputs; @@ -51,7 +52,7 @@ export async function buildWorkbook(data: ExportData): Promise { ...(view.isSplit ? view.legs.map(leg => [` ${leg.label}: ${leg.strategy.name}`, leg.amount]) : []), - ['Auto-Sized QFAF', data.results.sizing.qfafValue], + [brandText('Auto-Sized QFAF'), data.results.sizing.qfafValue], ['Total Exposure', data.results.sizing.totalExposure], [], ['Year 1 Tax Savings', data.results.years[0]?.taxSavings ?? 0], @@ -116,11 +117,11 @@ export async function buildWorkbook(data: ExportData): Promise { const yearHeaders: (string | number)[] = [ 'Year', 'Collateral Value', - 'QFAF Value', + brandText('QFAF Value'), 'Total Value', 'ST Losses Harvested', 'LT Gains Realized', - 'ST Gains (QFAF)', + brandText('ST Gains (QFAF)'), 'Ordinary Losses', 'Usable Ordinary Loss', 'NOL Carryforward', @@ -173,7 +174,7 @@ export async function buildWorkbook(data: ExportData): Promise { ['Filing Status', data.inputs.filingStatus.toUpperCase()], ['State', data.inputs.stateCode], ['Annual Income', data.inputs.annualIncome], - ['QFAF Enabled', data.inputs.qfafEnabled ? 'Yes' : 'No'], + [brandText('QFAF Enabled'), data.inputs.qfafEnabled ? 'Yes' : 'No'], [], ['Federal ST Rate', data.taxRates.federalStRate], ['Federal LT Rate', data.taxRates.federalLtRate], @@ -183,7 +184,7 @@ export async function buildWorkbook(data: ExportData): Promise { ['ST→LT Differential', data.taxRates.rateDifferential], [], ['Projection Years', data.settings.projectionYears], - ['QFAF Multiplier', data.settings.qfafMultiplier], + [brandText('QFAF Multiplier'), data.settings.qfafMultiplier], ['Annual Return', data.settings.defaultAnnualReturn], ['Growth Enabled', data.settings.growthEnabled ? 'Yes' : 'No'], ]; @@ -219,7 +220,9 @@ export async function buildWorkbook(data: ExportData): Promise { [], [ 'Investment Risks: Past performance does not guarantee future results. All investments involve risk ' + - 'of loss. Strategy returns, loss harvesting rates, and QFAF performance are based on historical ' + + brandText( + 'of loss. Strategy returns, loss harvesting rates, and QFAF performance are based on historical ' + ) + 'averages and may not be achieved.', ], [], diff --git a/src/workspace/WorkspaceTab.tsx b/src/workspace/WorkspaceTab.tsx index 047d22c..f276abf 100644 --- a/src/workspace/WorkspaceTab.tsx +++ b/src/workspace/WorkspaceTab.tsx @@ -41,8 +41,10 @@ import { DisclaimerFooter } from '../components/DisclaimerFooter'; import { QualifiedPurchaserModal } from '../components/QualifiedPurchaserModal'; import { useQualifiedPurchaser } from '../hooks/useQualifiedPurchaser'; import { InfoText } from '../InfoPopup'; -import { POPUP_CONTENT } from '../popupContent'; +import { getPopupContent } from '../popupContent'; import { STORAGE_KEYS } from '../constants/storageKeys'; +import { brandText, useBrandRevealed } from '../branding'; +import { useSecretReveal } from '../hooks/useSecretReveal'; // D-027 fonts: Hanken Grotesk (UI) + IBM Plex Mono (Graphite numerals), // bundled from @fontsource so vite-plugin-singlefile inlines them — the // distributable stays self-contained (no runtime CDN fetches). Mono is @@ -579,6 +581,8 @@ interface WorkspaceTabProps { } export function WorkspaceTab({ isActive = true }: WorkspaceTabProps) { + useBrandRevealed(); // re-render when the hidden brand-reveal toggle flips + const secretReveal = useSecretReveal(); const qualifiedPurchaser = useQualifiedPurchaser(); // D-026: same hook + storage as the Classic comparison panel — one shared // pin store, so an advisor can pin here and compare there (or vice versa). @@ -1103,7 +1107,7 @@ export function WorkspaceTab({ isActive = true }: WorkspaceTabProps) { ...GROUP_META.map(g => ({ id: `group-${g.id}`, kind: 'Inputs' as const, - label: g.label, + label: brandText(g.label), keywords: g.keywords, run: () => jumpToGroup(g.id), })), @@ -1255,11 +1259,11 @@ export function WorkspaceTab({ isActive = true }: WorkspaceTabProps) { flags.push({ key: 'fixed-qfaf-dlv', sev: 'warn', - tag: 'QFAF', + tag: brandText('QFAF'), body: ( <> - Fixed QFAF sizing won't shrink with deleveraging — expect ST gain - leakage as the harvest rate glides down (switch to Dynamic). + {brandText('Fixed QFAF sizing')} won't shrink with deleveraging — + expect ST gain leakage as the harvest rate glides down (switch to Dynamic). ), }); @@ -1480,9 +1484,16 @@ export function WorkspaceTab({ isActive = true }: WorkspaceTabProps) { {/* ───────────────── Top context bar ───────────────── */}
-
+ {/* Hidden reveal: 5 rapid clicks on the brand toggle real manager/fund + names on/off for internal use. No affordance — invisible to the public. */} +
Q EDI Calculator + {secretReveal.flash !== null && ( + + {secretReveal.flash ? 'Internal names on' : 'Anonymized'} + + )}
{editingName ? ( @@ -1539,7 +1550,7 @@ export function WorkspaceTab({ isActive = true }: WorkspaceTabProps) { )} - QFAF {inputs.qfafEnabled ? 'On' : 'Off'} + {brandText('QFAF')} {inputs.qfafEnabled ? 'On' : 'Off'}
@@ -1748,7 +1759,10 @@ export function WorkspaceTab({ isActive = true }: WorkspaceTabProps) { → Core {formatCurrency(effectiveInputs.splitAllocation?.coreAmount ?? 0)} + overlay {formatCurrency(effectiveInputs.splitAllocation?.overlayAmount ?? 0)} {inputs.qfafEnabled && ( - <> + QFAF {formatCurrency(results.sizing.qfafValue)} + <> + {' '} + + {brandText('QFAF')} {formatCurrency(results.sizing.qfafValue)} + )} {' = '} {formatCurrency(results.sizing.totalExposure)} @@ -1763,7 +1777,9 @@ export function WorkspaceTab({ isActive = true }: WorkspaceTabProps) { {formatPercent(split.overlayAmount / splitTotal)} overlay) )} - {inputs.qfafEnabled && <> · QFAF auto-sizes against the combined ST losses} + {inputs.qfafEnabled && ( + <> · {brandText('QFAF auto-sizes against the combined ST losses')} + )} )}

@@ -1776,7 +1792,7 @@ export function WorkspaceTab({ isActive = true }: WorkspaceTabProps) { {fundingMode === 'total' && (

The core and overlay amounts are used as allocation weights; the tool scales - both legs so collateral + QFAF equals the total portfolio. + both legs so collateral + {brandText('QFAF')} equals the total portfolio.

)} @@ -1840,7 +1856,10 @@ export function WorkspaceTab({ isActive = true }: WorkspaceTabProps) {

→ Collateral {formatCurrency(results.sizing.collateralValue)} {inputs.qfafEnabled && ( - <> + QFAF {formatCurrency(results.sizing.qfafValue)} + <> + {' '} + + {brandText('QFAF')} {formatCurrency(results.sizing.qfafValue)} + )}{' '} = {formatCurrency(results.sizing.totalExposure)}

@@ -1931,7 +1950,7 @@ export function WorkspaceTab({ isActive = true }: WorkspaceTabProps) { @@ -1951,7 +1970,7 @@ export function WorkspaceTab({ isActive = true }: WorkspaceTabProps) { onChange={e => set('qfafEnabled', e.target.checked)} /> - Enable QFAF + {brandText('Enable QFAF')} {inputs.qfafEnabled && ( <> @@ -2822,10 +2841,10 @@ export function WorkspaceTab({ isActive = true }: WorkspaceTabProps) { {!ediMode && (
- How the QFAF's tax treatment works (draft — pending - counsel review) + {brandText("How the QFAF's tax treatment works")} (draft — + pending counsel review) -

{POPUP_CONTENT['qfaf-treatment'].definition}

+

{getPopupContent('qfaf-treatment')?.definition ?? ''}

)}
@@ -2860,8 +2879,8 @@ export function WorkspaceTab({ isActive = true }: WorkspaceTabProps) {

Estimates only — not investment, tax, or legal advice. Headline savings are gross by default; financing fees, wash-sale haircuts, and present-value discounting are opt-in - layers. QFAF tax treatment is contingent on qualification. See the full disclosures - below. + layers. {brandText('QFAF tax treatment is contingent on qualification.')} See the full + disclosures below.

diff --git a/src/workspace/workspace.css b/src/workspace/workspace.css index cc44246..f0fe095 100644 --- a/src/workspace/workspace.css +++ b/src/workspace/workspace.css @@ -182,6 +182,15 @@ font-size: 14px; letter-spacing: -0.01em; } +/* Transient confirmation for the hidden brand-reveal toggle. No affordance on + the brand itself (no pointer cursor) so the toggle stays undiscoverable. */ +.wx-brand-reveal-flash { + font-size: 11px; + font-weight: 600; + color: var(--wx-accent); + opacity: 0.85; + white-space: nowrap; +} .wx-scenario { display: flex; align-items: center;