Public: anyone can read this; the crew can change it.
The form, the listing and the flyer read well on a phone held up in a meeting #21
Check · Review · Land · Live done — Landed and live in production.
mason/listing-launch:mason/11-the-form-the-listing-and-the-flyer-read → main 1 commit fork
2 files
changed +78
−0
src/design.test.ts +26
| @@ -124,3 +124,29 @@ test("the lint refuses a raw colour and a token that is not there", () => { | ||
| 124 | 124 | "static/extra.css:2: raw colour #fff — name it in design/tokens.json and use var(--…)", |
| 125 | 125 | ]); |
| 126 | 126 | }); |
| 127 | ||
| 128 | // A phone at 375px: the viewport is declared everywhere, and every image says | |
| 129 | // how big it is so nothing shifts as it loads. | |
| 130 | test("every page declares the viewport and every image its size", async () => { | |
| 131 | const { layout } = await import("./pages/layout.ts"); | |
| 132 | const html = layout("<p>x</p>", { role: "anon", name: "" } as never); | |
| 133 | expect(html).toContain( | |
| 134 | '<meta name="viewport" content="width=device-width, initial-scale=1">', | |
| 135 | ); | |
| 136 | const root = join(import.meta.dir, "pages"); | |
| 137 | for (const f of ["home.ts", "listing.ts", "email.ts"]) { | |
| 138 | const src = await Bun.file(join(root, f)).text(); | |
| 139 | for (const tag of src.match(/<img\b[^>]*>/g) ?? []) | |
| 140 | expect([f, tag, /\swidth="\d+"/.test(tag) && /\sheight="\d+"/.test(tag)]).toEqual([f, tag, true]); | |
| 141 | } | |
| 142 | }); | |
| 143 | ||
| 144 | test("the phone rules are in the stylesheet and print is untouched by them", async () => { | |
| 145 | const css = await Bun.file(join(import.meta.dir, "..", "static", "app.css")).text(); | |
| 146 | const phone = css.slice(css.indexOf("A phone held up in a meeting")); | |
| 147 | const end = phone.indexOf("/* The email preview"); | |
| 148 | const block = phone.slice(0, end); | |
| 149 | for (const rule of [".facts-grid", ".tablist", ".strip", ".listing-tools"]) | |
| 150 | expect(block).toContain(rule); | |
| 151 | expect(block).not.toContain("@media print"); | |
| 152 | }); |
static/app.css +52
| @@ -756,6 +756,58 @@ h3 { | ||
| 756 | 756 | } |
| 757 | 757 | } |
| 758 | 758 | |
| 759 | /* A phone held up in a meeting: 375px, nothing wider than the screen. */ | |
| 760 | @media (max-width: 30rem) { | |
| 761 | .head nav { | |
| 762 | flex-wrap: wrap; | |
| 763 | } | |
| 764 | .facts-grid, | |
| 765 | .brand-grid, | |
| 766 | .choices { | |
| 767 | grid-template-columns: minmax(0, 1fr); | |
| 768 | } | |
| 769 | .listing-tools { | |
| 770 | max-width: none; | |
| 771 | width: 100%; | |
| 772 | } | |
| 773 | .tone { | |
| 774 | flex-wrap: wrap; | |
| 775 | width: 100%; | |
| 776 | } | |
| 777 | .tone .select { | |
| 778 | flex: 1 1 auto; | |
| 779 | } | |
| 780 | .strip { | |
| 781 | overscroll-behavior-x: contain; | |
| 782 | scroll-snap-type: x proximity; | |
| 783 | } | |
| 784 | .strip li { | |
| 785 | flex: none; | |
| 786 | scroll-snap-align: start; | |
| 787 | } | |
| 788 | .strip img { | |
| 789 | width: min(15rem, 70vw); | |
| 790 | } | |
| 791 | .tablist { | |
| 792 | overscroll-behavior-x: contain; | |
| 793 | } | |
| 794 | [role="tab"] { | |
| 795 | flex: none; | |
| 796 | } | |
| 797 | .asset-actions .button, | |
| 798 | .launch-row .button { | |
| 799 | min-height: var(); | |
| 800 | } | |
| 801 | .flyer-more img, | |
| 802 | .flyer-hero img { | |
| 803 | max-width: 100%; | |
| 804 | } | |
| 805 | .textarea, | |
| 806 | .file { | |
| 807 | max-width: 100%; | |
| 808 | } | |
| 809 | } | |
| 810 | ||
| 759 | 811 | /* The email preview: a message on paper, in the agent's colours. */ |
| 760 | 812 | .email { |
| 761 | 813 | max-width: 37.5rem; |
new above
offline