From 107f5de07c01ecb78f09e124f59fa397c4fb57a5 Mon Sep 17 00:00:00 2001
From: Tim Thomas <0800tim@gmail.com>
Date: Fri, 5 Jun 2026 12:04:53 +1200
Subject: [PATCH 1/2] fix(bracket+bet): heavier gold ring on selected Top 8
3rds + dark text on bet CTA
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Two visual bugs Tim flagged on 2026-06-05:
* The selected/unselected contrast on the Top 8 3rd-placed teams tiles
was too subtle. The 3px inset gold ring + rgba(.., 0.6) border read
as a faint outline next to the unpicked tiles, not as an unambiguous
"this one is picked" affordance. Bumped to a 5px solid-gold inset
with a 1px soft inner highlight + larger outer glow (24px / 0.55
alpha vs 14px / 0.35). The picked tile now visibly lifts.
* /the-bet bottom CTA ("Pick your bracket to enter →") was rendering
gold text on a gold pill. The pill's class .vt-bet-cta-primary set
color: #15151a but .vt-bet-body a (same file, body-paragraph link
styling) is (0,1,1) specificity vs (0,1,0) and was winning the
cascade. Added !important to the CTA's color rule, matching the
pattern .vt-bet-cta-ghost already uses for the same reason.
Refs: sessions/2026-06-05_orchestrator_bracket-save-on-exit.md
Signed-off-by: Tim Thomas <0800tim@gmail.com>
---
apps/web/app/the-bet/the-bet.css | 6 +++++-
apps/web/app/world-cup-2026/bracket.css | 12 ++++++++----
2 files changed, 13 insertions(+), 5 deletions(-)
diff --git a/apps/web/app/the-bet/the-bet.css b/apps/web/app/the-bet/the-bet.css
index 9ff5560d..ba5c37f3 100644
--- a/apps/web/app/the-bet/the-bet.css
+++ b/apps/web/app/the-bet/the-bet.css
@@ -271,9 +271,13 @@
text-decoration: none !important;
transition: transform 100ms ease;
}
+/* `!important` because `.vt-bet-body a` (the body-link colour rule
+ * higher up in this file) is more specific than this class on its
+ * own, which made the CTA render as gold text on a gold pill.
+ * Tim 2026-06-05. */
.vt-bet-cta-primary {
background: #dca94b;
- color: #15151a;
+ color: #15151a !important;
}
.vt-bet-cta-primary:hover {
transform: translateY(-1px);
diff --git a/apps/web/app/world-cup-2026/bracket.css b/apps/web/app/world-cup-2026/bracket.css
index 23da89bf..822dee94 100644
--- a/apps/web/app/world-cup-2026/bracket.css
+++ b/apps/web/app/world-cup-2026/bracket.css
@@ -3228,12 +3228,16 @@ a.bracket-save-panel-cta-primary {
}
/* Selected: matches the .km-team.is-winner treatment - gold inner
- * ring + halo + a brighter scrim so the picked tile pops. */
+ * ring + halo + a brighter scrim so the picked tile pops. Tim
+ * 2026-06-05: bumped to a 5px solid-gold inset with a soft inner
+ * highlight + larger outer glow; the previous 3px ring wasn't
+ * reading strongly enough against the unselected tiles. */
.bracket-thirds-tile.is-selected {
- border-color: rgba(220, 169, 75, 0.6);
+ border-color: #dca94b;
box-shadow:
- inset 0 0 0 3px var(--vt-gold-400, #dca94b),
- 0 0 14px rgba(220, 169, 75, 0.35);
+ inset 0 0 0 5px var(--vt-gold-400, #dca94b),
+ inset 0 0 0 6px rgba(255, 220, 130, 0.35),
+ 0 0 24px rgba(220, 169, 75, 0.55);
}
.bracket-thirds-tile.is-selected::after {
background: linear-gradient(
From 962e983c154125a869b8f80a415d754f774199ff Mon Sep 17 00:00:00 2001
From: Tim Thomas <0800tim@gmail.com>
Date: Fri, 5 Jun 2026 12:11:20 +1200
Subject: [PATCH 2/2] fix(bracket): hoist cascade banner to top of round, keep
details at bottom
Tim 2026-06-05 (after the cascade-warnings English-ification ship):
> You see the friendly note on desktop now, but it's not visible on
> mobile. [...] move the first one to the top and leave the second
> one at the bottom.
The previous patch landed both the contextual banner and the details
collapsible at the bottom of the whole tabpanel grid, so on mobile the
banner sat below the matches and the Next-stage CTA, and users hit
the empty R32 slots without seeing the explanation.
Split the surface so the banner (the actionable bit) hoists to the top
of each round and the final-panel section, while the details list
(the curious-user reference) stays at the bottom.
* CascadeWarnings: new `mode` prop, default `"full"` for back-compat.
`"banner"` renders just the upstream-fix CTA; `"details"`
renders just the collapsible. In "banner" mode the component
returns null when no upstream stage is incomplete, so groups +
thirds tabs don't paint an empty wrapper.
* BracketBuilder: render `mode="banner"` inside the KO round section
(just below the round help text) and inside the final-panel section
(just above the final-match card). The existing bottom call is
changed to `mode="details"`.
Same component, two render slots. CSS untouched.
Refs: sessions/2026-06-05_orchestrator_bracket-save-on-exit.md
Signed-off-by: Tim Thomas <0800tim@gmail.com>
---
.../web/components/bracket/BracketBuilder.tsx | 28 ++++++++++--
.../components/bracket/CascadeWarnings.tsx | 44 +++++++++++++------
2 files changed, 55 insertions(+), 17 deletions(-)
diff --git a/apps/web/components/bracket/BracketBuilder.tsx b/apps/web/components/bracket/BracketBuilder.tsx
index 33566cd3..23a54868 100644
--- a/apps/web/components/bracket/BracketBuilder.tsx
+++ b/apps/web/components/bracket/BracketBuilder.tsx
@@ -1757,6 +1757,15 @@ export function BracketBuilder(props: BracketBuilderProps) {
{finalProgress.picked} of {finalProgress.total} picked
+ {/* Top banner: same hoist treatment as the KO rounds
+ * (Tim 2026-06-05) so the user sees the upstream-fix
+ * CTA without scrolling past the final-match card. */}
+ setTab(target as TabId)}
+ mode="banner"
+ />
+ {/* Tim 2026-06-05: hoist the upstream-cascade banner to
+ * the top of the round so mobile users see it before
+ * scrolling through empty slots; the details list still
+ * renders at the bottom of the whole tabpanel grid. */}
+ setTab(target as TabId)}
+ mode="banner"
+ />
- {/* Tim 2026-06-05: cascade warnings rendered through a
- * dedicated component that translates engine codes to plain
- * English and surfaces a contextual "go back to "
- * banner when the user is downstream of an incomplete stage. */}
+ {/* Tim 2026-06-05: details-only at the bottom. The contextual
+ * "Go to " banner is hoisted to the top of each
+ * round panel above so it's visible without scrolling; the
+ * collapsible details list stays here as a reference. */}
setTab(target as TabId)}
+ mode="details"
/>
diff --git a/apps/web/components/bracket/CascadeWarnings.tsx b/apps/web/components/bracket/CascadeWarnings.tsx
index 8521e0fd..08874a57 100644
--- a/apps/web/components/bracket/CascadeWarnings.tsx
+++ b/apps/web/components/bracket/CascadeWarnings.tsx
@@ -50,6 +50,17 @@ export interface CascadeWarningsProps {
* decides what "go back" means (typically `setTab(targetTab)`).
*/
readonly onJumpToTab: (target: BracketTabId) => void;
+ /**
+ * Which surfaces to render. Tim 2026-06-05 split the banner from
+ * the details list so the banner can hoist to the top of a round
+ * (it's the actionable bit) while the long-form details stay at
+ * the bottom (the curious-user reference).
+ *
+ * - `"full"` (default): renders both.
+ * - `"banner"`: just the contextual "go back to " CTA.
+ * - `"details"`: just the collapsible list.
+ */
+ readonly mode?: "full" | "banner" | "details";
}
/**
@@ -162,6 +173,7 @@ export function CascadeWarnings({
warnings,
currentTab,
onJumpToTab,
+ mode = "full",
}: CascadeWarningsProps): JSX.Element | null {
if (warnings.length === 0) return null;
@@ -177,9 +189,13 @@ export function CascadeWarnings({
const friendlyList = Array.from(uniqueByCode.entries());
const banner = pickBannerWarning(warnings, currentTab);
+ // In banner-only mode, render nothing when there's no upstream
+ // banner to show; the parent doesn't need an empty wrapper.
+ if (mode === "banner" && banner === null) return null;
+
return (