markup-landing-page — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited markup-landing-page (Agent Skill) and scored it 96/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 1 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 1 flagged
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
A landing page has one job: turn a stranger into a buyer or trial user; everything else is decoration. This skill covers a token-level design system with automatic dark/light mode, a section order that walks visitors to the buy decision, copy rules against AI-generic language, and the SEO/structured-data baseline.
Pair with content-voice, which owns how the words sound; this skill owns structure, design system and conversion mechanics.
Use the lightest stack: heavy frameworks slow time-to-interactive, hurt SEO and add maintenance to what should be ~80% prerendered HTML.
Recommended: SvelteKit + adapter-static (or Astro) for prerendered HTML (zero JS on most pages, full SEO, instant LCP); plain CSS custom properties + prefers-color-scheme for free theming; system font stack system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif (zero font load, native feel); TypeScript in <script>; Vite; Cloudflare Pages or Vercel (static + edge functions).
Avoid unless needed: React + Next.js for a marketing site (hydration cost); Tailwind for a single-domain landing (class soup, scattered tokens); GSAP or Framer Motion for fade-ins (CSS does it in 4 lines); web fonts (system fonts beat 90% of imports on Apple devices); Radix, shadcn and other component libraries (landing pages need brand-specific UI).
All tokens live in one CSS file as :root custom properties read via var(--token); hardcoded colors, spacing or radii in components are a smell.
:root {
/* Neutrals — Apple-style cold grays */
--gray-50: #fbfbfd;
--gray-100: #f5f5f7;
--gray-200: #e8e8ed;
--gray-300: #d2d2d7;
--gray-400: #aeaeb2;
--gray-500: #86868b;
--gray-600: #6e6e73;
--gray-700: #48484a;
--gray-800: #3a3a3c;
--gray-900: #1d1d1f;
/* Brand accent — ONE primary, use shades */
--accent-50: #eef2ff;
--accent-100: #dbe4ff;
--accent-200: #bfcfff;
--accent-400: #6d8aff;
--accent-500: #4a6cf7; /* primary — buttons, links */
--accent-600: #3451d1; /* hover */
/* Semantic — what components reference */
--bg: var(--white);
--bg-muted: var(--gray-100);
--card-bg: var(--white);
--text-primary: var(--gray-900);
--text-secondary: var(--gray-600);
--text-muted: var(--gray-500);
--border: var(--gray-200);
--border-light: var(--gray-100);
--on-accent: #ffffff; /* does NOT invert */
--nav-bg: rgba(255, 255, 255, 0.72);
--green-500: #30d158; /* privacy/security trust signals */
}:root {
--font-sans: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI',
Roboto, 'Helvetica Neue', Arial, sans-serif;
--font-mono: 'SF Mono', SFMono-Regular, ui-monospace, 'Cascadia Code', Menlo, monospace;
--text-xs: 0.75rem; --text-sm: 0.875rem; --text-base: 1rem; --text-lg: 1.125rem;
--text-xl: 1.25rem; --text-2xl: 1.5rem; --text-3xl: 2rem; --text-4xl: 2.5rem;
--space-1: 0.25rem; --space-2: 0.5rem; --space-3: 0.75rem; --space-4: 1rem;
--space-5: 1.25rem; --space-6: 1.5rem; --space-8: 2rem; --space-10: 2.5rem;
--space-12: 3rem; --space-16: 4rem; --space-20: 5rem; --space-24: 6rem;
--radius-sm: 0.5rem; --radius-md: 0.75rem; --radius-lg: 1rem; --radius-full: 9999px;
--shadow-sm: 0 1px 2px rgba(0,0,0,0.04);
--shadow-md: 0 4px 12px rgba(0,0,0,0.06);
--shadow-lg: 0 12px 32px rgba(0,0,0,0.08);
--transition: 0.2s ease;
--nav-height: 56px;
--max-width: 1100px;
}Non-negotiable. Implement once at the token level — components shouldn't know which mode they're in.
@media (prefers-color-scheme: dark) {
:root {
--bg: #000000;
--bg-muted: #0a0a0a;
--card-bg: #111113;
--text-primary: #f5f5f7;
--text-secondary: #a1a1a6;
--text-muted: #6e6e73;
--border: #2a2a2e;
--border-light: #1c1c1f;
--nav-bg: rgba(0, 0, 0, 0.72);
--accent-500: #6d8aff; /* brighter for contrast */
--shadow-md: 0 4px 12px rgba(0,0,0,0.4); /* heavier on dark */
--shadow-lg: 0 12px 32px rgba(0,0,0,0.6);
}
}Rules:
--on-accent does NOT invert — text on a colored button stays white in both modes.rgba(0,0,0,0.4) overlay) need dark overrides inside the component. <meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)" />
<meta name="theme-color" content="#000000" media="(prefers-color-scheme: dark)" />
<meta name="color-scheme" content="light dark" />:root { color-scheme: light dark; --bg: light-dark(#fff, #000); } (light-dark() is Baseline 2024).The page is a sales conversation; each section answers the next objection.
backdrop-filter: blur(20px) on a translucent background; border/shadow appears on scroll. Mobile: smaller paddings, no hamburger unless > 5 items.clamp(2.5rem, 6vw, 3.5rem), weight 700, letter-spacing -0.035em, line-height 1.08, text-wrap: balance (Baseline 2024) instead of <br>.--text-lg, --text-secondary, naming 2–3 features.--text-lg, generous padding; trust line under it with trial limits ("100 free dictations · No credit card"), --text-sm muted.brew install ... monospace block; system requirements last, muted.min-height: calc(100svh - var(--nav-height)) (svh avoids the mobile URL-bar jump of 100vh); subtle radial gradient to accent-50 at top.translateY(-2px) + shadow-md.--text-4xl, 700, letter-spacing -0.03em), 2–4 sentences, 3 accent-dot specifics, a real screenshot, demo card or stylized illustration.--text-4xl), period ("once" vs "/month"), year-2 cost line (the kicker), checkmarks (you) vs X marks (them).--accent-50, strikethrough promo prices, cost-after-2-years row in a heavier border. Often the highest-converting section.<details name="faq">: zero JS, accessible, exclusive accordion via shared name (Baseline 2024). Animating open state (::details-content + interpolate-size) isn't cross-browser in 2026 — enhancement only. Cover: what it is (headline echoed differently), privacy/data, platform/requirements, free trial, pricing, how each major feature works, objections ("What if X doesn't work?"), refund/cancellation.One primary action, zero or one secondary; equal-weight CTAs = no CTA.
Rules:
content-voice)Every page sets:
<title>Page Name — Brand</title>
<meta name="description" content="..." /> <!-- under 160 chars, unique per page -->
<link rel="canonical" href="https://domain.com/path" />
<meta property="og:type" content="website" /> <!-- "article" for blog posts -->
<meta property="og:url" content="..." />
<meta property="og:title" content="..." />
<meta property="og:description" content="..." />
<meta property="og:image" content="https://domain.com/og/page.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:site_name" content="Brand" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="..." />
<meta name="twitter:description" content="..." />
<meta name="twitter:image" content="..." />
<meta name="twitter:site" content="@handle" />OG images: build-time 1200×630 SVG → PNG per page (@resvg/resvg-js or similar); PNG only, since Facebook, Slack and LinkedIn don't render SVG. Layout: dark background, accent gradient bar top/bottom, logo top-left, headline center-left, domain footer; titles auto-wrap with dynamic sizing (80 → 64 → 52px). Gitignore the generated files.
JSON-LD for a software product:
{
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "...",
"description": "...",
"url": "...",
"applicationCategory": "UtilitiesApplication",
"operatingSystem": "macOS 14+",
"offers": {
"@type": "Offer",
"price": 49,
"priceCurrency": "USD",
"availability": "https://schema.org/InStock"
},
"publisher": { "@type": "Organization", "name": "...", "url": "..." }
}Add Organization. FAQPage is harmless, but since 2023 Google shows FAQ rich results only for authoritative government and health sites. The software-app rich result needs a real aggregateRating or review (otherwise no stars, though other parsers still benefit). Never invent ratings or review counts — it violates Google's structured-data policy and the US FTC fake-review rule.
Canonical and sitemap: <link rel="canonical"> on every page; static sitemap.xml of all routes with accurate lastmod (Google ignores priority and changefreq); robots.txt allowing everything except /api/ and /_.
One error component (+error.svelte, error.tsx or equivalent) branching on status: 404 a light on-brand line ("Page not found."), 5xx an honest "Something broke on our end", others the real message in a plain frame.
<title>404 — Brand</title>; <meta name="robots" content="noindex">; no third-party calls, so it renders when APIs are down.404.html from the same component (SvelteKit: adapter({ fallback: '404.html' })); Cloudflare Pages, Netlify, Vercel and S3 serve it for unknown routes.Default to none; add only when it serves communication.
const observer = new IntersectionObserver(
(entries) => entries.forEach((e) => e.isIntersecting && e.target.classList.add('visible')),
{ threshold: 0.1 }
);
document.querySelectorAll('.animate-on-scroll').forEach((el) => observer.observe(el));.animate-on-scroll { opacity: 0; transform: translateY(20px);
transition: opacity 0.6s ease, transform 0.6s ease; }
.animate-on-scroll.visible { opacity: 1; transform: translateY(0); }
@media (prefers-reduced-motion: reduce) {
.animate-on-scroll { opacity: 1; transform: none; transition: none; }
}
@keyframes pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.5; transform: scale(1.4); } }
.dot { animation: pulse 1.6s ease-in-out infinite; } /* live indicators: recording, launch promo */Rules:
opacity: 0.animation-timeline) lack stable Firefox in 2026: only inside @supports (animation-timeline: view()), IntersectionObserver as default.prefers-reduced-motion: reduce.translateY(-1px) + accent shift; cards translateY(-2px) + shadow upgrade; links color shift only, no underline-fade gimmicks.Two breakpoints cover ~95% of cases: @media (max-width: 768px) (tablet) and @media (max-width: 640px) (mobile).
@container, Baseline since 2023).clamp() handles headline sizing — no manual mobile override.overflow-x: auto wrapper, min-width on the table.Google judges Core Web Vitals on field data at p75, mobile and desktop separately: LCP ≤ 2.5s, INP ≤ 200ms (replaced FID in 2024), CLS ≤ 0.1. Lab Lighthouse is a smoke test — the verdict is CrUX / Search Console or your RUM.
fetchpriority="high", never loading="lazy" on it.aspect-ratio) on images and embeds; reserve space for late-injected content.<picture> with srcset/sizes, SVG for illustrations, PNG only for pixel-exact screenshots.WCAG 2.2 AA is the bar; the EU European Accessibility Act has effectively required it for consumer e-commerce and services since 28 June 2025.
scroll-padding-top: var(--nav-height) on html.<h1>, ordered headings, <header>/<nav>/<main>/<footer> landmarks, real <button> and <a>.prefers-reduced-motion; no auto-moving content over 5s without a pause control.alt, decorative images alt="".Every field costs conversions; ask only what the next step needs — usually just email.
<label> per field; placeholder is not a label.type, autocomplete (email, name, tel, one-time-code) and inputmode, so autofill and the right mobile keyboard work.aria-describedby; keep the user's input.playsinline loop only with poster, small file and pause control); full-bleed background videos dominate LCP and bandwidthcontent-voice)Patterns are calibrated for $20–200 indie/SaaS products for individuals or small teams; add depth and proof as price rises.
Hero:
Pricing:
CTA:
Comparison row:
content-voice rules (no dead vocabulary)target="_blank" (noopener is now default; rel="noreferrer" only to hide the referrer)alt~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.