diff --git a/.github/workflows/seo-audit.yml b/.github/workflows/seo-audit.yml new file mode 100644 index 00000000..d8b9f4d1 --- /dev/null +++ b/.github/workflows/seo-audit.yml @@ -0,0 +1,35 @@ +name: SEO audit + +on: + pull_request: + paths: + - '**/*.html' + - 'public/locales/**' + - 'scripts/generate-i18n-pages.mjs' + - 'scripts/generate-sitemap.mjs' + - 'scripts/seo-audit.mjs' + - 'src/partials/**' + - 'package.json' + - 'package-lock.json' + - 'vite.config.ts' + +jobs: + audit: + runs-on: ubuntu-latest + env: + HUSKY: 0 + SITE_URL: https://www.bentopdf.com + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: npm + - name: Install dependencies + run: npm ci + - name: Build + run: npm run build + - name: SEO audit + run: npm run seo-audit diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index 09ea416a..ea552ee5 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -26,8 +26,9 @@ concurrency: # You will likely need to set BASE_URL to be the name of your repo eg 'bentopdf' env: SIMPLE_MODE: ${{ vars.SIMPLE_MODE }} - BASE_URL: ${{ vars.BASE_URL }}/ - HUSKY: 0 + BASE_URL: ${{ vars.BASE_URL }}/ + SITE_URL: ${{ vars.SITE_URL }} + HUSKY: 0 jobs: # Single deploy job since we're just deploying diff --git a/404.html b/404.html index e7a9b348..42e8c0db 100644 --- a/404.html +++ b/404.html @@ -10,10 +10,10 @@ name="description" content="Oops! The page you're looking for doesn't exist. Return to BentoPDF's homepage to access our free PDF tools." /> - + - + diff --git a/Dockerfile b/Dockerfile index 9398d543..a6eefff6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -62,6 +62,12 @@ ENV VITE_FOOTER_TEXT=$VITE_FOOTER_TEXT ARG DISABLE_TOOLS ENV DISABLE_TOOLS=$DISABLE_TOOLS +# Public-facing canonical site URL. Defaults to the official site so self-hosters +# consolidate SEO signals back to bentopdf.com. Override with --build-arg +# SITE_URL=https://your-domain.example to claim canonical for your own deployment. +ARG SITE_URL=https://www.bentopdf.com +ENV SITE_URL=$SITE_URL + ENV NODE_OPTIONS="--max-old-space-size=3072" RUN --mount=type=secret,id=VITE_CORS_PROXY_URL,required=false \ @@ -92,6 +98,7 @@ COPY --chown=nginx:nginx nginx.conf /etc/nginx/nginx.conf COPY --chown=nginx:nginx --from=builder /app/security-headers.conf /etc/nginx/security-headers.conf COPY --chown=nginx:nginx --from=builder /app/security-headers-docs.conf /etc/nginx/security-headers-docs.conf COPY --chown=nginx:nginx --chmod=755 nginx-ipv6.sh /docker-entrypoint.d/99-disable-ipv6.sh +COPY --chown=nginx:nginx --chmod=755 nginx-noindex.sh /docker-entrypoint.d/98-noindex.sh RUN mkdir -p /etc/nginx/tmp && chown -R nginx:nginx /etc/nginx/tmp EXPOSE 8080 diff --git a/Dockerfile.nonroot b/Dockerfile.nonroot index ca5c9a44..38f2bb82 100644 --- a/Dockerfile.nonroot +++ b/Dockerfile.nonroot @@ -55,6 +55,10 @@ ENV VITE_BRAND_NAME=$VITE_BRAND_NAME ENV VITE_BRAND_LOGO=$VITE_BRAND_LOGO ENV VITE_FOOTER_TEXT=$VITE_FOOTER_TEXT +# Public-facing canonical site URL. See Dockerfile for details. +ARG SITE_URL=https://www.bentopdf.com +ENV SITE_URL=$SITE_URL + ENV NODE_OPTIONS="--max-old-space-size=3072" RUN --mount=type=secret,id=VITE_CORS_PROXY_URL,required=false \ @@ -83,6 +87,7 @@ COPY nginx.conf /etc/nginx/nginx.conf COPY --from=builder /app/security-headers.conf /etc/nginx/security-headers.conf COPY --from=builder /app/security-headers-docs.conf /etc/nginx/security-headers-docs.conf COPY --chmod=755 entrypoint.sh /entrypoint.sh +COPY --chmod=755 nginx-noindex.sh /usr/local/bin/bentopdf-noindex.sh RUN mkdir -p /etc/nginx/tmp \ /var/cache/nginx/client_temp \ diff --git a/STATIC-HOSTING.md b/STATIC-HOSTING.md index e69800da..c00dbe5b 100644 --- a/STATIC-HOSTING.md +++ b/STATIC-HOSTING.md @@ -1,64 +1,121 @@ -# Hosting BentoPDF as a static website - -As an alternative to runnning BenotPDF locally or in a Docker container, you can very easily host it as a set of static web pages. Here are a few examples: - -## Netlify - -### Netlify - static deployment - -One of simplest ways to host BentoPDF is to create a project at [Netlify](https://www.netlify.com/) and create a static deployment. You can accomplish this by first downloading the pre-built distribution file from our [GitHub releases](https://github.com/alam00000/bentopdf/releases). Each release includes a `dist-{version}.zip` file that contains all necessary files for self-hosting. - -1. Go to [BentoPDF Releases](https://github.com/alam00000/bentopdf/releases) -2. Download the latest `dist-{version}.zip` file -3. Next, if you have not already done so, create a Netlify account and log in. -4. From your Netlify projects page, add a new project and select "Deploy manually". -5. Drag and drop to upload the Bentopdf zip file you downloaded in step 3. -6. Your BentoPDF deployment should now be working! Optionally, you can go into Project Configuration and change the project name. - -When a new BentoPDF release becomes available, you will need to repeat steps 1-3, then go into "Deploys" and upload the new release. - -### Netlify - dynamic deployment - -Alternatively, you can configure a Netlify project to automatically deploy whenever your BentoPDF is updated. - -1. If you have not done so already, create a fork of BentoPDF into your own Github account. -2. If you have not already done so, create a Netlify account and log in. -3. From your Netlify projects page, add a new project and select 'Import an existing project'. -4. Select the GitHub button, authorize Netlify, then choose where to install the integration. You can choose 'All repositories' or 'Only select repositories' and choose your BentoPDF fork. -5. Select your repo and give it a project name. (add environment variables?) Then click the blue 'Deploy' button. -6. The Netlify build & deploy process will kick off. Once it finishes, you can click on the provided URL `https://[projectname]/netlify.app` to view your deployment of BentoPDF. - -Whenever the BentoPDF source code is updated, you can sync the changes into your repo. This will kick off a new build and deploy within Netlify. - -If you want to use BentoPDF's simple mode, go into Deploy Settings, then Environment Variables, and Add a variable. Add `SIMPLE_MODE` and set it to `true`. You will need to manually kick a new build to get this to take effect. - -## Vercel - -Vercel provides similar services to Netlify dynamic hosting. - -1. If you have not done so already, create a fork of BentoPDF into your own Github account. -2. If you have not already done so, create a Vercel account and log in. -3. From your Vercel Overview page, select 'Add new project' -4. Under 'Import Git Repository' and then choose 'Add GitHub Account'. Follow the prompts to authorize Vercel integration. You can choose 'All repositories' or 'Only select repositories' and choose your BentoPDF fork. -5. Select 'Import' to import the repo into Vercel. -6. Under 'Framework Preset', select 'Vite' and save the settings. -7. The Vercel build & deploy process will kick off. Once it finishes, you can click on the provided link to view your deployment of BentoPDF. - -Whenever the BentoPDF source code is updated, you can sync the changes into your repo. This will kick off a new build and deploy within BentoPDF. - -If you want to use BentoPDF's simple mode, go into Project Settings, then Environment Variables, and Create a new variable. Add `SIMPLE_MODE` and set it to `true`. You will need to redeploy to get this to take effect. - -## GitHub Pages - -You can also host your own instance of BentoPDF using GitHub pages. An advantage of this over the other options is you are able to do everything in GitHub without any third party service. - -1. If you have not done so already, create a fork of BentoPDF into your own Github account. -2. From your fork, go to `Settings->Pages`, and change the 'Source' to 'GitHub Actions' -3. Go to `Settings->Secrets and Variables > Actions`, then select 'Variables', and add the repository variable `BASE_URL`. Set the value to `/bentopdf`. *If you've renamed the repo to something other than bentopdf, put that here*. -4. Go to `Actions` in the top menu, and select 'I understand' to enable Actions -5. Within Actions, on the left, select 'Deploy static content to Pages', and then on the right select 'Run workflow', and in the dropdown, 'Run Workflow'. The action will now run to build BentoPDF and deploy it to GitHub Pages. - -When the build completes, you can find the website at `https://[your-github-username]/bentopdf` - - -If/when you merge changes from the source BentoPDF repository, the build and deploy action will automatically be kicked off and the new version will be automatically deployed to GitHub Pages. +# Hosting BentoPDF as a static website + +As an alternative to runnning BenotPDF locally or in a Docker container, you can very easily host it as a set of static web pages. Here are a few examples: + +## Netlify + +### Netlify - static deployment + +One of simplest ways to host BentoPDF is to create a project at [Netlify](https://www.netlify.com/) and create a static deployment. You can accomplish this by first downloading the pre-built distribution file from our [GitHub releases](https://github.com/alam00000/bentopdf/releases). Each release includes a `dist-{version}.zip` file that contains all necessary files for self-hosting. + +1. Go to [BentoPDF Releases](https://github.com/alam00000/bentopdf/releases) +2. Download the latest `dist-{version}.zip` file +3. Next, if you have not already done so, create a Netlify account and log in. +4. From your Netlify projects page, add a new project and select "Deploy manually". +5. Drag and drop to upload the Bentopdf zip file you downloaded in step 3. +6. Your BentoPDF deployment should now be working! Optionally, you can go into Project Configuration and change the project name. + +When a new BentoPDF release becomes available, you will need to repeat steps 1-3, then go into "Deploys" and upload the new release. + +### Netlify - dynamic deployment + +Alternatively, you can configure a Netlify project to automatically deploy whenever your BentoPDF is updated. + +1. If you have not done so already, create a fork of BentoPDF into your own Github account. +2. If you have not already done so, create a Netlify account and log in. +3. From your Netlify projects page, add a new project and select 'Import an existing project'. +4. Select the GitHub button, authorize Netlify, then choose where to install the integration. You can choose 'All repositories' or 'Only select repositories' and choose your BentoPDF fork. +5. Select your repo and give it a project name. (add environment variables?) Then click the blue 'Deploy' button. +6. The Netlify build & deploy process will kick off. Once it finishes, you can click on the provided URL `https://[projectname]/netlify.app` to view your deployment of BentoPDF. + +Whenever the BentoPDF source code is updated, you can sync the changes into your repo. This will kick off a new build and deploy within Netlify. + +If you want to use BentoPDF's simple mode, go into Deploy Settings, then Environment Variables, and Add a variable. Add `SIMPLE_MODE` and set it to `true`. You will need to manually kick a new build to get this to take effect. + +## Vercel + +Vercel provides similar services to Netlify dynamic hosting. + +1. If you have not done so already, create a fork of BentoPDF into your own Github account. +2. If you have not already done so, create a Vercel account and log in. +3. From your Vercel Overview page, select 'Add new project' +4. Under 'Import Git Repository' and then choose 'Add GitHub Account'. Follow the prompts to authorize Vercel integration. You can choose 'All repositories' or 'Only select repositories' and choose your BentoPDF fork. +5. Select 'Import' to import the repo into Vercel. +6. Under 'Framework Preset', select 'Vite' and save the settings. +7. The Vercel build & deploy process will kick off. Once it finishes, you can click on the provided link to view your deployment of BentoPDF. + +Whenever the BentoPDF source code is updated, you can sync the changes into your repo. This will kick off a new build and deploy within BentoPDF. + +If you want to use BentoPDF's simple mode, go into Project Settings, then Environment Variables, and Create a new variable. Add `SIMPLE_MODE` and set it to `true`. You will need to redeploy to get this to take effect. + +## GitHub Pages + +You can also host your own instance of BentoPDF using GitHub pages. An advantage of this over the other options is you are able to do everything in GitHub without any third party service. + +1. If you have not done so already, create a fork of BentoPDF into your own Github account. +2. From your fork, go to `Settings->Pages`, and change the 'Source' to 'GitHub Actions' +3. Go to `Settings->Secrets and Variables > Actions`, then select 'Variables', and add the repository variable `BASE_URL`. Set the value to `/bentopdf`. _If you've renamed the repo to something other than bentopdf, put that here_. +4. Go to `Actions` in the top menu, and select 'I understand' to enable Actions +5. Within Actions, on the left, select 'Deploy static content to Pages', and then on the right select 'Run workflow', and in the dropdown, 'Run Workflow'. The action will now run to build BentoPDF and deploy it to GitHub Pages. + +When the build completes, you can find the website at `https://[your-github-username]/bentopdf` + +If/when you merge changes from the source BentoPDF repository, the build and deploy action will automatically be kicked off and the new version will be automatically deployed to GitHub Pages. + +## SEO and search engine indexing + +By default, every self-hosted BentoPDF build emits canonical URLs and an +hreflang/`x-default` set that point back to `https://www.bentopdf.com`. This +is intentional for two reasons: + +1. **It consolidates SEO signals to the official site** so your instance + doesn't compete with bentopdf.com on brand searches like "bentopdf". +2. **It keeps your instance off public search results.** If you self-host + on a small VPS, a home server, or anywhere with limited bandwidth, you + probably don't want strangers from Google landing on it. With the + default canonical pointing back to the official site, search engines + treat your instance as a copy and won't surface it for organic traffic + and your bandwidth is reserved for the people you actually share the URL + with. + +You have two clean overrides depending on what you want. + +### Run your own SEO instance (claim canonical for yourself) + +If you operate a public deployment and want it to be the indexed copy for +your audience, set `SITE_URL` at build time to your origin. The build will +emit canonicals, hreflang, and structured data pointing at your domain. + +- Docker: + ```sh + docker build --build-arg SITE_URL=https://pdf.example.com -t bentopdf-self . + ``` +- Netlify, Vercel, GitHub Pages, or any other static host: set `SITE_URL` + in your project's environment variables and re-deploy. The build script + reads it during `npm run build`. + +### Stay invisible to search engines (no SEO competition either way) + +Set the runtime environment variable `ROBOTS_NOINDEX=true`. The container +injects `` into every served +HTML file at startup. The canonical link still points at the official site +by default, so when crawlers do follow internal links they still see +bentopdf.com as the authoritative source. + +```sh +docker run -d -p 8080:8080 -e ROBOTS_NOINDEX=true ghcr.io/alam00000/bentopdf:latest +``` + +You can combine the two: build with your own `SITE_URL` and run with +`ROBOTS_NOINDEX=true` for a private deployment that's neither competing +with bentopdf.com nor advertising itself. + +### Note on Simple Mode + +Simple Mode (`SIMPLE_MODE=true`) controls the **UI** — it ships a stripped-down +homepage suitable for company intranets and single-purpose deployments. It +does **not** by itself control whether your instance is indexed by search +engines. A public-facing simple-mode deployment is fully indexable by default, +just like the full-mode build. If you want a simple-mode deployment hidden +from search engines, combine `SIMPLE_MODE=true` at build time with +`ROBOTS_NOINDEX=true` at runtime. diff --git a/about.html b/about.html index 08a384da..2574cc78 100644 --- a/about.html +++ b/about.html @@ -18,7 +18,7 @@ - + diff --git a/contact.html b/contact.html index 5b3f10b1..ba5b35d2 100644 --- a/contact.html +++ b/contact.html @@ -13,7 +13,7 @@ - + @@ -48,8 +48,6 @@ /> - Contact BentoPDF - Get in Touch | Support - diff --git a/entrypoint.sh b/entrypoint.sh index 1827e6dd..82210427 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -45,4 +45,8 @@ if [ "$DISABLE_IPV6" = "true" ]; then sed -i '/^[[:space:]]*listen[[:space:]]*\[::\]:[0-9]*/s/^/#/' /etc/nginx/nginx.conf fi +if [ "${ROBOTS_NOINDEX:-false}" = "true" ] && [ -x /usr/local/bin/bentopdf-noindex.sh ]; then + NOINDEX_HTML_ROOT=/usr/share/nginx/html /usr/local/bin/bentopdf-noindex.sh || true +fi + exec su-exec "$PUID:$PGID" "$@" diff --git a/faq.html b/faq.html index 29cb074f..daee6bf1 100644 --- a/faq.html +++ b/faq.html @@ -13,7 +13,7 @@ - + @@ -48,7 +48,6 @@ /> - FAQ - Frequently Asked Questions | BentoPDF diff --git a/index.html b/index.html index a710c247..6300b353 100644 --- a/index.html +++ b/index.html @@ -25,7 +25,7 @@ /> - + @@ -231,7 +231,7 @@

Used by companies and people working at @@ -323,7 +323,7 @@

- + Why choose BentoPDF? @@ -938,7 +938,9 @@

- + Your data never leaves your device diff --git a/licensing.html b/licensing.html index f08af380..c0efbb65 100644 --- a/licensing.html +++ b/licensing.html @@ -13,7 +13,7 @@ - + @@ -48,8 +48,6 @@ /> - Licensing Information - Open Source PDF Tools | BentoPDF - diff --git a/nginx-noindex.sh b/nginx-noindex.sh new file mode 100644 index 00000000..c82a1a5f --- /dev/null +++ b/nginx-noindex.sh @@ -0,0 +1,41 @@ +#!/bin/sh + +set -e + +entrypoint_log() { + if [ -z "${NGINX_ENTRYPOINT_QUIET_LOGS:-}" ]; then + echo "$@" + fi +} + +if [ "${ROBOTS_NOINDEX:-false}" != "true" ]; then + exit 0 +fi + +ROOT="${NOINDEX_HTML_ROOT:-/usr/share/nginx/html}" +MARKER='' +META_TAG='' + +entrypoint_log "ROBOTS_NOINDEX=true: injecting noindex meta into HTML under $ROOT" + +if [ ! -d "$ROOT" ]; then + entrypoint_log "WARNING: $ROOT not found; skipping noindex injection" + exit 0 +fi + +find "$ROOT" -type f -name '*.html' | while read -r file; do + if grep -q "$MARKER" "$file"; then + continue + fi + if grep -q '| $META_TAG\\ + $MARKER\\ + |" "$file" +done + +entrypoint_log "ROBOTS_NOINDEX: injected meta into HTML files" diff --git a/nginx.conf b/nginx.conf index 60386e84..833dfa77 100644 --- a/nginx.conf +++ b/nginx.conf @@ -29,12 +29,29 @@ http { include /etc/nginx/security-headers.conf; - location ~ ^/(en|ar|be|da|de|es|fr|id|it|ko|nl|pt|ru|sv|tr|vi|zh|zh-TW)(/.*)?$ { + + location ^~ /pdfjs-viewer/ { + try_files $uri =404; + expires 1y; + } + location ^~ /pdfjs-annotation-viewer/ { + try_files $uri =404; + expires 1y; + } + + if ($request_uri ~ ^(/(?:en|ar|be|da|de|es|fr|id|it|ko|nl|pt|ru|sv|tr|vi|zh|zh-TW|uk|ja))?/index\.html(\?.*)?$) { + return 301 $1/$2; + } + if ($request_uri ~ ^(/(?:en|ar|be|da|de|es|fr|id|it|ko|nl|pt|ru|sv|tr|vi|zh|zh-TW|uk|ja))?/([a-z0-9][a-z0-9-]*)\.html(\?.*)?$) { + return 301 $1/$2$3; + } + + location ~ ^/(en|ar|be|da|de|es|fr|id|it|ko|nl|pt|ru|sv|tr|vi|zh|zh-TW|uk|ja)(/.*)?$ { try_files $uri $uri/ $uri.html /$1/index.html /index.html; expires 5m; } - location ~ ^/(.+?)/(en|ar|be|da|de|es|fr|id|it|ko|nl|pt|ru|sv|tr|vi|zh|zh-TW)(/.*)?$ { + location ~ ^/(.+?)/(en|ar|be|da|de|es|fr|id|it|ko|nl|pt|ru|sv|tr|vi|zh|zh-TW|uk|ja)(/.*)?$ { try_files $uri $uri/ $uri.html /$1/$2/index.html /$1/index.html /index.html; expires 5m; } diff --git a/package-lock.json b/package-lock.json index b15c3575..8528fbcb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "bento-pdf", - "version": "2.8.2", + "version": "2.8.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "bento-pdf", - "version": "2.8.2", + "version": "2.8.4", "license": "AGPL-3.0-only", "dependencies": { "@fontsource/cedarville-cursive": "^5.2.7", diff --git a/package.json b/package.json index c73c5d29..e795c1e8 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "type": "module", "scripts": { "dev": "vite", - "build": "tsc && vite build && NODE_OPTIONS='--max-old-space-size=3072' node scripts/generate-i18n-pages.mjs && node scripts/generate-sitemap.mjs && node scripts/generate-security-headers.mjs", + "build": "tsc && vite build && NODE_OPTIONS='--max-old-space-size=3072' node scripts/generate-i18n-pages.mjs && node scripts/generate-sitemap.mjs && node scripts/generate-security-headers.mjs && node scripts/seo-audit.mjs", + "seo-audit": "node scripts/seo-audit.mjs", "build:with-docs": "npm run build && npm run docs:build && node scripts/include-docs-in-dist.js", "build:gzip": "COMPRESSION_MODE=g npm run build", "build:brotli": "COMPRESSION_MODE=b npm run build", diff --git a/pdf-converter.html b/pdf-converter.html index 455c63f6..ad848f38 100644 --- a/pdf-converter.html +++ b/pdf-converter.html @@ -18,7 +18,7 @@ - + diff --git a/pdf-editor.html b/pdf-editor.html index 97379b18..31941089 100644 --- a/pdf-editor.html +++ b/pdf-editor.html @@ -18,7 +18,7 @@ - + diff --git a/pdf-merge-split.html b/pdf-merge-split.html index c609b6ed..afb2df68 100644 --- a/pdf-merge-split.html +++ b/pdf-merge-split.html @@ -20,10 +20,7 @@ - + diff --git a/pdf-security.html b/pdf-security.html index 418df5ab..19ca3f1c 100644 --- a/pdf-security.html +++ b/pdf-security.html @@ -18,7 +18,7 @@ - + diff --git a/privacy.html b/privacy.html index 439ae4d9..b7dbb7ac 100644 --- a/privacy.html +++ b/privacy.html @@ -13,7 +13,7 @@ - + @@ -48,7 +48,6 @@ /> - Privacy Policy - Your Data Stays Private | BentoPDF diff --git a/public/config.json b/public/config.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/public/config.json @@ -0,0 +1 @@ +{} diff --git a/public/locales/en/common.json b/public/locales/en/common.json index c22880ed..5b4074f7 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -7,7 +7,9 @@ "allTools": "All Tools", "docs": "Docs", "openMainMenu": "Open main menu", - "language": "Language" + "language": "Language", + "searchLanguage": "Search language…", + "noLanguagesFound": "No languages found" }, "donation": { "message": "Love BentoPDF? Help us keep it free and open source!", diff --git a/public/locales/ua/common.json b/public/locales/uk/common.json similarity index 100% rename from public/locales/ua/common.json rename to public/locales/uk/common.json diff --git a/public/locales/ua/tools.json b/public/locales/uk/tools.json similarity index 100% rename from public/locales/ua/tools.json rename to public/locales/uk/tools.json diff --git a/public/robots.txt b/public/robots.txt index 7fa48805..c62aca60 100644 --- a/public/robots.txt +++ b/public/robots.txt @@ -26,15 +26,10 @@ Disallow: /uploads/ # Disallow docs internal files (but allow /docs/ pages) Disallow: /docs/assets/ -# Crawl delay for polite crawling -Crawl-delay: 1 - # Google-specific User-agent: Googlebot Allow: / -Crawl-delay: 0 -# Bing-specific +# Bing-specific User-agent: Bingbot -Allow: / -Crawl-delay: 1 \ No newline at end of file +Allow: / \ No newline at end of file diff --git a/scripts/generate-i18n-pages.mjs b/scripts/generate-i18n-pages.mjs index 846cd0ad..f37f493b 100644 --- a/scripts/generate-i18n-pages.mjs +++ b/scripts/generate-i18n-pages.mjs @@ -8,7 +8,10 @@ const __dirname = path.dirname(__filename); const DIST_DIR = path.resolve(__dirname, '../dist'); const LOCALES_DIR = path.resolve(__dirname, '../public/locales'); -const SITE_URL = process.env.SITE_URL || 'https://bentopdf.com'; +const SITE_URL = (process.env.SITE_URL || 'https://www.bentopdf.com').replace( + /\/+$/, + '' +); const BASE_PATH = (process.env.BASE_URL || '/').replace(/\/$/, ''); const languages = fs.readdirSync(LOCALES_DIR).filter((file) => { @@ -42,6 +45,14 @@ function loadAllTranslations() { return translations; } +function loadEnglishTools() { + const toolsPath = path.join(LOCALES_DIR, 'en/tools.json'); + if (!fs.existsSync(toolsPath)) return {}; + return JSON.parse(fs.readFileSync(toolsPath, 'utf-8')); +} + +const ENGLISH_TOOLS = loadEnglishTools(); + // TODO@ALAM: Let users build only a single language function buildUrl(langPrefix, pagePath) { const parts = [SITE_URL]; @@ -51,6 +62,109 @@ function buildUrl(langPrefix, pagePath) { return parts.filter(Boolean).join('/').replace(/\/+$/, '') || SITE_URL; } +const ORGANIZATION_LD_MARKER = 'data-bentopdf-organization'; + +function injectOrganizationLd(document) { + if (document.querySelector(`script[${ORGANIZATION_LD_MARKER}]`)) return; + const existing = document.querySelectorAll( + 'script[type="application/ld+json"]' + ); + for (const node of existing) { + try { + const parsed = JSON.parse(node.textContent || ''); + if (parsed && parsed['@type'] === 'Organization') return; + } catch { + continue; + } + } + const data = { + '@context': 'https://schema.org', + '@type': 'Organization', + name: 'BentoPDF', + url: SITE_URL, + logo: `${SITE_URL}/images/favicon.svg`, + sameAs: [ + 'https://github.com/alam00000/bentopdf', + 'https://x.com/BentoPDF', + 'https://www.linkedin.com/company/bentopdf/', + 'https://www.instagram.com/thebentopdf/', + ], + }; + const script = document.createElement('script'); + script.setAttribute('type', 'application/ld+json'); + script.setAttribute(ORGANIZATION_LD_MARKER, ''); + script.textContent = JSON.stringify(data, null, 2); + document.body.appendChild(script); +} + +const BREADCRUMB_MARKER = 'data-bentopdf-breadcrumb'; + +function injectToolBreadcrumb(document, lang, toolName, toolUrl) { + const h1 = document.querySelector('h1[data-i18n^="tools:"]'); + if (!h1) return; + if (document.querySelector(`[${BREADCRUMB_MARKER}]`)) return; + + const homeUrl = buildUrl(lang === 'en' ? '' : lang, ''); + + const nav = document.createElement('nav'); + nav.setAttribute('aria-label', 'Breadcrumb'); + nav.setAttribute(BREADCRUMB_MARKER, ''); + nav.className = 'text-sm text-gray-400 mb-4'; + + const homeLink = document.createElement('a'); + homeLink.href = homeUrl; + homeLink.className = 'hover:text-indigo-300'; + homeLink.textContent = 'BentoPDF'; + + const sep = document.createElement('span'); + sep.setAttribute('aria-hidden', 'true'); + sep.className = 'mx-2'; + sep.textContent = '›'; + + const current = document.createElement('span'); + current.className = 'text-gray-300'; + current.setAttribute('aria-current', 'page'); + current.textContent = toolName; + + nav.appendChild(homeLink); + nav.appendChild(sep); + nav.appendChild(current); + + h1.parentNode.insertBefore(nav, h1); + + const ld = { + '@context': 'https://schema.org', + '@type': 'BreadcrumbList', + itemListElement: [ + { + '@type': 'ListItem', + position: 1, + name: 'BentoPDF', + item: homeUrl, + }, + { + '@type': 'ListItem', + position: 2, + name: toolName, + item: toolUrl, + }, + ], + }; + + const script = document.createElement('script'); + script.setAttribute('type', 'application/ld+json'); + script.setAttribute(BREADCRUMB_MARKER, ''); + script.textContent = JSON.stringify(ld, null, 2); + document.body.appendChild(script); +} + +function resolveToolName(translationKey, langTools) { + const langEntry = langTools && langTools[translationKey]; + if (langEntry && langEntry.name) return langEntry.name; + const enEntry = ENGLISH_TOOLS[translationKey]; + return enEntry && enEntry.name ? enEntry.name : null; +} + function processFileForLanguage( originalContent, file, @@ -126,13 +240,26 @@ function processFileForLanguage( defaultLink.href = buildUrl('', pagePath); document.head.appendChild(defaultLink); + const canonicalUrl = buildUrl(lang, pagePath); let canonical = document.querySelector('link[rel="canonical"]'); if (!canonical) { canonical = document.createElement('link'); canonical.rel = 'canonical'; document.head.appendChild(canonical); } - canonical.href = buildUrl(lang, pagePath); + canonical.href = canonicalUrl; + + const ogUrl = document.querySelector('meta[property="og:url"]'); + if (ogUrl) ogUrl.content = canonicalUrl; + const twitterUrl = document.querySelector('meta[name="twitter:url"]'); + if (twitterUrl) twitterUrl.content = canonicalUrl; + + injectOrganizationLd(document); + + const localizedToolName = resolveToolName(translationKey, tools); + if (localizedToolName) { + injectToolBreadcrumb(document, lang, localizedToolName, canonicalUrl); + } const links = document.querySelectorAll('a[href]'); links.forEach((link) => { @@ -189,6 +316,7 @@ function updateEnglishFile(filePath, originalContent) { .forEach((el) => el.remove()); const pagePath = filenameNoExt === 'index' ? '' : filenameNoExt; + const canonicalUrl = buildUrl('', pagePath); languages.forEach((l) => { const link = document.createElement('link'); @@ -201,9 +329,31 @@ function updateEnglishFile(filePath, originalContent) { const defaultLink = document.createElement('link'); defaultLink.rel = 'alternate'; defaultLink.hreflang = 'x-default'; - defaultLink.href = buildUrl('', pagePath); + defaultLink.href = canonicalUrl; document.head.appendChild(defaultLink); + let canonical = document.querySelector('link[rel="canonical"]'); + if (!canonical) { + canonical = document.createElement('link'); + canonical.rel = 'canonical'; + document.head.appendChild(canonical); + } + canonical.href = canonicalUrl; + + const ogUrl = document.querySelector('meta[property="og:url"]'); + if (ogUrl) ogUrl.content = canonicalUrl; + const twitterUrl = document.querySelector('meta[name="twitter:url"]'); + if (twitterUrl) twitterUrl.content = canonicalUrl; + + injectOrganizationLd(document); + + const enTranslationKey = + KEY_MAPPING[filenameNoExt] || toCamelCase(filenameNoExt); + const enToolName = resolveToolName(enTranslationKey, ENGLISH_TOOLS); + if (enToolName) { + injectToolBreadcrumb(document, 'en', enToolName, canonicalUrl); + } + const result = dom.serialize(); dom.window.close(); diff --git a/scripts/generate-sitemap.mjs b/scripts/generate-sitemap.mjs index 7a587fdb..ca8e160f 100644 --- a/scripts/generate-sitemap.mjs +++ b/scripts/generate-sitemap.mjs @@ -7,7 +7,11 @@ const __dirname = path.dirname(__filename); const DIST_DIR = path.resolve(__dirname, '../dist'); const LOCALES_DIR = path.resolve(__dirname, '../public/locales'); -const SITE_URL = process.env.SITE_URL || 'https://www.bentopdf.com'; +const SITE_URL = (process.env.SITE_URL || 'https://www.bentopdf.com').replace( + /\/+$/, + '' +); +const EXCLUDED_PAGES = new Set(['404', 'wasm-settings']); const languages = fs.readdirSync(LOCALES_DIR).filter((file) => { return fs.statSync(path.join(LOCALES_DIR, file)).isDirectory(); @@ -37,7 +41,6 @@ const PRIORITY_MAP = { privacy: 0.5, terms: 0.5, licensing: 0.5, - 404: 0.1, }; function getPriority(pageName) { @@ -57,13 +60,30 @@ function generateSitemap() { console.log(` SITE_URL: ${SITE_URL}`); console.log(` Languages: ${languages.join(', ')}`); - // Get all HTML files from dist root (English pages) const htmlFiles = fs .readdirSync(DIST_DIR) .filter((file) => file.endsWith('.html')) - .map((file) => file.replace('.html', '')); + .map((file) => file.replace('.html', '')) + .filter((name) => !EXCLUDED_PAGES.has(name)); - const today = new Date().toISOString().split('T')[0]; + const lastModCache = new Map(); + const getLastMod = (lang, pageName) => { + const cacheKey = `${lang}::${pageName}`; + if (lastModCache.has(cacheKey)) return lastModCache.get(cacheKey); + const fileName = `${pageName}.html`; + const filePath = + lang === 'en' + ? path.join(DIST_DIR, fileName) + : path.join(DIST_DIR, lang, fileName); + let iso; + try { + iso = fs.statSync(filePath).mtime.toISOString().slice(0, 10); + } catch { + iso = new Date().toISOString().slice(0, 10); + } + lastModCache.set(cacheKey, iso); + return iso; + }; let sitemap = ` ${url} - ${today} + ${lastmod} weekly ${priority} `; diff --git a/scripts/seo-audit.mjs b/scripts/seo-audit.mjs new file mode 100644 index 00000000..d5424bb4 --- /dev/null +++ b/scripts/seo-audit.mjs @@ -0,0 +1,284 @@ +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const DIST_DIR = path.resolve(__dirname, '../dist'); +const LOCALES_DIR = path.resolve(__dirname, '../public/locales'); +const SITE_URL = (process.env.SITE_URL || 'https://www.bentopdf.com').replace( + /\/+$/, + '' +); +const HOST = new URL(SITE_URL).hostname; + +const NOINDEX_ALLOWLIST = new Set(['404.html', 'wasm-settings.html']); +const SKIP_DIRS = new Set([ + 'assets', + 'docs', + 'pdfjs-viewer', + 'pdfjs-annotation-viewer', +]); +const SITEMAP_FORBIDDEN_PATTERNS = [/\/404(\b|\/)/, /index\.html(?:$|\?)/]; +const SITEMAP_MAX_URLS = 50000; + +const failures = []; +const warnings = []; + +function fail(rule, detail) { + failures.push({ rule, detail }); +} + +function warn(rule, detail) { + warnings.push({ rule, detail }); +} + +function listDistHtml() { + const files = []; + function walk(dir, prefix = '') { + if (!fs.existsSync(dir)) return; + for (const entry of fs.readdirSync(dir)) { + const full = path.join(dir, entry); + const rel = path.posix.join(prefix, entry); + const stat = fs.statSync(full); + if (stat.isDirectory()) { + if (SKIP_DIRS.has(entry) || entry.startsWith('.')) continue; + walk(full, rel); + } else if (entry.endsWith('.html')) { + files.push({ full, rel }); + } + } + } + walk(DIST_DIR); + return files; +} + +function findOne(html, regex) { + const m = html.match(regex); + return m ? m[1] : null; +} + +function findAll(html, regex) { + const out = []; + let m; + while ((m = regex.exec(html)) !== null) out.push(m[1]); + return out; +} + +function expectedCanonicalForFile(rel) { + const parts = rel.split('/'); + const fileName = parts.pop(); + const langPrefix = parts.length > 0 ? parts.join('/') : ''; + const baseName = fileName.replace(/\.html$/, ''); + const slug = baseName === 'index' ? '' : baseName; + const segments = [SITE_URL]; + if (langPrefix) segments.push(langPrefix); + if (slug) segments.push(slug); + return segments.join('/').replace(/\/+$/, '') || SITE_URL; +} + +function auditHtml(file) { + const html = fs.readFileSync(file.full, 'utf-8'); + const titles = findAll(html, /]*>[\s\S]*?<\/title>/g); + if (titles.length === 0) { + fail('title', `${file.rel}: no `); + } else if (titles.length > 1) { + fail('title', `${file.rel}: ${titles.length} <title> tags (expected 1)`); + } + + const canonicals = findAll( + html, + /<link[^>]+rel=["']canonical["'][^>]*href=["']([^"']+)["']/g + ); + if (canonicals.length === 0) { + fail('canonical', `${file.rel}: missing <link rel="canonical">`); + } else if (canonicals.length > 1) { + fail( + 'canonical', + `${file.rel}: ${canonicals.length} canonical tags (expected 1)` + ); + } else { + const actual = canonicals[0]; + if (file.rel !== '404.html' && actual.endsWith('.html')) { + fail( + 'canonical', + `${file.rel}: canonical "${actual}" still ends in .html` + ); + } + try { + const url = new URL(actual); + if (url.hostname !== HOST) { + warn( + 'canonical', + `${file.rel}: canonical host "${url.hostname}" != expected "${HOST}"` + ); + } + } catch { + fail( + 'canonical', + `${file.rel}: canonical "${actual}" is not a valid URL` + ); + } + if (file.rel !== '404.html') { + const expected = expectedCanonicalForFile(file.rel); + if (actual.replace(/\/+$/, '') !== expected.replace(/\/+$/, '')) { + fail( + 'canonical', + `${file.rel}: canonical "${actual}" != expected "${expected}"` + ); + } + } + } + + const robots = findOne( + html, + /<meta[^>]+name=["']robots["'][^>]*content=["']([^"']+)["']/i + ); + if ( + robots && + /noindex/i.test(robots) && + !NOINDEX_ALLOWLIST.has(file.rel) && + !NOINDEX_ALLOWLIST.has(path.basename(file.rel)) + ) { + fail( + 'robots', + `${file.rel}: noindex on indexable page (robots="${robots}")` + ); + } + + const emptyI18n = html.match(/<span[^>]*data-i18n="[^"]+"[^>]*>\s*<\/span>/g); + if (emptyI18n && emptyI18n.length > 0) { + fail( + 'data-i18n', + `${file.rel}: ${emptyI18n.length} empty data-i18n spans (e.g. ${emptyI18n[0].slice(0, 80)})` + ); + } + + const aggregateRating = html.includes('"aggregateRating"'); + if (aggregateRating) { + fail('aggregateRating', `${file.rel}: contains aggregateRating JSON-LD`); + } + + const hreflangTags = [ + ...html.matchAll( + /<link[^>]+rel=["']alternate["'][^>]*hreflang=["'][^"']+["'][^>]*>/g + ), + ].map((m) => m[0]); + for (const tag of hreflangTags) { + const m = tag.match(/href=["']([^"']+)["']/); + if (!m) continue; + try { + const url = new URL(m[1]); + if (url.hostname !== HOST) { + fail( + 'hreflang', + `${file.rel}: hreflang href host "${url.hostname}" != expected "${HOST}"` + ); + } + } catch { + fail('hreflang', `${file.rel}: hreflang href "${m[1]}" not a valid URL`); + } + } +} + +function auditSitemap() { + const sitemapPath = path.join(DIST_DIR, 'sitemap.xml'); + if (!fs.existsSync(sitemapPath)) { + fail('sitemap', 'dist/sitemap.xml not found'); + return; + } + const xml = fs.readFileSync(sitemapPath, 'utf-8'); + const locs = findAll(xml, /<loc>([^<]+)<\/loc>/g); + + if (locs.length === 0) fail('sitemap', 'sitemap has no <loc> entries'); + if (locs.length > SITEMAP_MAX_URLS) { + fail( + 'sitemap', + `sitemap has ${locs.length} URLs (max ${SITEMAP_MAX_URLS}); split into a sitemap index` + ); + } + + const seen = new Set(); + for (const loc of locs) { + if (seen.has(loc)) { + fail('sitemap', `sitemap has duplicate <loc>: ${loc}`); + } + seen.add(loc); + + for (const pattern of SITEMAP_FORBIDDEN_PATTERNS) { + if (pattern.test(loc)) { + fail('sitemap', `sitemap contains forbidden URL: ${loc}`); + } + } + + try { + const url = new URL(loc); + if (url.hostname !== HOST) { + fail( + 'sitemap', + `sitemap URL host "${url.hostname}" != expected "${HOST}" (${loc})` + ); + } + } catch { + fail('sitemap', `sitemap URL is not valid: ${loc}`); + } + } + + const expectedLocales = fs + .readdirSync(LOCALES_DIR) + .filter((d) => fs.statSync(path.join(LOCALES_DIR, d)).isDirectory()); + for (const lang of expectedLocales) { + if (lang === 'en') continue; + const sample = locs.find( + (l) => l.includes(`/${lang}/`) || l.endsWith(`/${lang}`) + ); + if (!sample) { + warn('sitemap', `sitemap has no entry for locale "${lang}"`); + } + } +} + +function main() { + if (!fs.existsSync(DIST_DIR)) { + console.error(`SEO audit: dist not found at ${DIST_DIR}`); + process.exit(1); + } + + const files = listDistHtml(); + if (files.length === 0) { + fail('audit', 'no HTML files found in dist/'); + } + + for (const file of files) auditHtml(file); + auditSitemap(); + + const total = failures.length + warnings.length; + if (total === 0) { + console.log(`SEO audit: ${files.length} HTML files passed, sitemap clean.`); + return; + } + + if (warnings.length) { + console.warn(`\nSEO audit warnings (${warnings.length}):`); + for (const { rule, detail } of warnings.slice(0, 50)) { + console.warn(` [${rule}] ${detail}`); + } + if (warnings.length > 50) { + console.warn(` ... and ${warnings.length - 50} more warnings`); + } + } + + if (failures.length) { + console.error(`\nSEO audit failures (${failures.length}):`); + for (const { rule, detail } of failures.slice(0, 100)) { + console.error(` [${rule}] ${detail}`); + } + if (failures.length > 100) { + console.error(` ... and ${failures.length - 100} more failures`); + } + process.exit(1); + } +} + +main(); diff --git a/simple-index.html b/simple-index.html index 8925585a..5fe4d5aa 100644 --- a/simple-index.html +++ b/simple-index.html @@ -11,7 +11,6 @@ name="description" content="Free online PDF tools - Merge, split, compress, convert PDFs. 100% browser-based, no signup required, unlimited files." /> - <meta name="robots" content="noindex, nofollow" /> <!-- Mobile Web App --> <meta name="mobile-web-app-capable" content="yes" /> diff --git a/src/js/i18n/i18n.ts b/src/js/i18n/i18n.ts index 4bf6198b..8b8791d8 100644 --- a/src/js/i18n/i18n.ts +++ b/src/js/i18n/i18n.ts @@ -22,7 +22,7 @@ export const supportedLanguages = [ 'sv', 'ko', 'ja', - 'ua', + 'uk', ] as const; export type SupportedLanguage = (typeof supportedLanguages)[number]; @@ -46,7 +46,7 @@ export const languageNames: Record<SupportedLanguage, string> = { sv: 'Svenska', ko: '한국어', ja: '日本語', - ua: 'Українська', + uk: 'Українська', }; export const getLanguageFromUrl = (): SupportedLanguage => { @@ -62,7 +62,7 @@ export const getLanguageFromUrl = (): SupportedLanguage => { } const langMatch = path.match( - /^\/(en|ar|fr|es|de|zh|zh-TW|vi|tr|id|it|pt|nl|be|da|ko|sv|ru|ja|ua)(?:\/|$)/ + /^\/(en|ar|fr|es|de|zh|zh-TW|vi|tr|id|it|pt|nl|be|da|ko|sv|ru|ja|uk)(?:\/|$)/ ); if ( langMatch && @@ -152,7 +152,7 @@ export const changeLanguage = (lang: SupportedLanguage): void => { let pagePathWithoutLang = relativePath; const langPrefixMatch = relativePath.match( - /^\/(en|ar|fr|es|de|zh|zh-TW|vi|tr|id|it|pt|nl|be|da|ko|sv|ru|ja|ua)(\/.*)?$/ + /^\/(en|ar|fr|es|de|zh|zh-TW|vi|tr|id|it|pt|nl|be|da|ko|sv|ru|ja|uk)(\/.*)?$/ ); if (langPrefixMatch) { pagePathWithoutLang = langPrefixMatch[2] || '/'; @@ -247,7 +247,7 @@ export const rewriteLinks = (): void => { } const langPrefixRegex = new RegExp( - `^(${basePath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})?/?(en|ar|fr|es|de|zh|zh-TW|vi|tr|id|it|pt|nl|be|da|ko|sv|ru|ja|ua)(/|$)` + `^(${basePath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})?/?(en|ar|fr|es|de|zh|zh-TW|vi|tr|id|it|pt|nl|be|da|ko|sv|ru|ja|uk)(/|$)` ); if (langPrefixRegex.test(href)) { return; diff --git a/src/js/i18n/language-switcher.ts b/src/js/i18n/language-switcher.ts index 029d37f7..d4e37742 100644 --- a/src/js/i18n/language-switcher.ts +++ b/src/js/i18n/language-switcher.ts @@ -3,6 +3,7 @@ import { languageNames, getLanguageFromUrl, changeLanguage, + t, } from './i18n'; export const createLanguageSwitcher = (): HTMLElement => { @@ -39,24 +40,63 @@ export const createLanguageSwitcher = (): HTMLElement => { const dropdown = document.createElement('div'); dropdown.className = ` - hidden absolute right-0 mt-2 w-40 rounded-lg - bg-gray-800 border border-gray-700 shadow-xl - py-1 z-50 + hidden absolute right-0 mt-2 z-50 + w-64 max-w-[calc(100vw-2rem)] + rounded-lg bg-gray-800 border border-gray-700 shadow-xl + flex flex-col overflow-hidden `.trim(); dropdown.setAttribute('role', 'menu'); + const searchWrapper = document.createElement('div'); + searchWrapper.className = + 'p-2 border-b border-gray-700 bg-gray-800 flex-shrink-0'; + + const searchPlaceholder = + t('nav.searchLanguage') !== 'nav.searchLanguage' + ? t('nav.searchLanguage') + : 'Search language…'; + + const searchInput = document.createElement('input'); + searchInput.type = 'search'; + searchInput.placeholder = searchPlaceholder; + searchInput.className = ` + w-full px-3 py-1.5 text-sm + bg-gray-900 text-gray-200 + border border-gray-700 rounded-md + focus:outline-none focus:border-indigo-500 + placeholder-gray-500 + `.trim(); + searchInput.setAttribute('aria-label', searchPlaceholder); + searchWrapper.appendChild(searchInput); + dropdown.appendChild(searchWrapper); + + const list = document.createElement('div'); + list.className = 'max-h-64 overflow-y-auto py-1'; + list.setAttribute('role', 'none'); + + const emptyState = document.createElement('p'); + emptyState.className = 'hidden px-4 py-3 text-sm text-gray-400 text-center'; + const emptyText = + t('nav.noLanguagesFound') !== 'nav.noLanguagesFound' + ? t('nav.noLanguagesFound') + : 'No languages found'; + emptyState.textContent = emptyText; + + const options: HTMLButtonElement[] = []; supportedLanguages.forEach((lang) => { const option = document.createElement('button'); + option.type = 'button'; option.className = ` w-full px-4 py-2 text-left text-sm text-gray-200 - hover:bg-gray-700 flex items-center gap-2 + hover:bg-gray-700 flex items-center gap-2 transition-colors ${lang === currentLang ? 'bg-gray-700' : ''} `.trim(); option.setAttribute('role', 'menuitem'); + option.dataset.lang = lang; + option.dataset.searchKey = `${languageNames[lang]} ${lang}`.toLowerCase(); const name = document.createElement('span'); name.textContent = languageNames[lang]; - option.appendChild(name); option.addEventListener('click', () => { @@ -65,7 +105,37 @@ export const createLanguageSwitcher = (): HTMLElement => { } }); - dropdown.appendChild(option); + options.push(option); + list.appendChild(option); + }); + + list.appendChild(emptyState); + dropdown.appendChild(list); + + const filterOptions = () => { + const query = searchInput.value.trim().toLowerCase(); + let visible = 0; + options.forEach((option) => { + const key = option.dataset.searchKey || ''; + const match = !query || key.includes(query); + option.classList.toggle('hidden', !match); + if (match) visible++; + }); + emptyState.classList.toggle('hidden', visible > 0); + }; + + searchInput.addEventListener('input', filterOptions); + dropdown.addEventListener('click', (e) => { + if (e.target instanceof HTMLButtonElement && e.target.dataset.lang) return; + e.stopPropagation(); + }); + searchInput.addEventListener('keydown', (e) => { + if (e.key === 'Escape') { + e.preventDefault(); + dropdown.classList.add('hidden'); + button.setAttribute('aria-expanded', 'false'); + button.focus(); + } }); container.appendChild(button); @@ -74,8 +144,15 @@ export const createLanguageSwitcher = (): HTMLElement => { button.addEventListener('click', (e) => { e.stopPropagation(); const isExpanded = button.getAttribute('aria-expanded') === 'true'; - button.setAttribute('aria-expanded', (!isExpanded).toString()); - dropdown.classList.toggle('hidden'); + const nextOpen = !isExpanded; + button.setAttribute('aria-expanded', nextOpen.toString()); + dropdown.classList.toggle('hidden', !nextOpen); + if (nextOpen) { + searchInput.value = ''; + filterOptions(); + list.scrollTop = 0; + requestAnimationFrame(() => searchInput.focus()); + } }); document.addEventListener('click', () => { @@ -117,7 +194,7 @@ export const injectLanguageSwitcher = (): void => { if (socialIconsContainer) { const wrapper = document.createElement('div'); - wrapper.className = 'inline-flex flex-col gap-4'; // gap-4 adds space between icons and switcher + wrapper.className = 'inline-flex flex-col gap-4'; socialIconsContainer.parentNode?.insertBefore( wrapper, @@ -139,9 +216,11 @@ export const injectLanguageSwitcher = (): void => { `.trim(); } - const dropdown = switcher.querySelector('div[role="menu"]'); + const dropdown = switcher.querySelector( + 'div[role="menu"]' + ) as HTMLElement | null; if (dropdown) { - dropdown.classList.remove('mt-2', 'w-40'); + dropdown.classList.remove('mt-2', 'w-64'); dropdown.classList.add('bottom-full', 'mb-2', 'w-full'); } diff --git a/src/js/utils/disabled-tools.ts b/src/js/utils/disabled-tools.ts index 876e635a..5f35c622 100644 --- a/src/js/utils/disabled-tools.ts +++ b/src/js/utils/disabled-tools.ts @@ -14,6 +14,11 @@ export async function loadRuntimeConfig(): Promise<void> { }); if (!response.ok) return; + const contentType = response.headers.get('content-type') || ''; + if (!contentType.toLowerCase().includes('application/json')) { + return; + } + const config: AppConfig = await response.json(); if (Array.isArray(config.disabledTools)) { for (const toolId of config.disabledTools) { @@ -27,8 +32,8 @@ export async function loadRuntimeConfig(): Promise<void> { (c): c is string => typeof c === 'string' ); } - } catch { - console.error('[LOAD_RUNTIME_CONFIG] Failed to load runtime configuration'); + } catch (err) { + console.warn('[LOAD_RUNTIME_CONFIG] Skipped runtime config:', err); } } diff --git a/src/pages/add-attachments.html b/src/pages/add-attachments.html index aef1b37b..6cca53cf 100644 --- a/src/pages/add-attachments.html +++ b/src/pages/add-attachments.html @@ -5,7 +5,6 @@ <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <!-- Primary Meta Tags --> - <title>Add Attachments Online Free - Add Attachments Tool | BentoPDF - + @@ -456,11 +452,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.7", - "ratingCount": "4068" } } diff --git a/src/pages/add-blank-page.html b/src/pages/add-blank-page.html index 016d0e2e..a5f33b6f 100644 --- a/src/pages/add-blank-page.html +++ b/src/pages/add-blank-page.html @@ -5,7 +5,6 @@ - Add Blank Page Online Free - Add Blank Page Tool | BentoPDF - + @@ -440,11 +439,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.8", - "ratingCount": "2632" } } diff --git a/src/pages/add-page-labels.html b/src/pages/add-page-labels.html index 637d4844..2c4c01a4 100644 --- a/src/pages/add-page-labels.html +++ b/src/pages/add-page-labels.html @@ -23,10 +23,7 @@ content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1" /> - + - Add Stamps Online Free - Add Stamps Tool | BentoPDF - + @@ -371,11 +370,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.9", - "ratingCount": "4453" } } diff --git a/src/pages/add-watermark.html b/src/pages/add-watermark.html index b5cde36d..00e0d74a 100644 --- a/src/pages/add-watermark.html +++ b/src/pages/add-watermark.html @@ -5,7 +5,6 @@ - Add Watermark Online Free - Add Watermark Tool | BentoPDF - + @@ -770,11 +769,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.7", - "ratingCount": "3066" } } diff --git a/src/pages/adjust-colors.html b/src/pages/adjust-colors.html index bf9ba04a..b79696b9 100644 --- a/src/pages/adjust-colors.html +++ b/src/pages/adjust-colors.html @@ -4,9 +4,6 @@ - - Adjust Colors Online Free - PDF Color Adjustment Tool | BentoPDF - - + @@ -547,11 +544,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.7", - "ratingCount": "1856" } } diff --git a/src/pages/alternate-merge.html b/src/pages/alternate-merge.html index 4692fe26..fe49584a 100644 --- a/src/pages/alternate-merge.html +++ b/src/pages/alternate-merge.html @@ -5,7 +5,6 @@ - Alternate Merge Online Free - Alternate Merge Tool | BentoPDF - + @@ -401,11 +397,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.8", - "ratingCount": "2876" } } diff --git a/src/pages/background-color.html b/src/pages/background-color.html index efa3e349..6a23ae17 100644 --- a/src/pages/background-color.html +++ b/src/pages/background-color.html @@ -5,9 +5,6 @@ - - Background Color Online Free - Background Color Tool | BentoPDF - - + @@ -400,11 +394,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.8", - "ratingCount": "3038" } } diff --git a/src/pages/bates-numbering.html b/src/pages/bates-numbering.html index 696b64d1..b7c537e6 100644 --- a/src/pages/bates-numbering.html +++ b/src/pages/bates-numbering.html @@ -16,10 +16,7 @@ - + - Bmp To Pdf Online Free - Bmp To Pdf Tool | BentoPDF - + @@ -386,11 +385,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.8", - "ratingCount": "2860" } } diff --git a/src/pages/bookmark.html b/src/pages/bookmark.html index 783daa91..5b0e54e9 100644 --- a/src/pages/bookmark.html +++ b/src/pages/bookmark.html @@ -5,7 +5,6 @@ - Bookmark Online Free - Bookmark Tool | BentoPDF - + @@ -809,11 +808,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.7", - "ratingCount": "2638" } } diff --git a/src/pages/cbz-to-pdf.html b/src/pages/cbz-to-pdf.html index 4d23f847..f06d9978 100644 --- a/src/pages/cbz-to-pdf.html +++ b/src/pages/cbz-to-pdf.html @@ -5,7 +5,6 @@ - Cbz To Pdf Online Free - Cbz To Pdf Tool | BentoPDF - + @@ -393,11 +392,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.8", - "ratingCount": "4409" } } diff --git a/src/pages/change-permissions.html b/src/pages/change-permissions.html index 61c9864f..8c0d8366 100644 --- a/src/pages/change-permissions.html +++ b/src/pages/change-permissions.html @@ -5,9 +5,6 @@ - - Change Permissions Online Free - Change Permissions Tool | BentoPDF - - + @@ -513,11 +507,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.7", - "ratingCount": "4041" } } diff --git a/src/pages/combine-single-page.html b/src/pages/combine-single-page.html index 41717748..90b2de18 100644 --- a/src/pages/combine-single-page.html +++ b/src/pages/combine-single-page.html @@ -5,9 +5,6 @@ - - Combine Single Page Online Free - Combine Single Page Tool | BentoPDF - - + @@ -480,11 +474,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.6", - "ratingCount": "3891" } } diff --git a/src/pages/compare-pdfs.html b/src/pages/compare-pdfs.html index 06123a36..2ea17730 100644 --- a/src/pages/compare-pdfs.html +++ b/src/pages/compare-pdfs.html @@ -5,7 +5,6 @@ - Compare Pdfs Online Free - Compare Pdfs Tool | BentoPDF - + @@ -1198,11 +1197,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.9", - "ratingCount": "2494" } } diff --git a/src/pages/compress-pdf.html b/src/pages/compress-pdf.html index bc325b53..e52dd717 100644 --- a/src/pages/compress-pdf.html +++ b/src/pages/compress-pdf.html @@ -27,7 +27,7 @@ /> - + @@ -589,11 +589,6 @@ "price": "0", "priceCurrency": "USD" }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.7", - "ratingCount": "3421" - }, "featureList": [ "Reduce PDF file size", "Multiple compression algorithms", diff --git a/src/pages/crop-pdf.html b/src/pages/crop-pdf.html index a42ecbde..62785b92 100644 --- a/src/pages/crop-pdf.html +++ b/src/pages/crop-pdf.html @@ -5,7 +5,6 @@ - Crop Pdf Online Free - Crop Pdf Tool | BentoPDF - + @@ -441,11 +440,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.8", - "ratingCount": "1146" } } diff --git a/src/pages/csv-to-pdf.html b/src/pages/csv-to-pdf.html index 06342c70..c6c5c8f0 100644 --- a/src/pages/csv-to-pdf.html +++ b/src/pages/csv-to-pdf.html @@ -5,7 +5,6 @@ - Csv To Pdf Online Free - Csv To Pdf Tool | BentoPDF - + @@ -391,11 +390,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.7", - "ratingCount": "1552" } } diff --git a/src/pages/decrypt-pdf.html b/src/pages/decrypt-pdf.html index 700f383a..8a2c0a99 100644 --- a/src/pages/decrypt-pdf.html +++ b/src/pages/decrypt-pdf.html @@ -5,7 +5,6 @@ - Decrypt PDF Online Free - Decrypt PDF Tool | BentoPDF - + @@ -417,11 +416,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.7", - "ratingCount": "1342" } } diff --git a/src/pages/delete-pages.html b/src/pages/delete-pages.html index 6f5f7a76..c24dfed6 100644 --- a/src/pages/delete-pages.html +++ b/src/pages/delete-pages.html @@ -5,7 +5,6 @@ - Delete Pages Online Free - Delete Pages Tool | BentoPDF - + @@ -389,11 +388,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.7", - "ratingCount": "1206" } } diff --git a/src/pages/deskew-pdf.html b/src/pages/deskew-pdf.html index 2b31ab7e..691117d2 100644 --- a/src/pages/deskew-pdf.html +++ b/src/pages/deskew-pdf.html @@ -23,7 +23,7 @@ content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1" /> - + @@ -420,11 +420,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.8", - "ratingCount": "1892" } } diff --git a/src/pages/digital-sign-pdf.html b/src/pages/digital-sign-pdf.html index 0e46512e..c93008d6 100644 --- a/src/pages/digital-sign-pdf.html +++ b/src/pages/digital-sign-pdf.html @@ -25,10 +25,7 @@ content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1" /> - + diff --git a/src/pages/divide-pages.html b/src/pages/divide-pages.html index b455a560..45b79956 100644 --- a/src/pages/divide-pages.html +++ b/src/pages/divide-pages.html @@ -5,7 +5,6 @@ - Divide Pages Online Free - Divide Pages Tool | BentoPDF - + @@ -431,11 +430,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.6", - "ratingCount": "4555" } } diff --git a/src/pages/edit-attachments.html b/src/pages/edit-attachments.html index 3a6a9975..6fb1823f 100644 --- a/src/pages/edit-attachments.html +++ b/src/pages/edit-attachments.html @@ -5,9 +5,6 @@ - - Edit Attachments Online Free - Edit Attachments Tool | BentoPDF - - + @@ -394,11 +388,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.9", - "ratingCount": "4238" } } diff --git a/src/pages/edit-metadata.html b/src/pages/edit-metadata.html index 811bb708..49dbdc83 100644 --- a/src/pages/edit-metadata.html +++ b/src/pages/edit-metadata.html @@ -5,7 +5,6 @@ - Edit Metadata Online Free - Edit Metadata Tool | BentoPDF - + @@ -496,11 +495,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.8", - "ratingCount": "3583" } } diff --git a/src/pages/edit-pdf.html b/src/pages/edit-pdf.html index 0757f662..7c4ebdec 100644 --- a/src/pages/edit-pdf.html +++ b/src/pages/edit-pdf.html @@ -5,7 +5,6 @@ - Edit PDF Online Free - Edit PDF Tool | BentoPDF - + @@ -376,11 +375,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.7", - "ratingCount": "4169" } } diff --git a/src/pages/email-to-pdf.html b/src/pages/email-to-pdf.html index f4d208e2..a0315212 100644 --- a/src/pages/email-to-pdf.html +++ b/src/pages/email-to-pdf.html @@ -5,9 +5,6 @@ - - Email to PDF Converter Free Online - EML MSG to PDF | BentoPDF - - + @@ -467,11 +464,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.7", - "ratingCount": "2156" } } diff --git a/src/pages/encrypt-pdf.html b/src/pages/encrypt-pdf.html index 52795925..201d6461 100644 --- a/src/pages/encrypt-pdf.html +++ b/src/pages/encrypt-pdf.html @@ -5,7 +5,6 @@ - Encrypt PDF Online Free - Encrypt PDF Tool | BentoPDF - + @@ -425,11 +424,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.7", - "ratingCount": "1162" } } diff --git a/src/pages/epub-to-pdf.html b/src/pages/epub-to-pdf.html index e844f715..810f35a5 100644 --- a/src/pages/epub-to-pdf.html +++ b/src/pages/epub-to-pdf.html @@ -5,7 +5,6 @@ - Epub To Pdf Online Free - Epub To Pdf Tool | BentoPDF - + @@ -394,11 +393,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.6", - "ratingCount": "4043" } } diff --git a/src/pages/excel-to-pdf.html b/src/pages/excel-to-pdf.html index fb3b177d..375efcda 100644 --- a/src/pages/excel-to-pdf.html +++ b/src/pages/excel-to-pdf.html @@ -5,7 +5,6 @@ - Excel to PDF Converter Free Online - Convert Files | BentoPDF - + @@ -398,11 +397,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.9", - "ratingCount": "1436" } } diff --git a/src/pages/extract-attachments.html b/src/pages/extract-attachments.html index 36a8738f..4757a749 100644 --- a/src/pages/extract-attachments.html +++ b/src/pages/extract-attachments.html @@ -5,9 +5,6 @@ - - Extract Attachments Online Free - Extract Attachments Tool | BentoPDF - - + @@ -406,11 +400,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.7", - "ratingCount": "1841" } } diff --git a/src/pages/extract-images.html b/src/pages/extract-images.html index 0e70f016..68b6e306 100644 --- a/src/pages/extract-images.html +++ b/src/pages/extract-images.html @@ -5,7 +5,6 @@ - Extract Images Online Free - Extract Images Tool | BentoPDF - + @@ -406,11 +405,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.8", - "ratingCount": "1693" } } diff --git a/src/pages/extract-pages.html b/src/pages/extract-pages.html index 5742db99..8bdd30dd 100644 --- a/src/pages/extract-pages.html +++ b/src/pages/extract-pages.html @@ -5,7 +5,6 @@ - Extract Pages Online Free - Extract Pages Tool | BentoPDF - + @@ -392,11 +391,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.9", - "ratingCount": "3221" } } diff --git a/src/pages/extract-tables.html b/src/pages/extract-tables.html index 297bdd80..e9ea9e59 100644 --- a/src/pages/extract-tables.html +++ b/src/pages/extract-tables.html @@ -5,7 +5,6 @@ - Extract Tables Online Free - Extract Tables Tool | BentoPDF - + @@ -410,11 +409,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.7", - "ratingCount": "1056" } } diff --git a/src/pages/fb2-to-pdf.html b/src/pages/fb2-to-pdf.html index 778fd8d3..4d43c5a4 100644 --- a/src/pages/fb2-to-pdf.html +++ b/src/pages/fb2-to-pdf.html @@ -5,7 +5,6 @@ - Fb2 To Pdf Online Free - Fb2 To Pdf Tool | BentoPDF - + @@ -393,11 +392,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.9", - "ratingCount": "2174" } } diff --git a/src/pages/fix-page-size.html b/src/pages/fix-page-size.html index 5cae9b8e..aa18d0d7 100644 --- a/src/pages/fix-page-size.html +++ b/src/pages/fix-page-size.html @@ -5,7 +5,6 @@ - Fix Page Size Online Free - Fix Page Size Tool | BentoPDF - + @@ -521,11 +520,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.8", - "ratingCount": "1030" } } diff --git a/src/pages/flatten-pdf.html b/src/pages/flatten-pdf.html index 62123d5d..e277dd19 100644 --- a/src/pages/flatten-pdf.html +++ b/src/pages/flatten-pdf.html @@ -5,7 +5,6 @@ - Flatten Pdf Online Free - Flatten Pdf Tool | BentoPDF - + @@ -415,11 +414,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.7", - "ratingCount": "4300" } } diff --git a/src/pages/font-to-outline.html b/src/pages/font-to-outline.html index 3e5ddc1b..0380ad9b 100644 --- a/src/pages/font-to-outline.html +++ b/src/pages/font-to-outline.html @@ -25,10 +25,7 @@ content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1" /> - + diff --git a/src/pages/form-creator.html b/src/pages/form-creator.html index 3ad9ba1b..93819596 100644 --- a/src/pages/form-creator.html +++ b/src/pages/form-creator.html @@ -5,7 +5,6 @@ - Form Creator Online Free - Form Creator Tool | BentoPDF - + @@ -730,11 +729,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.9", - "ratingCount": "1752" } } diff --git a/src/pages/form-filler.html b/src/pages/form-filler.html index fc7fa0f9..b51825ea 100644 --- a/src/pages/form-filler.html +++ b/src/pages/form-filler.html @@ -5,7 +5,6 @@ - Form Filler Online Free - Form Filler Tool | BentoPDF - + @@ -399,11 +398,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.9", - "ratingCount": "3881" } } diff --git a/src/pages/header-footer.html b/src/pages/header-footer.html index c8e023fa..bef2cc7d 100644 --- a/src/pages/header-footer.html +++ b/src/pages/header-footer.html @@ -5,7 +5,6 @@ - Header Footer Online Free - Header Footer Tool | BentoPDF - + @@ -506,11 +505,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.7", - "ratingCount": "2302" } } diff --git a/src/pages/heic-to-pdf.html b/src/pages/heic-to-pdf.html index 18b8f5f4..e9b9ab86 100644 --- a/src/pages/heic-to-pdf.html +++ b/src/pages/heic-to-pdf.html @@ -5,7 +5,6 @@ - Heic To Pdf Online Free - Heic To Pdf Tool | BentoPDF - + @@ -389,11 +388,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.7", - "ratingCount": "3112" } } diff --git a/src/pages/image-to-pdf.html b/src/pages/image-to-pdf.html index 07b60de1..f3d06288 100644 --- a/src/pages/image-to-pdf.html +++ b/src/pages/image-to-pdf.html @@ -5,7 +5,6 @@ - Image To Pdf Online Free - Image To Pdf Tool | BentoPDF - + @@ -416,11 +415,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.9", - "ratingCount": "3747" } } diff --git a/src/pages/invert-colors.html b/src/pages/invert-colors.html index 19f92c16..5ec58ed5 100644 --- a/src/pages/invert-colors.html +++ b/src/pages/invert-colors.html @@ -5,7 +5,6 @@ - Invert Colors Online Free - Invert Colors Tool | BentoPDF - + @@ -378,11 +377,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.7", - "ratingCount": "2768" } } diff --git a/src/pages/jpg-to-pdf.html b/src/pages/jpg-to-pdf.html index ec487bfc..7d02cc83 100644 --- a/src/pages/jpg-to-pdf.html +++ b/src/pages/jpg-to-pdf.html @@ -5,7 +5,6 @@ - JPG to PDF Converter Free Online - Convert Files | BentoPDF - + @@ -417,11 +416,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.6", - "ratingCount": "1763" } } diff --git a/src/pages/json-to-pdf.html b/src/pages/json-to-pdf.html index b31e2047..ba491057 100644 --- a/src/pages/json-to-pdf.html +++ b/src/pages/json-to-pdf.html @@ -5,7 +5,6 @@ - Json To Pdf Online Free - Json To Pdf Tool | BentoPDF - + @@ -340,11 +339,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.6", - "ratingCount": "2805" } } diff --git a/src/pages/linearize-pdf.html b/src/pages/linearize-pdf.html index 1e1ef13a..520d636a 100644 --- a/src/pages/linearize-pdf.html +++ b/src/pages/linearize-pdf.html @@ -5,7 +5,6 @@ - Linearize Pdf Online Free - Linearize Pdf Tool | BentoPDF - + @@ -411,11 +410,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.8", - "ratingCount": "1598" } } diff --git a/src/pages/markdown-to-pdf.html b/src/pages/markdown-to-pdf.html index 6f10ad64..92fc70a1 100644 --- a/src/pages/markdown-to-pdf.html +++ b/src/pages/markdown-to-pdf.html @@ -5,7 +5,6 @@ - Markdown To Pdf Online Free - Markdown To Pdf Tool | BentoPDF - + @@ -312,11 +308,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.6", - "ratingCount": "4044" } } diff --git a/src/pages/merge-pdf.html b/src/pages/merge-pdf.html index 332573c1..e938b7f6 100644 --- a/src/pages/merge-pdf.html +++ b/src/pages/merge-pdf.html @@ -27,7 +27,7 @@ /> - + @@ -471,11 +471,6 @@ "price": "0", "priceCurrency": "USD" }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.8", - "ratingCount": "4156" - }, "featureList": [ "Merge multiple PDFs into one", "Drag and drop to reorder files", diff --git a/src/pages/mobi-to-pdf.html b/src/pages/mobi-to-pdf.html index 4ac89dc6..bf12d886 100644 --- a/src/pages/mobi-to-pdf.html +++ b/src/pages/mobi-to-pdf.html @@ -5,7 +5,6 @@ - Mobi To Pdf Online Free - Mobi To Pdf Tool | BentoPDF - + @@ -392,11 +391,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.9", - "ratingCount": "4948" } } diff --git a/src/pages/n-up-pdf.html b/src/pages/n-up-pdf.html index 8cd1fc30..72e6f996 100644 --- a/src/pages/n-up-pdf.html +++ b/src/pages/n-up-pdf.html @@ -5,7 +5,6 @@ - N Up Pdf Online Free - N Up Pdf Tool | BentoPDF - + @@ -474,11 +473,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.7", - "ratingCount": "2995" } } diff --git a/src/pages/ocr-pdf.html b/src/pages/ocr-pdf.html index ec6c1237..e21f08ed 100644 --- a/src/pages/ocr-pdf.html +++ b/src/pages/ocr-pdf.html @@ -5,7 +5,6 @@ - OCR PDF Online Free - OCR PDF Tool | BentoPDF - + @@ -603,11 +602,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.7", - "ratingCount": "2133" } } diff --git a/src/pages/odg-to-pdf.html b/src/pages/odg-to-pdf.html index f50fef2a..f5322fa7 100644 --- a/src/pages/odg-to-pdf.html +++ b/src/pages/odg-to-pdf.html @@ -5,7 +5,6 @@ - Odg To Pdf Online Free - Odg To Pdf Tool | BentoPDF - + @@ -399,11 +398,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.6", - "ratingCount": "2649" } } diff --git a/src/pages/odp-to-pdf.html b/src/pages/odp-to-pdf.html index f130e862..5ec7dc2f 100644 --- a/src/pages/odp-to-pdf.html +++ b/src/pages/odp-to-pdf.html @@ -5,7 +5,6 @@ - Odp To Pdf Online Free - Odp To Pdf Tool | BentoPDF - + @@ -401,11 +400,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.9", - "ratingCount": "1143" } } diff --git a/src/pages/ods-to-pdf.html b/src/pages/ods-to-pdf.html index 7bceb491..1fc95698 100644 --- a/src/pages/ods-to-pdf.html +++ b/src/pages/ods-to-pdf.html @@ -5,7 +5,6 @@ - Ods To Pdf Online Free - Ods To Pdf Tool | BentoPDF - + @@ -401,11 +400,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.8", - "ratingCount": "2071" } } diff --git a/src/pages/odt-to-pdf.html b/src/pages/odt-to-pdf.html index fd5dbc7f..4d3f23f1 100644 --- a/src/pages/odt-to-pdf.html +++ b/src/pages/odt-to-pdf.html @@ -5,7 +5,6 @@ - Odt To Pdf Online Free - Odt To Pdf Tool | BentoPDF - + @@ -392,11 +391,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.7", - "ratingCount": "2724" } } diff --git a/src/pages/organize-pdf.html b/src/pages/organize-pdf.html index 5e6a365e..3b860004 100644 --- a/src/pages/organize-pdf.html +++ b/src/pages/organize-pdf.html @@ -5,7 +5,6 @@ - Organize PDF Online Free - Organize PDF Tool | BentoPDF - + @@ -412,11 +411,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.6", - "ratingCount": "3604" } } diff --git a/src/pages/overlay-pdf.html b/src/pages/overlay-pdf.html index ccd14f14..5458fda5 100644 --- a/src/pages/overlay-pdf.html +++ b/src/pages/overlay-pdf.html @@ -23,7 +23,7 @@ content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1" /> - + diff --git a/src/pages/page-dimensions.html b/src/pages/page-dimensions.html index 7775ecd4..924cd62f 100644 --- a/src/pages/page-dimensions.html +++ b/src/pages/page-dimensions.html @@ -5,7 +5,6 @@ - Page Dimensions Online Free - Page Dimensions Tool | BentoPDF - + @@ -413,11 +409,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.8", - "ratingCount": "1191" } } diff --git a/src/pages/page-numbers.html b/src/pages/page-numbers.html index e474463d..14077402 100644 --- a/src/pages/page-numbers.html +++ b/src/pages/page-numbers.html @@ -5,7 +5,6 @@ - Page Numbers Online Free - Page Numbers Tool | BentoPDF - + @@ -438,11 +437,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.9", - "ratingCount": "4066" } } diff --git a/src/pages/pages-to-pdf.html b/src/pages/pages-to-pdf.html index 1bd6b0fb..63db2c50 100644 --- a/src/pages/pages-to-pdf.html +++ b/src/pages/pages-to-pdf.html @@ -5,7 +5,6 @@ - Pages To Pdf Online Free - Pages To Pdf Tool | BentoPDF - + @@ -400,11 +399,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.8", - "ratingCount": "1515" } } diff --git a/src/pages/pdf-booklet.html b/src/pages/pdf-booklet.html index ffd3c3ff..eb6117bb 100644 --- a/src/pages/pdf-booklet.html +++ b/src/pages/pdf-booklet.html @@ -5,7 +5,6 @@ - Pdf Booklet Online Free - Pdf Booklet Tool | BentoPDF - + @@ -618,11 +617,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.6", - "ratingCount": "1453" } } diff --git a/src/pages/pdf-layers.html b/src/pages/pdf-layers.html index 663c4ac7..21eeaf77 100644 --- a/src/pages/pdf-layers.html +++ b/src/pages/pdf-layers.html @@ -5,7 +5,6 @@ - Pdf Layers Online Free - Pdf Layers Tool | BentoPDF - + @@ -545,11 +544,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.7", - "ratingCount": "1593" } } diff --git a/src/pages/pdf-multi-tool.html b/src/pages/pdf-multi-tool.html index 0928af24..d7865f3b 100644 --- a/src/pages/pdf-multi-tool.html +++ b/src/pages/pdf-multi-tool.html @@ -5,7 +5,6 @@ - Pdf Multi Tool Online Free - Pdf Multi Tool Tool | BentoPDF - + @@ -493,11 +492,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.7", - "ratingCount": "3335" } } diff --git a/src/pages/pdf-to-bmp.html b/src/pages/pdf-to-bmp.html index 6f044ca4..a37bed4f 100644 --- a/src/pages/pdf-to-bmp.html +++ b/src/pages/pdf-to-bmp.html @@ -5,7 +5,6 @@ - Pdf To Bmp Online Free - Pdf To Bmp Tool | BentoPDF - + @@ -374,11 +373,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.8", - "ratingCount": "2641" } } diff --git a/src/pages/pdf-to-cbz.html b/src/pages/pdf-to-cbz.html index ea1bf351..cc63b262 100644 --- a/src/pages/pdf-to-cbz.html +++ b/src/pages/pdf-to-cbz.html @@ -25,7 +25,7 @@ content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1" /> - + diff --git a/src/pages/pdf-to-csv.html b/src/pages/pdf-to-csv.html index 6f20a8b0..2779d05b 100644 --- a/src/pages/pdf-to-csv.html +++ b/src/pages/pdf-to-csv.html @@ -5,7 +5,6 @@ - Pdf To Csv Online Free - Pdf To Csv Tool | BentoPDF - + @@ -358,11 +357,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.9", - "ratingCount": "3069" } } diff --git a/src/pages/pdf-to-docx.html b/src/pages/pdf-to-docx.html index 70872f8f..d19bb2c3 100644 --- a/src/pages/pdf-to-docx.html +++ b/src/pages/pdf-to-docx.html @@ -5,7 +5,6 @@ - PDF to Word Converter Free Online - Convert Files | BentoPDF - + @@ -389,11 +388,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.6", - "ratingCount": "4973" } } diff --git a/src/pages/pdf-to-excel.html b/src/pages/pdf-to-excel.html index a18e6b1c..a59d8968 100644 --- a/src/pages/pdf-to-excel.html +++ b/src/pages/pdf-to-excel.html @@ -5,7 +5,6 @@ - PDF to Excel Converter Free Online - Convert Files | BentoPDF - + @@ -361,11 +360,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.7", - "ratingCount": "2771" } } diff --git a/src/pages/pdf-to-greyscale.html b/src/pages/pdf-to-greyscale.html index 1f4dfe4f..5f2b2230 100644 --- a/src/pages/pdf-to-greyscale.html +++ b/src/pages/pdf-to-greyscale.html @@ -5,9 +5,6 @@ - - Pdf To Greyscale Online Free - Pdf To Greyscale Tool | BentoPDF - - + @@ -384,11 +378,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.7", - "ratingCount": "1897" } } diff --git a/src/pages/pdf-to-jpg.html b/src/pages/pdf-to-jpg.html index 98acf4f6..e38bb213 100644 --- a/src/pages/pdf-to-jpg.html +++ b/src/pages/pdf-to-jpg.html @@ -5,7 +5,6 @@ - PDF to JPG Converter Free Online - Convert Files | BentoPDF - + @@ -425,11 +424,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.7", - "ratingCount": "3191" } } diff --git a/src/pages/pdf-to-json.html b/src/pages/pdf-to-json.html index 9cf0e58a..df9a7270 100644 --- a/src/pages/pdf-to-json.html +++ b/src/pages/pdf-to-json.html @@ -5,7 +5,6 @@ - Pdf To Json Online Free - Pdf To Json Tool | BentoPDF - + @@ -336,11 +335,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.7", - "ratingCount": "1430" } } diff --git a/src/pages/pdf-to-markdown.html b/src/pages/pdf-to-markdown.html index 7be0ef10..b295782b 100644 --- a/src/pages/pdf-to-markdown.html +++ b/src/pages/pdf-to-markdown.html @@ -5,7 +5,6 @@ - Pdf To Markdown Online Free - Pdf To Markdown Tool | BentoPDF - + @@ -406,11 +402,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.8", - "ratingCount": "1689" } } diff --git a/src/pages/pdf-to-pdfa.html b/src/pages/pdf-to-pdfa.html index 0525b2fb..0fec7d78 100644 --- a/src/pages/pdf-to-pdfa.html +++ b/src/pages/pdf-to-pdfa.html @@ -5,7 +5,6 @@ - Pdf To Pdfa Online Free - Pdf To Pdfa Tool | BentoPDF - + @@ -436,11 +435,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.8", - "ratingCount": "4186" } } diff --git a/src/pages/pdf-to-png.html b/src/pages/pdf-to-png.html index 4a13ab81..95cde067 100644 --- a/src/pages/pdf-to-png.html +++ b/src/pages/pdf-to-png.html @@ -5,7 +5,6 @@ - Pdf To Png Online Free - Pdf To Png Tool | BentoPDF - + @@ -405,11 +404,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.8", - "ratingCount": "2693" } } diff --git a/src/pages/pdf-to-svg.html b/src/pages/pdf-to-svg.html index 317159ad..4df6bb30 100644 --- a/src/pages/pdf-to-svg.html +++ b/src/pages/pdf-to-svg.html @@ -5,7 +5,6 @@ - Pdf To Svg Online Free - Pdf To Svg Tool | BentoPDF - + @@ -389,11 +388,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.8", - "ratingCount": "2683" } } diff --git a/src/pages/pdf-to-text.html b/src/pages/pdf-to-text.html index a6dff7cd..2919eb92 100644 --- a/src/pages/pdf-to-text.html +++ b/src/pages/pdf-to-text.html @@ -5,7 +5,6 @@ - Pdf To Text Online Free - Pdf To Text Tool | BentoPDF - + @@ -420,11 +419,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.8", - "ratingCount": "1972" } } diff --git a/src/pages/pdf-to-tiff.html b/src/pages/pdf-to-tiff.html index 56d32a56..9fed5ad8 100644 --- a/src/pages/pdf-to-tiff.html +++ b/src/pages/pdf-to-tiff.html @@ -5,7 +5,6 @@ - Pdf To Tiff Online Free - Pdf To Tiff Tool | BentoPDF - + @@ -463,11 +462,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.6", - "ratingCount": "2158" } } diff --git a/src/pages/pdf-to-webp.html b/src/pages/pdf-to-webp.html index 5ea9c612..f65de7f5 100644 --- a/src/pages/pdf-to-webp.html +++ b/src/pages/pdf-to-webp.html @@ -5,7 +5,6 @@ - Pdf To Webp Online Free - Pdf To Webp Tool | BentoPDF - + @@ -405,11 +404,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.9", - "ratingCount": "2435" } } diff --git a/src/pages/pdf-to-zip.html b/src/pages/pdf-to-zip.html index 01da05ec..dd900119 100644 --- a/src/pages/pdf-to-zip.html +++ b/src/pages/pdf-to-zip.html @@ -5,7 +5,6 @@ - Pdf To Zip Online Free - Pdf To Zip Tool | BentoPDF - + @@ -396,11 +395,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.8", - "ratingCount": "3143" } } diff --git a/src/pages/pdf-workflow.html b/src/pages/pdf-workflow.html index 9092d307..e5166922 100644 --- a/src/pages/pdf-workflow.html +++ b/src/pages/pdf-workflow.html @@ -23,7 +23,7 @@ content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1" /> - + diff --git a/src/pages/png-to-pdf.html b/src/pages/png-to-pdf.html index 62fd03b8..b8651adf 100644 --- a/src/pages/png-to-pdf.html +++ b/src/pages/png-to-pdf.html @@ -5,7 +5,6 @@ - PNG to PDF Converter Free Online - Convert Files | BentoPDF - + @@ -414,11 +413,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.8", - "ratingCount": "4987" } } diff --git a/src/pages/posterize-pdf.html b/src/pages/posterize-pdf.html index d82b4403..f77dd50a 100644 --- a/src/pages/posterize-pdf.html +++ b/src/pages/posterize-pdf.html @@ -5,7 +5,6 @@ - Posterize Pdf Online Free - Posterize Pdf Tool | BentoPDF - + @@ -575,11 +574,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.7", - "ratingCount": "3382" } } diff --git a/src/pages/powerpoint-to-pdf.html b/src/pages/powerpoint-to-pdf.html index 95417626..ad4b571c 100644 --- a/src/pages/powerpoint-to-pdf.html +++ b/src/pages/powerpoint-to-pdf.html @@ -5,9 +5,6 @@ - - PowerPoint to PDF Converter Free Online - Convert Files | BentoPDF - - + @@ -415,11 +409,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.6", - "ratingCount": "4288" } } diff --git a/src/pages/prepare-pdf-for-ai.html b/src/pages/prepare-pdf-for-ai.html index aab1e948..b8e9c07f 100644 --- a/src/pages/prepare-pdf-for-ai.html +++ b/src/pages/prepare-pdf-for-ai.html @@ -5,9 +5,6 @@ - - Prepare Pdf For Ai Online Free - Prepare Pdf For Ai Tool | BentoPDF - - + @@ -426,11 +420,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.8", - "ratingCount": "2267" } } diff --git a/src/pages/psd-to-pdf.html b/src/pages/psd-to-pdf.html index 13432f61..796c2261 100644 --- a/src/pages/psd-to-pdf.html +++ b/src/pages/psd-to-pdf.html @@ -5,7 +5,6 @@ - Psd To Pdf Online Free - Psd To Pdf Tool | BentoPDF - + @@ -389,11 +388,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.8", - "ratingCount": "1585" } } diff --git a/src/pages/pub-to-pdf.html b/src/pages/pub-to-pdf.html index 8e3e83b5..69eb091f 100644 --- a/src/pages/pub-to-pdf.html +++ b/src/pages/pub-to-pdf.html @@ -5,7 +5,6 @@ - Pub To Pdf Online Free - Pub To Pdf Tool | BentoPDF - + @@ -399,11 +398,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.8", - "ratingCount": "2200" } } diff --git a/src/pages/rasterize-pdf.html b/src/pages/rasterize-pdf.html index 5a1961ae..3cf84706 100644 --- a/src/pages/rasterize-pdf.html +++ b/src/pages/rasterize-pdf.html @@ -5,7 +5,6 @@ - Rasterize Pdf Online Free - Rasterize Pdf Tool | BentoPDF - + @@ -431,11 +430,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.8", - "ratingCount": "2489" } } diff --git a/src/pages/remove-annotations.html b/src/pages/remove-annotations.html index bff07885..b5e07c09 100644 --- a/src/pages/remove-annotations.html +++ b/src/pages/remove-annotations.html @@ -5,9 +5,6 @@ - - Remove Annotations Online Free - Remove Annotations Tool | BentoPDF - - + @@ -398,11 +392,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.6", - "ratingCount": "2505" } } diff --git a/src/pages/remove-blank-pages.html b/src/pages/remove-blank-pages.html index 84b38f6a..f88e432b 100644 --- a/src/pages/remove-blank-pages.html +++ b/src/pages/remove-blank-pages.html @@ -5,9 +5,6 @@ - - Remove Blank Pages Online Free - Remove Blank Pages Tool | BentoPDF - - + @@ -434,11 +428,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.7", - "ratingCount": "4414" } } diff --git a/src/pages/remove-metadata.html b/src/pages/remove-metadata.html index 5f3cddbc..f6543fe5 100644 --- a/src/pages/remove-metadata.html +++ b/src/pages/remove-metadata.html @@ -5,7 +5,6 @@ - Remove Metadata Online Free - Remove Metadata Tool | BentoPDF - + @@ -400,11 +396,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.9", - "ratingCount": "2893" } } diff --git a/src/pages/remove-restrictions.html b/src/pages/remove-restrictions.html index 12934b0d..bbec6b49 100644 --- a/src/pages/remove-restrictions.html +++ b/src/pages/remove-restrictions.html @@ -5,9 +5,6 @@ - - Remove Restrictions Online Free - Remove Restrictions Tool | BentoPDF - - + @@ -428,11 +422,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.8", - "ratingCount": "4996" } } diff --git a/src/pages/repair-pdf.html b/src/pages/repair-pdf.html index 4d4dc1a9..0cb8dc88 100644 --- a/src/pages/repair-pdf.html +++ b/src/pages/repair-pdf.html @@ -5,7 +5,6 @@ - Repair Pdf Online Free - Repair Pdf Tool | BentoPDF - + @@ -391,11 +390,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.6", - "ratingCount": "3968" } } diff --git a/src/pages/reverse-pages.html b/src/pages/reverse-pages.html index 01ef8cd5..13217b3d 100644 --- a/src/pages/reverse-pages.html +++ b/src/pages/reverse-pages.html @@ -5,7 +5,6 @@ - Reverse Pages Online Free - Reverse Pages Tool | BentoPDF - + @@ -395,11 +394,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.9", - "ratingCount": "1184" } } diff --git a/src/pages/rotate-custom.html b/src/pages/rotate-custom.html index eceeb6e6..bafc5c37 100644 --- a/src/pages/rotate-custom.html +++ b/src/pages/rotate-custom.html @@ -5,7 +5,6 @@ - Rotate Custom Online Free - Rotate Custom Tool | BentoPDF - + @@ -409,11 +408,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.9", - "ratingCount": "2923" } } diff --git a/src/pages/rotate-pdf.html b/src/pages/rotate-pdf.html index dc2c33e0..964ead9f 100644 --- a/src/pages/rotate-pdf.html +++ b/src/pages/rotate-pdf.html @@ -5,7 +5,6 @@ - Rotate PDF Online Free - Rotate PDF Tool | BentoPDF - + @@ -409,11 +408,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.8", - "ratingCount": "4745" } } diff --git a/src/pages/rtf-to-pdf.html b/src/pages/rtf-to-pdf.html index 51716102..e2ab8e32 100644 --- a/src/pages/rtf-to-pdf.html +++ b/src/pages/rtf-to-pdf.html @@ -5,7 +5,6 @@ - Rtf To Pdf Online Free - Rtf To Pdf Tool | BentoPDF - + @@ -391,11 +390,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.7", - "ratingCount": "2404" } } diff --git a/src/pages/sanitize-pdf.html b/src/pages/sanitize-pdf.html index aa54baea..e036149d 100644 --- a/src/pages/sanitize-pdf.html +++ b/src/pages/sanitize-pdf.html @@ -5,7 +5,6 @@ - Sanitize Pdf Online Free - Sanitize Pdf Tool | BentoPDF - + @@ -487,11 +486,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.9", - "ratingCount": "2439" } } diff --git a/src/pages/scanner-effect.html b/src/pages/scanner-effect.html index acc83bc0..ec39fe89 100644 --- a/src/pages/scanner-effect.html +++ b/src/pages/scanner-effect.html @@ -4,7 +4,6 @@ - Scanner Effect Online Free - Scanner Effect Tool | BentoPDF - + @@ -607,11 +606,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.7", - "ratingCount": "2143" } } diff --git a/src/pages/sign-pdf.html b/src/pages/sign-pdf.html index 3d78fc52..46a8ffd9 100644 --- a/src/pages/sign-pdf.html +++ b/src/pages/sign-pdf.html @@ -5,7 +5,6 @@ - Sign PDF Online Free - Sign PDF Tool | BentoPDF - + @@ -406,11 +405,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.9", - "ratingCount": "3805" } } diff --git a/src/pages/split-pdf.html b/src/pages/split-pdf.html index bf8e28bd..c629bffa 100644 --- a/src/pages/split-pdf.html +++ b/src/pages/split-pdf.html @@ -5,7 +5,6 @@ - Split PDF Online Free - Split PDF Tool | BentoPDF - + @@ -606,11 +605,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.8", - "ratingCount": "3584" } } diff --git a/src/pages/svg-to-pdf.html b/src/pages/svg-to-pdf.html index 8aa499c3..88455961 100644 --- a/src/pages/svg-to-pdf.html +++ b/src/pages/svg-to-pdf.html @@ -5,7 +5,6 @@ - Svg To Pdf Online Free - Svg To Pdf Tool | BentoPDF - + @@ -417,11 +416,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.9", - "ratingCount": "1705" } } diff --git a/src/pages/table-of-contents.html b/src/pages/table-of-contents.html index 79db66cf..356ef69c 100644 --- a/src/pages/table-of-contents.html +++ b/src/pages/table-of-contents.html @@ -5,9 +5,6 @@ - - Table Of Contents Online Free - Table Of Contents Tool | BentoPDF - - + @@ -428,11 +422,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.7", - "ratingCount": "3702" } } diff --git a/src/pages/text-color.html b/src/pages/text-color.html index 7c5c2c53..d0bab13a 100644 --- a/src/pages/text-color.html +++ b/src/pages/text-color.html @@ -5,7 +5,6 @@ - Text Color Online Free - Text Color Tool | BentoPDF - + @@ -386,11 +385,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.6", - "ratingCount": "3364" } } diff --git a/src/pages/tiff-to-pdf.html b/src/pages/tiff-to-pdf.html index 4969ac7d..9d40555f 100644 --- a/src/pages/tiff-to-pdf.html +++ b/src/pages/tiff-to-pdf.html @@ -5,7 +5,6 @@ - Tiff To Pdf Online Free - Tiff To Pdf Tool | BentoPDF - + @@ -410,11 +409,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.9", - "ratingCount": "4393" } } diff --git a/src/pages/timestamp-pdf.html b/src/pages/timestamp-pdf.html index d6c57209..e26d9941 100644 --- a/src/pages/timestamp-pdf.html +++ b/src/pages/timestamp-pdf.html @@ -25,7 +25,7 @@ content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1" /> - + @@ -429,11 +429,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.9", - "ratingCount": "1850" } } diff --git a/src/pages/txt-to-pdf.html b/src/pages/txt-to-pdf.html index 337e452f..ef7b4d15 100644 --- a/src/pages/txt-to-pdf.html +++ b/src/pages/txt-to-pdf.html @@ -5,7 +5,6 @@ - Txt To Pdf Online Free - Txt To Pdf Tool | BentoPDF - + @@ -534,11 +533,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.9", - "ratingCount": "2303" } } diff --git a/src/pages/validate-signature-pdf.html b/src/pages/validate-signature-pdf.html index 5f15fa46..78efa364 100644 --- a/src/pages/validate-signature-pdf.html +++ b/src/pages/validate-signature-pdf.html @@ -27,7 +27,7 @@ diff --git a/src/pages/view-metadata.html b/src/pages/view-metadata.html index beee4675..86c9fa12 100644 --- a/src/pages/view-metadata.html +++ b/src/pages/view-metadata.html @@ -5,7 +5,6 @@ - View Metadata Online Free - View Metadata Tool | BentoPDF - + @@ -383,11 +382,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.7", - "ratingCount": "1531" } } diff --git a/src/pages/vsd-to-pdf.html b/src/pages/vsd-to-pdf.html index 9d8b24e7..fea2f6de 100644 --- a/src/pages/vsd-to-pdf.html +++ b/src/pages/vsd-to-pdf.html @@ -5,7 +5,6 @@ - Vsd To Pdf Online Free - Vsd To Pdf Tool | BentoPDF - + @@ -399,11 +398,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.6", - "ratingCount": "2993" } } diff --git a/src/pages/wasm-settings.html b/src/pages/wasm-settings.html index a383e5eb..36efbb47 100644 --- a/src/pages/wasm-settings.html +++ b/src/pages/wasm-settings.html @@ -24,7 +24,7 @@ - + diff --git a/src/pages/webp-to-pdf.html b/src/pages/webp-to-pdf.html index 81564b7b..2599dcf3 100644 --- a/src/pages/webp-to-pdf.html +++ b/src/pages/webp-to-pdf.html @@ -5,7 +5,6 @@ - Webp To Pdf Online Free - Webp To Pdf Tool | BentoPDF - + @@ -417,11 +416,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.9", - "ratingCount": "2726" } } diff --git a/src/pages/word-to-pdf.html b/src/pages/word-to-pdf.html index ac1ba2cb..42abd13a 100644 --- a/src/pages/word-to-pdf.html +++ b/src/pages/word-to-pdf.html @@ -5,7 +5,6 @@ - Word to PDF Converter Free Online - Convert Files | BentoPDF - + @@ -425,11 +424,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.8", - "ratingCount": "4950" } } diff --git a/src/pages/wpd-to-pdf.html b/src/pages/wpd-to-pdf.html index 69f5fcb3..327cdc0e 100644 --- a/src/pages/wpd-to-pdf.html +++ b/src/pages/wpd-to-pdf.html @@ -5,7 +5,6 @@ - Wpd To Pdf Online Free - Wpd To Pdf Tool | BentoPDF - + @@ -399,11 +398,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.6", - "ratingCount": "1072" } } diff --git a/src/pages/wps-to-pdf.html b/src/pages/wps-to-pdf.html index 8fedb3cb..a45c36d9 100644 --- a/src/pages/wps-to-pdf.html +++ b/src/pages/wps-to-pdf.html @@ -5,7 +5,6 @@ - Wps To Pdf Online Free - Wps To Pdf Tool | BentoPDF - + @@ -398,11 +397,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.9", - "ratingCount": "4621" } } diff --git a/src/pages/xml-to-pdf.html b/src/pages/xml-to-pdf.html index 7d7fa537..fbea7d91 100644 --- a/src/pages/xml-to-pdf.html +++ b/src/pages/xml-to-pdf.html @@ -5,7 +5,6 @@ - Xml To Pdf Online Free - Xml To Pdf Tool | BentoPDF - + @@ -388,11 +387,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.8", - "ratingCount": "1001" } } diff --git a/src/pages/xps-to-pdf.html b/src/pages/xps-to-pdf.html index b7b562c3..fd33c655 100644 --- a/src/pages/xps-to-pdf.html +++ b/src/pages/xps-to-pdf.html @@ -5,7 +5,6 @@ - Xps To Pdf Online Free - Xps To Pdf Tool | BentoPDF - + @@ -392,11 +391,6 @@ "@type": "Offer", "price": "0", "priceCurrency": "USD" - }, - "aggregateRating": { - "@type": "AggregateRating", - "ratingValue": "4.7", - "ratingCount": "2331" } } diff --git a/src/partials/footer.html b/src/partials/footer.html index a158c235..d01d5f04 100644 --- a/src/partials/footer.html +++ b/src/partials/footer.html @@ -15,7 +15,7 @@ -

+

Version {{appVersion}}

diff --git a/terms.html b/terms.html index 87ec836b..2a2164b6 100644 --- a/terms.html +++ b/terms.html @@ -13,7 +13,7 @@ - + @@ -48,7 +48,6 @@ /> - Terms and Conditions - Service Agreement | BentoPDF diff --git a/tools.html b/tools.html index c3141730..01dcc701 100644 --- a/tools.html +++ b/tools.html @@ -17,7 +17,7 @@ /> - +