@@ -13,6 +13,7 @@ import { getWireItems } from "@/lib/news";
1313import { refreshOutcomesOnRead } from "@/lib/outcomes" ;
1414import { WatchNow } from "@/components/watch/WatchNow" ;
1515import { currentEventId } from "@/lib/events-core" ;
16+ import { majorChampion , majorWrappedSectionId , type MajorChampion } from "@/lib/stage-wrapped-launch" ;
1617
1718export const dynamic = "force-dynamic" ;
1819
@@ -30,7 +31,7 @@ export default async function DashboardPage() {
3031 // and no added render latency. Mirrors the news wire's refreshWireOnRead.
3132 await refreshOutcomesOnRead ( EVENT_ID ) ;
3233
33- const [ resolvedRows , outcomeRows , allPicks , allPlayers , wireItemsAll ] = await Promise . all ( [
34+ const [ resolvedRows , outcomeRows , allPicks , allPlayers , wireItemsAll , champion ] = await Promise . all ( [
3435 prisma . stageOutcome . findMany ( {
3536 where : { eventId : EVENT_ID } ,
3637 select : { sectionId : true , groupId : true , slotIndex : true } ,
@@ -44,6 +45,9 @@ export default async function DashboardPage() {
4445 select : { id : true , displayName : true , avatarUrl : true } ,
4546 } ) ,
4647 getWireItems ( 3 ) ,
48+ // The crowned champion, or null until the Grand Final resolves. Non-null
49+ // flips the whole dashboard into its "Major complete" send-off (PHA-1274).
50+ majorChampion ( EVENT_ID ) ,
4751 ] ) ;
4852
4953 const wireItems = wireItemsAll ;
@@ -144,7 +148,12 @@ export default async function DashboardPage() {
144148 }
145149
146150 const eventStarted = resolvedRows . length > 0 ;
147- const eventLabel = eventStarted ? "Live now" : "Pre-event" ;
151+ // Once the Grand Final crowns a champion the Major is DONE — the eyebrow stops
152+ // saying "Live now" (which it would otherwise say forever, since outcomes stay
153+ // resolved) and the hero becomes a send-off (PHA-1274: "the homepage needs
154+ // updated").
155+ const eventLabel = champion ? "Major complete" : eventStarted ? "Live now" : "Pre-event" ;
156+ const wrappedHref = `/reveal/${ majorWrappedSectionId ( ) } ?wrapped=1` ;
148157
149158 return (
150159 < >
@@ -155,7 +164,10 @@ export default async function DashboardPage() {
155164 </ span >
156165 </ div >
157166
158- { /* Stage briefing */ }
167+ { /* Stage briefing — or, once a champion is crowned, the Major send-off. */ }
168+ { champion ? (
169+ < ConcludedHero champion = { champion } wrappedHref = { wrappedHref } />
170+ ) : (
159171 < section className = "brk" style = { {
160172 position : "relative" ,
161173 background : "var(--surf-1)" ,
@@ -248,6 +260,7 @@ export default async function DashboardPage() {
248260 </ div >
249261 </ div >
250262 </ section >
263+ ) }
251264
252265 { /* Stats + Leaderboard cols */ }
253266 < div className = "dash-cols" >
@@ -284,12 +297,16 @@ export default async function DashboardPage() {
284297 < div className = "lbl" > POINTS</ div >
285298 < div className = "val foil" > { selfRow ?. score ?? 0 } </ div >
286299 < div className = "sub" >
287- of { maxPoints } · { activeLabel } { " " }
288- { active . pick . pickable
289- ? "open"
290- : active . pick . reason === "locked-time-passed"
291- ? "live"
292- : "pending" }
300+ of { maxPoints } ·{ " " }
301+ { champion
302+ ? "final"
303+ : `${ activeLabel } ${
304+ active . pick . pickable
305+ ? "open"
306+ : active . pick . reason === "locked-time-passed"
307+ ? "live"
308+ : "pending"
309+ } `}
293310 </ div >
294311 </ div >
295312 </ div >
@@ -375,6 +392,89 @@ export default async function DashboardPage() {
375392 ) ;
376393}
377394
395+ /**
396+ * The dashboard hero AFTER the Major is decided (PHA-1274) — replaces the stage
397+ * briefing the moment the Grand Final crowns a champion. Same broadcast shell as
398+ * the briefing (keyline corners + faint HeatMark), but it names the champion and
399+ * points everyone at the Wrapped recap and the final board instead of a now-dead
400+ * "picks are locked, watch the live bracket" call.
401+ */
402+ function ConcludedHero ( {
403+ champion,
404+ wrappedHref,
405+ } : {
406+ champion : MajorChampion ;
407+ wrappedHref : string ;
408+ } ) {
409+ return (
410+ < section className = "brk" style = { {
411+ position : "relative" ,
412+ background : "var(--surf-1)" ,
413+ border : "1px solid var(--hair-2)" ,
414+ padding : "26px 28px 28px" ,
415+ overflow : "hidden" ,
416+ } } >
417+ < span className = "br-tr" />
418+ < span className = "br-bl" />
419+ < div style = { {
420+ position : "absolute" ,
421+ right : - 20 ,
422+ top : "50%" ,
423+ transform : "translateY(-50%)" ,
424+ width : 240 ,
425+ height : 240 ,
426+ opacity : 0.05 ,
427+ pointerEvents : "none" ,
428+ } } >
429+ < HeatMark size = { 240 } />
430+ </ div >
431+ < div style = { { position : "relative" , zIndex : 1 } } >
432+ < div style = { { display : "flex" , alignItems : "center" , gap : 12 , marginBottom : 8 } } >
433+ < span className = "eyebrow-mono" > CHAMPIONS</ span >
434+ < span className = "live-tag" style = { { color : "var(--ink-mid)" , borderColor : "var(--hair-2)" , background : "var(--surf-2)" } } >
435+ That's a wrap
436+ </ span >
437+ </ div >
438+ < div style = { { display : "flex" , alignItems : "center" , gap : 16 , marginBottom : 14 } } >
439+ { champion . logoSrc && (
440+ // eslint-disable-next-line @next/next/no-img-element
441+ < img
442+ src = { champion . logoSrc }
443+ alt = ""
444+ width = { 64 }
445+ height = { 64 }
446+ style = { { objectFit : "contain" , filter : "drop-shadow(0 4px 10px rgba(0,0,0,0.5))" , flexShrink : 0 } }
447+ />
448+ ) }
449+ < h1 className = "font-display" style = { {
450+ fontWeight : 800 ,
451+ fontSize : "clamp(34px, 5vw, 48px)" ,
452+ textTransform : "uppercase" ,
453+ lineHeight : 0.92 ,
454+ margin : 0 ,
455+ } } >
456+ { champion . name }
457+ </ h1 >
458+ </ div >
459+ < p style = { { fontSize : 14 , color : "var(--ink-mid)" , margin : 0 , maxWidth : 460 , textWrap : "pretty" } } >
460+ { champion . name } are your IEM Cologne Major 2026 champions. Thirty-two
461+ walked in; one walks out with the trophy. Thanks for calling it with us
462+ all Major long — the recap below has the whole run.
463+ </ p >
464+ < div style = { { display : "flex" , gap : 12 , flexWrap : "wrap" , alignItems : "center" , marginTop : 18 } } >
465+ < Link href = { wrappedHref } className = "btn-heat" prefetch = { false } >
466+ Watch the Wrapped recap
467+ < svg viewBox = "0 0 24 24" strokeLinecap = "round" strokeLinejoin = "round" >
468+ < polyline points = "9 18 15 12 9 6" />
469+ </ svg >
470+ </ Link >
471+ < Link href = "/leaderboard" className = "btn-ghost" prefetch = { false } > Final Ranks</ Link >
472+ </ div >
473+ </ div >
474+ </ section >
475+ ) ;
476+ }
477+
378478function StageStatusTag ( {
379479 pickability,
380480} : {
0 commit comments