Skip to content

Commit 8484777

Browse files
Merge pull request #46 from programmerShinobi/feat/phase2d-advisor-flow-insights-visuals
[feat](advisor+insights): Fase 2d — interactive step rail, collapsible sections, 21 living Catalog symbols, lens banners
2 parents 17b11b3 + 903ab29 commit 8484777

14 files changed

Lines changed: 949 additions & 101 deletions

File tree

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,23 @@ The decision **model** carries its own version, recorded in the
99

1010
## [Unreleased]
1111

12+
### Added / Changed (Fase 2d — guided Advisor flow + visual Insights — 2026-07-17)
13+
14+
- **Advisor journey rail is now interactive** — sticky under the app bar, each step (1–4) is a
15+
button that scrolls to its section, and a scroll-spy highlights the step you are on.
16+
- **Advisor sections are modern collapsible cards** — coin + title + chevron on native
17+
`<details>` (`StepSection`), open by default so the guided flow still reads top-to-bottom.
18+
- **Footer simplified** — one professional identity line (brand glyph + copyright + licenses);
19+
browser guidance folded into a hover title (FR-EDGE-4 preserved).
20+
- **How-it-works step numbers** get breathing room above the art.
21+
- **Catalog: all 21 architectures now have living symbolic art** (`ArchArt`) — on every Catalog
22+
card AND a larger labelled diagram on the detail page (universal mono captions like
23+
"EVENT BUS · PUB/SUB"), so newcomers can SEE each idea. Same calm animation utilities as the
24+
landing (`aa-anim-*`, frozen under reduced-motion / aurora-static).
25+
- **Playbook / Review / Library each open with a static explanatory visual** (`LensBanner`)
26+
showing what the lens itself is: steps → build, evidence → verdict, the reference shelf.
27+
28+
1229
### Added / Changed (Fase 2 — richer scenarios & guidance, full-feature Guide, calm chrome — 2026-07-16)
1330

1431
- **10 scenario presets** — five new helper scenarios (B2B SaaS, consumer mobile, data platform,

docs/03-blueprint/design-specification.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ utilities via `tailwind.config.js`); view code references these instead of hardc
279279
| **Type** | `--aa-fs-2xs..2xl` = 11/12/12.5/13.5/15 px + fluid lg/xl/2xl via `clamp()`; `--aa-lh` 1.6 |
280280
| **Elevation** | `--aa-shadow-sm/md/lg` over a theme-aware `--aa-shadow-color` |
281281
| **Touch & focus** | `--aa-touch` 44 px (coarse pointers); `--aa-focus-ring` 2 px info, global `:focus-visible` |
282-
| **Breakpoints** | canonical (cannot be `var()` in `@media`): **phone ≤ 640 px · tablet 641–1024 px · laptop ≥ 1025 px**. **Navigation chrome switches at ≤ 1024 px** (Fase 2b): phones AND tablets share the compact mobile chrome (bottom tab bar, action bar, settings sheet, compact top bar — brand left + Guide) so the app bar never wraps; content-density rules (`.f-div`, overlay padding, grid columns) keep the 640 px phone tier. Historical `-phone` class names mean "compact tier". |
282+
| **Breakpoints** | canonical (cannot be `var()` in `@media`): **phone ≤ 640 px · tablet 641–1024 px · laptop ≥ 1025 px**. **Navigation chrome switches at ≤ 1180 px** (Fase 2b, raised Fase 2d: ANY width where the app bar would wrap gets the compact chrome): phones, tablets and small laptops share the compact mobile chrome (bottom tab bar, action bar, settings sheet, compact top bar — brand left + Guide) so the app bar never wraps; content-density rules (`.f-div`, overlay padding, grid columns) keep the 640 px phone tier. Historical `-phone` class names mean "compact tier". |
283283

284284
Base classes: `.aa-page` / `.aa-frame` / `.aa-surface` / `.aa-panel` / `.aa-card` / `.aa-wrap` /
285285
`.aa-touch`. New classes are namespaced `aa-`; no `!important`.

docs/03-blueprint/mobile-experience-plan.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ Desktop (≥641px) is unchanged (top nav + header controls as today).
6262

6363
- ~~Phone tier: **≤640px** (canonical, design-spec §6.1). Bottom nav + action bar + settings sheet are
6464
phone-only; ≥641px keeps the current top-nav desktop layout.~~ **Updated (Fase 2b, 2026-07-16):**
65-
the navigation chrome now switches at **1024px** — tablets share the phone's bottom nav /
65+
the navigation chrome now switches at **1180px** (raised Fase 2d) — tablets share the phone's bottom nav /
6666
action bar / settings sheet / compact top bar so the app bar never wraps; only content-density
6767
rules stay on the 640px phone tier (design-spec §6.1).
6868
- Safe area: `padding-bottom: env(safe-area-inset-bottom)` on the fixed bars; the viewport already

src/App.tsx

Lines changed: 48 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { PrintReport } from './components/overlays/PrintReport';
1313
import { Collapsible } from './components/advisor/Collapsible';
1414
import { GuidedBanner } from './components/chrome/GuidedBanner';
1515
import { StepTracker } from './components/chrome/StepTracker';
16+
import { StepSection } from './components/chrome/StepSection';
1617
import { PresetBar } from './components/advisor/PresetBar';
1718
import { Toolbar } from './components/chrome/Toolbar';
1819
import { C4Preview } from './components/advisor/C4Preview';
@@ -295,45 +296,41 @@ export default function App() {
295296

296297
<div className="f-div" />
297298

298-
{/* Step 1 — project factors + derived priorities */}
299-
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '14px' }}>
300-
<span className="f-num">1</span>
301-
<span style={{ fontSize: '15px', fontWeight: 500 }}>
302-
<span className="guided-only">{t('step1.g')}</span>
303-
<span className="expert-only">{t('step1.e')}</span>
304-
</span>
305-
</div>
306-
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit,minmax(280px,1fr))', gap: '18px' }}>
299+
{/* Step 1 — project factors (its own dropdown section; owner feedback: factors and
300+
priorities must be SEPARATE so nobody gets confused). */}
301+
<StepSection id="aa-sec-1" n="1" titleG="step1.g" titleE="step1.e">
307302
<FactorInputs levels={levels} onChange={setLevels} />
308-
{/* The adjust editor opens RIGHT under the priorities card (owner feedback: it used to
309-
land far below the tall factor column, so nobody connected it to "Adjust"). */}
310-
<div style={{ display: 'grid', gap: '14px', alignContent: 'start' }}>
311-
<PrioritiesCard weights={weights} onAdjust={() => setEditWeights((v) => !v)} editing={mode === 'expert' && editWeights} />
312-
{mode === 'expert' && editWeights && <QaOverridePanel weights={weights} overrides={overrides} onChange={setOverrides} />}
303+
</StepSection>
304+
305+
<div className="f-div" />
306+
307+
{/* Step 2 — derived quality priorities; the adjust editor opens right underneath.
308+
Ungated (Fase 2d rev.3, owner): guided users can customise weights too — the
309+
plain-language adjuster is newcomer-safe. */}
310+
<StepSection id="aa-sec-2" n="2" titleG="step2.g" titleE="step2.e">
311+
<div style={{ display: 'grid', gap: '14px' }}>
312+
<PrioritiesCard weights={weights} onAdjust={() => setEditWeights((v) => !v)} editing={editWeights} />
313+
{editWeights && <QaOverridePanel weights={weights} overrides={overrides} onChange={setOverrides} />}
313314
</div>
314-
</div>
315+
</StepSection>
315316

316317
<div className="f-div" />
317318

318-
{/* Step 3 — recommendation across dimensions */}
319-
<div id="adv-plan" style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '13px', scrollMarginTop: '12px' }}>
320-
<span className="f-num">3</span>
321-
<span style={{ fontSize: '15px', fontWeight: 500 }}>
322-
<span className="guided-only">{t('results.title.g')}</span>
323-
<span className="expert-only">{t('results.title.e')}</span>
324-
</span>
325-
</div>
326-
<DimensionCards rankings={rankings} current={currentDim} onSelect={setCurrentDim} />
327-
<DimensionDetail dim={currentDim} ranked={rankings[currentDim]} weights={weights} />
328-
<RadarPanel weights={weights} mode={mode} />
319+
{/* Step 3 — recommendation across dimensions (collapsible card, Fase 2d). */}
320+
<div id="adv-plan" style={{ scrollMarginTop: '132px' }} />
321+
<StepSection id="aa-sec-3" n="3" titleG="results.title.g" titleE="results.title.e">
322+
<DimensionCards rankings={rankings} current={currentDim} onSelect={setCurrentDim} />
323+
<DimensionDetail dim={currentDim} ranked={rankings[currentDim]} weights={weights} />
324+
<RadarPanel weights={weights} mode={mode} />
329325

330-
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit,minmax(250px,1fr))', gap: '14px', marginTop: '16px' }}>
331-
<SensitivityCard flips={flips} levels={levels} />
332-
<MigrationCard value={migKey} onChange={setMigKey} />
333-
</div>
326+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit,minmax(250px,1fr))', gap: '14px', marginTop: '16px' }}>
327+
<SensitivityCard flips={flips} levels={levels} />
328+
<MigrationCard value={migKey} onChange={setMigKey} />
329+
</div>
334330

335-
<AntiPatternWarning rules={antiPatterns} mode={mode} />
336-
<HowItDecides />
331+
<AntiPatternWarning rules={antiPatterns} mode={mode} />
332+
<HowItDecides />
333+
</StepSection>
337334

338335
{/* Expert-only depth (build-spec features beyond the prototype mockup). Guided mode
339336
stays faithful to the prototype; experts get the extra analysis. */}
@@ -364,8 +361,11 @@ export default function App() {
364361
</div>
365362
</section>
366363

367-
<div id="adv-save" className="f-div" style={{ scrollMarginTop: '12px' }} />
368-
<Toolbar run={run} status={exportStatus} setStatus={setExportStatus} mode={mode} onImport={importScenario} />
364+
<div id="adv-save" className="f-div" style={{ scrollMarginTop: '132px' }} />
365+
{/* Step 4 — save & share (collapsible card, Fase 2d). */}
366+
<StepSection id="aa-sec-4" n="4" titleG="step4.g" titleE="step4.e">
367+
<Toolbar run={run} status={exportStatus} setStatus={setExportStatus} mode={mode} onImport={importScenario} />
368+
</StepSection>
369369

370370
<p
371371
className="f-gloss"
@@ -402,21 +402,27 @@ export default function App() {
402402
}}
403403
/>
404404

405-
{/* Global footer — always visible (both Advisor & Insights); browser guidance (FR-EDGE-4 / SRS §2.3)
406-
+ copyright & identity (Fase 1, DECISIONS.md). Borderless: whitespace, no hairline. */}
405+
{/* Global footer (Fase 2d rev.2 — owner: "rapi & simple"): a tidy two-line centered
406+
stack — brand line, then one short legal line. Browser guidance (FR-EDGE-4)
407+
lives in the hover title. */}
407408
<footer
408409
className="screen-only"
410+
title={t('footer.browsers')}
409411
style={{
410-
padding: 'var(--aa-space-6) var(--aa-panel-pad) var(--aa-space-4)',
411-
fontSize: 'var(--aa-fs-2xs)',
412-
color: 'var(--color-text-tertiary)',
412+
display: 'grid',
413+
justifyItems: 'center',
414+
gap: '6px',
415+
padding: 'var(--aa-space-7) var(--aa-panel-pad) var(--aa-space-5)',
413416
textAlign: 'center',
414417
}}
415418
>
416-
<div>{t('footer.browsers')}</div>
417-
<div style={{ marginTop: 'var(--aa-space-2)' }}>
419+
<span style={{ display: 'inline-flex', alignItems: 'center', gap: '8px', color: 'var(--color-text-secondary)', fontFamily: 'var(--font-display)', fontSize: '12.5px', fontWeight: 600, letterSpacing: '-0.01em' }}>
420+
<BrandMark size={15} />
421+
{t('app.title')}
422+
</span>
423+
<span style={{ fontSize: 'var(--aa-fs-2xs)', color: 'var(--color-text-tertiary)' }}>
418424
{SITE_COPYRIGHT} · {t('footer.rights')}
419-
</div>
425+
</span>
420426
</footer>
421427
</div>
422428
</div>

src/components/advisor/PrioritiesCard.tsx

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,12 @@ export function PrioritiesCard({ weights, onAdjust, editing = false }: Props) {
1818

1919
return (
2020
<div style={{ border: '0.5px solid var(--color-border-tertiary)', borderRadius: 'var(--border-radius-lg)', padding: '15px 16px' }}>
21-
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '3px' }}>
22-
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
23-
<span className="f-num">2</span>
24-
<span style={{ fontSize: '14px', fontWeight: 500 }}>
25-
<span className="guided-only">{t('prio.title.g')}</span>
26-
<span className="expert-only">{t('step2.e')}</span>
27-
</span>
28-
</div>
21+
{/* No internal number/title — the StepSection card already shows "2 · …" (owner
22+
feedback: factors and priorities are now SEPARATE dropdown sections). */}
23+
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', marginBottom: '3px' }}>
2924
<button
3025
type="button"
31-
className="expert-only f-btn"
26+
className="f-btn"
3227
onClick={onAdjust}
3328
aria-pressed={editing}
3429
style={
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import type { ReactNode } from 'react';
2+
import { IconChevronDown } from '@tabler/icons-react';
3+
import { useI18n } from '../../i18n/I18nContext';
4+
import type { DictKey } from '../../i18n/dict';
5+
6+
interface Props {
7+
/** Anchor id (`aa-sec-N`) — the sticky StepTracker scrolls here and spies on it. */
8+
id: string;
9+
n: string;
10+
titleG: DictKey;
11+
titleE: DictKey;
12+
children: ReactNode;
13+
}
14+
15+
// One numbered Advisor section as a modern collapsible card (Fase 2d, owner request):
16+
// coin + title + chevron on a native <details> (keyboard/screen-reader friendly), open by
17+
// default so the guided flow still reads top-to-bottom.
18+
export function StepSection({ id, n, titleG, titleE, children }: Readonly<Props>) {
19+
const { t } = useI18n();
20+
return (
21+
<details id={id} className="aa-sec" open>
22+
<summary className="aa-sec-head">
23+
<span className="f-num">{n}</span>
24+
<span className="aa-sec-title">
25+
<span className="guided-only">{t(titleG)}</span>
26+
<span className="expert-only">{t(titleE)}</span>
27+
</span>
28+
<IconChevronDown size={16} className="aa-sec-chev" aria-hidden />
29+
</summary>
30+
<div className="aa-sec-body">{children}</div>
31+
</details>
32+
);
33+
}
Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useEffect, useState } from 'react';
12
import { useI18n } from '../../i18n/I18nContext';
23
import type { DictKey } from '../../i18n/dict';
34

@@ -9,24 +10,52 @@ const STEPS: { n: string; g: DictKey; e: DictKey }[] = [
910
];
1011

1112
// The 4-step journey rail (guided/expert labels swap via .guided-only/.expert-only).
12-
// Fase 2c redesign: numbered coins on a soft connector rail — modern, calm, and obvious
13-
// for newcomers ("this is the path you are walking").
13+
// Fase 2d (owner request): the rail FLOATS (sticky under the app bar), each step is a BUTTON
14+
// that scrolls to its section, and a scroll-spy highlights WHERE YOU ARE right now.
1415
export function StepTracker() {
1516
const { t } = useI18n();
17+
const [active, setActive] = useState(1);
18+
19+
useEffect(() => {
20+
let raf = 0;
21+
const onScroll = () => {
22+
cancelAnimationFrame(raf);
23+
raf = requestAnimationFrame(() => {
24+
let cur = 1;
25+
for (let i = 1; i <= STEPS.length; i++) {
26+
const el = document.getElementById(`aa-sec-${i}`);
27+
if (el && el.getBoundingClientRect().top <= window.innerHeight * 0.4) cur = i;
28+
}
29+
setActive(cur);
30+
});
31+
};
32+
onScroll();
33+
window.addEventListener('scroll', onScroll, { passive: true });
34+
return () => {
35+
cancelAnimationFrame(raf);
36+
window.removeEventListener('scroll', onScroll);
37+
};
38+
}, []);
39+
40+
const go = (i: number) => document.getElementById(`aa-sec-${i}`)?.scrollIntoView({ behavior: 'smooth', block: 'start' });
41+
1642
return (
17-
<div className="aa-steps">
18-
{STEPS.map((s, i) => (
19-
<span key={s.n} style={{ display: 'contents' }}>
20-
<div className="aa-step">
21-
<span className="f-num">{s.n}</span>
22-
<span className="aa-step-label">
23-
<span className="guided-only">{t(s.g)}</span>
24-
<span className="expert-only">{t(s.e)}</span>
25-
</span>
26-
</div>
27-
{i < STEPS.length - 1 && <span className="aa-step-line" aria-hidden />}
28-
</span>
29-
))}
30-
</div>
43+
<nav className="aa-steps aa-glass" aria-label={t('steps.aria')}>
44+
{STEPS.map((s, i) => {
45+
const on = active === i + 1;
46+
return (
47+
<span key={s.n} style={{ display: 'contents' }}>
48+
<button type="button" className={'aa-step' + (on ? ' on' : '')} aria-current={on ? 'step' : undefined} onClick={() => go(i + 1)}>
49+
<span className="f-num">{s.n}</span>
50+
<span className="aa-step-label">
51+
<span className="guided-only">{t(s.g)}</span>
52+
<span className="expert-only">{t(s.e)}</span>
53+
</span>
54+
</button>
55+
{i < STEPS.length - 1 && <span className="aa-step-line" aria-hidden />}
56+
</span>
57+
);
58+
})}
59+
</nav>
3160
);
3261
}

src/components/chrome/Toolbar.tsx

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,8 @@ export function Toolbar({ run, status, setStatus, mode, onImport }: Props) {
5656

5757
return (
5858
<div>
59-
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '11px' }}>
60-
<span className="f-num">4</span>
61-
<span style={{ fontSize: '15px', fontWeight: 500 }}>
62-
<span className="guided-only">{t('export.title.g')}</span>
63-
<span className="expert-only">{t('step4.e')}</span>
64-
</span>
65-
</div>
66-
59+
{/* No internal heading — the StepSection card already shows "4 · Save & share"
60+
(owner feedback: the number was doubled). */}
6761
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '9px' }}>
6862
<button
6963
type="button"

0 commit comments

Comments
 (0)