diff --git a/docs/changelog.md b/docs/changelog.md index ed4273b..2f4d511 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,5 +1,21 @@ # Changelog +## 2026-10-31 — Mobile DevCon26 hero + +- Keep the theme on two centred lines with a compact centred tagline, tighter introduction spacing and a centred content-sized ticket button on mobile. Centre the date and balance the wrapped venue text beneath it. Preserve the exact approved wording and desktop layout. + +## 2026-10-31 — Homepage growth accent + +- Highlight “grow.” in pink with a curved underline that reveals once on entering view. Preserve headline wording and use a static underline for reduced motion or unavailable JavaScript. + +## 2026-10-31 — DevCon26 theme copy + +- Replace the hero headline, tagline and introduction with the supplied Technology for the Next Generation wording. + +## 2026-10-31 — Center checkout access recovery + +- Center missing-browser-access guidance on the existing cream background with concise copy, a fixed 64 × 48 px ticket-and-envelope illustration, a plain duplicate-payment reminder, selectable payment reference and a contact email action carrying that reference. Keep retry and ticket details hidden, and clear recovery content when the confirmation state changes. + ## 2026-10-30 — Polish private ticket claim controls - Match the claim page's primary action to the DevCon26 checkout pink while keeping the styling scoped to ticket-access pages. @@ -369,3 +385,8 @@ - Use an explicit two-column mobile quantity row so the label stays centered and the stepper sits at the right edge. Keep 44px button targets and allow the label to wrap on narrow screens. - Added a softly masked community photo behind the expanded mobile order summary at 10% opacity, preserving text contrast and the compact collapsed state. + +## 2026-10-30 — Checkout recovery + +- Explain the 15-minute checkout hold and late-payment outcome. +- When a payment confirmation link has no browser grant, direct the buyer to their private ticket email instead of offering a Retry that cannot make a request; warn against paying again while payment may still complete. diff --git a/docs/devcon26-flexible-checkout.md b/docs/devcon26-flexible-checkout.md index 5399877..c533614 100644 --- a/docs/devcon26-flexible-checkout.md +++ b/docs/devcon26-flexible-checkout.md @@ -129,3 +129,9 @@ Below 821px, progress uses the current step name, step count and three segments. ### Post-payment photo adjustment The payment badge editor opens the photo adjuster after a new image is selected and exposes Adjust photo in both editing and preview steps. Zoom and horizontal/vertical position use the existing seat card crop API (`scale`, `x`, `y`). Saving waits for the selected seat's upload, persists its crop, and preserves retry controls on failure. Previously saved crops are restored when reopening the editor. Desktop uses side-by-side controls; mobile stacks the preview and 44px controls within a scrollable dialog. + +## Failure recovery + +An order that cannot accept payment retains its request identity; the buyer is directed to check email or contact the organizer before starting again. The payment confirmation page hides Retry if its browser grant is absent and directs the buyer to their private ticket email. This does not infer that payment failed or authorize a new charge. The admission backend separately reconciles stored payment references. + +The missing-access state uses a centered, narrow layout on the existing cream background, with a fixed-size ticket-and-envelope illustration beside the heading on desktop and mobile. It displays the buyer-formatted reference as a support identifier and includes it in the contact-email subject; it is not proof of payment. Other confirmation states hide and clear these recovery details. diff --git a/docs/screenshots/checkout-recovery/checkout-hold.png b/docs/screenshots/checkout-recovery/checkout-hold.png new file mode 100644 index 0000000..49ccb78 Binary files /dev/null and b/docs/screenshots/checkout-recovery/checkout-hold.png differ diff --git a/docs/screenshots/checkout-recovery/recovery-desktop.png b/docs/screenshots/checkout-recovery/recovery-desktop.png new file mode 100644 index 0000000..0bc83db Binary files /dev/null and b/docs/screenshots/checkout-recovery/recovery-desktop.png differ diff --git a/docs/screenshots/checkout-recovery/recovery-mobile.png b/docs/screenshots/checkout-recovery/recovery-mobile.png new file mode 100644 index 0000000..1ed427e Binary files /dev/null and b/docs/screenshots/checkout-recovery/recovery-mobile.png differ diff --git a/scripts/check-devcon26-managed-checkout.mjs b/scripts/check-devcon26-managed-checkout.mjs index 248bd90..0c6ee98 100644 --- a/scripts/check-devcon26-managed-checkout.mjs +++ b/scripts/check-devcon26-managed-checkout.mjs @@ -428,10 +428,13 @@ for (const options of [{ missingGrant: true }, { responseMode: 'live' }]) { const fixture = confirmationFixture(initialized('test').reference, 'test', options); await tick(); - assert.equal(fixture.root.dataset.paymentState, 'error'); + assert.equal(fixture.root.dataset.paymentState, options.missingGrant ? 'access_lost' : 'error'); assert.equal(fixture.root.querySelector('[data-payment-receipt]').hidden, true); assert.equal(fixture.badgeCalls.length, 0); - if (options.missingGrant) assert.equal(fixture.requests.length, 0); + if (options.missingGrant) { + assert.equal(fixture.requests.length, 0); + assert.equal(fixture.root.querySelector('[data-payment-retry]').hidden, true); + } } for (const [reference, verifyPath] of [ @@ -506,3 +509,17 @@ assert.ok(!config.includes('WORKERS_CI_BRANCH') && !config.includes('full-test-e assert.ok(!page.includes('PUBLIC_DEVCON26_COMPLETE_TEST') && !page.includes('data-complete-test')); assert.ok(page.includes('data-payment-mode-notice') && page.includes('data-checkout-challenge')); console.log('PASS managed checkout: runtime test/live, stale-settings review without resubmission, bounded retries, fail-closed catalog/redirects, historical callbacks, and actual private email links.'); + +for (const mode of ['test', 'live']) { + const fixture = confirmationFixture(initialized(mode).reference, mode, { missingGrant: true }); + + await tick(); + assert.equal(fixture.requests.length, 0); + assert.equal(fixture.root.querySelector('[data-payment-retry]').hidden, true); + assert.equal(fixture.root.dataset.paymentState, 'access_lost'); + assert.equal(fixture.root.querySelector('[data-checkout-recovery]').hidden, false); + assert.equal(fixture.root.querySelector('[data-payment-receipt]').hidden, true); + assert.equal(fixture.root.querySelector('[data-recovery-reference]').textContent, fixture.exports.devcon26BuyerReference(initialized(mode).reference)); + assert.ok(fixture.root.querySelector('[data-recovery-contact]').attributes.get('href').startsWith('mailto:hello@devcongress.org?subject=')); +} +console.log('PASS lost-session recovery: no unauthenticated request or useless Retry in either mode.'); diff --git a/src/components/HeroSection.astro b/src/components/HeroSection.astro index 0695586..8616c77 100644 --- a/src/components/HeroSection.astro +++ b/src/components/HeroSection.astro @@ -20,6 +20,8 @@ interface Props { youtubeEmbedUrl: string; } const { hero, slackUrl, donationPath, youtubeEmbedUrl } = Astro.props; +const highlightGrowth = /\bgrow\.$/.test(hero.headline); +const headlinePrefix = highlightGrowth ? hero.headline.slice(0, -5) : hero.headline; const colorClass: Record = { pink: 'btn-primary', @@ -34,7 +36,7 @@ const colorClass: Record = {
{hero.eyebrow} -

{hero.headline}

+

{headlinePrefix}{highlightGrowth && grow.}

We connect all year. This December, we come together at DevCon26.

@@ -95,6 +97,24 @@ const colorClass: Record = {
+ +