Repository navigation
Conversation
The header and footer carried a placeholder: a teal rounded square with a green square inside, next to the name in live text. Both are replaced by the real wordmark — "Execil" in Satoshi Black with the tittle of the "i" swapped for a cross badge, drawn from a client reference image. The letterforms are outlines, not <text>. The badge has to land exactly where the dot was at every size, which live text cannot promise across fallback fonts. Wordmark carries the paths inline rather than pointing at the file in public/brand/, so the letters can take currentColor and the badge can take theme tokens; an <img> could do neither. Two token changes fall out of that: textOnBrand, because the cross sits on the brand fill and needs a colour that survives the Dark theme. Button currently uses surfaceRaised for this, which the Dark theme turns translucent — a latent bug, left alone here. surfaceBrand overridden to teal600 on the inverse surface, because teal700 on ink900 leaves the badge reading as an unstyled dot. Nothing else on a dark subtree uses surfaceBrand today. public/brand/ also holds the second direction the client asked to keep. Nothing references it; see the README there.
The badge was surfaceBrand, teal700. Against the ink50 page that measures 5.97:1, which looks fine in isolation — but the badge does not sit on the page, it sits against the ink900 letterforms, and there it separates by only 2.74:1. At header size the mark stopped reading as a badge and became part of the "i": one dark mass with a speck in it. teal600 lifts that adjacency to 4.13:1 and still holds the white cross inside the badge at 4.18:1. Brighter trades one for the other — teal500 would reach 5.72:1 against the letters but drop the cross to 3.02:1, mush at 16px. teal600 is the balance point, and it is already the accent the hero uses on this ground. That makes the badge one colour on both grounds, so surfaceMark carries it rather than surfaceBrand, and the Dark theme no longer needs the surfaceBrand override this branch added. The tagline keeps teal700. It is real text at a small size and needs the 6.31:1.
This reverts commit 558ba50.
Colour was the wrong lever. The badge replaces the tittle of the "i", so it does not sit against the page — it sits against the letterforms, and teal700 separates from ink900 by only 2.74:1. Reaching for a brighter teal fixed that number and cost the white cross inside the badge, which is the trade the previous attempt lost. Space works where value did not. A ring 20% of the badge radius is masked out of the letterforms, so the ground shows through around the badge. A mask rather than a filled circle, because the ring then shows whatever the mark is placed on — the same drawing works on the light page and on the inverse footer with no second colour. Narrower than ~15% stops registering at 28px; wider than ~25% starts eating the stem of the "i". Every colour stays where it was. The component needs a unique mask id per instance, since the header and footer both render one, so this is the first hook in Wordmark. Verified in the prerendered HTML: two distinct ids, each matching its reference, and the same values after hydration.
Below the nav breakpoint the square source is cover-cropped into a 4:3 band. Centred, the crop took the top of her head; anchored to the top, it showed the empty band above her hair instead. "right 18%" sits between: the head clears with a small margin, and the same anchor still serves desktop because a box taller than the image only crops sideways. The band itself is now capped at 480px through layout.heroBandMax. Just under the breakpoint it reached 700px, which is most of a tablet's first screen spent on the picture.
The Worker's own workers.dev subdomain serves the whole site and stays enabled by choice (#34), and the preview aliases from non-production branch builds live on the same domain. Both are public, and robots.txt says Allow: /. The prerendered canonical already points at execil.net, which Google usually honours; X-Robots-Tag is the signal that does not depend on it being honoured. _headers matches host-first when the rule is an absolute URL, and `:name` is a placeholder for one label, so a single rule covers execil.<subdomain>.workers.dev and every <alias>-execil.<subdomain> preview. No Worker change and no run_worker_first: these are asset responses, which is what every prerendered page is. nosniff and Referrer-Policy come along while the file is open. Not HSTS — that one binds the apex and every subdomain for as long as browsers remember it, so it is a decision, not a commit.
logo.png is new. Organization.logo pointed at og.png, which is a 1200x630 social card with a headline across it — Google reads that field for the knowledge panel and wants the logo itself, on white, 112px minimum in both axes. The new file is the wordmark on white at 1024x406. The OG card's lockup was still the placeholder mark, a teal square with a green square inside it. Both now draw the real wordmark from the same geometry as public/brand/cross-dot/. Two things about drawing it in Pillow. Glyph pen positions are listed rather than laid out, because this Pillow has no Raqm: its own layout drops the kerning, which would set the word 25 units wide and land the badge 78 units late. And the knockout ring samples the canvas before a letter is drawn — after the text, that point is the tittle, and the ring came out black.
The meta description was 215 characters against Google's ~155, so it was being cut mid-sentence in results. Now 155. The 404 inherited the home page's title and its `index, follow`. Its 404 status is what actually keeps it out of the index, but the tab said "Execil | Medical Billing & RCM" and a crawler reading markup before status saw an invitation. It cannot set a route head — nothing matched, so there is no route to hang one on. React 19 hoists a rendered <title> into <head>, but the root head's title is emitted first and wins, which left the document with two. So the title moves to the home route, and the root keeps none. The robots meta is deleted outright rather than moved: `index, follow` is the default, so it only existed to collide. Sitemap gains lastmod, the one field there Google reads.
The picture is the LCP element on narrow screens, where it sits above the copy. Markup alone is not enough there: the preload scanner finds the <img> only once the CSS that sizes it has arrived, and fetchpriority cannot start a request nobody has discovered yet. Verified on the built site at 375px — one request for the hero, initiator "link", the 900w candidate, and no "preloaded but not used". That needs the same src, srcset and sizes in two files, so they move to hero-image.ts. A mismatch there is not a no-op: the browser resolves a different candidate and downloads the picture twice. FAQPage covers the eight questions. Expectations first: since 2023 Google shows FAQ rich results only for government and health *authority* sites, and Execil is neither. It is valid, cheap, and read by other consumers of structured data. It sits on the home route rather than beside organizationLd in the root, because the questions render on one page.
Two separate things, both visible as "too much space at the sides". The header put its gutter inside the container rather than outside it, which is the reverse of Section and SiteFooter. That inset the brand by a gutter relative to every section below, so the wordmark sat 24px right of the hero copy it sits directly above — close enough to read as a mistake rather than a margin. And containerWide was the canvas's 1160. At 1440 and up that left the header's three groups closer to each other than to the edges of the screen: 244px of nothing at each side against 110px between the groups, which reads as a narrow strip floating in a wide page. 1280 puts the outer margins and the inner gaps in the same range. The container is shared, so sections widen too. Prose is unaffected — it has its own containerText — and the grids simply get their columns back.
The badge sits on surfaceAccent over the hero's wash, which ends at surfaceAccentSoft — near enough that the 1px border was doing all the separating and not much of it. shadow.sm lifts it. The dot gets a glow: a tight ring plus a soft bloom, alpha doing the work, because a solid ring at 8px just looks like a bigger dot. It is a Dot prop rather than a Badge style, so it stays keyed to tone — a brand dot glows brand, and `subtle` does not glow at all, having nothing to announce. Badge is the only caller today. Eyebrow moves from base to md. At 13px, uppercase and letterspaced, it was reading as a caption under the heading rather than a label over it.
The provider range moves from 1-20 to 35-40 in the three places it is
user-facing — the meta description, the hero lead and the Fit list — plus
the OG card, which is regenerated, and the glossary entry for Practice
that defines the term.
The last trust point was "US-facing team · 24-hour processing": one list
item carrying its own separator, so it rendered a text middle dot where
the other four have a teal Dot. Now two items, six in the row.
"13 wks" was the only abbreviation among four stat cards ("60%", "13.5%",
"25 min"), and the one whose unit a reader has to expand. Now "13 weeks".
Six changes from a review of the first pass. The desktop header was appearing at 940px, but the nav links, the phone number and the CTA need 1000px before they all stop wrapping — so between 940 and 1000 the "desktop" header rendered three lines deep. The breakpoint moves to 1040, a gutter of slack past where it fits. Hero shares that token for its side-by-side layout, and HERO_SIZES repeats the number because a `sizes` attribute cannot read a token; both move together, and the token comment now says so. The comparison table used auto layout, so each column sized to its longest cell and the surplus went to whichever asked for most. That left the Execil column half again as wide as the vendor column it is compared against, with its text stopping well short of filling it. Fixed layout, 22% for the labels and the value columns even, so the comparison does not look weighted before it is read. The hero's top padding drops to the bottom's value. The canvas drew it taller, which made sense when the hero was centred text; the picture now supplies that height on the right. The badge dot only had a static bloom, which reads as a slightly fuzzy dot rather than a live one. It gains a travelling ring on ::after, animating opacity and transform only — no colour named, composited not repainted, and the global prefers-reduced-motion rule in app.css already covers ::after. Stat sources go from xs to base. At 11px they were the smallest text on the page by two steps; one step would not have been visible. Provider bands gain 21-34, 35-40 and 40+ in place of 20+, so the range the copy now names is selectable rather than folded into a catch-all.
The observer only ever called setCurrent when it found a section in the band, so with nothing there the last match stayed highlighted. Scrolling back over the hero left Services lit.
Sources were xs in Results and base in Problem; both are sm now. The assessment findings line was sm, which is caption size for a sentence that carries the offer — md. Pulse 2400ms to 1300ms.
Long block comments explaining what was tried and why, across the files this session touched. Replaced with one line where a constraint is worth recording and nothing where the code already says it. The Wordmark emitter in the scratchpad produces the shorter docstring too, so a regenerate does not undo this.
A link is current from its section's top to the next linked section's top, so Systems reads as Specialties and Fit and Results as Why Us. The last chapter ends at its own bottom, leaving the form unlit. Replaces the 10% intersection band, which went blank over every unlinked section.
Two crisp 2px rings on ::before and ::after, half a period apart, easing out to three times the dot's size, and the dot breathes to 1.18x on the same beat. Replaces the single blurred ring. New motion.easeOut token.
og.png and logo.png are drawn at 3x and downsampled: PIL draws shapes without antialiasing, so the badge circle was a staircase and the cross a blob. The favicon cross is heavier so it holds at 16px. The header wordmark goes from 28px to 32px, which takes the badge from 9px to 10px and the cross past a pixel on 1x screens.
Chrome rasterises masked SVG content through an intermediate surface, so the header wordmark rendered soft while the nav text beside it was crisp. The ring is now subtracted from the glyph outlines with skia-pathops in the generator, leaving plain filled paths: no <mask>, no useId, and the same drawing in public/brand/cross-dot/.
Six of nine suggested changes. The breach clause deferred entirely to the business associate agreement; 45 CFR 164.410(b) sets a ceiling the agreement cannot exceed, so the statutory 60 calendar days is now stated alongside it. The rest are additions to existing wording, each checked against what the site does: transport is TLS 1.2 or higher and older versions are refused (measured against execil.net); no advertising and no profiling by health condition; email is named as an insecure channel for patient data; the under-18 clause now says what happens if we find such data. Turnstile keeps the existing hedge about browser storage rather than taking the suggested "no persistent cookies" — Cloudflare's own docs say Turnstile can issue a cf_clearance cookie, so that claim would be false. What Cloudflare does state, and what is now quoted, is that the signals are not used to identify, profile or target, and that the cookie is partitioned to one site. Both changed pages get today's date, as the Changes section promises. Retention periods, at-rest encryption and a 30-day response deadline are left out: each asserts an operational fact no one has confirmed.
Squashed from seven commits on feat/header-utility-bar. Prototype G, chosen from eight header variants explored on prototype/header-layouts. Header. The old row carried three groups, and space-between could never centre the nav between a 104px wordmark and a 380px cluster of phone plus CTA. The phone moves to a contact strip, 32px on the Inverse surface, right-justified in line with the CTA below it; below the nav breakpoint the email address drops and the phone stays. The main row is full-bleed, so the wordmark and the CTA each sit a gutter from the viewport edge rather than 160px in at 1600. Nav links are 32 apart, 40 above screen.navWide, and the CTA is held 32 off the last link, 64 above it, so the button reads as an action and not a sixth nav item. Also fixed on the way: the header applied its gutter inside the container where Section and SiteFooter apply theirs outside, which inset the wordmark by a gutter against every section below it. Behaviour is unchanged. The scroll-spy highlight, the pin through smooth scroll, and nav_clicked / phone_clicked / cta_clicked all carry over; the prototype had none of them. The email link is untracked, since the typed event union has no event for it. Hero. The strip made the header 101px while the hero kept its 80px top padding, so its first line fell from 149px down the page to 181px. heroTop is now 32px lighter than sectionMd, exactly the strip, and the badge sits back at 149. The mobile bleed derives from the same token, so the picture still meets the header with no seam. The trust row is a grid rather than a wrapped flex row, which broke wherever content ran out and left the last item unaligned. Three equal columns on desktop, two on mobile. Copy. The provider range is 20+ in all four user-facing places, the glossary entry defining Practice, and the OG card, which is regenerated. Form bands lose 21-34 and 35-40. "Specialty-assigned" is rephrased rather than shortened so the claim survives: coders assigned by specialty is the thing being said, and "specialty billing" would read as billing for a specialty clinic. The hero lead is rewritten to drop a subjectless opening fragment and three parallel promise-clauses. New tokens: space.s64, screen.navWide. Link gains a `utility` variant for the strip, so its small size lives in the variant rather than being pushed through `style`.
Abuubkar
added a commit
that referenced
this pull request
Sep 11, 2026
OG wordmark fix, LinkedIn Page assets, mobile comparison table. Merged with a merge commit rather than a squash. #41 was squashed, so its merge commit had a single parent and none of dev's commits were ancestors of main even though their content was; the merge base stayed at 0b1bac1 and this PR reported 29 commits and a whole-project diff. Recording dev as a parent here reconnects the two histories, so the next release PR shows only its own work. main held nothing dev lacked, so the merged tree is dev's.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Everything on
devsince the last production deploy. 43 commits, so this isgrouped by theme rather than listed.
Before and after
Desktop,
execil.netabove and the dev preview below:Mobile, side by side:
If those do not render, the repo is private and GitHub's markdown proxy fetches
images anonymously, so it cannot reach them. They are also on the
pr-shotsbranch: desktop.png
· mobile.png.
Preview: https://dev-execil.solitary-waterfall-ef5b.workers.dev/
Brand
BrandMarkwas a placeholder, a teal rounded square with a green square insideit. It is now the real wordmark: Satoshi Black outlines with the tittle of the
"i" replaced by a cross badge, drawn from a client reference image.
The letterforms are outlines, not
<text>, because the badge has to landexactly on the tittle at every size and live text cannot promise that across
fallback fonts. The badge's clearance ring is cut into the glyph paths with a
boolean subtraction rather than masked, because Chrome rasterises masked SVG
through an intermediate surface and the header wordmark came out visibly soft.
public/brand/holds five variants plus a README.og.pngand a newlogo.pngare generated at 3x and downsampled, since Pillow draws shapeswithout antialiasing and the badge edge was a staircase.
Hero
New photograph, encoded to WebP at three widths (39 / 68 / 113 KB). On desktop
it fills the right half and dissolves into the wash with a mask; below the nav
breakpoint it becomes a full-bleed band above the copy, faded at the foot so the
headline rises into it.
It is the LCP element on narrow screens, so the home route preloads it with a
matching
srcsetandsizes. Verified on the built site: one request,initiator
link, correct candidate, no double fetch.Imageis a new base component, since raw<img>is forbidden in sections andaltneeds to come frommessages.json.Header
Rebuilt after eight prototype variants. The old row carried three groups, and
space-betweencould never centre the nav between a 104px wordmark and a 380pxcluster of phone plus CTA. The phone moves to a contact strip; the main row is
full-bleed, so the wordmark and the CTA each sit a gutter from the viewport edge
rather than 160px in at 1600.
Two bugs fixed along the way. The header applied its gutter inside the container
where
SectionandSiteFooterapply theirs outside, so the wordmark sat agutter right of the hero copy beneath it. And the nav highlight only updated
when a section was in the observer band, so it went blank over every unlinked
section and stuck on the last match; it now works by chapter range.
SEO
Meta description cut from 215 characters to 140. Titles restructured so each
page emits exactly one, which also lets the 404 carry its own
noindexwhilekeeping its 404 status.
Organization.logonow points at a real logo ratherthan the social banner. FAQPage added. Sitemap gains
lastmod._headerskeeps non-canonical hostnames out of the index with one rule, whichis why this preview returns
x-robots-tag: noindex— verified live.Lighthouse on the build: SEO 100, best practices 100, accessibility 100.
Email delivery and the form
Assessment submissions now go through the Resend SDK from
contact@execil.net, never from a local build. Turnstile verification checkshostname and action and has a siteverify timeout. Deploys run through Cloudflare
Workers Builds with the production step gated.
Legal
Breach notification now states the 60 calendar day ceiling from 45 CFR
164.410(b) alongside the business associate agreement, rather than deferring
entirely to the agreement. Transport is stated as TLS 1.2 or higher, measured
against the apex. Advertising, children and Turnstile wording expanded.
Copy
Provider range is
20+throughout, including the glossary entry that definesPractice and the OG card. "Specialty-assigned" is rephrased rather than
shortened, so the claim survives: coders assigned by specialty is the thing
being said. Hero lead rewritten.
13 wksbecomes13 weeks.Worth a look before merging
says 20+. Everything user-facing follows it, but
docs/research/rcm-problem-statistics.mdstill scopes its statistics to1-20 provider practices, and rejects hospital surveys specifically because
"a 1-20 provider practice has no discharges". Most of the reasoning survives
at 20+, but the sourcing notes now describe a narrower audience than the page
claims.
docs/design/rcm-landing.dc.htmlalso still reads 1-20.Four suggested additions were deliberately left out, because each asserts an
operational fact nobody has confirmed: retention periods, at-rest
encryption, TLS on the practices' own systems, and a 30 day response
deadline.
wwwto apex redirect, and the Google Business Profile decision.
www.execil.netcurrently answers 200 rather than redirecting.
Checked
pnpm checkandpnpm buildpass. Header, hero, trust row and wordmarkverified at 1600 / 1366 / 1040 / 375 in headless Chrome, and the wordmark
pixel-checked at 2x in both the header and the footer.