Skip to content

Commit 726853c

Browse files
committed
Singapore schedule: side-by-side speaker card via flex
Replaces the float + shape-outside approach with a simpler flex layout: at xl+, the last two paragraphs and the speaker card sit in a flex row aligned to the bottom of the description, with the card pinned to the section's right and bottom edges via negative margins. No per-session padding tuning needed — the card lines up flush regardless of description length.
1 parent 1a8065a commit 726853c

2 files changed

Lines changed: 39 additions & 26 deletions

File tree

src/app/day/2026/singapore/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export default function SingaporePage() {
6464
<ScheduleSection />
6565
<MarqueeRows
6666
variant="secondary"
67-
className="my-8 xl:mb-16 xl:mt-10"
67+
className="my-8 xl:my-16"
6868
items={MARQUEE_ITEMS}
6969
/>
7070
</main>

src/app/day/2026/singapore/schedule-section.tsx

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -68,27 +68,31 @@ function SessionBlock({
6868
session: SingaporeSession
6969
isFirst: boolean
7070
}) {
71-
// On xl+ with a single speaker we float the card into the bottom-right of
72-
// the description so prose flows around it. Multi-speaker sessions keep
73-
// the regular "speakers below" layout at every breakpoint.
74-
const floatSpeaker =
71+
// On xl+ with a single speaker we slot the card next to the last two
72+
// paragraphs of the description so it sits in the bottom-right corner.
73+
// Multi-speaker sessions keep the regular "speakers below" layout.
74+
const sideSpeaker =
7575
session.speakers.length === 1 ? session.speakers[0] : null
7676

7777
return (
7878
<article>
79-
<Hr className={isFirst ? "mt-8 lg:mt-12" : "mt-12 lg:mt-16"} />
80-
<SessionHeader session={session} className="px-2 pt-8 sm:px-3 lg:pt-12" />
79+
<Hr
80+
className={
81+
isFirst ? "mt-8 lg:mt-12 xl:mt-0" : "mt-12 lg:mt-16 xl:mt-0"
82+
}
83+
/>
84+
<SessionHeader session={session} className="px-2 py-8 sm:px-3 lg:py-12" />
8185
{session.description && (
8286
<>
83-
<Hr className="mt-10 2xl:mt-16" />
87+
<Hr className="mt-10 2xl:mt-16 xl:mt-0" />
8488
<SessionDescription
8589
description={session.description}
86-
floatSpeaker={floatSpeaker}
90+
sideSpeaker={sideSpeaker}
8791
/>
8892
</>
8993
)}
9094
{session.speakers.length > 0 && (
91-
<div className={floatSpeaker ? "xl:hidden" : undefined}>
95+
<div className={sideSpeaker ? "xl:hidden" : undefined}>
9296
<Hr />
9397
<SessionSpeakers
9498
speakers={session.speakers}
@@ -102,38 +106,47 @@ function SessionBlock({
102106

103107
function SessionDescription({
104108
description,
105-
floatSpeaker,
109+
sideSpeaker,
106110
}: {
107111
description: string
108-
floatSpeaker: SingaporeSpeaker | null
112+
sideSpeaker: SingaporeSpeaker | null
109113
}) {
110114
const paragraphs = parseParagraphs(description)
115+
const splitAt =
116+
sideSpeaker && paragraphs.length >= 2
117+
? paragraphs.length - 2
118+
: paragraphs.length
119+
const lead = paragraphs.slice(0, splitAt)
120+
const tail = paragraphs.slice(splitAt)
111121

112122
return (
113123
<div className="typography-body-lg mt-8 px-2 pb-8 sm:px-3 lg:mt-12 xl:pb-12 [&>p+p]:mt-4 [&_a]:break-words">
114-
{floatSpeaker && (
115-
<div
116-
className="hidden xl:float-right xl:ml-6 xl:block xl:w-[340px] xl:pt-[170px]"
117-
style={{
118-
shapeOutside: "inset(170px 0 0 0)",
119-
shapeMargin: "16px",
120-
}}
121-
>
122-
<SpeakerCard speaker={floatSpeaker} index={0} />
124+
{lead.map((html, i) => (
125+
<p key={`lead-${i}`} dangerouslySetInnerHTML={{ __html: html }} />
126+
))}
127+
{tail.length > 0 && (
128+
<div className="mt-4 xl:flex xl:items-end xl:gap-6">
129+
<div className="[&>p+p]:mt-4 xl:flex-1">
130+
{tail.map((html, i) => (
131+
<p key={`tail-${i}`} dangerouslySetInnerHTML={{ __html: html }} />
132+
))}
133+
</div>
134+
{sideSpeaker && (
135+
<div className="hidden xl:-mb-12 xl:-mr-3 xl:block xl:w-[580px] xl:shrink-0 xl:[&>article]:border-b-0 xl:[&>article]:border-r-0 xl:[&>article]:border-t">
136+
<SpeakerCard speaker={sideSpeaker} index={0} />
137+
</div>
138+
)}
123139
</div>
124140
)}
125-
{paragraphs.map((html, i) => (
126-
<p key={i} dangerouslySetInnerHTML={{ __html: html }} />
127-
))}
128141
</div>
129142
)
130143
}
131144

132145
/**
133146
* Split FOST description HTML (a sequence of `<p>...</p>` blocks) into the
134147
* inner HTML of each paragraph so we can render them as real React `<p>`
135-
* siblings — needed because `shape-outside` only takes effect when the float
136-
* shares a block formatting context with the surrounding text.
148+
* siblings — needed so we can splice the speaker card in alongside the last
149+
* couple of paragraphs at xl+.
137150
*/
138151
function parseParagraphs(html: string): string[] {
139152
const formatted = formatDescription(html)

0 commit comments

Comments
 (0)