diff --git a/offers/9p2by3s8x3x1l4jd60ci/figures.json b/offers/9p2by3s8x3x1l4jd60ci/figures.json index 38a4d47..26d5ecb 100644 --- a/offers/9p2by3s8x3x1l4jd60ci/figures.json +++ b/offers/9p2by3s8x3x1l4jd60ci/figures.json @@ -8,22 +8,21 @@ "measured": "October 28, 2026", "pages": 11, "source": { - "commit": "e42c19b", + "commit": "cd97ed3", "run": "docs/discovery/seo-audit/2026-10-28-after-88/result.json" }, "rows": [ { "label": "Wait before the page shows on a phone", - "why": "Visitors see your page in 1.8 s instead of 4.9 s.", + "why": "Your pages take 4.9 s to show on a phone; 0 of 11 are within Google's 2.5 s.", "impact": "People leave slow pages before they find your number.", - "fine": "Largest Contentful Paint, mean of 11 pages; 0 of 11 pages within 2.5 s today, 11 of 11 rebuilt.", + "fine": "Largest Contentful Paint, mean of 11 pages.", "before": "4.9 s", - "after": "1.8 s", + "after": null, "bar": { "before": 4.855921597272726, - "after": 1.7968751454545455, - "max": 5, - "lowerIsBetter": true + "after": null, + "max": 5 }, "target": { "value": 2.5, @@ -39,10 +38,10 @@ "impact": "This is their first impression of you.", "fine": "Google Lighthouse performance score, mean of 11 pages.", "before": "63", - "after": "99", + "after": null, "bar": { "before": 63, - "after": 99, + "after": null, "max": 100 }, "target": { @@ -55,13 +54,13 @@ }, { "label": "Ready for Google, out of 100", - "why": "Nothing basic is missing when Google decides how to list you.", - "fine": "Google Lighthouse SEO score on phones, mean of 11 pages; 100 on 8 of 11 today, 11 of 11 rebuilt.", + "why": "3 of 11 pages miss some of the basic checks Google makes before it lists a page.", + "fine": "Google Lighthouse SEO score on phones, mean of 11 pages; 100 on 8 of 11.", "before": "98", - "after": "100", + "after": null, "bar": { "before": 98, - "after": 100, + "after": null, "max": 100 }, "target": { @@ -74,13 +73,13 @@ }, { "label": "Usable by every visitor, out of 100", - "why": "Customers using a screen reader or large text can read and use every page.", - "fine": "Google Lighthouse accessibility score on phones, mean of 11 pages.", + "why": "Close, but a scanner found 39 problems, such as links a screen reader cannot name.", + "fine": "Google Lighthouse accessibility score on phones, mean of 11 pages; axe-core WCAG 2.2 A and AA failures on desktop, all pages.", "before": "97", - "after": "100", + "after": null, "bar": { "before": 97, - "after": 100, + "after": null, "max": 100 }, "target": { @@ -93,13 +92,14 @@ }, { "label": "Pages with a clear outline", - "why": "Search engines and AI assistants can tell what each page is about.", + "why": "1 of 11 pages has its headings in order.", + "impact": "Search engines and AI assistants get a muddled picture of the rest.", "fine": "Headings in order, no skipped levels.", "before": "1 of 11", - "after": "11 of 11", + "after": null, "bar": { "before": 1, - "after": 11, + "after": null, "max": 11 }, "target": { @@ -112,13 +112,13 @@ }, { "label": "Pages with your business details for Google", - "why": "Kept as it is today: your name, phone and services stay readable to Google.", + "why": "Your pages already carry this; the new site keeps it.", "fine": "Structured data (JSON-LD) on the page.", "before": "11 of 11", - "after": "11 of 11", + "after": null, "bar": { "before": 11, - "after": 11, + "after": null, "max": 11 }, "target": { @@ -130,6 +130,7 @@ } } ], + "currentOnly": true, "ai": null }, { diff --git a/offers/9p2by3s8x3x1l4jd60ci/figures.mjs b/offers/9p2by3s8x3x1l4jd60ci/figures.mjs index 0d8ba48..44315c3 100644 --- a/offers/9p2by3s8x3x1l4jd60ci/figures.mjs +++ b/offers/9p2by3s8x3x1l4jd60ci/figures.mjs @@ -160,6 +160,85 @@ function headline(comparison) { ]; } +// The current site alone, for First Touch: its rebuild so far follows Hibu's design and +// is being replaced by an original one (that repo's ADR 0030), so nothing of the rebuild +// is shown. Same measurements and benchmark lines; the why-lines speak to today. +function currentOnly(comparison) { + const h = comparison.headline; + const n = comparison.pageCount; + const structured = check(comparison, 'Business details for search engines'); + const headings = check(comparison, 'Headings in order, no skipped levels'); + const of = (text) => text.replace(/ pages$/, ''); + const one = (label, why, fine, before, value, max, target) => + row(label, why, fine, before, null, { before: value, after: null, max }, target); + const missSeo = n - h.seoPerfectPages.before; + return [ + one( + 'Wait before the page shows on a phone', + [ + `Your pages take ${seconds(h.lcpSeconds.before)} to show on a phone; ${h.lcpGood.before} of ${n} are within Google's 2.5 s.`, + 'People leave slow pages before they find your number.' + ], + `Largest Contentful Paint, mean of ${n} pages.`, + seconds(h.lcpSeconds.before), + h.lcpSeconds.before, + Math.ceil(h.lcpSeconds.before), + { ...LCP_GOOD } + ), + one( + 'Speed on a phone, out of 100', + [ + 'Most people check out a business on a phone first.', + 'This is their first impression of you.' + ], + `Google Lighthouse performance score, mean of ${n} pages.`, + String(h.performance.before), + h.performance.before, + 100, + LIGHTHOUSE_GOOD + ), + one( + 'Ready for Google, out of 100', + `${missSeo} of ${n} pages miss some of the basic checks Google makes before it lists a page.`, + `Google Lighthouse SEO score on phones, mean of ${n} pages; 100 on ${h.seoPerfectPages.before} of ${n}.`, + String(h.seo.before), + h.seo.before, + 100, + LIGHTHOUSE_GOOD + ), + one( + 'Usable by every visitor, out of 100', + `Close, but a scanner found ${h.axeDesktopNodes.before} problems, such as links a screen reader cannot name.`, + `Google Lighthouse accessibility score on phones, mean of ${n} pages; axe-core WCAG 2.2 A and AA failures on desktop, all pages.`, + String(h.accessibilityScore.before), + h.accessibilityScore.before, + 100, + LIGHTHOUSE_GOOD + ), + one( + 'Pages with a clear outline', + [ + `${ofPages(headings.before)} of ${n} pages has its headings in order.`, + 'Search engines and AI assistants get a muddled picture of the rest.' + ], + 'Headings in order, no skipped levels.', + of(headings.before), + ofPages(headings.before), + n, + allPages(n) + ), + one( + 'Pages with your business details for Google', + 'Your pages already carry this; the new site keeps it.', + 'Structured data (JSON-LD) on the page.', + of(structured.before), + ofPages(structured.before), + n, + allPages(n) + ) + ]; +} + const ftpsLib = await load(ftpsRepo, 'src/lib/site-comparison.ts'); const seLib = await load(seRepo, 'src/lib/site-comparison.ts'); const seAgents = await load(seRepo, 'src/lib/agent-readiness.ts'); @@ -225,7 +304,8 @@ const figures = { measured: ftps.method.beforeDate, pages: ftps.pageCount, source: { commit: commit(ftpsRepo), run: FTPS_AUDIT }, - rows: headline(ftps), + rows: currentOnly(ftps), + currentOnly: true, ai: null }, { diff --git a/offers/9p2by3s8x3x1l4jd60ci/offer.ts b/offers/9p2by3s8x3x1l4jd60ci/offer.ts index 53f6508..d8ab75f 100644 --- a/offers/9p2by3s8x3x1l4jd60ci/offer.ts +++ b/offers/9p2by3s8x3x1l4jd60ci/offer.ts @@ -30,7 +30,7 @@ export default defineOffer({ { title: 'Code you do not control on every page', detail: - 'Dozens of scripts from outside services, including an accessibility overlay that papers over problems instead of fixing them.', + 'Dozens of outside scripts load on every page. One is an overlay that hides accessibility problems instead of fixing them.', impact: 'Each one is code you depend on and do not control.' } ], diff --git a/offers/9p2by3s8x3x1l4jd60ci/page.svelte b/offers/9p2by3s8x3x1l4jd60ci/page.svelte index 2e80317..97a0d4d 100644 --- a/offers/9p2by3s8x3x1l4jd60ci/page.svelte +++ b/offers/9p2by3s8x3x1l4jd60ci/page.svelte @@ -82,8 +82,6 @@ }; }); - const bioLine = ENGINEER.bio[0].split('. ')[0] + '.'; - // Dark theme only: a sparse, dim, static star field as texture (the home page's // live sky draws ~9k catalogue stars every frame, too heavy for this page). Seeded, // so the server and the browser draw the same stars and hydration matches. @@ -98,6 +96,23 @@ })); })(); + // QR modules in a deep Void violet on white, the same in both themes: dark on light, + // with the block's 4-module quiet zone, so phones read it as easily as black. + const QR_INK = '#2a1450'; + + // The hero's "Accept offer" jumps to the plan picker, since monthly or annual is + // chosen there first; it moves focus to that section's heading for keyboard and + // screen reader users. Without JavaScript it is a plain anchor link. + function toPlans(e: MouseEvent) { + const heading = document.getElementById('offer-plans'); + if (!heading) return; + e.preventDefault(); + const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches; + heading.setAttribute('tabindex', '-1'); + heading.scrollIntoView({ behavior: reduce ? 'auto' : 'smooth', block: 'start' }); + heading.focus({ preventScroll: true }); + } + // Screenshot evidence for a concern, keyed by its title in offer.ts. Captured from the // live current sites on 2026-11-04 (Playwright, iPhone 15 emulation, cookie banner // declined), cropped to the paragraph and nothing else changed. @@ -175,9 +190,9 @@ // on the server only so the trajectory table never ships in the page script. The // client reads the server's markup back before hydrating, so {@html} matches. const FAVICON_SEED = 42; - // After mount the marks switch to a fresh one from the site's /api/mark.svg endpoint - // (the footer badge's), one random seed per page load shared by both, as the site - // header rolls a new mark each visit. The fixed-seed mark stays for no-JS visitors. + // After mount the header mark switches to a fresh one from the site's /api/mark.svg + // endpoint (the footer badge's), a random seed per page load, as the site header rolls + // a new mark each visit. The fixed-seed mark stays for no-JS visitors. let markSrc = $state(''); const voidMark = import.meta.env.SSR ? generateMarkSvg(TRAJECTORIES, FAVICON_SEED, __BRAND_PRIMARY__) @@ -193,8 +208,8 @@ why: string; fine: string; before: string; - after: string; - bar: { before: number; after: number; max: number }; + after: string | null; // null where only the current site is shown (First Touch) + bar: { before: number; after: number | null; max: number }; impact?: string; // the consequence, shown as a callout after `why` target?: { value: number; label: string; good: string; text: string; source: string }; // benchmark line, see figures.mjs } @@ -212,7 +227,7 @@ 'Dozens of outside scripts on every page' ]; const MOVE = [ - 'Pages show in under 2 seconds on a phone', // the one callout in this list + 'Pages that show in under 2 seconds on a phone, as Software Earnings’ rebuild does', // the one callout in this list 'Your own words on every screen', 'Contact details every search engine and assistant can read', 'Clean pages, and someone checking they stay that way', @@ -317,6 +332,77 @@ {/if} {/snippet} +{#snippet devices(id: string, name: string, preview: string, building: boolean)} + + + {#if building}Under construction{/if} + + + {building + + + {building + + +{/snippet} + +{#snippet underConstruction()} +
+ +
+

Under construction

+

A new First Touch site, designed from scratch

+

+ Hibu, your current host, designed the template your site is built on. Its design and wording + may not be yours to carry over, so we are building First Touch an original design. It keeps every fact your site states today: your products, people, phone numbers and + address. +

+

+ It gets the same speed, search and accessibility work you can see on the Software Earnings + rebuild below. +

+
+
+{/snippet} + {#snippet info(text: string, name: string)}
@@ -349,15 +435,17 @@ >{/if} {row.before} - {NEXT} - {#if row.target}{/if} - {row.after} + {#if row.after !== null && row.bar.after !== null} + {NEXT} + {#if row.target}{/if} + {row.after} + {/if} {#if row.target} {@const end = row.target.value >= row.bar.max} @@ -388,7 +476,7 @@ {#each STARS as star, i (i)}{/each} -
+
-
+
+

Proposal

-

+

Hosting and maintenance proposal for your two websites

-

- Larry, we propose to host, secure and maintain both websites we rebuilt for you, First Touch - Payment Solutions and Software Earnings, and keep them fast and easy for search engines and - AI assistants to read. Below is where your sites are today and where they can be; sign up at - the bottom or reply with any questions. +

+ Larry, we propose to host, secure and maintain both of your websites. We have rebuilt + Software Earnings, and we are building a new design for First Touch Payment Solutions. We + keep both fast and easy for search engines and AI assistants to read. Below is where your + sites are today and where they can be. Accept the offer when you are ready, or reply with + any questions.

{money(monthly)} a month for both sites, or {money(yearly)} a year

+

+ Accept offer + Questions? Email {ENGINEER.name.split(' ')[0]} +

@@ -485,17 +576,16 @@

{ENGINEER.name.split(' ')[0]} built both of your new sites and is your contact for anything - you need.{ENGINEER.name.split(' ')[0]} built your new Software Earnings site, is building the new + First Touch site, and is your contact for anything you need. - {bioLine}

Always a human, never a bot - When you write to us, {ENGINEER.name.split(' ')[0]} answers. AI agents do the upkeep behind - the scenes; every conversation is with a person. + When you write to us, {ENGINEER.name.split(' ')[0]} answers. AI agents do the upkeep in the + background. Every conversation is with a person.

-

+

@@ -506,53 +596,36 @@ {#each figures.sites as site (site.id)}
{@render info( - `Measured ${site.measured} on ${site.pages} pages of both versions, as a phone loads them.`, + site.currentOnly + ? `Measured ${site.measured} on ${site.pages} pages of your current site, as a phone loads them.` + : `Measured ${site.measured} on ${site.pages} pages of both versions, as a phone loads them.`, 'When and how these were measured' )}

{site.name}

- + {#if !site.currentOnly} + + {/if}
- - - - The rebuilt {site.name} home page on a computer - - - The rebuilt {site.name} home page on a phone - - + {#if site.currentOnly} + {@render underConstruction()} + {@render devices(site.id, site.name, site.preview, true)} +

Your current site today

+

+ {NOW}: your current site +

+ {:else} + {@render devices(site.id, site.name, site.preview, false)} -

- {NOW}: your current site - {NEXT}: the site we rebuilt -

+

+ {NOW}: your current site + {NEXT}: the site we rebuilt +

+ {/if} {@render tiles(site.rows)} {#if site.ai} @@ -560,11 +633,6 @@

What AI assistants can read about you

{@render tiles(site.ai.rows)} - {:else} -

- AI assistant checks have not been run on this site yet; running them is part of the - upkeep. -

{/if}
{/each} @@ -617,9 +685,9 @@

Security in the age of AI

- AI makes attacks cheaper: bots scan for outdated software, copy a business's site for - phishing and collect published contact details. No one can promise a site is never attacked; - we keep the risk low. + AI makes attacks cheaper. Bots scan for outdated software, copy a business’s site for + phishing and collect contact details. No one can promise a site is never attacked; we keep + the risk low.

    {#each SECURITY as item (item.title)} @@ -634,7 +702,7 @@
-
+
Sign up securelyAccept offer - +

Your plan renews automatically each month, or each year on the annual plan, until you - cancel. You can cancel any time from your billing page; your sites stay up to the end of the - period you have paid for, and we email a reminder before each annual renewal. + cancel. Cancel any time from your billing page. Your sites stay up until the end of the + period you paid for, and we email you a reminder before each annual renewal.

@@ -709,11 +783,14 @@ :global([data-theme='dark']) .stars { display: block; } + /* Phones: the lockup takes the full width (its logos wrap to two rows below 40rem) + and the theme toggle sits on its own row under it; side by side from 40rem. */ .top { display: flex; + flex-direction: column; align-items: center; justify-content: space-between; - gap: 0.5rem; + gap: 0.75rem; max-width: 56rem; margin: 0 auto; } @@ -721,8 +798,8 @@ display: grid; place-items: center; flex: none; - width: 2.5rem; - height: 2.5rem; + width: 2.75rem; + height: 2.75rem; border: 1px solid var(--offer-border); border-radius: 999px; background: var(--offer-surface); @@ -743,6 +820,7 @@ stroke-linejoin: round; } .lockup { + align-self: stretch; display: flex; flex-wrap: wrap; align-items: center; @@ -782,8 +860,7 @@ } .mark :global(svg), .hero-mark :global(svg), - .mark img, - .hero-mark img { + .mark img { display: block; width: 100%; height: 100%; @@ -821,6 +898,13 @@ border-radius: 1.5rem; background: var(--offer-surface); } + @media (max-width: 29.99rem) { + .engineer { + flex-direction: column; + gap: 0.75rem; + padding: 1.25rem; + } + } .face { flex: none; width: 4.5rem; @@ -830,7 +914,7 @@ box-shadow: 0 0 0 1px var(--offer-border); } .promise { - font-size: 0.9375rem; + font-size: 1rem; color: var(--offer-muted); } .badge { @@ -918,7 +1002,7 @@ right: 0; bottom: 0; width: 22%; - min-width: 5.5rem; + min-width: 6.5rem; padding: 0.3rem; border-radius: 1.1rem; background: oklch(0.21 0.015 285); @@ -938,19 +1022,19 @@ } .tile > .info { position: absolute; - top: 0.75rem; - right: 0.75rem; + top: 0.25rem; + right: 0.25rem; } .site > .info { position: absolute; - top: 2.75rem; - right: 0; + top: 2.25rem; + right: -0.5rem; } .info summary { display: grid; place-items: center; - width: 1.75rem; - height: 1.75rem; + width: 2.75rem; + height: 2.75rem; border-radius: 999px; color: var(--offer-muted); cursor: pointer; @@ -980,7 +1064,7 @@ .info-panel { position: absolute; z-index: 2; - top: 2rem; + top: 2.75rem; right: 0; width: min(17rem, calc(100vw - 4rem)); padding: 0.75rem 0.875rem; @@ -988,13 +1072,13 @@ border-radius: 0.75rem; background: var(--offer-surface); color: var(--offer-muted); - font-size: 0.8125rem; + font-size: 0.875rem; font-weight: 400; line-height: 1.45; box-shadow: 0 16px 32px -16px oklch(0 0 0 / 0.35); } .tile-label { - padding-right: 2rem; + padding-right: 2.25rem; } /* Dark theme surfaces: the main site's glass (GlassCard: bg-background/8 with @@ -1025,7 +1109,7 @@ display: grid; gap: 0.25rem; width: 9rem; - font-size: 0.75rem; + font-size: 0.875rem; font-weight: 600; color: var(--offer-muted); } @@ -1045,7 +1129,7 @@ } .evidence-note { flex-basis: 100%; - font-size: 0.75rem; + font-size: 0.875rem; color: var(--offer-muted); } /* Centered in the viewport and never taller than it, so the page never scrolls. */ @@ -1141,6 +1225,62 @@ font-size: 1.125rem; color: var(--offer-muted); } + /* Phones: price and the call to action come straight after the heading, so the + first screen says what this is, what it costs and how to accept. */ + @media (max-width: 39.99rem) { + .hero .price { + order: 1; + } + .hero .cta { + order: 2; + } + .hero .lead { + order: 3; + font-size: 1rem; + } + .hero { + padding-top: 0; + } + .price strong { + font-size: 1.75rem; + } + } + /* Standalone links get a 44px tap height; links inside sentences stay inline. */ + .cta a, + .links a, + footer > a { + display: inline-flex; + align-items: center; + min-height: 2.75rem; + } + .cta { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.75rem 1.5rem; + margin-top: 0.25rem; + } + .accept { + display: inline-flex; + align-items: center; + height: 2.75rem; + padding: 0 1.5rem; + border-radius: 999px; + background: var(--void-primary-strong); + color: #fff; + font-weight: 600; + } + .accept:hover { + text-decoration: none; + background: oklch(0.52 0.1876 301.62); + } + .accept:focus-visible { + outline: 2px solid var(--void-primary-text); + outline-offset: 2px; + } + :global(#offer-plans) { + scroll-margin-top: 1.5rem; + } .price strong { font-size: 2rem; letter-spacing: -0.02em; @@ -1174,6 +1314,64 @@ /* Each site is its own section on the page background: a rule and space above, not a card around the tiles. */ + .building { + display: flex; + align-items: flex-start; + gap: 1.25rem; + padding: 1.5rem; + border: 1px dashed oklch(0.6534 0.1876 301.62 / 0.45); + border-radius: 1.5rem; + background: linear-gradient(to bottom right, oklch(0.6534 0.1876 301.62 / 0.1), transparent); + } + .building h3 { + margin: 0; + font-size: 1.375rem; + } + .building-mark { + flex: none; + width: 3rem; + height: 3rem; + } + .building-mark :global(svg) { + display: block; + width: 100%; + height: 100%; + } + @media (max-width: 29.99rem) { + .building { + flex-direction: column; + gap: 0.75rem; + padding: 1.25rem; + } + } + /* In-progress preview: a solid label on top so it never reads as a finished site. */ + .building-preview .frame img, + .building-preview .phone img { + opacity: 0.85; + } + .ribbon { + position: absolute; + z-index: 1; + top: 3.25rem; /* under the browser chrome */ + left: 50%; + transform: translateX(-50%); + padding: 0.375rem 1rem; + border-radius: 999px; + background: var(--void-night); + color: #fff; + font-size: 0.875rem; + font-weight: 700; + letter-spacing: 0.04em; + white-space: nowrap; + } + :global([data-theme='dark']) .ribbon { + background: #fff; + color: var(--void-night); + } + .today { + margin: 0.5rem 0 0; + font-size: 1.25rem; + } .site-name { margin: 0; font-size: 1.75rem; @@ -1214,7 +1412,7 @@ margin-top: 0.25rem; } .who { - font-size: 0.75rem; + font-size: 0.8125rem; font-weight: 600; color: var(--offer-muted); white-space: nowrap; @@ -1250,7 +1448,7 @@ position: absolute; top: 0.125rem; margin-left: -1px; - font-size: 0.75rem; + font-size: 0.8125rem; font-weight: 600; white-space: nowrap; } @@ -1271,10 +1469,10 @@ color: var(--void-primary-text); } .why { - font-size: 0.9375rem; + font-size: 1rem; } .fine { - font-size: 0.75rem; + font-size: 0.875rem; color: var(--offer-muted); } @@ -1284,7 +1482,7 @@ margin: 0.25rem 0; padding: 0.75rem 0; border-block: 1px solid var(--offer-border); - font-size: 0.875rem; + font-size: 0.9375rem; } .agents { color: var(--offer-muted); @@ -1297,6 +1495,7 @@ } .part small { display: block; + font-size: 0.875rem; color: var(--offer-muted); } .part .amount { @@ -1405,9 +1604,14 @@ height: 40px; } .lockup { + align-self: auto; flex-wrap: nowrap; border-radius: 999px; } + .top { + flex-direction: row; + gap: 0.5rem; + } .preview { padding: 0 2.5rem 2.5rem 0; } diff --git a/offers/9p2by3s8x3x1l4jd60ci/theme.css b/offers/9p2by3s8x3x1l4jd60ci/theme.css index 285817a..519b3f3 100644 --- a/offers/9p2by3s8x3x1l4jd60ci/theme.css +++ b/offers/9p2by3s8x3x1l4jd60ci/theme.css @@ -14,7 +14,9 @@ --void-primary: oklch(0.6534 0.1876 301.62); --void-primary-strong: oklch(0.58 0.1876 301.62); --void-night: oklch(0.17 0.018 285); /* the site's dark background, behind the header lockup */ - --void-primary-text: oklch(0.55 0.1876 301.62); /* violet text: 5:1 on the background */ + --void-primary-text: oklch( + 0.52 0.1876 301.62 + ); /* violet text: 5.6:1 on the background, 5:1 on violet tints */ --void-neutral: oklch(0.64 0.006 300); /* "where you are" bars: 3.2:1 on the background */ --offer-bg: oklch(0.984 0.01 311.18); --offer-fg: oklch(0.145 0 0); @@ -96,3 +98,8 @@ h2 { border-color: var(--offer-accent); box-shadow: none; } + +/* 44px tap targets for the plan picker's Monthly / Annual toggle on this page. */ +:root .toggle button[role='radio'] { + min-height: 2.75rem; +} diff --git a/src/lib/offers/blocks/qr-code.svelte b/src/lib/offers/blocks/qr-code.svelte index 910c3c8..7e08412 100644 --- a/src/lib/offers/blocks/qr-code.svelte +++ b/src/lib/offers/blocks/qr-code.svelte @@ -5,8 +5,17 @@ qr, url, title = 'Sign up on your phone', + fg = '#000', + bg = '#fff', class: className = '' - }: { qr: QrMatrix; url: string; title?: string; class?: string } = $props(); + }: { + qr: QrMatrix; + url: string; + title?: string; + fg?: string; // module colour: keep it dark, on a light `bg`, or phones will not read it + bg?: string; + class?: string; + } = $props(); const QUIET = 4; // modules of light margin the QR spec asks for around the code let side = $derived(qr.size + QUIET * 2); @@ -19,8 +28,8 @@ aria-label="QR code for {url}" shape-rendering="crispEdges" > - - + +
{title} @@ -29,7 +38,7 @@