diff --git a/.github/workflows/asset-budget.yml b/.github/workflows/asset-budget.yml new file mode 100644 index 0000000000..ed87623059 --- /dev/null +++ b/.github/workflows/asset-budget.yml @@ -0,0 +1,78 @@ +name: Asset & Font Budget + +# Guardrails from the web-quality plan: +# X1 - no committed image > 500 KB +# X3 - no new render-blocking external font stylesheets (self-host instead) +# +# Both checks are scoped to the files CHANGED in the PR (diff against the base +# branch), so pre-existing large assets (e.g. GIFs still awaiting MP4/WebM +# conversion) never fail an unrelated PR — only newly added/modified files are +# held to the budget. + +on: + pull_request: + branches: ["main"] + +jobs: + asset-budget: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: New/changed images must be under 500 KB (X1) + run: | + # checkout ran with fetch-depth: 0, so origin/ is already present + # with full ancestry for the three-dot merge-base below. + base="origin/${{ github.base_ref }}" + max=512000 # 500 KiB + fail=0 + while IFS= read -r f; do + [ -z "$f" ] && continue + [ -f "$f" ] || continue # skip deletions + size=$(wc -c < "$f") + if [ "$size" -gt "$max" ]; then + echo "::error file=$f::$f is $((size / 1024)) KB (> 500 KB). Optimize (WebP/AVIF) or convert to video before committing." + fail=1 + fi + done < <(git diff --name-only --diff-filter=AMR "$base"...HEAD | grep -iE '\.(png|jpe?g|gif|webp|avif|svg)$' || true) + # AMR, not AM: a rename (R) that also re-encodes the file larger would + # otherwise slip the budget entirely, since git reports it as R. + if [ "$fail" -eq 0 ]; then echo "All new/changed images are within the 500 KB budget."; fi + exit "$fail" + + - name: Block new render-blocking font stylesheets (X3) + run: | + base="origin/${{ github.base_ref }}" + # Match the actual render-blocking stylesheet URL (…googleapis.com/css…), + # not prose mentions of the domain, to avoid false positives on comments. + added=$(git diff --diff-filter=AMR "$base"...HEAD -- '*.js' '*.jsx' '*.ts' '*.tsx' '*.json' '*.md' '*.mdx' '*.html' '*.css' \ + | grep -E '^\+' | grep -E 'fonts\.googleapis\.com/css' || true) + if [ -n "$added" ]; then + echo "::error::New external Google Fonts reference detected. Self-host the font (src/fonts/*.woff2 + @font-face in src/css/custom.css) instead of a render-blocking :" + echo "$added" + exit 1 + fi + echo "No new external font stylesheets." + + - name: New local asset references in markdown must resolve (D2/D3) + run: | + base="origin/${{ github.base_ref }}" + # Docusaurus serves static/ at baseUrl /docs/, so an added + # src="/docs/

" must have a file at static/

. onBrokenLinks only + # validates links, not /

- - + + - - - + + +
@@ -484,6 +504,12 @@ fbq('track', 'PageView');`, }, {}, ], + // D5 (CLS): stamp intrinsic width/height onto raw tags so the + // browser reserves layout space. Docusaurus already sizes Markdown + // images natively, so this only touches hand-written . + // Dependency-free; skips remote/relative/webp/svg and never + // overwrites author dimensions. + remarkImageSize, ], }, // Will be passed to @docusaurus/plugin-content-blog @@ -504,6 +530,9 @@ fbq('track', 'PageView');`, theme: { customCss: require.resolve("./src/css/custom.css"), }, + // GA fires eagerly via the standard gtag preset (loads on page load + + // auto-tracks SPA route changes) so analytics are accurate from the + // first paint. Intentionally NOT idle-deferred. gtag: { trackingID: "G-LLS95VWZPC", // Optional fields. @@ -609,21 +638,13 @@ fbq('track', 'PageView');`, clientModules: [require.resolve("./src/metaPixelRouteTracker.js")], scripts: [ - { - src: "/docs/scripts/feedback.js", - async: true, - defer: true, - }, - { - src: "/docs/scripts/clarity.js", - async: true, - defer: true, - }, - { - src: "/docs/js/apollo-init.js", - async: true, - defer: true, - }, + // Analytics loading (see src/metaPixelRouteTracker.js for the full strategy): + // - GA -> eager via the gtag preset (auto SPA tracking) + // - Meta Pixel -> eager via the inline snippet in headTags; SPA re-fire + // from the client module + // - Clarity + Apollo -> lazy, on first user interaction (client module) + // - Hotjar -> removed + // keploy's own first-party telemetry (~2 KiB) stays eager below. { src: "https://telemetry.keploy.io/sessions/sdk.js", async: true, @@ -631,16 +652,6 @@ fbq('track', 'PageView');`, "data-endpoint": "https://telemetry.keploy.io/sessions/collect", "data-source": "docs", }, - /*{ - src: "/docs/scripts/chat.js", - async: true, - defer: true, - }, - { - src: "/scripts/fullstory.js", - async: true, - defer: true, - },*/ ], }; diff --git a/src/components/Intro.js b/src/components/Intro.js index d1f90ebc75..8306a24a31 100644 --- a/src/components/Intro.js +++ b/src/components/Intro.js @@ -7,6 +7,7 @@ function Languages() {

- As developers, the hardest bugs were never the ones we could reproduce. They were the “works on my machine, breaks in prod” ones—because real user behavior and real dependencies don’t show up in unit tests. + As developers, the hardest bugs were never the ones we could + reproduce. They were the “works on my machine, breaks in prod” + ones—because{" "} + + real user behavior and real dependencies don’t show up in unit + tests. +

- We struggled to replicate real user behavior exactly like production on the leftmost part of the SDLC (local dev and CI/CD regression checks). That pain is what led to Keploy: capture real API behavior (and its dependencies) and make it replayable, so tests stay reliable across environments. + We struggled to replicate real user behavior exactly like + production on the leftmost part of the SDLC (local dev and + CI/CD regression checks). That pain is what led to Keploy: capture + real API behavior (and its dependencies) and make it replayable, so + tests stay reliable across environments.

@@ -92,8 +114,8 @@ export const WhatIsKeploy = () => { id={card.id} className={ card.tone === "primary" - ? "group relative overflow-hidden rounded-2xl border border-orange-200 bg-gradient-to-br from-white to-orange-50/30 p-6 transition-all duration-300 hover:border-orange-400 hover:shadow-lg hover:shadow-orange-100/50 hover:no-underline dark:border-orange-900/50 dark:from-gray-900 dark:to-orange-950/10 dark:hover:border-orange-500 dark:hover:shadow-orange-900/20" - : "group relative overflow-hidden rounded-2xl border border-purple-200 bg-gradient-to-br from-white to-purple-50/30 p-6 transition-all duration-300 hover:border-purple-400 hover:shadow-lg hover:shadow-purple-100/50 hover:no-underline dark:border-purple-900/50 dark:from-gray-900 dark:to-purple-950/10 dark:hover:border-purple-500 dark:hover:shadow-purple-900/20" + ? "group relative overflow-hidden rounded-2xl border border-orange-200 bg-gradient-to-br from-white to-orange-50/30 p-6 transition-all duration-300 hover:border-orange-400 hover:no-underline hover:shadow-lg hover:shadow-orange-100/50 dark:border-orange-900/50 dark:from-gray-900 dark:to-orange-950/10 dark:hover:border-orange-500 dark:hover:shadow-orange-900/20" + : "group relative overflow-hidden rounded-2xl border border-purple-200 bg-gradient-to-br from-white to-purple-50/30 p-6 transition-all duration-300 hover:border-purple-400 hover:no-underline hover:shadow-lg hover:shadow-purple-100/50 dark:border-purple-900/50 dark:from-gray-900 dark:to-purple-950/10 dark:hover:border-purple-500 dark:hover:shadow-purple-900/20" } >
@@ -133,7 +155,7 @@ export const WhatIsKeploy = () => {
-
+
@@ -145,7 +167,23 @@ export const WhatIsKeploy = () => { Built for enterprise security & compliance

- Keploy is compliant with SOC 2, ISO, GDPR, and HIPAA—with strong security controls designed for modern teams. + Keploy is compliant with{" "} + + SOC 2 + + ,{" "} + + ISO + + ,{" "} + + GDPR + + , and{" "} + + HIPAA + + —with strong security controls designed for modern teams.

@@ -174,7 +212,7 @@ export const WhatIsKeploy = () => { href="https://trust.keploy.io" target="_blank" rel="noreferrer" - className="inline-flex items-center justify-center gap-2 rounded-xl bg-gradient-to-r from-indigo-600 to-purple-600 px-5 py-3 text-sm font-semibold text-white shadow-lg shadow-indigo-500/20 transition-all hover:from-indigo-700 hover:to-purple-700 hover:text-white hover:shadow-indigo-500/30 hover:no-underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:shadow-indigo-900/30 dark:focus-visible:ring-offset-gray-950" + className="inline-flex items-center justify-center gap-2 rounded-xl bg-gradient-to-r from-indigo-600 to-purple-600 px-5 py-3 text-sm font-semibold text-white shadow-lg shadow-indigo-500/20 transition-all hover:from-indigo-700 hover:to-purple-700 hover:text-white hover:no-underline hover:shadow-indigo-500/30 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:shadow-indigo-900/30 dark:focus-visible:ring-offset-gray-950" aria-label="Visit Keploy Trust Center" > Trust Center @@ -193,4 +231,3 @@ export const WhatIsKeploy = () => { }; export default WhatIsKeploy; - diff --git a/src/css/custom.css b/src/css/custom.css index ae685e2506..62c965b8cc 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -1,4 +1,5 @@ -/* --- DM Sans loaded via headTags in docusaurus.config.js --- */ +/* --- DM Sans self-hosted below (see @font-face). Previously loaded via a + render-blocking Google Fonts stylesheet in docusaurus.config.js headTags. --- */ /* Also reveal copy button when the container or button has keyboard focus */ div[class*="codeBlockContainer"]:focus-within button[class*="copyButton"], @@ -226,6 +227,12 @@ html[data-theme="light"] { font-display: swap; } +/* DM Sans @font-face lives in docusaurus.config.js headTags as an inline +