v0.2.0:21 — Wider buy page (1040px) so 3-tier grids breathe

The public /buy/<slug> page was capped at 560px. With three tier
cards side-by-side that made everything narrow and tall in a desktop
browser. Bumped the outer container to 1040px so the tier picker
matches the admin Policies page layout.

- .wrap max-width: 560px → 1040px.
- .wrap > :not(.tiers) max-width:560px + margin-auto so the form,
  price card, and intro text stay centered at reading width below
  the wider tier picker.
- .topbar .inner widened 680px → 1040px to align with .wrap.
- .eyebrow display:inline-flex → flex + width:fit-content so the
  margin:auto centering rule applies.

Mobile breakpoints unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Grant
2026-05-11 14:09:15 -05:00
parent 094cf75e52
commit 6fd7dd9302
2 changed files with 16 additions and 4 deletions
+13 -3
View File
@@ -186,7 +186,7 @@ body {{
padding:14px 24px; padding:14px 24px;
}} }}
.topbar .inner {{ .topbar .inner {{
max-width:680px; margin:0 auto; max-width:1040px; margin:0 auto;
display:flex; align-items:center; gap:12px; display:flex; align-items:center; gap:12px;
font-family:var(--font-display); font-weight:500; font-size:14px; font-family:var(--font-display); font-weight:500; font-size:14px;
letter-spacing:0.28em; text-transform:uppercase; color:var(--navy-900); letter-spacing:0.28em; text-transform:uppercase; color:var(--navy-900);
@@ -197,11 +197,21 @@ body {{
color:var(--ink-500); color:var(--ink-500);
margin-left:auto; margin-left:auto;
}} }}
.wrap {{ max-width:560px; margin:48px auto; padding:0 24px; }} /* Outer container width — was 560px (single-column friendly), now
wider so the 3-tier grid below has room to breathe and matches the
admin Policies page layout. Inner text + form blocks are constrained
back to ~560px reading width by the `.wrap > :not(.tiers)` rule
below so only the tier grid breaks out. */
.wrap {{ max-width:1040px; margin:48px auto; padding:0 24px; }}
.wrap > :not(.tiers) {{ max-width:560px; margin-left:auto; margin-right:auto; }}
.eyebrow {{ .eyebrow {{
font-size:11.5px; font-weight:700; letter-spacing:0.18em; font-size:11.5px; font-weight:700; letter-spacing:0.18em;
text-transform:uppercase; color:var(--gold-700); margin-bottom:14px; text-transform:uppercase; color:var(--gold-700); margin-bottom:14px;
display:inline-flex; align-items:center; gap:10px; /* `flex; width:fit-content` instead of `inline-flex` so the
wrap-children margin:auto centering rule applies — otherwise
this inline element would sit flush left of the wider 1040px
container while its centered block-level siblings sit middle. */
display:flex; width:fit-content; align-items:center; gap:10px;
}} }}
.eyebrow::before {{ content:''; display:inline-block; width:28px; height:1px; background:var(--gold-500); }} .eyebrow::before {{ content:''; display:inline-block; width:28px; height:1px; background:var(--gold-500); }}
h1 {{ h1 {{
+3 -1
View File
@@ -58,6 +58,8 @@ const RELEASE_NOTES = [
// in RELEASE_NOTES above (the milestone). Subsequent revisions // in RELEASE_NOTES above (the milestone). Subsequent revisions
// append here. // append here.
const ROUTINE_NOTES = [ const ROUTINE_NOTES = [
'0.2.0:21 — **Wider buy page so 3-tier grids breathe.** The public /buy/<slug> page was capped at 560px, which packed three tier cards into a too-narrow column on desktop browsers. Bumped the outer container to 1040px so the tier picker matches the admin Policies page layout. The form, price card, and intro text below the tier picker remain centered at the 560px reading-width so the buy form doesn\'t look stretched. Mobile (≤480px) breakpoint unchanged. Topbar inner widened to match. UI-only; no API or schema change.',
'',
'0.2.0:20 — **Discount codes can apply to multiple policies, not just one.** Operator picks a subset (e.g. "Patron AND Pro but not Creator") on a single code instead of cloning the code under different names.', '0.2.0:20 — **Discount codes can apply to multiple policies, not just one.** Operator picks a subset (e.g. "Patron AND Pro but not Creator") on a single code instead of cloning the code under different names.',
'', '',
'**What changed.** Previously, a discount code\'s tier scope was a single policy (`applies_to_policy_id`) or "any policy on this product" / global. To offer the same discount across two of three tiers required creating two codes with distinct strings — operationally messy and harder for buyers. The form now has a tier multi-select pill picker: click tiers to toggle inclusion. 0 picked = "any policy on this product" (unchanged). 1 picked = single-policy scope (writes to the legacy column for clarity). 2+ picked = the code applies if and only if the chosen tier is in the picked set.', '**What changed.** Previously, a discount code\'s tier scope was a single policy (`applies_to_policy_id`) or "any policy on this product" / global. To offer the same discount across two of three tiers required creating two codes with distinct strings — operationally messy and harder for buyers. The form now has a tier multi-select pill picker: click tiers to toggle inclusion. 0 picked = "any policy on this product" (unchanged). 1 picked = single-policy scope (writes to the legacy column for clarity). 2+ picked = the code applies if and only if the chosen tier is in the picked set.',
@@ -362,7 +364,7 @@ const ROUTINE_NOTES = [
].join('\n\n') ].join('\n\n')
export const v0_2_0 = VersionInfo.of({ export const v0_2_0 = VersionInfo.of({
version: '0.2.0:20', version: '0.2.0:21',
releaseNotes: { en_US: ROUTINE_NOTES }, releaseNotes: { en_US: ROUTINE_NOTES },
// No on-disk transformation needed — v0.2.0:0 is a label change. // No on-disk transformation needed — v0.2.0:0 is a label change.
// SQLite-level migrations live separately under // SQLite-level migrations live separately under