feat: SaaS hero and verified-core redesign, Impact Estimator, internal-link validation in the broken-link scan - #258
Merged
Merged
Conversation
The client rejected hero-app-platform.webp. Its core fault was meaning, not craft: a rising line chart promises revenue growth on a page that sells container security, and nothing in the render was about security at all. Four regenerations could not shake the stock-illustration read, because an illustration OF security is generic by nature. Competitor research settled the direction. Chainguard's homepage hero is a row of real image cards carrying FIPS VALIDATED / STIG HARDENED badges and live CVE-reduction counts; Docker Hardened Images does the same with OS / Architecture / Compliance rows. Neither market leader uses an illustration. Product truth is the convention here. So the artifact is now a column of real CleanStart images: nginx at the edge, redis for cache, postgres for data, node for the runtime, which is a SaaS service stack rather than a list of popular images. The finance page could not run this artifact. Every claim is sourced from images.cleanstart.com. All four images exist in the 947-image catalogue, verified against a control (a real detail page returns ~210KB, a non-existent one ~81KB). "Security Hardened", "FIPS Available", SBOM, Signature and Provenance are badges and tabs that catalogue publishes. Per-image CVE counts are deliberately absent: the catalogue does not publish them, so any number would be invented. Built as a column rather than the overlapping fan tried first — the fan's negative margins made height depend on card content, and it stood only 185px tall in a 627px hero. It now measures 440x411, with a 163px gap to the headline at 1280 and no horizontal overflow. Hidden below xl as before, so mobile loses nothing. lint, typecheck and build pass.
Replaces the stack-card column with the dashboard the page actually wanted: a light product surface floating on the dark hero, which is the familiar SaaS hero move. The difference from the render the client rejected is the content — that one led with a rising line chart, which promises revenue growth on a page selling container security. There is no chart here. The panel shows a catalogue: three figures, then four images with their hardening state. Brand and depth, per feedback. The panel sits in perspective (rotateY -13deg, near edge on the left so it turns back toward the copy), seated on a blue-to-violet bloom so a white surface does not read as a cut-out on the mesh. Status chips moved off a conventional green — the one colour on the panel that could belong to any dashboard anywhere — onto CleanStart blue for hardened and violet for FIPS, with a blue keyline over each figure. A dark provenance card is pushed forward on Z over the lower-left corner: it breaks the rectangle, gives the composition a second tone, and carries SBOM / Signature / Provenance. The artifact wrapper now sets z-index 1. The hero's bottom fade is a later sibling and was painting white over the panel's lower third; the old 3D render wanted that wash, but a UI panel with dissolving edges reads as a rendering bug. Verified the nav mega menus still open above it (menu positioner is z-50 in the root stacking context). Figures are the ones images.cleanstart.com publishes (947 images, 34%+ average CVE reduction, 4M+ packages from verified source) and the badges are the ones its detail pages carry. Per-image CVE counts are absent on purpose: the catalogue does not publish them, so any number would be invented. Measured at 1440: panel 520x341 spanning y 113..464, no horizontal overflow, and none at 1280 either. Hidden below xl as before.
The same container image twice on one baseline at one scale: the public base image on the left, the CleanStart rebuild on the right. The right stack is a fraction of the height, and the packages stripped out of it drift away above, so the picture reads as subtraction rather than as two unrelated objects. This replaces the product panel, which the page did not want. That one put a SaaS dashboard forward as the subject, and this page sells container security TO SaaS companies: the container is the subject and SaaS is the audience. SaaS now stays a light touch, the way the finance sibling is only "finance" through a small bank glyph in its backdrop. Chosen over the alternatives on legibility. It is one idea, readable in about a second, where the hexagon lattice, the component constellation, the catalogue panel and a cutaway concept carrying eight simultaneous ideas all needed decoding, which a hero beside a large headline does not get. Contrast also comes free: a bright minimal object on a dark hero is the natural composition, where a dark cross-section would fight the mesh. Two signals, not one. Size carries the headline idea, and order carries it again up close: the public image gets jittered package heights and baselines so it reads as unruly, the rebuild gets three identical blocks per band so it reads as governed. Size alone was not doing enough work in the first pass. Package layout is deterministic (Math.imul mixing, not Math.random) so server and client agree and hydration does not mismatch. CVE marks are three, muted and round: the finance risk-chain section already signals vulnerabilities with red and amber triangles and the two industry pages should not trade the same mark. No figures are drawn on it — a number beside two stacks invites reading the height difference as that ratio, and this is an illustration, not a chart. Width is a deliberately narrow clamp. Fixed at 560 left only 43px between artifact and headline at 1280; clamp(520px, 40vw, 560px) gives 83px there and the full size from 1440 up, while the 10px labels only move to 10.8px across that range. Hidden below xl as before. lint, typecheck and build pass.
Both halves of the H1 now appear at once. Every earlier attempt showed
only one of them: the catalogue panel and the product dashboard were
SaaS with no container, and the attack-surface comparison was containers
with no SaaS — and its flat bands did not read as container images
either, they read as generic block grids.
A SaaS application window sits just above an isometric container image,
with a soft contact shadow so it is plainly running ON it. The container
is the subject: largest object, holds the verified seal. The app is the
context, and says SaaS through tenant rows, avatars and status pills
rather than charts, since a rising line on this page promises revenue
growth and that is what got the original render rejected.
Same formula as the approved financial services hero (industry surface
as context, container as subject), which is right for a page family.
What differs is the industry signal and the composition: that one
magnifies a container inside a finance console, this one seats an app on
top of an image.
Three fixes after seeing the first pass on the page:
- The container was one solid box and read as a plinth, which is the
"app on a platform" idea already rejected once. It is now three
stacked layers with a 6px reveal between them and bright leading
edges, so it reads as an IMAGE rather than a slab.
- The contact shadow was a flat dark ellipse and looked like a hole
punched in the top face. It is now a soft-edged radial.
- A "VERIFIED CONTAINER IMAGE" caption landed 60px inside the hero's
white bottom fade, where 55%-white type is invisible. The artifact is
taller than the dark band above that fade, so it could not just move
up; the caption is gone and the object carries itself.
Measured: 560x422 at 1440 with 130px to the headline, 520px wide with
83px at 1280, no horizontal overflow at either, hidden below xl.
lint, typecheck and build pass.
The artifact was gated at xl, so anyone on a 768-1279px window saw a headline and a CTA against an empty right half. It now renders from md and is hidden on phones only, where there genuinely is no room beside the headline and a decorative artifact should drop rather than stack. Making it work below 1280 means scaling both sides, since the artifact and the copy compete for one row. The artifact goes to clamp(280px, 38vw, 560px): 286px at 768, 386px at 1030, 542px at 1440. It is pure geometry with no type in it, so it survives scaling in a way the earlier caption-bearing versions would not have. The copy column now yields room via calc(100% - clamp(280px,38vw,560px) - 56px), so its budget tracks the artifact instead of being re-guessed per breakpoint. 100% of the padded container, NOT 100vw: vw includes the scrollbar, which handed the copy 15px it did not have and left only a 24px gap to the artifact at 1030px. With 100% the gap is a steady 56px through md and lg, widening to 148px at 1440 where the tighter xl cap takes over. Measured: 768 gives a 3-line headline, 286px artifact, 56px gap; 1030 gives 386px and 56px; 1440 gives 542px and 148px. No horizontal overflow at any width, still hidden at 375. lint, typecheck and build pass.
Two problems, one cause. The headline is short, so its text ended at
533px while the artifact began at 844px — a 310px hole in the middle of
the hero against a 40px margin on the right. And the drawing itself was
mostly padding: the subject occupied 264px of a 520px viewBox, so much
of what sat on the right was empty space rather than illustration.
Filled the frame:
- Two dim satellite stacks flank the subject. They carry a real idea
as well as ballast: this is one image out of a catalogue, not a
single artifact. Dropped well back in opacity so they read as depth
rather than as three competing objects.
- A masked dot field gives the frame texture and fades out at the
edges, so it stops rather than being sliced by the viewBox.
- The container stack and the app window are both larger, and the
layer geometry widened with them.
Then moved the whole artifact off the right edge. `right` was a flat
40px mirroring the copy's left padding, which was correct on paper and
wrong on screen; it is now clamp(24px, 4.5vw, 76px), and the width went
from 38vw/560 to 42vw/600 now that the extra box holds content instead
of air.
Measured at 1440: the visual gap drops 310px to 229px and the right
margin rises 40px to 64px, which is a far better balance for a
right-weighted composition. 1030 gives a 426px artifact with a 42px box
gap, 768 gives 316px with 54px and a 3-line headline. No horizontal
overflow at any width, still hidden at 375.
lint, typecheck and build pass.
The base was a bare gradient plane, which read as unfinished rather than
restrained: a material with no surface. Two additions fix that, and both
are structural rather than decorative.
Corrugation ribs on every visible side face. This is the single
strongest cue that the object is a CONTAINER rather than a stack of
slabs, and it is the same ribbing the Clean Images artifact already uses
elsewhere on the site. Vertical on screen, because the isometric
projection maps height straight down. Satellites take ribs={0}: at their
rendered size the lines would only turn to mush.
Surface treatment on the one visible top face: an isometric grid echoing
the hero's own gridline overlay, an inset edge line for craft, and a
soft sheen so it reads as a lit material.
Deliberately no logo, which was the alternative considered. This is the
top of a container IMAGE, and a mark stamped on it turns the object into
branded packaging — a cheaper read, and the 3D-render-with-logo look the
client rejected in the first place. The app window also covers the middle
of that face, so a centred graphic would be half-occluded and look like a
defect rather than a decision.
lint, typecheck and build pass.
The previous commit put shipping-container corner castings at the base.
Wrong register: this is a container IMAGE, and literal freight hardware
turns it into a box of cargo. Removed.
Replaced with signals from the software vocabulary, so the bottom layer
reads as the hardened base image the rest is built on:
- A digest strip along its front face: a short run of uneven marks
standing in for a content hash, which is what actually identifies
and pins an image.
- A violet underglow beneath the base, in the same violet as the
verified seal in front of it, so the foundation and the seal read as
one statement rather than two unrelated marks.
lint and build pass.
The verified mark at the stack's front corner was a disc with a tick; it is now a shield carrying the same tick. Same position, same violet, one clearer read. It is deliberately the ONLY security mark on the artifact. A second shield laid flat on the top plane was built and pulled: two marks say the same thing twice, and a symbol foreshortened into the isometric plane reads as a sticker dropped on the surface, where one facing the viewer reads as a seal applied to the whole image. The lit occupancy cells briefly added to the top face came out with it. One idea per surface — the plane keeps its isometric grid, sheen and inset edge, which give it material without competing with the seal. Worth noting for later: the FIPS page owns the shield as its signature object (shield-glow.webp, shield-only.webp). At this size, inside a container stack and rendered flat rather than as a 3D hero object, the two do not collide — but if FIPS and SaaS ever sit side by side in the same view, this is the element to re-check. lint and build pass.
"Modern Applications Move Faster. Security Must Keep Up." replaces the SEO team's "Container Security for SaaS Companies". The title tag still carries that phrase, so the page keeps the keyword in the SERP; the H1 no longer contains it. Same trade the financial services page made, and the same parallel construction. Each sentence gets its own block span. Left to wrap naturally the break landed mid-clause — "Move Faster. Security" sharing a line — which destroys the parallel the headline is built on. Same treatment FinanceStack's heading uses, and not a <br>. Verified in the built HTML: one h1, reading "Modern Applications Move Faster.Security Must Keep Up.", with the title tag unchanged. lint, typecheck and build pass.
New direction from the client, with a reference: no container and no base object. A central application window carrying the complexity of a real product, with the pieces it is built from floating around it at varying depth and angle. Built in code rather than as an image. The style is rounded rectangles, flat fills, small glyphs and slight rotations, which is native to SVG — an easier build than the isometric container it replaces, where every vertex was projected by hand. Code also holds exact brand colour, stays crisp at every DPR, costs a few KB rather than a raster, and sidesteps the reference asset itself: it is a free stock vector, so it is attribution-bound and already on thousands of sites. The security story moved into WHAT floats. The six cards are not generic UI chrome; they are what a SaaS application is assembled from — source, base images, packages, libraries, a signature digest — and three carry a verified tick. That is what keeps this container security rather than generic app development, and it is why the composition belongs on this page rather than on any other landing page using the same reference. Dashed tethers run from each card back to the window. Without them the cards read as unrelated confetti; with them every piece plainly belongs to the application in the middle. The reference's flourishes (light rays, bubbles, scattered gears) are deliberately left out. That decorative layer is the stock-vector look the client rejected in the original render. Measured: 599x470 at 1440 with a 67px gap to the headline, 531px at 1280 with 55px, 316px at 768 with 54px, hidden at 375. No horizontal overflow at any width. lint, typecheck and build pass.
The first pass sat six tidy, well-separated, identically-styled cards
around the window and read as far too simple beside the reference. Four
things were missing, and all four are what create the density:
- Cards now OVERLAP the surface and each other, some in front and some
behind. Separation was the main reason it looked sparse.
- Cards are TRANSLUCENT, so the surface reads through them. That is
what makes depth rather than a flat collage.
- Real object VARIETY instead of one card repeated: a code badge, a
cog panel, a package, an avatar row, a dense manifest, an image
grid, and a selection frame with corner handles.
- Fine internal detail — thin line runs and small bars — so each piece
looks like it contains something. Plus rays behind the subject.
Fixed while building: the ray gradient was inverted. Its stops lit the
far tips and faded toward the centre, which turned the fan into a dark
crown ringing the surface instead of light coming from behind it. The
polygon runs tip-to-base up the bounding box, so the stops had to be
swapped.
The reference's pink and teal palette and its scattered bubbles are not
copied — brand blue, cyan and violet, and no confetti. That decorative
layer is the stock-vector look the client rejected in the original
render.
Measured at 1440: 599x475, clear of the header at 51 vs 73, 67px to the
headline, no horizontal overflow.
lint, typecheck and build pass.
…frame The surface was grey bars on white. Looking at it large, the specific faults were: stat tiles holding no value, so they read as empty placeholders; five identical list rows; a rail of featureless grey blobs; and no header or primary action anywhere. Nothing in it had hierarchy, and almost nothing had colour. What makes a UI look real is hierarchy and colour, so the surface now has a header with a filled primary action, rail icons that are actually drawn (grid, list, box, shield, cog) with one active in brand blue, three stat tiles each carrying a value bar under a coloured marker, and a list whose rows differ from one another: different chip tints, two-line entries, matching status pills, and one selected row with a tinted background and an accent rail. The floating pieces were also all one fill, which flattened them into a single material. Each now has its own tint — violet on the selection frame and the package, cyan on the cog panel and the code badge, blue on the avatar and image cards, deep navy on the manifest — so they read as different kinds of thing rather than seven copies. Still no numerals or words anywhere. The artifact scales from 599px down to 316px, where real type would be illegible, so hierarchy is carried by weight, size and colour instead. lint, typecheck and build pass.
More inside the surface. A tab strip with an active underline, a
secondary header chip beside the primary action, and a side panel so the
body is no longer one flat column — the panel carries four colour-coded
entries and a filled progress bar. The surface itself is wider (312 from
288) to hold the second column without crowding the list.
More around it. Two new pieces: a terminal, dark with traffic lights and
prompt marks, which is the one element that says a human builds this;
and a dependency graph, the one piece that is a relationship rather than
an object, which is what a supply chain actually is.
Depth is now built three ways rather than assumed:
- The back row is blurred through a single shared feGaussianBlur, so
those cards sit BEHIND the surface rather than merely under it.
- Dashed tethers run from each piece to the centre. Without them the
pieces read as confetti; with them each one belongs to the
application.
- A fixed particle field fills the corners the cards never reach.
Fixed, not random: this renders on the server and must match on the
client or hydration mismatches.
Still no numerals or words. The artifact scales to 316px at 768, where
real type would be illegible, so hierarchy stays carried by weight, size
and colour. Verified at that width: 316x251, 54px to the headline, no
horizontal overflow.
lint, typecheck and build pass.
The violet package card sat in the blurred back row, where it washed out behind the surface. It is now in the front row: sharp, and overlapping the window's right edge rather than hiding behind it. Placed first within that group rather than last, so the manifest below still crosses over it. Dropping it at the end would have flattened the right side into a single front plane; this way the stack keeps three depths — blurred back, this card, then the manifest across it. lint and build pass.
The verified ticks now follow a rule instead of being distributed for visual balance: they mark source, container images, packages and the signed manifest, and nothing else. Adding one to the source badge completes the set at four. The users panel, the build config and the terminal stay unmarked on purpose. CleanStart verifies components, not your tenants or your CLI, and a tick on those would be a claim the product does not make. This also keeps the artifact saying two things at once, which is what the page needs. Its own sections frame this complexity as risk — AI generated code, open source dependencies, public container images, component visibility — so the composition has to read as "look how much is packed in here" AND "the parts that came from outside are accounted for". Ticking everything would collapse it to the first half and turn the marks into wallpaper. lint and build pass.
Two new pieces, each chosen for a kind of structure the set did not
already have rather than for another rectangle:
- A source tree. Indentation is the point: it is the one card whose
shape says "this is nested", which no flat run of bars can express.
- An image layer stack. Narrow bars for thin layers, wide for fat
ones, each with its size read off to the right — the one card that
shows a container image's internals rather than referring to it.
Two existing cards gained internal detail: a toggle row on the config
panel, and a second smaller entry with a status dot on the users card,
so neither reads as a single idea repeated.
Placement corrections after looking at both in situ:
- The tree was on csa-deep at y=2, which put a near-black card against
a near-black background at the frame edge. Moved down so it overlaps
the surface top, and onto csa-blue so it is actually visible.
- The layer stack ran to y 454 in a 460 frame before its rotation was
applied, so a corner crossed the edge. Lifted.
Eleven pieces now. Verified at 1440: 599x475, 67px to the headline, no
horizontal overflow, nothing crossing the viewBox.
lint, typecheck and build pass.
A 3px vertical drift on the terminal and the code badge, and nothing else. The point is to stop the artifact reading as a flat print, not to animate it: a hero that keeps moving competes with the headline beside it. Both pieces are isolated in the composition, so 3px never carries one into another. The pair run at 6s and 8s with a negative delay on the second, because started together they bob in lockstep and the whole image reads as wobbling rather than two things floating. Applied to WRAPPER groups that carry no transform of their own. The cards are positioned with SVG transform="rotate(...) translate(...)" attributes, and animating transform on those would fight the attribute; a bare wrapper has nothing to collide with and needs no transform-origin. Inside the existing prefers-reduced-motion: no-preference block, so reduced motion gets no drift and the wrappers stay untransformed. Verified by driving the animation clock by hand: the preview pane holds the page visibilityState hidden, which freezes currentTime, so measuring position over wall-clock time reports no movement even though the animation is attached and correct. Stepped through the timeline instead: the terminal sweeps -3 to 0 to +3 across its 6s cycle and the badge runs offset on its 8s one. lint and build pass.
Drop the dashed socket and the grey base rail: the tiles stand alone until Verified Components lands, then the green line draws. Swap the stage icons for lucide glyphs at 40% of the tile (hammer, flask, rocket, shield with magnifier, checked package); none of the pipeline glyphs carries a check, since the badge that lands on each tile is the check. Restructure the flight into glide, hover, absorb and descend so the chips are legible for about two seconds and fold into the hero before touchdown. Drive the phase clock from animation frames with elapsed-time beats: timers in a background tab fire in one-second bursts, which fast-forwarded the phases while the transitions sat frozen. Add a fake-timer test that pins the beat order and the absorb-before-descend invariant.
Drop the noindex,nofollow pair, list the path in the sitemap's STATIC_ROUTES and restore the Solutions > By industry nav row. Breadcrumb, JsonLdGraph and pageRegistry row were already in place. Render the Outcomes band as one responsive tree instead of a desktop and a mobile copy, so each H3 exists once in the DOM.
jadhavgaurav
force-pushed
the
development
branch
from
September 2, 2026 11:10
ce82115 to
6a75632
Compare
…rity Must Be Smarter."
…logy Nav label becomes Modern Technology. The page had not reached main, but /industries/saas-container-security resolved publicly (noindex) for two days, so it 301s to the new path; the short /saas-container-security 301 now points straight at the final URL so nothing chains. Breadcrumb, metadata path, sitemap entry and JSON-LD path follow. The pageRegistry row keys on path and must be updated in prod to keep the WebPage node in the graph.
…am's title and description The SEO doc specifies URL /modern-applications, title "Modern Application Security | CleanStart" and a new description; all three applied verbatim. The page leaves the /industries segment; nav label stays Modern Technology. Both legacy 301s (/saas-container-security and /industries/saas-container-security) point straight at the final URL. The intermediate /industries/modern-technology never deployed, so it gets no redirect. The pageRegistry row keys on path and must be updated in prod to keep the WebPage node in the graph.
Final URL, back under the /industries segment beside the finance page. Nav label becomes Modern Applications. The page was never indexed or linked under its earlier slugs, so the two saas-container-security redirects are removed rather than repointed. The pageRegistry row keys on path and must be updated in prod to keep the WebPage node in the graph.
A cloud reads as hosting. The layers mark is the one the page's own hero uses for Verified Components, so the menu row and the page share a symbol.
…the page The page never produced a currency figure. It scores an Operational Burden and reports reductions and recovered hours, so it is now /impact-estimator, with a 308 from /roi-calculator (never indexed: still noindex,nofollow, nav built:false). Page work, in the order it matters: - Sticky inputs beside the results on desktop; a fixed live summary strip on phones while the gauge is below the fold. - Inputs round-trip through the URL (url-state.ts, unit-tested). "Copy link to results" and "Book a demo with these numbers" under the hours card. - "What drives your score" renders the model's contribution breakdown, and the gauge carries its Operational Burden Score caption. - Chain link 1 is "Inherited base-OS packages", so the cut marker is honest. - Impact Estimator eyebrow, tier-aware rail labels, both cadence controls run least-frequent first, defaults moved mid-band (200 images, 40 engineers), hours-per-week stat replaces the duplicated FTE figure. - Two-column KPI grid on phones, 3-up stats that never wrap 2 plus 1, 11px minimum labels, release accent from the tier palette, no em-dashes anywhere in the page's copy or comments. Scoring, bands and the client-owned metric names are untouched; model.test.ts still pins them. New Playwright spec covers link adoption, address-bar sync, invalid-link fallback, clipboard contents and the mobile strip.
Rebuilt /compare/cleanstart-vs-docker-hardened-images from scratch against
the new SEO source doc ("Docker Hardened Images vs CleanStart - Final"). It
replaces the 2026-07-31 build, which was never signed off: the new doc has a
different meta title, a five-section outline instead of fourteen, a 20-row
matrix grouped into four categories instead of a flat 15-row one, and eight
FAQs.
Six bands in the site's light/dark rhythm, one per document heading:
CompareHero title, standfirst, CTA pair, and a new
CompareFoundationStacks diagram drawing the one
asymmetry the page turns on: Docker's stack has an
inherited parent block, CleanStart's has an empty
frame where that parent would be
CompareFoundations the two platforms side by side
CompareMatrix the capability table
CompareBuildFlow both build pipelines on one baseline, on dark
CompareDifferentiators three corner tiles on the existing 3D icon set
CompareFAQ single-open accordion
CompareCTA the footer-slot card
The matrix is one real <table> (caption, column headers, scope="colgroup"
group rows) that reflows to labelled cards below lg rather than shipping a
second mobile copy of twenty rows. Its column head is sticky, which is why
the card wrapper uses overflow: clip and the section moves overflow-hidden
onto its own decoration layer: either one would otherwise have become the
scroll container that sticky resolves against.
Copy is the document's, verbatim, with one class of edit: two em-dashes
became colons per the house writing rule. Heading levels are the document's,
shifted one for the page title, giving 1 H1, 6 H2 and 3 H3 plus the eight FAQ
questions. Vendor names, matrix group labels and the "focuses on" lead-ins
stay non-headings.
Every string, the matrix and the FAQ live in compare-data.ts; the matrix
counts and the FAQPage JSON-LD both derive from it. CTAs are the stock
cs-btn-blue and cs-btn-ghost pair at the sizings SaasHero and PricingHero
already use.
Still noindex,nofollow and de-listed from app/sitemap.ts pending sign-off.
Row 12 went back to reading "ROI Calculator" in e737c0f. Two sessions were editing this file at once and the losing write happened to be the one that got committed; nothing about the rename itself changed, only its entry in the inventory. Restored verbatim from the version that was on disk before the race.
…scale The simulator section carried overflow-hidden to clip a decorative blob, and an overflow-clipping ancestor turns position: sticky off, so the inputs card never pinned. The blob now lives in its own clipped absolute layer and the section no longer clips. A Playwright test scrolls the row and asserts the pin offset. Scale is brought down a notch across the results column (gauge 240 to 208px, tier heading h2 to h3, KPI and hours figures smaller, tighter card padding and control spacing) after the client found the page too large at 1440. The "What drives your score" breakdown is removed at the client's request; the gauge, tier blurb and tooltip still explain the score.
Drop the built:false flag so the Capability column lists it beside Vulnerability Remediation and Attack Surface Reduction, on desktop and in the mobile drawer. It takes the tools glyph rather than sharing CleanSight's radar. The page itself stays noindex,nofollow and out of the sitemap until sign-off; this only makes it reachable from the nav.
A half-dial with a needle, drawn in the set's 24px / 1.6 stroke idiom. It is the page's own radial gauge reduced, so the menu row and the page share a symbol; "tools" read as a how-to and "radar" already belongs to CleanSight.
/cleanstart-platform was never completed. It shipped noindex, absent from nav-config.ts and de-listed from the sitemap, so it had no rankings, no internal links and no sitemap entry to lose. Removing it rather than leaving a half-built route in the tree; git history is the archive for the rebuild. Deleted: the route, the five `cleanstart-platform` section components, and 13 of its 14 image assets. The fourteenth, cta-cube-textured.webp, moves to public/images/teams/ because TeamsCTA was its only other consumer, and it would otherwise have been the sole survivor of a directory named after a page that no longer exists. Also dropped the page's entry from public/llms.txt, which was still pointing AI crawlers at it, and replaced the sitemap's "de-listed pending completion" note with a pointer to history. No redirect is seeded: the URL was never indexed or linked. It did resolve publicly, so register a 301 in the CMS redirects collection if traffic to the bare URL shows up.
The shared Accordion panel styled every descendant link for prose FAQs: underline plus hover:text-foreground. Its only consumer is the mobile nav drawer, which sits on a dark sheet, so a hovered row's text dropped to the dark foreground token and vanished. The drawer already fought the underline with an !important override. Remove the link rules at the source and the override with them; the row's own hover:text-white now wins.
The demo form does not read the estimator inputs, so "Book a demo with these numbers" promised something the form could not deliver. Dropped. The copy-link button leaves the hours card, which is back to its compact shape, and sits on the trust line: text left, blue CTA right, stacked only on phones. Blue rather than glass because the band is light grey and glass is built for dark panels.
…e SEO
Go-live flip: noindex and nofollow dropped, route added to the sitemap's
STATIC_ROUTES, WEB-PAGES row set to live.
On-page fixes from the review:
- Description cut from 228 to 158 characters so it survives the snippet.
- H1 names the subject ("hardened container images"), not "runtimes".
- Heading outline no longer skips a level: the inputs card title is an h2 and
the results column gets a screen-reader h2, so the chain's h3s nest properly.
- The body had zero internal links. Two contextual ones added: the trust line
links to /book-a-demo, the chain banner to /cleanstart-images.
Title (59 chars), canonical, OG/Twitter cards and the breadcrumb node were
already correct. The WebPage node needs the CMS pageRegistry row, seeded in the
companion cms commit and applied on the droplet after deploy.
Static row with webPageType WebPage so the page emits its WebPage node, the same shape as the other Solutions pages. Order 49 sits after every seeded row so no existing dashboard position moves. Apply on the droplet after the next cms deploy: pnpm exec tsx --env-file=.env scripts/seed-page-registry.ts --dry-run pnpm exec tsx --env-file=.env scripts/seed-page-registry.ts
…us questions Two sections after the compounding chain: - "How the estimate works": the four-step scoring pipeline in prose, with a weight bar, the count bands and the Runtime Complexity tier strip. Every number is read from model.ts (SCORE_WEIGHTS and TIER_SUMMARY, new read-only exports), so the description cannot drift from the calculation the tests pin. - "Questions about the estimate": six questions as a single-open accordion in the compare page's recipe, with answers kept in the DOM and a matching FAQPage node in the page graph. The chain section drops its footer-overlap padding now that the FAQ is last. Word count moves from about 415 to about 1,090; the outline is h1, five h2s, nested h3s.
…t Estimator Removed at the client's request. The FAQ stays and still carries the methodology in plain words; the chain section still explains the mechanism. The model's read-only weight and tier views go with it, since the section was their only consumer. Page order is now hero, simulator, chain, FAQ.
Questions from the h5 token to 15.5px, answers to 14px with a 62ch measure, row padding and gaps tightened, the chevron and radius smaller, the heading one step down and the section's top padding at the small token. Same accordion, same FAQPage node, about a third less height.
Added in the SEO pass without sign-off; removed at the client's request.
Contributor
Author
|
Merged past the bundle-budget ratchet on purpose. The +27.5 KB on /vulnerability-remediation is Turbopack emitting the framer-motion runtime twice after the platform page removal (cb7448b), not new code. Tracked in #260; the ratchet will keep failing until that is resolved or the baseline is knowingly updated. |
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.
Ships the SaaS hero/verified-core work plus one CMS fix that came out of the crawl-audit investigation.
SaaS page (31 commits)
The SaaS hero artifact rebuild and the verified-core pipeline redesign, with their plan and spec docs, plus removal of the SaaS late-review route. Layering, stage icons and grid/scanner behaviour are locked by tests.
Broken-link scanner: validate hand-typed internal links
extractAllLinksonly emitted absolute http(s) URLs, so a site-relative path an editor typed into rich text was dropped before the SSRF guard ever saw it. The slug-change hook does not cover these either: it follows internal-doc relationships, and a typed path carries none. Nothing in the system validated them.Three shipped as live 404s in published bodies and went unnoticed until a manual crawl:
/guide/orchestrationand/guide/securityon the container-security guide/images/redis/detailson the redis comparison postRoot-relative paths are now resolved against the public origin so they are HEAD-checked like any other link.
Three edge cases handled deliberately:
//other.example/pathis a foreign host; prefixing our origin would retarget it at ourselves and report a broken external link as healthy. This is the subtle failure mode in the change, so it has its own test.mailto:andtel:still fall through to the SSRF guard and are dropped.scan.tsalready dedupes to one HEAD-chain per unique URL across all documents, and resolving means/pricingandhttps://www.cleanstart.com/pricingcollapse into a single check rather than two.This reverses the contract the old
drops site-relative URLstest pinned. That test is rewritten to assert the new behaviour and explain why it changed, with four more added for the body path, the protocol-relative guard, dedupe collapsing, and the anchor/mailto/tel cases. Both guards were mutation-tested: removing the//check fails 1 test, reverting to drop-relative fails 3.Expect the
brokenLinkscount to jump on the first nightly run (04:30 UTC). That is the fix working, not a regression: it will surface pre-existing internal 404s that were previously invisible.Notes
8f458310with a realpnpm install, since the local tree is dirty.SaasShiftLeft.test.tsx,SaasVerifiedCore.module.css,SaasVerifiedCore.tsx.web lint ✓ · web typecheck ✓ · web build ✓ · cms lint ✓ · cms typecheck ✓ · cms test ✓ (1916)
Impact Estimator (12 commits,
e737c0f2tofefdca31)The ROI calculator is renamed and rebuilt as the Impact Estimator at
/impact-estimator. The old path 308s to it.Why the rename. The page never produced a currency figure. It scores an Operational Burden and reports reductions and recovered hours, so the name now says what it does.
Page work
overflow-hiddenhad been silently disablingposition: sticky, so the blob is clipped in its own layer now); a fixed live summary strip on phones while the gauge is below the fold.url-state.ts, unit-tested), with a "Copy link to results" button on the trust line.model.test.tsstill pins them against the client's sheet.Navigation and SEO
gaugeglyph.noindex,nofollowdropped, route inSTATIC_ROUTES, description shortened to 158 characters, H1 names "hardened container images", heading outline no longer skips a level.pageRegistryrow seeded in code (7ddcad6e) and already created in production (id 44), so the page emits its WebPage node on first deploy. Nothing to run post-merge for this.Verification. New Playwright spec
tests/e2e/impact-estimator.spec.tscovers link adoption, address-bar sync, invalid-link fallback, clipboard contents, the mobile strip and the sticky pin (10 passed, 2 skipped by design). web lint ✓ · typecheck ✓ · build ✓ · tests ✓ (65) · cms lint ✓ · typecheck ✓ · build ✓ · seed test ✓.