diff --git a/app/src/app/(home)/loading.tsx b/app/src/app/(home)/loading.tsx
index 008c98d6..47252222 100644
--- a/app/src/app/(home)/loading.tsx
+++ b/app/src/app/(home)/loading.tsx
@@ -46,6 +46,7 @@ export default function Loading() {
{["w-[106px] sm:w-[122px]", "w-[96px] sm:w-[170px]", "w-[133px] sm:w-[152px]"].map((w) => )}
+
@@ -56,6 +57,8 @@ export default function Loading() {
+
+
@@ -70,8 +73,8 @@ export default function Loading() {
+
-
{/* Trending (TrendingStrip.tsx): a row to swipe below 1024px, a leader over four runners from there. The row's own stylesheet
class is used, so its padding, gap and card width are the real ones. */}
diff --git a/app/src/components/launchpad/LaunchHero.module.css b/app/src/components/launchpad/LaunchHero.module.css
index b8004b9d..671d07f9 100644
--- a/app/src/components/launchpad/LaunchHero.module.css
+++ b/app/src/components/launchpad/LaunchHero.module.css
@@ -1,16 +1,26 @@
/* The hero's grid, in three arrangements. Phones: copy, the locker, then the totals. From 768px the text sits left of the
locker and the totals run under both. From 1024px the totals move up into the left column under the text, and the locker
takes the whole height beside them: copy hugs the bottom of its row and the totals the top of theirs, so the pair stays
- together however tall the locker is. */
-.layout { display: grid; align-items: center; column-gap: 2.5rem; row-gap: 1.25rem; grid-template-columns: minmax(0, 1fr); grid-template-areas: "copy" "stage" "totals"; }
+ together however tall the locker is. The live line is the grid's last row up to 1023px (full width). From 1024px it moves
+ into the locker's column, right under the frame as its caption, and the pair centers together (two renderings of the
+ line, one shown at each size: the hidden one is display:none, so nothing reads or tabs through it twice). */
+.layout { display: grid; align-items: center; column-gap: 2.5rem; row-gap: 1.25rem; grid-template-columns: minmax(0, 1fr); grid-template-areas: "copy" "stage" "totals" "ticker"; }
.copyArea { grid-area: copy; min-width: 0; }
.totalsArea { grid-area: totals; min-width: 0; }
-@media (min-width: 640px) { .layout { row-gap: 2rem; } }
-@media (min-width: 768px) { .layout { grid-template-columns: minmax(0, 1.06fr) minmax(0, 1fr); grid-template-areas: "copy stage" "totals totals"; } }
+/* the line used to sit outside the grid, 16px (phones) or 20px under it: the negative margin keeps that spacing against the row gap */
+.tickerArea { grid-area: ticker; min-width: 0; margin-top: -0.25rem; }
+/* the locker's column: below 1024px it only wraps the stage (display: contents leaves the stage a grid item, as before) */
+.stageCol { display: contents; }
+.layout .tickerStage { display: none; }
+@media (min-width: 640px) { .layout { row-gap: 2rem; } .tickerArea { margin-top: -0.75rem; } }
+@media (min-width: 768px) { .layout { grid-template-columns: minmax(0, 1.06fr) minmax(0, 1fr); grid-template-areas: "copy stage" "totals totals" "ticker ticker"; } }
@media (min-width: 1024px) {
.layout { grid-template-areas: "copy stage" "totals stage"; row-gap: 1.25rem; }
.copyArea { align-self: end; }
.totalsArea { align-self: start; }
+ .layout .tickerArea { display: none; }
+ .stageCol { display: flex; flex-direction: column; gap: 0.875rem; grid-area: stage; align-self: center; min-width: 0; }
+ .layout .tickerStage { display: flex; }
}
@media (min-width: 1280px) { .layout { column-gap: 3.5rem; } }
diff --git a/app/src/components/launchpad/LaunchHero.tsx b/app/src/components/launchpad/LaunchHero.tsx
index f19e9fc9..217cab32 100644
--- a/app/src/components/launchpad/LaunchHero.tsx
+++ b/app/src/components/launchpad/LaunchHero.tsx
@@ -74,16 +74,21 @@ export default function LaunchHero({ configured }: { configured: boolean }) {
) : null}
{/* the stage: a faint plotting grid and four corner marks frame the mechanism like a drawing on a bench */}
-
-
- {["tl", "tr", "bl", "br"].map((corner) =>
)}
-
+
+
+
+ {["tl", "tr", "bl", "br"].map((corner) =>
)}
+
+
+ {/* from 1024px the live line is the locker's caption, right under its frame */}
+
+ {/* below 1024px the line is the grid's last row, full width */}
+
-
);
}
diff --git a/app/src/components/launchpad/hero-layout.test.ts b/app/src/components/launchpad/hero-layout.test.ts
index 863bdbea..f8d1e410 100644
--- a/app/src/components/launchpad/hero-layout.test.ts
+++ b/app/src/components/launchpad/hero-layout.test.ts
@@ -27,8 +27,8 @@ test("the headline is stepped so it stays two lines, and the hero grid has three
assert.ok(HERO.section.split(" ").includes("lg:pt-4"), "tight rhythm from 1024px");
// phones: copy, the locker, then the totals. 768px: text beside the locker, totals under both. 1024px: the totals
// move up under the text and the locker spans both rows, with copy hugging its row's bottom and the totals its top
- assert.match(heroCss, /\.layout \{[^}]*align-items: center;[^}]*grid-template-columns: minmax\(0, 1fr\); grid-template-areas: "copy" "stage" "totals"; \}/);
- assert.match(heroCss, /@media \(min-width: 768px\) \{ \.layout \{ grid-template-columns: minmax\(0, 1\.06fr\) minmax\(0, 1fr\); grid-template-areas: "copy stage" "totals totals"; \} \}/);
+ assert.match(heroCss, /\.layout \{[^}]*align-items: center;[^}]*grid-template-columns: minmax\(0, 1fr\); grid-template-areas: "copy" "stage" "totals" "ticker"; \}/);
+ assert.match(heroCss, /@media \(min-width: 768px\) \{ \.layout \{ grid-template-columns: minmax\(0, 1\.06fr\) minmax\(0, 1fr\); grid-template-areas: "copy stage" "totals totals" "ticker ticker"; \} \}/);
assert.match(heroCss, /@media \(min-width: 1024px\) \{\n\s*\.layout \{ grid-template-areas: "copy stage" "totals stage";[^}]*\}\n\s*\.copyArea \{ align-self: end; \}\n\s*\.totalsArea \{ align-self: start; \}/);
assert.match(heroCss, /\.stage \{ grid-area: stage;/);
});
diff --git a/app/src/components/launchpad/hero-layout.ts b/app/src/components/launchpad/hero-layout.ts
index eed7f957..38636545 100644
--- a/app/src/components/launchpad/hero-layout.ts
+++ b/app/src/components/launchpad/hero-layout.ts
@@ -19,6 +19,4 @@ export const HERO = {
the page. From 640px the row never wraps, as before: the two-column hero at tablet widths is laid out around one row. */
actions: "mt-6 flex flex-wrap items-center gap-2.5 sm:mt-8 sm:flex-nowrap sm:gap-4 lg:mt-6",
proofs: "mt-5 flex flex-wrap gap-2 lg:mt-4",
- /** the live ticker that closes the hero */
- ticker: "mt-4 sm:mt-5",
} as const;
diff --git a/app/src/components/launchpad/live-ticker.test.ts b/app/src/components/launchpad/live-ticker.test.ts
index f56a144b..1b24cab2 100644
--- a/app/src/components/launchpad/live-ticker.test.ts
+++ b/app/src/components/launchpad/live-ticker.test.ts
@@ -34,6 +34,19 @@ test("the poll is the clock: no timer, no request, and a new event replays its e
assert.doesNotMatch(css, /infinite/, "no ambient animation");
});
-test("the hero ends on the ticker", () => {
- assert.match(hero, /\n\s*<\/section>/);
+test("the live line is the grid's last row up to 1023px and the locker's caption from 1024px", () => {
+ // two renderings of the same one-link component, one shown per size: the hidden one is display:none, so nothing reads or tabs twice
+ assert.match(hero, /\n\s*<\/div>\n\s*/, "inside the locker's column, right after the stage frame");
+ assert.match(hero, /\n\s*<\/div>\n\s*<\/section>/, "and the last child of the grid, which is the last thing in the hero");
+ const grid = read("./LaunchHero.module.css");
+ assert.match(grid, /grid-template-areas: "copy" "stage" "totals" "ticker";/);
+ assert.match(grid, /grid-template-areas: "copy stage" "totals totals" "ticker ticker";/, "tablets: under both columns, as it always was");
+ assert.match(grid, /@media \(min-width: 1024px\) \{\n\s*\.layout \{ grid-template-areas: "copy stage" "totals stage";/, "laptops: no row of its own, so the hero is a row shorter");
+ // below 1024px the line sits 16px (phones) or 20px under the grid, as it did when it was outside it: negative margins against the 20px and 32px row gaps
+ assert.match(grid, /\.tickerArea \{ grid-area: ticker; min-width: 0; margin-top: -0\.25rem; \}/);
+ assert.match(grid, /\.tickerArea \{ margin-top: -0\.75rem; \}/);
+ assert.match(grid, /\.stageCol \{ display: contents; \}/, "below 1024px the column only wraps the stage");
+ assert.match(grid, /\.layout \.tickerStage \{ display: none; \}/);
+ assert.match(grid, /\.layout \.tickerArea \{ display: none; \}\n\s*\.stageCol \{ display: flex; flex-direction: column; gap: 0\.875rem; grid-area: stage; align-self: center; min-width: 0; \}\n\s*\.layout \.tickerStage \{ display: flex; \}/);
+ assert.doesNotMatch(read("./hero-layout.ts"), /ticker:/, "no spacing constant outside the grid any more");
});