abqari 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +889 -0
- data/LICENSE +21 -0
- data/README.md +2739 -0
- data/SECURITY.md +73 -0
- data/THIRD_PARTY_NOTICES.md +46 -0
- data/app/assets/css/_common.css +3125 -0
- data/app/assets/js/carousel.js +39 -0
- data/app/assets/js/contact-button.js +28 -0
- data/app/assets/js/random-pool.js +44 -0
- data/app/assets/js/search-init.js +11 -0
- data/app/assets/js/theme-toggle.js +39 -0
- data/app/assets/js/viz-tag-bubble.js +108 -0
- data/app/assets/js/viz-tag-cooccurrence.js +125 -0
- data/app/assets/js/viz-topic-constellation.js +142 -0
- data/app/icons/bluesky.svg +3 -0
- data/app/icons/github.svg +3 -0
- data/app/icons/linkedin.svg +3 -0
- data/app/icons/mail.svg +4 -0
- data/app/icons/mastodon.svg +3 -0
- data/app/icons/moon.svg +3 -0
- data/app/icons/rss.svg +3 -0
- data/app/icons/sun.svg +4 -0
- data/app/icons/x.svg +3 -0
- data/app/views/layouts/about.html.erb +70 -0
- data/app/views/layouts/application.html.erb +65 -0
- data/app/views/layouts/book_series_index.html.erb +43 -0
- data/app/views/layouts/bundle.html.erb +111 -0
- data/app/views/layouts/bundles_index.html.erb +46 -0
- data/app/views/layouts/country_index.html.erb +59 -0
- data/app/views/layouts/gallery_index.html.erb +126 -0
- data/app/views/layouts/home.html.erb +107 -0
- data/app/views/layouts/photo.html.erb +100 -0
- data/app/views/layouts/photos_archive.html.erb +101 -0
- data/app/views/layouts/photos_index.html.erb +204 -0
- data/app/views/layouts/post.html.erb +153 -0
- data/app/views/layouts/posts_archive.html.erb +98 -0
- data/app/views/layouts/posts_index.html.erb +80 -0
- data/app/views/layouts/publication.html.erb +116 -0
- data/app/views/layouts/publications_archive.html.erb +89 -0
- data/app/views/layouts/publications_index.html.erb +246 -0
- data/app/views/layouts/related_index.html.erb +48 -0
- data/app/views/layouts/series.html.erb +110 -0
- data/app/views/layouts/series_index.html.erb +202 -0
- data/app/views/layouts/tag.html.erb +16 -0
- data/app/views/layouts/visualization.html.erb +32 -0
- data/app/views/layouts/workshop.html.erb +163 -0
- data/app/views/layouts/workshops_index.html.erb +209 -0
- data/app/views/partials/_back_link.html.erb +27 -0
- data/app/views/partials/_back_to_index.html.erb +25 -0
- data/app/views/partials/_book_grid.html.erb +76 -0
- data/app/views/partials/_book_jsonld.html.erb +72 -0
- data/app/views/partials/_book_related.html.erb +87 -0
- data/app/views/partials/_breadcrumb_jsonld.html.erb +60 -0
- data/app/views/partials/_bundle_cta.html.erb +26 -0
- data/app/views/partials/_carousel.html.erb +64 -0
- data/app/views/partials/_collection_page_jsonld.html.erb +81 -0
- data/app/views/partials/_contact_button.html.erb +42 -0
- data/app/views/partials/_course_jsonld.html.erb +95 -0
- data/app/views/partials/_draft_banner.html.erb +20 -0
- data/app/views/partials/_event_jsonld.html.erb +122 -0
- data/app/views/partials/_footer.html.erb +88 -0
- data/app/views/partials/_future_banner.html.erb +25 -0
- data/app/views/partials/_head.html.erb +440 -0
- data/app/views/partials/_image_gallery.html.erb +64 -0
- data/app/views/partials/_image_object_jsonld.html.erb +61 -0
- data/app/views/partials/_nav.html.erb +63 -0
- data/app/views/partials/_newsletter_subscribe.html.erb +85 -0
- data/app/views/partials/_next_in_series.html.erb +27 -0
- data/app/views/partials/_next_item.html.erb +38 -0
- data/app/views/partials/_pagination.html.erb +15 -0
- data/app/views/partials/_person_jsonld.html.erb +46 -0
- data/app/views/partials/_plausible.html.erb +24 -0
- data/app/views/partials/_post_list.html.erb +101 -0
- data/app/views/partials/_post_series_previews.html.erb +92 -0
- data/app/views/partials/_publication_card.html.erb +75 -0
- data/app/views/partials/_publication_header.html.erb +61 -0
- data/app/views/partials/_related_item.html.erb +68 -0
- data/app/views/partials/_related_items.html.erb +105 -0
- data/app/views/partials/_search.html.erb +26 -0
- data/app/views/partials/_series_pager.html.erb +48 -0
- data/app/views/partials/_skip_link.html.erb +12 -0
- data/app/views/partials/_taxonomy_row.html.erb +62 -0
- data/app/views/partials/_testimonials.html.erb +149 -0
- data/app/views/partials/_theme_toggle.html.erb +19 -0
- data/app/views/partials/_toc.html.erb +136 -0
- data/app/views/partials/_webmentions.html.erb +131 -0
- data/app/views/partials/_website_jsonld.html.erb +39 -0
- data/app/views/partials/home/_contact.html.erb +19 -0
- data/app/views/partials/home/_featured_publication.html.erb +107 -0
- data/app/views/partials/home/_latest_photo.html.erb +95 -0
- data/app/views/partials/home/_latest_post.html.erb +48 -0
- data/app/views/partials/home/_latest_publication.html.erb +89 -0
- data/app/views/partials/home/_next_workshop.html.erb +119 -0
- data/app/views/partials/home/_photo_gallery.html.erb +71 -0
- data/app/views/partials/home/_random_photo.html.erb +106 -0
- data/app/views/partials/home/_random_post.html.erb +64 -0
- data/exe/abqari +37 -0
- data/lib/abqari/artifact_writer.rb +196 -0
- data/lib/abqari/asset_pipeline.rb +235 -0
- data/lib/abqari/audit/a11y.rb +80 -0
- data/lib/abqari/audit/content.rb +154 -0
- data/lib/abqari/audit/dependencies.rb +127 -0
- data/lib/abqari/audit/links.rb +120 -0
- data/lib/abqari/audit/performance.rb +120 -0
- data/lib/abqari/audit/privacy.rb +42 -0
- data/lib/abqari/audit/rule.rb +56 -0
- data/lib/abqari/audit/seo.rb +105 -0
- data/lib/abqari/audit.rb +149 -0
- data/lib/abqari/bundle_assets.rb +127 -0
- data/lib/abqari/cli/new.rb +541 -0
- data/lib/abqari/cli/theme.rb +181 -0
- data/lib/abqari/cli.rb +130 -0
- data/lib/abqari/erbi_template.rb +72 -0
- data/lib/abqari/feed.rb +159 -0
- data/lib/abqari/folio.rb +195 -0
- data/lib/abqari/font_pipeline.rb +104 -0
- data/lib/abqari/headers_file.rb +324 -0
- data/lib/abqari/helpers.rb +308 -0
- data/lib/abqari/hooks.rb +136 -0
- data/lib/abqari/http_safe.rb +222 -0
- data/lib/abqari/image_pipeline.rb +800 -0
- data/lib/abqari/importers/base.rb +351 -0
- data/lib/abqari/importers/cli.rb +169 -0
- data/lib/abqari/importers/ghost.rb +113 -0
- data/lib/abqari/importers/jekyll.rb +256 -0
- data/lib/abqari/importers/substack.rb +204 -0
- data/lib/abqari/importers/support/html_to_markdown.rb +126 -0
- data/lib/abqari/importers/support/image_downloader.rb +170 -0
- data/lib/abqari/importers/support/report.rb +87 -0
- data/lib/abqari/importers/support/slug_normalizer.rb +53 -0
- data/lib/abqari/importers.rb +64 -0
- data/lib/abqari/indieweb.rb +551 -0
- data/lib/abqari/llms_txt.rb +110 -0
- data/lib/abqari/log.rb +61 -0
- data/lib/abqari/manifest.rb +58 -0
- data/lib/abqari/minifier.rb +108 -0
- data/lib/abqari/og_image_generator.rb +223 -0
- data/lib/abqari/page/bundle.rb +152 -0
- data/lib/abqari/page/incremental.rb +74 -0
- data/lib/abqari/page/parser.rb +129 -0
- data/lib/abqari/page/path.rb +153 -0
- data/lib/abqari/page.rb +424 -0
- data/lib/abqari/paginated_shard.rb +109 -0
- data/lib/abqari/paginator.rb +35 -0
- data/lib/abqari/path_safe.rb +62 -0
- data/lib/abqari/plausible_script.rb +20 -0
- data/lib/abqari/publication_normaliser.rb +111 -0
- data/lib/abqari/publisher_pages.rb +342 -0
- data/lib/abqari/redirects.rb +134 -0
- data/lib/abqari/related_index_page.rb +79 -0
- data/lib/abqari/render_context/cover_helpers.rb +338 -0
- data/lib/abqari/render_context/picture_helpers.rb +178 -0
- data/lib/abqari/render_context/publication_helpers.rb +120 -0
- data/lib/abqari/render_context.rb +251 -0
- data/lib/abqari/renderer.rb +788 -0
- data/lib/abqari/robots.rb +93 -0
- data/lib/abqari/search.rb +68 -0
- data/lib/abqari/security_txt.rb +44 -0
- data/lib/abqari/server.rb +508 -0
- data/lib/abqari/site.rb +952 -0
- data/lib/abqari/site_config.rb +212 -0
- data/lib/abqari/site_loader.rb +265 -0
- data/lib/abqari/sitemap.rb +87 -0
- data/lib/abqari/speculation_rules_guard.rb +26 -0
- data/lib/abqari/syndication.rb +466 -0
- data/lib/abqari/syntax_highlighter.rb +146 -0
- data/lib/abqari/taxonomy_page.rb +129 -0
- data/lib/abqari/templates/site_skeleton/.github/dependabot.yml +53 -0
- data/lib/abqari/templates/site_skeleton/.github/workflows/deploy-cloudflare.yml +107 -0
- data/lib/abqari/templates/site_skeleton/.github/workflows/deploy-github-pages.yml +107 -0
- data/lib/abqari/templates/site_skeleton/.github/workflows/deploy-render.yml +87 -0
- data/lib/abqari/templates/site_skeleton/.gitignore +24 -0
- data/lib/abqari/templates/site_skeleton/.ruby-version.tt +1 -0
- data/lib/abqari/templates/site_skeleton/Gemfile.tt +20 -0
- data/lib/abqari/templates/site_skeleton/README.md.tt +60 -0
- data/lib/abqari/templates/site_skeleton/app/assets/css/site.css +9 -0
- data/lib/abqari/templates/site_skeleton/bin/audit +46 -0
- data/lib/abqari/templates/site_skeleton/bin/build +32 -0
- data/lib/abqari/templates/site_skeleton/bin/fetch-folio +32 -0
- data/lib/abqari/templates/site_skeleton/bin/fetch-fonts +20 -0
- data/lib/abqari/templates/site_skeleton/bin/import +31 -0
- data/lib/abqari/templates/site_skeleton/bin/index +30 -0
- data/lib/abqari/templates/site_skeleton/bin/new +235 -0
- data/lib/abqari/templates/site_skeleton/bin/serve +30 -0
- data/lib/abqari/templates/site_skeleton/config/site.blank.yml.tt +65 -0
- data/lib/abqari/templates/site_skeleton/config/site.yml.tt +266 -0
- data/lib/abqari/templates/site_skeleton/content/404.md +17 -0
- data/lib/abqari/templates/site_skeleton/content/about.blank.md.tt +12 -0
- data/lib/abqari/templates/site_skeleton/content/about.md.tt +18 -0
- data/lib/abqari/templates/site_skeleton/content/index.blank.md.tt +11 -0
- data/lib/abqari/templates/site_skeleton/content/index.md.tt +34 -0
- data/lib/abqari/templates/site_skeleton/content/photos/index.md.tt +12 -0
- data/lib/abqari/templates/site_skeleton/content/posts/index.md +12 -0
- data/lib/abqari/templates/site_skeleton/content/posts/welcome/index.md.tt +50 -0
- data/lib/abqari/templates/site_skeleton/content/publications/index.md.tt +13 -0
- data/lib/abqari/templates/site_skeleton/content/workshops/index.md.tt +13 -0
- data/lib/abqari/templates/site_skeleton/public/robots.txt +6 -0
- data/lib/abqari/templates/site_skeleton/themes/.gitkeep +0 -0
- data/lib/abqari/templates/site_skeleton/vendor/folio/.gitkeep +0 -0
- data/lib/abqari/templates/site_skeleton/vendor/fonts/.gitkeep +0 -0
- data/lib/abqari/theme_bootstrap.rb +19 -0
- data/lib/abqari/theme_fonts.rb +51 -0
- data/lib/abqari/version.rb +18 -0
- data/lib/abqari/visualizations.rb +215 -0
- data/lib/abqari.rb +154 -0
- data/themes/bootstrap/css/app.css +97 -0
- data/themes/bootstrap/public/LICENSE +21 -0
- data/themes/bootstrap/public/css/bootstrap.min.css +6 -0
- data/themes/bootstrap/public/css/bootstrap.min.css.map +1 -0
- data/themes/bootstrap/public/js/bootstrap.bundle.min.js +7 -0
- data/themes/bootstrap/public/js/bootstrap.bundle.min.js.map +1 -0
- data/themes/custom/css/app.css +176 -0
- data/themes/magazine/css/app.css +242 -0
- data/themes/minimal/css/app.css +151 -0
- data/themes/notebook/css/app.css +205 -0
- data/themes/sand/css/app.css +179 -0
- data/vendor/d3/d3.v7.min.js +2 -0
- data/vendor/heroicons/16/solid/academic-cap.svg +4 -0
- data/vendor/heroicons/16/solid/adjustments-horizontal.svg +3 -0
- data/vendor/heroicons/16/solid/adjustments-vertical.svg +3 -0
- data/vendor/heroicons/16/solid/archive-box-arrow-down.svg +4 -0
- data/vendor/heroicons/16/solid/archive-box-x-mark.svg +4 -0
- data/vendor/heroicons/16/solid/archive-box.svg +4 -0
- data/vendor/heroicons/16/solid/arrow-down-circle.svg +3 -0
- data/vendor/heroicons/16/solid/arrow-down-left.svg +3 -0
- data/vendor/heroicons/16/solid/arrow-down-on-square-stack.svg +4 -0
- data/vendor/heroicons/16/solid/arrow-down-on-square.svg +3 -0
- data/vendor/heroicons/16/solid/arrow-down-right.svg +3 -0
- data/vendor/heroicons/16/solid/arrow-down-tray.svg +4 -0
- data/vendor/heroicons/16/solid/arrow-down.svg +3 -0
- data/vendor/heroicons/16/solid/arrow-left-circle.svg +3 -0
- data/vendor/heroicons/16/solid/arrow-left-end-on-rectangle.svg +4 -0
- data/vendor/heroicons/16/solid/arrow-left-start-on-rectangle.svg +3 -0
- data/vendor/heroicons/16/solid/arrow-left.svg +3 -0
- data/vendor/heroicons/16/solid/arrow-long-down.svg +3 -0
- data/vendor/heroicons/16/solid/arrow-long-left.svg +3 -0
- data/vendor/heroicons/16/solid/arrow-long-right.svg +3 -0
- data/vendor/heroicons/16/solid/arrow-long-up.svg +3 -0
- data/vendor/heroicons/16/solid/arrow-path-rounded-square.svg +3 -0
- data/vendor/heroicons/16/solid/arrow-path.svg +3 -0
- data/vendor/heroicons/16/solid/arrow-right-circle.svg +3 -0
- data/vendor/heroicons/16/solid/arrow-right-end-on-rectangle.svg +4 -0
- data/vendor/heroicons/16/solid/arrow-right-start-on-rectangle.svg +3 -0
- data/vendor/heroicons/16/solid/arrow-right.svg +3 -0
- data/vendor/heroicons/16/solid/arrow-top-right-on-square.svg +4 -0
- data/vendor/heroicons/16/solid/arrow-trending-down.svg +3 -0
- data/vendor/heroicons/16/solid/arrow-trending-up.svg +3 -0
- data/vendor/heroicons/16/solid/arrow-turn-down-left.svg +3 -0
- data/vendor/heroicons/16/solid/arrow-turn-down-right.svg +3 -0
- data/vendor/heroicons/16/solid/arrow-turn-left-down.svg +3 -0
- data/vendor/heroicons/16/solid/arrow-turn-left-up.svg +3 -0
- data/vendor/heroicons/16/solid/arrow-turn-right-down.svg +3 -0
- data/vendor/heroicons/16/solid/arrow-turn-right-up.svg +3 -0
- data/vendor/heroicons/16/solid/arrow-turn-up-left.svg +3 -0
- data/vendor/heroicons/16/solid/arrow-turn-up-right.svg +3 -0
- data/vendor/heroicons/16/solid/arrow-up-circle.svg +3 -0
- data/vendor/heroicons/16/solid/arrow-up-left.svg +3 -0
- data/vendor/heroicons/16/solid/arrow-up-on-square-stack.svg +4 -0
- data/vendor/heroicons/16/solid/arrow-up-on-square.svg +3 -0
- data/vendor/heroicons/16/solid/arrow-up-right.svg +3 -0
- data/vendor/heroicons/16/solid/arrow-up-tray.svg +4 -0
- data/vendor/heroicons/16/solid/arrow-up.svg +3 -0
- data/vendor/heroicons/16/solid/arrow-uturn-down.svg +3 -0
- data/vendor/heroicons/16/solid/arrow-uturn-left.svg +3 -0
- data/vendor/heroicons/16/solid/arrow-uturn-right.svg +3 -0
- data/vendor/heroicons/16/solid/arrow-uturn-up.svg +3 -0
- data/vendor/heroicons/16/solid/arrows-pointing-in.svg +3 -0
- data/vendor/heroicons/16/solid/arrows-pointing-out.svg +3 -0
- data/vendor/heroicons/16/solid/arrows-right-left.svg +3 -0
- data/vendor/heroicons/16/solid/arrows-up-down.svg +3 -0
- data/vendor/heroicons/16/solid/at-symbol.svg +3 -0
- data/vendor/heroicons/16/solid/backspace.svg +3 -0
- data/vendor/heroicons/16/solid/backward.svg +3 -0
- data/vendor/heroicons/16/solid/banknotes.svg +4 -0
- data/vendor/heroicons/16/solid/bars-2.svg +3 -0
- data/vendor/heroicons/16/solid/bars-3-bottom-left.svg +3 -0
- data/vendor/heroicons/16/solid/bars-3-bottom-right.svg +3 -0
- data/vendor/heroicons/16/solid/bars-3-center-left.svg +3 -0
- data/vendor/heroicons/16/solid/bars-3.svg +3 -0
- data/vendor/heroicons/16/solid/bars-4.svg +3 -0
- data/vendor/heroicons/16/solid/bars-arrow-down.svg +3 -0
- data/vendor/heroicons/16/solid/bars-arrow-up.svg +3 -0
- data/vendor/heroicons/16/solid/battery-0.svg +3 -0
- data/vendor/heroicons/16/solid/battery-100.svg +4 -0
- data/vendor/heroicons/16/solid/battery-50.svg +4 -0
- data/vendor/heroicons/16/solid/beaker.svg +3 -0
- data/vendor/heroicons/16/solid/bell-alert.svg +4 -0
- data/vendor/heroicons/16/solid/bell-slash.svg +4 -0
- data/vendor/heroicons/16/solid/bell-snooze.svg +3 -0
- data/vendor/heroicons/16/solid/bell.svg +3 -0
- data/vendor/heroicons/16/solid/bold.svg +3 -0
- data/vendor/heroicons/16/solid/bolt-slash.svg +3 -0
- data/vendor/heroicons/16/solid/bolt.svg +3 -0
- data/vendor/heroicons/16/solid/book-open.svg +3 -0
- data/vendor/heroicons/16/solid/bookmark-slash.svg +3 -0
- data/vendor/heroicons/16/solid/bookmark-square.svg +3 -0
- data/vendor/heroicons/16/solid/bookmark.svg +3 -0
- data/vendor/heroicons/16/solid/briefcase.svg +4 -0
- data/vendor/heroicons/16/solid/bug-ant.svg +3 -0
- data/vendor/heroicons/16/solid/building-library.svg +3 -0
- data/vendor/heroicons/16/solid/building-office-2.svg +3 -0
- data/vendor/heroicons/16/solid/building-office.svg +3 -0
- data/vendor/heroicons/16/solid/building-storefront.svg +3 -0
- data/vendor/heroicons/16/solid/cake.svg +3 -0
- data/vendor/heroicons/16/solid/calculator.svg +3 -0
- data/vendor/heroicons/16/solid/calendar-date-range.svg +4 -0
- data/vendor/heroicons/16/solid/calendar-days.svg +4 -0
- data/vendor/heroicons/16/solid/calendar.svg +3 -0
- data/vendor/heroicons/16/solid/camera.svg +4 -0
- data/vendor/heroicons/16/solid/chart-bar-square.svg +3 -0
- data/vendor/heroicons/16/solid/chart-bar.svg +3 -0
- data/vendor/heroicons/16/solid/chart-pie.svg +4 -0
- data/vendor/heroicons/16/solid/chat-bubble-bottom-center-text.svg +3 -0
- data/vendor/heroicons/16/solid/chat-bubble-bottom-center.svg +3 -0
- data/vendor/heroicons/16/solid/chat-bubble-left-ellipsis.svg +3 -0
- data/vendor/heroicons/16/solid/chat-bubble-left-right.svg +4 -0
- data/vendor/heroicons/16/solid/chat-bubble-left.svg +3 -0
- data/vendor/heroicons/16/solid/chat-bubble-oval-left-ellipsis.svg +3 -0
- data/vendor/heroicons/16/solid/chat-bubble-oval-left.svg +3 -0
- data/vendor/heroicons/16/solid/check-badge.svg +3 -0
- data/vendor/heroicons/16/solid/check-circle.svg +3 -0
- data/vendor/heroicons/16/solid/check.svg +3 -0
- data/vendor/heroicons/16/solid/chevron-double-down.svg +3 -0
- data/vendor/heroicons/16/solid/chevron-double-left.svg +3 -0
- data/vendor/heroicons/16/solid/chevron-double-right.svg +3 -0
- data/vendor/heroicons/16/solid/chevron-double-up.svg +3 -0
- data/vendor/heroicons/16/solid/chevron-down.svg +3 -0
- data/vendor/heroicons/16/solid/chevron-left.svg +3 -0
- data/vendor/heroicons/16/solid/chevron-right.svg +3 -0
- data/vendor/heroicons/16/solid/chevron-up-down.svg +3 -0
- data/vendor/heroicons/16/solid/chevron-up.svg +3 -0
- data/vendor/heroicons/16/solid/circle-stack.svg +5 -0
- data/vendor/heroicons/16/solid/clipboard-document-check.svg +4 -0
- data/vendor/heroicons/16/solid/clipboard-document-list.svg +4 -0
- data/vendor/heroicons/16/solid/clipboard-document.svg +4 -0
- data/vendor/heroicons/16/solid/clipboard.svg +3 -0
- data/vendor/heroicons/16/solid/clock.svg +3 -0
- data/vendor/heroicons/16/solid/cloud-arrow-down.svg +3 -0
- data/vendor/heroicons/16/solid/cloud-arrow-up.svg +3 -0
- data/vendor/heroicons/16/solid/cloud.svg +3 -0
- data/vendor/heroicons/16/solid/code-bracket-square.svg +3 -0
- data/vendor/heroicons/16/solid/code-bracket.svg +3 -0
- data/vendor/heroicons/16/solid/cog-6-tooth.svg +3 -0
- data/vendor/heroicons/16/solid/cog-8-tooth.svg +3 -0
- data/vendor/heroicons/16/solid/cog.svg +3 -0
- data/vendor/heroicons/16/solid/command-line.svg +3 -0
- data/vendor/heroicons/16/solid/computer-desktop.svg +3 -0
- data/vendor/heroicons/16/solid/cpu-chip.svg +4 -0
- data/vendor/heroicons/16/solid/credit-card.svg +4 -0
- data/vendor/heroicons/16/solid/cube-transparent.svg +3 -0
- data/vendor/heroicons/16/solid/cube.svg +3 -0
- data/vendor/heroicons/16/solid/currency-bangladeshi.svg +3 -0
- data/vendor/heroicons/16/solid/currency-dollar.svg +4 -0
- data/vendor/heroicons/16/solid/currency-euro.svg +3 -0
- data/vendor/heroicons/16/solid/currency-pound.svg +3 -0
- data/vendor/heroicons/16/solid/currency-rupee.svg +3 -0
- data/vendor/heroicons/16/solid/currency-yen.svg +3 -0
- data/vendor/heroicons/16/solid/cursor-arrow-rays.svg +3 -0
- data/vendor/heroicons/16/solid/cursor-arrow-ripple.svg +5 -0
- data/vendor/heroicons/16/solid/device-phone-mobile.svg +4 -0
- data/vendor/heroicons/16/solid/device-tablet.svg +4 -0
- data/vendor/heroicons/16/solid/divide.svg +4 -0
- data/vendor/heroicons/16/solid/document-arrow-down.svg +3 -0
- data/vendor/heroicons/16/solid/document-arrow-up.svg +3 -0
- data/vendor/heroicons/16/solid/document-chart-bar.svg +3 -0
- data/vendor/heroicons/16/solid/document-check.svg +3 -0
- data/vendor/heroicons/16/solid/document-currency-bangladeshi.svg +3 -0
- data/vendor/heroicons/16/solid/document-currency-dollar.svg +4 -0
- data/vendor/heroicons/16/solid/document-currency-euro.svg +3 -0
- data/vendor/heroicons/16/solid/document-currency-pound.svg +3 -0
- data/vendor/heroicons/16/solid/document-currency-rupee.svg +3 -0
- data/vendor/heroicons/16/solid/document-currency-yen.svg +3 -0
- data/vendor/heroicons/16/solid/document-duplicate.svg +4 -0
- data/vendor/heroicons/16/solid/document-magnifying-glass.svg +4 -0
- data/vendor/heroicons/16/solid/document-minus.svg +3 -0
- data/vendor/heroicons/16/solid/document-plus.svg +3 -0
- data/vendor/heroicons/16/solid/document-text.svg +3 -0
- data/vendor/heroicons/16/solid/document.svg +3 -0
- data/vendor/heroicons/16/solid/ellipsis-horizontal-circle.svg +3 -0
- data/vendor/heroicons/16/solid/ellipsis-horizontal.svg +3 -0
- data/vendor/heroicons/16/solid/ellipsis-vertical.svg +3 -0
- data/vendor/heroicons/16/solid/envelope-open.svg +3 -0
- data/vendor/heroicons/16/solid/envelope.svg +4 -0
- data/vendor/heroicons/16/solid/equals.svg +3 -0
- data/vendor/heroicons/16/solid/exclamation-circle.svg +3 -0
- data/vendor/heroicons/16/solid/exclamation-triangle.svg +3 -0
- data/vendor/heroicons/16/solid/eye-dropper.svg +3 -0
- data/vendor/heroicons/16/solid/eye-slash.svg +4 -0
- data/vendor/heroicons/16/solid/eye.svg +4 -0
- data/vendor/heroicons/16/solid/face-frown.svg +3 -0
- data/vendor/heroicons/16/solid/face-smile.svg +3 -0
- data/vendor/heroicons/16/solid/film.svg +3 -0
- data/vendor/heroicons/16/solid/finger-print.svg +3 -0
- data/vendor/heroicons/16/solid/fire.svg +3 -0
- data/vendor/heroicons/16/solid/flag.svg +3 -0
- data/vendor/heroicons/16/solid/folder-arrow-down.svg +3 -0
- data/vendor/heroicons/16/solid/folder-minus.svg +3 -0
- data/vendor/heroicons/16/solid/folder-open.svg +3 -0
- data/vendor/heroicons/16/solid/folder-plus.svg +3 -0
- data/vendor/heroicons/16/solid/folder.svg +3 -0
- data/vendor/heroicons/16/solid/forward.svg +3 -0
- data/vendor/heroicons/16/solid/funnel.svg +3 -0
- data/vendor/heroicons/16/solid/gif.svg +3 -0
- data/vendor/heroicons/16/solid/gift-top.svg +4 -0
- data/vendor/heroicons/16/solid/gift.svg +4 -0
- data/vendor/heroicons/16/solid/globe-alt.svg +3 -0
- data/vendor/heroicons/16/solid/globe-americas.svg +3 -0
- data/vendor/heroicons/16/solid/globe-asia-australia.svg +3 -0
- data/vendor/heroicons/16/solid/globe-europe-africa.svg +3 -0
- data/vendor/heroicons/16/solid/h1.svg +3 -0
- data/vendor/heroicons/16/solid/h2.svg +3 -0
- data/vendor/heroicons/16/solid/h3.svg +3 -0
- data/vendor/heroicons/16/solid/hand-raised.svg +3 -0
- data/vendor/heroicons/16/solid/hand-thumb-down.svg +3 -0
- data/vendor/heroicons/16/solid/hand-thumb-up.svg +3 -0
- data/vendor/heroicons/16/solid/hashtag.svg +3 -0
- data/vendor/heroicons/16/solid/heart.svg +3 -0
- data/vendor/heroicons/16/solid/home-modern.svg +3 -0
- data/vendor/heroicons/16/solid/home.svg +3 -0
- data/vendor/heroicons/16/solid/identification.svg +3 -0
- data/vendor/heroicons/16/solid/inbox-arrow-down.svg +4 -0
- data/vendor/heroicons/16/solid/inbox-stack.svg +4 -0
- data/vendor/heroicons/16/solid/inbox.svg +3 -0
- data/vendor/heroicons/16/solid/information-circle.svg +3 -0
- data/vendor/heroicons/16/solid/italic.svg +3 -0
- data/vendor/heroicons/16/solid/key.svg +3 -0
- data/vendor/heroicons/16/solid/language.svg +3 -0
- data/vendor/heroicons/16/solid/lifebuoy.svg +3 -0
- data/vendor/heroicons/16/solid/light-bulb.svg +3 -0
- data/vendor/heroicons/16/solid/link-slash.svg +3 -0
- data/vendor/heroicons/16/solid/link.svg +4 -0
- data/vendor/heroicons/16/solid/list-bullet.svg +3 -0
- data/vendor/heroicons/16/solid/lock-closed.svg +3 -0
- data/vendor/heroicons/16/solid/lock-open.svg +3 -0
- data/vendor/heroicons/16/solid/magnifying-glass-circle.svg +4 -0
- data/vendor/heroicons/16/solid/magnifying-glass-minus.svg +4 -0
- data/vendor/heroicons/16/solid/magnifying-glass-plus.svg +4 -0
- data/vendor/heroicons/16/solid/magnifying-glass.svg +3 -0
- data/vendor/heroicons/16/solid/map-pin.svg +3 -0
- data/vendor/heroicons/16/solid/map.svg +3 -0
- data/vendor/heroicons/16/solid/megaphone.svg +3 -0
- data/vendor/heroicons/16/solid/microphone.svg +4 -0
- data/vendor/heroicons/16/solid/minus-circle.svg +3 -0
- data/vendor/heroicons/16/solid/minus.svg +3 -0
- data/vendor/heroicons/16/solid/moon.svg +3 -0
- data/vendor/heroicons/16/solid/musical-note.svg +3 -0
- data/vendor/heroicons/16/solid/newspaper.svg +4 -0
- data/vendor/heroicons/16/solid/no-symbol.svg +3 -0
- data/vendor/heroicons/16/solid/numbered-list.svg +3 -0
- data/vendor/heroicons/16/solid/paint-brush.svg +3 -0
- data/vendor/heroicons/16/solid/paper-airplane.svg +3 -0
- data/vendor/heroicons/16/solid/paper-clip.svg +3 -0
- data/vendor/heroicons/16/solid/pause-circle.svg +3 -0
- data/vendor/heroicons/16/solid/pause.svg +3 -0
- data/vendor/heroicons/16/solid/pencil-square.svg +4 -0
- data/vendor/heroicons/16/solid/pencil.svg +3 -0
- data/vendor/heroicons/16/solid/percent-badge.svg +3 -0
- data/vendor/heroicons/16/solid/phone-arrow-down-left.svg +4 -0
- data/vendor/heroicons/16/solid/phone-arrow-up-right.svg +4 -0
- data/vendor/heroicons/16/solid/phone-x-mark.svg +4 -0
- data/vendor/heroicons/16/solid/phone.svg +3 -0
- data/vendor/heroicons/16/solid/photo.svg +3 -0
- data/vendor/heroicons/16/solid/play-circle.svg +3 -0
- data/vendor/heroicons/16/solid/play-pause.svg +3 -0
- data/vendor/heroicons/16/solid/play.svg +3 -0
- data/vendor/heroicons/16/solid/plus-circle.svg +3 -0
- data/vendor/heroicons/16/solid/plus.svg +3 -0
- data/vendor/heroicons/16/solid/power.svg +3 -0
- data/vendor/heroicons/16/solid/presentation-chart-bar.svg +3 -0
- data/vendor/heroicons/16/solid/presentation-chart-line.svg +3 -0
- data/vendor/heroicons/16/solid/printer.svg +3 -0
- data/vendor/heroicons/16/solid/puzzle-piece.svg +3 -0
- data/vendor/heroicons/16/solid/qr-code.svg +9 -0
- data/vendor/heroicons/16/solid/question-mark-circle.svg +3 -0
- data/vendor/heroicons/16/solid/queue-list.svg +3 -0
- data/vendor/heroicons/16/solid/radio.svg +3 -0
- data/vendor/heroicons/16/solid/receipt-percent.svg +3 -0
- data/vendor/heroicons/16/solid/receipt-refund.svg +3 -0
- data/vendor/heroicons/16/solid/rectangle-group.svg +3 -0
- data/vendor/heroicons/16/solid/rectangle-stack.svg +3 -0
- data/vendor/heroicons/16/solid/rocket-launch.svg +4 -0
- data/vendor/heroicons/16/solid/rss.svg +3 -0
- data/vendor/heroicons/16/solid/scale.svg +3 -0
- data/vendor/heroicons/16/solid/scissors.svg +4 -0
- data/vendor/heroicons/16/solid/server-stack.svg +4 -0
- data/vendor/heroicons/16/solid/server.svg +4 -0
- data/vendor/heroicons/16/solid/share.svg +3 -0
- data/vendor/heroicons/16/solid/shield-check.svg +3 -0
- data/vendor/heroicons/16/solid/shield-exclamation.svg +3 -0
- data/vendor/heroicons/16/solid/shopping-bag.svg +3 -0
- data/vendor/heroicons/16/solid/shopping-cart.svg +3 -0
- data/vendor/heroicons/16/solid/signal-slash.svg +3 -0
- data/vendor/heroicons/16/solid/signal.svg +5 -0
- data/vendor/heroicons/16/solid/slash.svg +3 -0
- data/vendor/heroicons/16/solid/sparkles.svg +3 -0
- data/vendor/heroicons/16/solid/speaker-wave.svg +4 -0
- data/vendor/heroicons/16/solid/speaker-x-mark.svg +3 -0
- data/vendor/heroicons/16/solid/square-2-stack.svg +4 -0
- data/vendor/heroicons/16/solid/square-3-stack-3d.svg +5 -0
- data/vendor/heroicons/16/solid/squares-2x2.svg +3 -0
- data/vendor/heroicons/16/solid/squares-plus.svg +3 -0
- data/vendor/heroicons/16/solid/star.svg +3 -0
- data/vendor/heroicons/16/solid/stop-circle.svg +3 -0
- data/vendor/heroicons/16/solid/stop.svg +3 -0
- data/vendor/heroicons/16/solid/strikethrough.svg +3 -0
- data/vendor/heroicons/16/solid/sun.svg +3 -0
- data/vendor/heroicons/16/solid/swatch.svg +4 -0
- data/vendor/heroicons/16/solid/table-cells.svg +3 -0
- data/vendor/heroicons/16/solid/tag.svg +3 -0
- data/vendor/heroicons/16/solid/ticket.svg +3 -0
- data/vendor/heroicons/16/solid/trash.svg +3 -0
- data/vendor/heroicons/16/solid/trophy.svg +3 -0
- data/vendor/heroicons/16/solid/truck.svg +5 -0
- data/vendor/heroicons/16/solid/tv.svg +4 -0
- data/vendor/heroicons/16/solid/underline.svg +3 -0
- data/vendor/heroicons/16/solid/user-circle.svg +3 -0
- data/vendor/heroicons/16/solid/user-group.svg +3 -0
- data/vendor/heroicons/16/solid/user-minus.svg +3 -0
- data/vendor/heroicons/16/solid/user-plus.svg +3 -0
- data/vendor/heroicons/16/solid/user.svg +3 -0
- data/vendor/heroicons/16/solid/users.svg +3 -0
- data/vendor/heroicons/16/solid/variable.svg +3 -0
- data/vendor/heroicons/16/solid/video-camera-slash.svg +3 -0
- data/vendor/heroicons/16/solid/video-camera.svg +3 -0
- data/vendor/heroicons/16/solid/view-columns.svg +3 -0
- data/vendor/heroicons/16/solid/viewfinder-circle.svg +3 -0
- data/vendor/heroicons/16/solid/wallet.svg +3 -0
- data/vendor/heroicons/16/solid/wifi.svg +3 -0
- data/vendor/heroicons/16/solid/window.svg +3 -0
- data/vendor/heroicons/16/solid/wrench-screwdriver.svg +4 -0
- data/vendor/heroicons/16/solid/wrench.svg +3 -0
- data/vendor/heroicons/16/solid/x-circle.svg +3 -0
- data/vendor/heroicons/16/solid/x-mark.svg +3 -0
- data/vendor/heroicons/20/solid/academic-cap.svg +3 -0
- data/vendor/heroicons/20/solid/adjustments-horizontal.svg +3 -0
- data/vendor/heroicons/20/solid/adjustments-vertical.svg +3 -0
- data/vendor/heroicons/20/solid/archive-box-arrow-down.svg +3 -0
- data/vendor/heroicons/20/solid/archive-box-x-mark.svg +4 -0
- data/vendor/heroicons/20/solid/archive-box.svg +4 -0
- data/vendor/heroicons/20/solid/arrow-down-circle.svg +3 -0
- data/vendor/heroicons/20/solid/arrow-down-left.svg +3 -0
- data/vendor/heroicons/20/solid/arrow-down-on-square-stack.svg +3 -0
- data/vendor/heroicons/20/solid/arrow-down-on-square.svg +3 -0
- data/vendor/heroicons/20/solid/arrow-down-right.svg +3 -0
- data/vendor/heroicons/20/solid/arrow-down-tray.svg +4 -0
- data/vendor/heroicons/20/solid/arrow-down.svg +3 -0
- data/vendor/heroicons/20/solid/arrow-left-circle.svg +3 -0
- data/vendor/heroicons/20/solid/arrow-left-end-on-rectangle.svg +4 -0
- data/vendor/heroicons/20/solid/arrow-left-on-rectangle.svg +4 -0
- data/vendor/heroicons/20/solid/arrow-left-start-on-rectangle.svg +4 -0
- data/vendor/heroicons/20/solid/arrow-left.svg +3 -0
- data/vendor/heroicons/20/solid/arrow-long-down.svg +3 -0
- data/vendor/heroicons/20/solid/arrow-long-left.svg +3 -0
- data/vendor/heroicons/20/solid/arrow-long-right.svg +3 -0
- data/vendor/heroicons/20/solid/arrow-long-up.svg +3 -0
- data/vendor/heroicons/20/solid/arrow-path-rounded-square.svg +3 -0
- data/vendor/heroicons/20/solid/arrow-path.svg +3 -0
- data/vendor/heroicons/20/solid/arrow-right-circle.svg +3 -0
- data/vendor/heroicons/20/solid/arrow-right-end-on-rectangle.svg +4 -0
- data/vendor/heroicons/20/solid/arrow-right-on-rectangle.svg +4 -0
- data/vendor/heroicons/20/solid/arrow-right-start-on-rectangle.svg +4 -0
- data/vendor/heroicons/20/solid/arrow-right.svg +3 -0
- data/vendor/heroicons/20/solid/arrow-small-down.svg +3 -0
- data/vendor/heroicons/20/solid/arrow-small-left.svg +3 -0
- data/vendor/heroicons/20/solid/arrow-small-right.svg +3 -0
- data/vendor/heroicons/20/solid/arrow-small-up.svg +3 -0
- data/vendor/heroicons/20/solid/arrow-top-right-on-square.svg +4 -0
- data/vendor/heroicons/20/solid/arrow-trending-down.svg +3 -0
- data/vendor/heroicons/20/solid/arrow-trending-up.svg +3 -0
- data/vendor/heroicons/20/solid/arrow-turn-down-left.svg +3 -0
- data/vendor/heroicons/20/solid/arrow-turn-down-right.svg +3 -0
- data/vendor/heroicons/20/solid/arrow-turn-left-down.svg +3 -0
- data/vendor/heroicons/20/solid/arrow-turn-left-up.svg +3 -0
- data/vendor/heroicons/20/solid/arrow-turn-right-down.svg +3 -0
- data/vendor/heroicons/20/solid/arrow-turn-right-up.svg +3 -0
- data/vendor/heroicons/20/solid/arrow-turn-up-left.svg +3 -0
- data/vendor/heroicons/20/solid/arrow-turn-up-right.svg +3 -0
- data/vendor/heroicons/20/solid/arrow-up-circle.svg +3 -0
- data/vendor/heroicons/20/solid/arrow-up-left.svg +3 -0
- data/vendor/heroicons/20/solid/arrow-up-on-square-stack.svg +3 -0
- data/vendor/heroicons/20/solid/arrow-up-on-square.svg +3 -0
- data/vendor/heroicons/20/solid/arrow-up-right.svg +3 -0
- data/vendor/heroicons/20/solid/arrow-up-tray.svg +4 -0
- data/vendor/heroicons/20/solid/arrow-up.svg +3 -0
- data/vendor/heroicons/20/solid/arrow-uturn-down.svg +3 -0
- data/vendor/heroicons/20/solid/arrow-uturn-left.svg +3 -0
- data/vendor/heroicons/20/solid/arrow-uturn-right.svg +3 -0
- data/vendor/heroicons/20/solid/arrow-uturn-up.svg +3 -0
- data/vendor/heroicons/20/solid/arrows-pointing-in.svg +3 -0
- data/vendor/heroicons/20/solid/arrows-pointing-out.svg +3 -0
- data/vendor/heroicons/20/solid/arrows-right-left.svg +3 -0
- data/vendor/heroicons/20/solid/arrows-up-down.svg +3 -0
- data/vendor/heroicons/20/solid/at-symbol.svg +3 -0
- data/vendor/heroicons/20/solid/backspace.svg +3 -0
- data/vendor/heroicons/20/solid/backward.svg +3 -0
- data/vendor/heroicons/20/solid/banknotes.svg +3 -0
- data/vendor/heroicons/20/solid/bars-2.svg +3 -0
- data/vendor/heroicons/20/solid/bars-3-bottom-left.svg +3 -0
- data/vendor/heroicons/20/solid/bars-3-bottom-right.svg +3 -0
- data/vendor/heroicons/20/solid/bars-3-center-left.svg +3 -0
- data/vendor/heroicons/20/solid/bars-3.svg +3 -0
- data/vendor/heroicons/20/solid/bars-4.svg +3 -0
- data/vendor/heroicons/20/solid/bars-arrow-down.svg +3 -0
- data/vendor/heroicons/20/solid/bars-arrow-up.svg +3 -0
- data/vendor/heroicons/20/solid/battery-0.svg +3 -0
- data/vendor/heroicons/20/solid/battery-100.svg +4 -0
- data/vendor/heroicons/20/solid/battery-50.svg +4 -0
- data/vendor/heroicons/20/solid/beaker.svg +3 -0
- data/vendor/heroicons/20/solid/bell-alert.svg +4 -0
- data/vendor/heroicons/20/solid/bell-slash.svg +3 -0
- data/vendor/heroicons/20/solid/bell-snooze.svg +3 -0
- data/vendor/heroicons/20/solid/bell.svg +3 -0
- data/vendor/heroicons/20/solid/bold.svg +3 -0
- data/vendor/heroicons/20/solid/bolt-slash.svg +4 -0
- data/vendor/heroicons/20/solid/bolt.svg +3 -0
- data/vendor/heroicons/20/solid/book-open.svg +3 -0
- data/vendor/heroicons/20/solid/bookmark-slash.svg +3 -0
- data/vendor/heroicons/20/solid/bookmark-square.svg +3 -0
- data/vendor/heroicons/20/solid/bookmark.svg +3 -0
- data/vendor/heroicons/20/solid/briefcase.svg +4 -0
- data/vendor/heroicons/20/solid/bug-ant.svg +3 -0
- data/vendor/heroicons/20/solid/building-library.svg +3 -0
- data/vendor/heroicons/20/solid/building-office-2.svg +3 -0
- data/vendor/heroicons/20/solid/building-office.svg +3 -0
- data/vendor/heroicons/20/solid/building-storefront.svg +3 -0
- data/vendor/heroicons/20/solid/cake.svg +3 -0
- data/vendor/heroicons/20/solid/calculator.svg +3 -0
- data/vendor/heroicons/20/solid/calendar-date-range.svg +4 -0
- data/vendor/heroicons/20/solid/calendar-days.svg +4 -0
- data/vendor/heroicons/20/solid/calendar.svg +3 -0
- data/vendor/heroicons/20/solid/camera.svg +3 -0
- data/vendor/heroicons/20/solid/chart-bar-square.svg +3 -0
- data/vendor/heroicons/20/solid/chart-bar.svg +3 -0
- data/vendor/heroicons/20/solid/chart-pie.svg +4 -0
- data/vendor/heroicons/20/solid/chat-bubble-bottom-center-text.svg +3 -0
- data/vendor/heroicons/20/solid/chat-bubble-bottom-center.svg +3 -0
- data/vendor/heroicons/20/solid/chat-bubble-left-ellipsis.svg +3 -0
- data/vendor/heroicons/20/solid/chat-bubble-left-right.svg +4 -0
- data/vendor/heroicons/20/solid/chat-bubble-left.svg +3 -0
- data/vendor/heroicons/20/solid/chat-bubble-oval-left-ellipsis.svg +3 -0
- data/vendor/heroicons/20/solid/chat-bubble-oval-left.svg +3 -0
- data/vendor/heroicons/20/solid/check-badge.svg +3 -0
- data/vendor/heroicons/20/solid/check-circle.svg +3 -0
- data/vendor/heroicons/20/solid/check.svg +3 -0
- data/vendor/heroicons/20/solid/chevron-double-down.svg +3 -0
- data/vendor/heroicons/20/solid/chevron-double-left.svg +3 -0
- data/vendor/heroicons/20/solid/chevron-double-right.svg +3 -0
- data/vendor/heroicons/20/solid/chevron-double-up.svg +3 -0
- data/vendor/heroicons/20/solid/chevron-down.svg +3 -0
- data/vendor/heroicons/20/solid/chevron-left.svg +3 -0
- data/vendor/heroicons/20/solid/chevron-right.svg +3 -0
- data/vendor/heroicons/20/solid/chevron-up-down.svg +3 -0
- data/vendor/heroicons/20/solid/chevron-up.svg +3 -0
- data/vendor/heroicons/20/solid/circle-stack.svg +3 -0
- data/vendor/heroicons/20/solid/clipboard-document-check.svg +4 -0
- data/vendor/heroicons/20/solid/clipboard-document-list.svg +4 -0
- data/vendor/heroicons/20/solid/clipboard-document.svg +4 -0
- data/vendor/heroicons/20/solid/clipboard.svg +3 -0
- data/vendor/heroicons/20/solid/clock.svg +3 -0
- data/vendor/heroicons/20/solid/cloud-arrow-down.svg +3 -0
- data/vendor/heroicons/20/solid/cloud-arrow-up.svg +3 -0
- data/vendor/heroicons/20/solid/cloud.svg +3 -0
- data/vendor/heroicons/20/solid/code-bracket-square.svg +3 -0
- data/vendor/heroicons/20/solid/code-bracket.svg +3 -0
- data/vendor/heroicons/20/solid/cog-6-tooth.svg +3 -0
- data/vendor/heroicons/20/solid/cog-8-tooth.svg +3 -0
- data/vendor/heroicons/20/solid/cog.svg +4 -0
- data/vendor/heroicons/20/solid/command-line.svg +3 -0
- data/vendor/heroicons/20/solid/computer-desktop.svg +3 -0
- data/vendor/heroicons/20/solid/cpu-chip.svg +4 -0
- data/vendor/heroicons/20/solid/credit-card.svg +3 -0
- data/vendor/heroicons/20/solid/cube-transparent.svg +3 -0
- data/vendor/heroicons/20/solid/cube.svg +3 -0
- data/vendor/heroicons/20/solid/currency-bangladeshi.svg +3 -0
- data/vendor/heroicons/20/solid/currency-dollar.svg +4 -0
- data/vendor/heroicons/20/solid/currency-euro.svg +3 -0
- data/vendor/heroicons/20/solid/currency-pound.svg +3 -0
- data/vendor/heroicons/20/solid/currency-rupee.svg +3 -0
- data/vendor/heroicons/20/solid/currency-yen.svg +3 -0
- data/vendor/heroicons/20/solid/cursor-arrow-rays.svg +3 -0
- data/vendor/heroicons/20/solid/cursor-arrow-ripple.svg +5 -0
- data/vendor/heroicons/20/solid/device-phone-mobile.svg +4 -0
- data/vendor/heroicons/20/solid/device-tablet.svg +3 -0
- data/vendor/heroicons/20/solid/divide.svg +3 -0
- data/vendor/heroicons/20/solid/document-arrow-down.svg +3 -0
- data/vendor/heroicons/20/solid/document-arrow-up.svg +3 -0
- data/vendor/heroicons/20/solid/document-chart-bar.svg +3 -0
- data/vendor/heroicons/20/solid/document-check.svg +3 -0
- data/vendor/heroicons/20/solid/document-currency-bangladeshi.svg +3 -0
- data/vendor/heroicons/20/solid/document-currency-dollar.svg +3 -0
- data/vendor/heroicons/20/solid/document-currency-euro.svg +3 -0
- data/vendor/heroicons/20/solid/document-currency-pound.svg +3 -0
- data/vendor/heroicons/20/solid/document-currency-rupee.svg +3 -0
- data/vendor/heroicons/20/solid/document-currency-yen.svg +3 -0
- data/vendor/heroicons/20/solid/document-duplicate.svg +4 -0
- data/vendor/heroicons/20/solid/document-magnifying-glass.svg +4 -0
- data/vendor/heroicons/20/solid/document-minus.svg +3 -0
- data/vendor/heroicons/20/solid/document-plus.svg +3 -0
- data/vendor/heroicons/20/solid/document-text.svg +3 -0
- data/vendor/heroicons/20/solid/document.svg +3 -0
- data/vendor/heroicons/20/solid/ellipsis-horizontal-circle.svg +3 -0
- data/vendor/heroicons/20/solid/ellipsis-horizontal.svg +3 -0
- data/vendor/heroicons/20/solid/ellipsis-vertical.svg +3 -0
- data/vendor/heroicons/20/solid/envelope-open.svg +3 -0
- data/vendor/heroicons/20/solid/envelope.svg +4 -0
- data/vendor/heroicons/20/solid/equals.svg +3 -0
- data/vendor/heroicons/20/solid/exclamation-circle.svg +3 -0
- data/vendor/heroicons/20/solid/exclamation-triangle.svg +3 -0
- data/vendor/heroicons/20/solid/eye-dropper.svg +3 -0
- data/vendor/heroicons/20/solid/eye-slash.svg +4 -0
- data/vendor/heroicons/20/solid/eye.svg +4 -0
- data/vendor/heroicons/20/solid/face-frown.svg +3 -0
- data/vendor/heroicons/20/solid/face-smile.svg +3 -0
- data/vendor/heroicons/20/solid/film.svg +3 -0
- data/vendor/heroicons/20/solid/finger-print.svg +3 -0
- data/vendor/heroicons/20/solid/fire.svg +3 -0
- data/vendor/heroicons/20/solid/flag.svg +3 -0
- data/vendor/heroicons/20/solid/folder-arrow-down.svg +3 -0
- data/vendor/heroicons/20/solid/folder-minus.svg +3 -0
- data/vendor/heroicons/20/solid/folder-open.svg +3 -0
- data/vendor/heroicons/20/solid/folder-plus.svg +3 -0
- data/vendor/heroicons/20/solid/folder.svg +3 -0
- data/vendor/heroicons/20/solid/forward.svg +3 -0
- data/vendor/heroicons/20/solid/funnel.svg +3 -0
- data/vendor/heroicons/20/solid/gif.svg +3 -0
- data/vendor/heroicons/20/solid/gift-top.svg +3 -0
- data/vendor/heroicons/20/solid/gift.svg +4 -0
- data/vendor/heroicons/20/solid/globe-alt.svg +3 -0
- data/vendor/heroicons/20/solid/globe-americas.svg +3 -0
- data/vendor/heroicons/20/solid/globe-asia-australia.svg +3 -0
- data/vendor/heroicons/20/solid/globe-europe-africa.svg +3 -0
- data/vendor/heroicons/20/solid/h1.svg +3 -0
- data/vendor/heroicons/20/solid/h2.svg +3 -0
- data/vendor/heroicons/20/solid/h3.svg +3 -0
- data/vendor/heroicons/20/solid/hand-raised.svg +3 -0
- data/vendor/heroicons/20/solid/hand-thumb-down.svg +3 -0
- data/vendor/heroicons/20/solid/hand-thumb-up.svg +3 -0
- data/vendor/heroicons/20/solid/hashtag.svg +3 -0
- data/vendor/heroicons/20/solid/heart.svg +3 -0
- data/vendor/heroicons/20/solid/home-modern.svg +3 -0
- data/vendor/heroicons/20/solid/home.svg +3 -0
- data/vendor/heroicons/20/solid/identification.svg +3 -0
- data/vendor/heroicons/20/solid/inbox-arrow-down.svg +4 -0
- data/vendor/heroicons/20/solid/inbox-stack.svg +4 -0
- data/vendor/heroicons/20/solid/inbox.svg +3 -0
- data/vendor/heroicons/20/solid/information-circle.svg +3 -0
- data/vendor/heroicons/20/solid/italic.svg +3 -0
- data/vendor/heroicons/20/solid/key.svg +3 -0
- data/vendor/heroicons/20/solid/language.svg +4 -0
- data/vendor/heroicons/20/solid/lifebuoy.svg +3 -0
- data/vendor/heroicons/20/solid/light-bulb.svg +3 -0
- data/vendor/heroicons/20/solid/link-slash.svg +3 -0
- data/vendor/heroicons/20/solid/link.svg +4 -0
- data/vendor/heroicons/20/solid/list-bullet.svg +3 -0
- data/vendor/heroicons/20/solid/lock-closed.svg +3 -0
- data/vendor/heroicons/20/solid/lock-open.svg +3 -0
- data/vendor/heroicons/20/solid/magnifying-glass-circle.svg +4 -0
- data/vendor/heroicons/20/solid/magnifying-glass-minus.svg +4 -0
- data/vendor/heroicons/20/solid/magnifying-glass-plus.svg +4 -0
- data/vendor/heroicons/20/solid/magnifying-glass.svg +3 -0
- data/vendor/heroicons/20/solid/map-pin.svg +3 -0
- data/vendor/heroicons/20/solid/map.svg +3 -0
- data/vendor/heroicons/20/solid/megaphone.svg +3 -0
- data/vendor/heroicons/20/solid/microphone.svg +4 -0
- data/vendor/heroicons/20/solid/minus-circle.svg +3 -0
- data/vendor/heroicons/20/solid/minus-small.svg +3 -0
- data/vendor/heroicons/20/solid/minus.svg +3 -0
- data/vendor/heroicons/20/solid/moon.svg +3 -0
- data/vendor/heroicons/20/solid/musical-note.svg +3 -0
- data/vendor/heroicons/20/solid/newspaper.svg +4 -0
- data/vendor/heroicons/20/solid/no-symbol.svg +3 -0
- data/vendor/heroicons/20/solid/numbered-list.svg +3 -0
- data/vendor/heroicons/20/solid/paint-brush.svg +3 -0
- data/vendor/heroicons/20/solid/paper-airplane.svg +3 -0
- data/vendor/heroicons/20/solid/paper-clip.svg +3 -0
- data/vendor/heroicons/20/solid/pause-circle.svg +3 -0
- data/vendor/heroicons/20/solid/pause.svg +3 -0
- data/vendor/heroicons/20/solid/pencil-square.svg +4 -0
- data/vendor/heroicons/20/solid/pencil.svg +3 -0
- data/vendor/heroicons/20/solid/percent-badge.svg +3 -0
- data/vendor/heroicons/20/solid/phone-arrow-down-left.svg +3 -0
- data/vendor/heroicons/20/solid/phone-arrow-up-right.svg +3 -0
- data/vendor/heroicons/20/solid/phone-x-mark.svg +3 -0
- data/vendor/heroicons/20/solid/phone.svg +3 -0
- data/vendor/heroicons/20/solid/photo.svg +3 -0
- data/vendor/heroicons/20/solid/play-circle.svg +3 -0
- data/vendor/heroicons/20/solid/play-pause.svg +3 -0
- data/vendor/heroicons/20/solid/play.svg +3 -0
- data/vendor/heroicons/20/solid/plus-circle.svg +3 -0
- data/vendor/heroicons/20/solid/plus-small.svg +3 -0
- data/vendor/heroicons/20/solid/plus.svg +3 -0
- data/vendor/heroicons/20/solid/power.svg +3 -0
- data/vendor/heroicons/20/solid/presentation-chart-bar.svg +3 -0
- data/vendor/heroicons/20/solid/presentation-chart-line.svg +3 -0
- data/vendor/heroicons/20/solid/printer.svg +3 -0
- data/vendor/heroicons/20/solid/puzzle-piece.svg +3 -0
- data/vendor/heroicons/20/solid/qr-code.svg +3 -0
- data/vendor/heroicons/20/solid/question-mark-circle.svg +3 -0
- data/vendor/heroicons/20/solid/queue-list.svg +3 -0
- data/vendor/heroicons/20/solid/radio.svg +3 -0
- data/vendor/heroicons/20/solid/receipt-percent.svg +3 -0
- data/vendor/heroicons/20/solid/receipt-refund.svg +3 -0
- data/vendor/heroicons/20/solid/rectangle-group.svg +3 -0
- data/vendor/heroicons/20/solid/rectangle-stack.svg +3 -0
- data/vendor/heroicons/20/solid/rocket-launch.svg +4 -0
- data/vendor/heroicons/20/solid/rss.svg +4 -0
- data/vendor/heroicons/20/solid/scale.svg +3 -0
- data/vendor/heroicons/20/solid/scissors.svg +4 -0
- data/vendor/heroicons/20/solid/server-stack.svg +4 -0
- data/vendor/heroicons/20/solid/server.svg +4 -0
- data/vendor/heroicons/20/solid/share.svg +3 -0
- data/vendor/heroicons/20/solid/shield-check.svg +3 -0
- data/vendor/heroicons/20/solid/shield-exclamation.svg +3 -0
- data/vendor/heroicons/20/solid/shopping-bag.svg +3 -0
- data/vendor/heroicons/20/solid/shopping-cart.svg +3 -0
- data/vendor/heroicons/20/solid/signal-slash.svg +3 -0
- data/vendor/heroicons/20/solid/signal.svg +4 -0
- data/vendor/heroicons/20/solid/slash.svg +3 -0
- data/vendor/heroicons/20/solid/sparkles.svg +3 -0
- data/vendor/heroicons/20/solid/speaker-wave.svg +4 -0
- data/vendor/heroicons/20/solid/speaker-x-mark.svg +3 -0
- data/vendor/heroicons/20/solid/square-2-stack.svg +4 -0
- data/vendor/heroicons/20/solid/square-3-stack-3d.svg +5 -0
- data/vendor/heroicons/20/solid/squares-2x2.svg +3 -0
- data/vendor/heroicons/20/solid/squares-plus.svg +3 -0
- data/vendor/heroicons/20/solid/star.svg +3 -0
- data/vendor/heroicons/20/solid/stop-circle.svg +3 -0
- data/vendor/heroicons/20/solid/stop.svg +3 -0
- data/vendor/heroicons/20/solid/strikethrough.svg +3 -0
- data/vendor/heroicons/20/solid/sun.svg +3 -0
- data/vendor/heroicons/20/solid/swatch.svg +3 -0
- data/vendor/heroicons/20/solid/table-cells.svg +3 -0
- data/vendor/heroicons/20/solid/tag.svg +3 -0
- data/vendor/heroicons/20/solid/ticket.svg +3 -0
- data/vendor/heroicons/20/solid/trash.svg +3 -0
- data/vendor/heroicons/20/solid/trophy.svg +3 -0
- data/vendor/heroicons/20/solid/truck.svg +4 -0
- data/vendor/heroicons/20/solid/tv.svg +4 -0
- data/vendor/heroicons/20/solid/underline.svg +3 -0
- data/vendor/heroicons/20/solid/user-circle.svg +3 -0
- data/vendor/heroicons/20/solid/user-group.svg +3 -0
- data/vendor/heroicons/20/solid/user-minus.svg +3 -0
- data/vendor/heroicons/20/solid/user-plus.svg +3 -0
- data/vendor/heroicons/20/solid/user.svg +3 -0
- data/vendor/heroicons/20/solid/users.svg +3 -0
- data/vendor/heroicons/20/solid/variable.svg +3 -0
- data/vendor/heroicons/20/solid/video-camera-slash.svg +3 -0
- data/vendor/heroicons/20/solid/video-camera.svg +3 -0
- data/vendor/heroicons/20/solid/view-columns.svg +3 -0
- data/vendor/heroicons/20/solid/viewfinder-circle.svg +3 -0
- data/vendor/heroicons/20/solid/wallet.svg +3 -0
- data/vendor/heroicons/20/solid/wifi.svg +3 -0
- data/vendor/heroicons/20/solid/window.svg +3 -0
- data/vendor/heroicons/20/solid/wrench-screwdriver.svg +4 -0
- data/vendor/heroicons/20/solid/wrench.svg +3 -0
- data/vendor/heroicons/20/solid/x-circle.svg +3 -0
- data/vendor/heroicons/20/solid/x-mark.svg +3 -0
- data/vendor/heroicons/24/outline/academic-cap.svg +3 -0
- data/vendor/heroicons/24/outline/adjustments-horizontal.svg +3 -0
- data/vendor/heroicons/24/outline/adjustments-vertical.svg +3 -0
- data/vendor/heroicons/24/outline/archive-box-arrow-down.svg +3 -0
- data/vendor/heroicons/24/outline/archive-box-x-mark.svg +3 -0
- data/vendor/heroicons/24/outline/archive-box.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-down-circle.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-down-left.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-down-on-square-stack.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-down-on-square.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-down-right.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-down-tray.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-down.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-left-circle.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-left-end-on-rectangle.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-left-on-rectangle.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-left-start-on-rectangle.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-left.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-long-down.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-long-left.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-long-right.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-long-up.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-path-rounded-square.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-path.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-right-circle.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-right-end-on-rectangle.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-right-on-rectangle.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-right-start-on-rectangle.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-right.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-small-down.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-small-left.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-small-right.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-small-up.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-top-right-on-square.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-trending-down.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-trending-up.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-turn-down-left.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-turn-down-right.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-turn-left-down.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-turn-left-up.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-turn-right-down.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-turn-right-up.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-turn-up-left.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-turn-up-right.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-up-circle.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-up-left.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-up-on-square-stack.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-up-on-square.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-up-right.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-up-tray.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-up.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-uturn-down.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-uturn-left.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-uturn-right.svg +3 -0
- data/vendor/heroicons/24/outline/arrow-uturn-up.svg +3 -0
- data/vendor/heroicons/24/outline/arrows-pointing-in.svg +3 -0
- data/vendor/heroicons/24/outline/arrows-pointing-out.svg +3 -0
- data/vendor/heroicons/24/outline/arrows-right-left.svg +3 -0
- data/vendor/heroicons/24/outline/arrows-up-down.svg +3 -0
- data/vendor/heroicons/24/outline/at-symbol.svg +3 -0
- data/vendor/heroicons/24/outline/backspace.svg +3 -0
- data/vendor/heroicons/24/outline/backward.svg +3 -0
- data/vendor/heroicons/24/outline/banknotes.svg +3 -0
- data/vendor/heroicons/24/outline/bars-2.svg +3 -0
- data/vendor/heroicons/24/outline/bars-3-bottom-left.svg +3 -0
- data/vendor/heroicons/24/outline/bars-3-bottom-right.svg +3 -0
- data/vendor/heroicons/24/outline/bars-3-center-left.svg +3 -0
- data/vendor/heroicons/24/outline/bars-3.svg +3 -0
- data/vendor/heroicons/24/outline/bars-4.svg +3 -0
- data/vendor/heroicons/24/outline/bars-arrow-down.svg +3 -0
- data/vendor/heroicons/24/outline/bars-arrow-up.svg +3 -0
- data/vendor/heroicons/24/outline/battery-0.svg +3 -0
- data/vendor/heroicons/24/outline/battery-100.svg +3 -0
- data/vendor/heroicons/24/outline/battery-50.svg +3 -0
- data/vendor/heroicons/24/outline/beaker.svg +3 -0
- data/vendor/heroicons/24/outline/bell-alert.svg +3 -0
- data/vendor/heroicons/24/outline/bell-slash.svg +3 -0
- data/vendor/heroicons/24/outline/bell-snooze.svg +3 -0
- data/vendor/heroicons/24/outline/bell.svg +3 -0
- data/vendor/heroicons/24/outline/bold.svg +3 -0
- data/vendor/heroicons/24/outline/bolt-slash.svg +3 -0
- data/vendor/heroicons/24/outline/bolt.svg +3 -0
- data/vendor/heroicons/24/outline/book-open.svg +3 -0
- data/vendor/heroicons/24/outline/bookmark-slash.svg +3 -0
- data/vendor/heroicons/24/outline/bookmark-square.svg +3 -0
- data/vendor/heroicons/24/outline/bookmark.svg +3 -0
- data/vendor/heroicons/24/outline/briefcase.svg +3 -0
- data/vendor/heroicons/24/outline/bug-ant.svg +3 -0
- data/vendor/heroicons/24/outline/building-library.svg +3 -0
- data/vendor/heroicons/24/outline/building-office-2.svg +3 -0
- data/vendor/heroicons/24/outline/building-office.svg +3 -0
- data/vendor/heroicons/24/outline/building-storefront.svg +3 -0
- data/vendor/heroicons/24/outline/cake.svg +3 -0
- data/vendor/heroicons/24/outline/calculator.svg +3 -0
- data/vendor/heroicons/24/outline/calendar-date-range.svg +3 -0
- data/vendor/heroicons/24/outline/calendar-days.svg +3 -0
- data/vendor/heroicons/24/outline/calendar.svg +3 -0
- data/vendor/heroicons/24/outline/camera.svg +4 -0
- data/vendor/heroicons/24/outline/chart-bar-square.svg +3 -0
- data/vendor/heroicons/24/outline/chart-bar.svg +3 -0
- data/vendor/heroicons/24/outline/chart-pie.svg +4 -0
- data/vendor/heroicons/24/outline/chat-bubble-bottom-center-text.svg +3 -0
- data/vendor/heroicons/24/outline/chat-bubble-bottom-center.svg +3 -0
- data/vendor/heroicons/24/outline/chat-bubble-left-ellipsis.svg +3 -0
- data/vendor/heroicons/24/outline/chat-bubble-left-right.svg +3 -0
- data/vendor/heroicons/24/outline/chat-bubble-left.svg +3 -0
- data/vendor/heroicons/24/outline/chat-bubble-oval-left-ellipsis.svg +3 -0
- data/vendor/heroicons/24/outline/chat-bubble-oval-left.svg +3 -0
- data/vendor/heroicons/24/outline/check-badge.svg +3 -0
- data/vendor/heroicons/24/outline/check-circle.svg +3 -0
- data/vendor/heroicons/24/outline/check.svg +3 -0
- data/vendor/heroicons/24/outline/chevron-double-down.svg +3 -0
- data/vendor/heroicons/24/outline/chevron-double-left.svg +3 -0
- data/vendor/heroicons/24/outline/chevron-double-right.svg +3 -0
- data/vendor/heroicons/24/outline/chevron-double-up.svg +4 -0
- data/vendor/heroicons/24/outline/chevron-down.svg +3 -0
- data/vendor/heroicons/24/outline/chevron-left.svg +3 -0
- data/vendor/heroicons/24/outline/chevron-right.svg +3 -0
- data/vendor/heroicons/24/outline/chevron-up-down.svg +3 -0
- data/vendor/heroicons/24/outline/chevron-up.svg +3 -0
- data/vendor/heroicons/24/outline/circle-stack.svg +3 -0
- data/vendor/heroicons/24/outline/clipboard-document-check.svg +3 -0
- data/vendor/heroicons/24/outline/clipboard-document-list.svg +3 -0
- data/vendor/heroicons/24/outline/clipboard-document.svg +3 -0
- data/vendor/heroicons/24/outline/clipboard.svg +3 -0
- data/vendor/heroicons/24/outline/clock.svg +3 -0
- data/vendor/heroicons/24/outline/cloud-arrow-down.svg +3 -0
- data/vendor/heroicons/24/outline/cloud-arrow-up.svg +3 -0
- data/vendor/heroicons/24/outline/cloud.svg +3 -0
- data/vendor/heroicons/24/outline/code-bracket-square.svg +3 -0
- data/vendor/heroicons/24/outline/code-bracket.svg +3 -0
- data/vendor/heroicons/24/outline/cog-6-tooth.svg +4 -0
- data/vendor/heroicons/24/outline/cog-8-tooth.svg +4 -0
- data/vendor/heroicons/24/outline/cog.svg +3 -0
- data/vendor/heroicons/24/outline/command-line.svg +3 -0
- data/vendor/heroicons/24/outline/computer-desktop.svg +3 -0
- data/vendor/heroicons/24/outline/cpu-chip.svg +3 -0
- data/vendor/heroicons/24/outline/credit-card.svg +3 -0
- data/vendor/heroicons/24/outline/cube-transparent.svg +3 -0
- data/vendor/heroicons/24/outline/cube.svg +3 -0
- data/vendor/heroicons/24/outline/currency-bangladeshi.svg +3 -0
- data/vendor/heroicons/24/outline/currency-dollar.svg +3 -0
- data/vendor/heroicons/24/outline/currency-euro.svg +3 -0
- data/vendor/heroicons/24/outline/currency-pound.svg +3 -0
- data/vendor/heroicons/24/outline/currency-rupee.svg +3 -0
- data/vendor/heroicons/24/outline/currency-yen.svg +3 -0
- data/vendor/heroicons/24/outline/cursor-arrow-rays.svg +3 -0
- data/vendor/heroicons/24/outline/cursor-arrow-ripple.svg +3 -0
- data/vendor/heroicons/24/outline/device-phone-mobile.svg +3 -0
- data/vendor/heroicons/24/outline/device-tablet.svg +3 -0
- data/vendor/heroicons/24/outline/divide.svg +3 -0
- data/vendor/heroicons/24/outline/document-arrow-down.svg +3 -0
- data/vendor/heroicons/24/outline/document-arrow-up.svg +3 -0
- data/vendor/heroicons/24/outline/document-chart-bar.svg +3 -0
- data/vendor/heroicons/24/outline/document-check.svg +3 -0
- data/vendor/heroicons/24/outline/document-currency-bangladeshi.svg +3 -0
- data/vendor/heroicons/24/outline/document-currency-dollar.svg +3 -0
- data/vendor/heroicons/24/outline/document-currency-euro.svg +3 -0
- data/vendor/heroicons/24/outline/document-currency-pound.svg +3 -0
- data/vendor/heroicons/24/outline/document-currency-rupee.svg +3 -0
- data/vendor/heroicons/24/outline/document-currency-yen.svg +3 -0
- data/vendor/heroicons/24/outline/document-duplicate.svg +3 -0
- data/vendor/heroicons/24/outline/document-magnifying-glass.svg +3 -0
- data/vendor/heroicons/24/outline/document-minus.svg +3 -0
- data/vendor/heroicons/24/outline/document-plus.svg +3 -0
- data/vendor/heroicons/24/outline/document-text.svg +3 -0
- data/vendor/heroicons/24/outline/document.svg +3 -0
- data/vendor/heroicons/24/outline/ellipsis-horizontal-circle.svg +3 -0
- data/vendor/heroicons/24/outline/ellipsis-horizontal.svg +3 -0
- data/vendor/heroicons/24/outline/ellipsis-vertical.svg +3 -0
- data/vendor/heroicons/24/outline/envelope-open.svg +3 -0
- data/vendor/heroicons/24/outline/envelope.svg +3 -0
- data/vendor/heroicons/24/outline/equals.svg +3 -0
- data/vendor/heroicons/24/outline/exclamation-circle.svg +3 -0
- data/vendor/heroicons/24/outline/exclamation-triangle.svg +3 -0
- data/vendor/heroicons/24/outline/eye-dropper.svg +3 -0
- data/vendor/heroicons/24/outline/eye-slash.svg +3 -0
- data/vendor/heroicons/24/outline/eye.svg +4 -0
- data/vendor/heroicons/24/outline/face-frown.svg +3 -0
- data/vendor/heroicons/24/outline/face-smile.svg +3 -0
- data/vendor/heroicons/24/outline/film.svg +3 -0
- data/vendor/heroicons/24/outline/finger-print.svg +3 -0
- data/vendor/heroicons/24/outline/fire.svg +4 -0
- data/vendor/heroicons/24/outline/flag.svg +3 -0
- data/vendor/heroicons/24/outline/folder-arrow-down.svg +3 -0
- data/vendor/heroicons/24/outline/folder-minus.svg +3 -0
- data/vendor/heroicons/24/outline/folder-open.svg +3 -0
- data/vendor/heroicons/24/outline/folder-plus.svg +3 -0
- data/vendor/heroicons/24/outline/folder.svg +3 -0
- data/vendor/heroicons/24/outline/forward.svg +3 -0
- data/vendor/heroicons/24/outline/funnel.svg +3 -0
- data/vendor/heroicons/24/outline/gif.svg +3 -0
- data/vendor/heroicons/24/outline/gift-top.svg +3 -0
- data/vendor/heroicons/24/outline/gift.svg +3 -0
- data/vendor/heroicons/24/outline/globe-alt.svg +3 -0
- data/vendor/heroicons/24/outline/globe-americas.svg +3 -0
- data/vendor/heroicons/24/outline/globe-asia-australia.svg +3 -0
- data/vendor/heroicons/24/outline/globe-europe-africa.svg +3 -0
- data/vendor/heroicons/24/outline/h1.svg +3 -0
- data/vendor/heroicons/24/outline/h2.svg +3 -0
- data/vendor/heroicons/24/outline/h3.svg +3 -0
- data/vendor/heroicons/24/outline/hand-raised.svg +3 -0
- data/vendor/heroicons/24/outline/hand-thumb-down.svg +3 -0
- data/vendor/heroicons/24/outline/hand-thumb-up.svg +3 -0
- data/vendor/heroicons/24/outline/hashtag.svg +3 -0
- data/vendor/heroicons/24/outline/heart.svg +3 -0
- data/vendor/heroicons/24/outline/home-modern.svg +3 -0
- data/vendor/heroicons/24/outline/home.svg +3 -0
- data/vendor/heroicons/24/outline/identification.svg +3 -0
- data/vendor/heroicons/24/outline/inbox-arrow-down.svg +3 -0
- data/vendor/heroicons/24/outline/inbox-stack.svg +3 -0
- data/vendor/heroicons/24/outline/inbox.svg +3 -0
- data/vendor/heroicons/24/outline/information-circle.svg +3 -0
- data/vendor/heroicons/24/outline/italic.svg +3 -0
- data/vendor/heroicons/24/outline/key.svg +3 -0
- data/vendor/heroicons/24/outline/language.svg +3 -0
- data/vendor/heroicons/24/outline/lifebuoy.svg +3 -0
- data/vendor/heroicons/24/outline/light-bulb.svg +3 -0
- data/vendor/heroicons/24/outline/link-slash.svg +3 -0
- data/vendor/heroicons/24/outline/link.svg +3 -0
- data/vendor/heroicons/24/outline/list-bullet.svg +3 -0
- data/vendor/heroicons/24/outline/lock-closed.svg +3 -0
- data/vendor/heroicons/24/outline/lock-open.svg +3 -0
- data/vendor/heroicons/24/outline/magnifying-glass-circle.svg +3 -0
- data/vendor/heroicons/24/outline/magnifying-glass-minus.svg +3 -0
- data/vendor/heroicons/24/outline/magnifying-glass-plus.svg +3 -0
- data/vendor/heroicons/24/outline/magnifying-glass.svg +3 -0
- data/vendor/heroicons/24/outline/map-pin.svg +4 -0
- data/vendor/heroicons/24/outline/map.svg +3 -0
- data/vendor/heroicons/24/outline/megaphone.svg +3 -0
- data/vendor/heroicons/24/outline/microphone.svg +3 -0
- data/vendor/heroicons/24/outline/minus-circle.svg +3 -0
- data/vendor/heroicons/24/outline/minus-small.svg +3 -0
- data/vendor/heroicons/24/outline/minus.svg +3 -0
- data/vendor/heroicons/24/outline/moon.svg +3 -0
- data/vendor/heroicons/24/outline/musical-note.svg +3 -0
- data/vendor/heroicons/24/outline/newspaper.svg +3 -0
- data/vendor/heroicons/24/outline/no-symbol.svg +3 -0
- data/vendor/heroicons/24/outline/numbered-list.svg +3 -0
- data/vendor/heroicons/24/outline/paint-brush.svg +3 -0
- data/vendor/heroicons/24/outline/paper-airplane.svg +3 -0
- data/vendor/heroicons/24/outline/paper-clip.svg +3 -0
- data/vendor/heroicons/24/outline/pause-circle.svg +3 -0
- data/vendor/heroicons/24/outline/pause.svg +3 -0
- data/vendor/heroicons/24/outline/pencil-square.svg +3 -0
- data/vendor/heroicons/24/outline/pencil.svg +3 -0
- data/vendor/heroicons/24/outline/percent-badge.svg +3 -0
- data/vendor/heroicons/24/outline/phone-arrow-down-left.svg +3 -0
- data/vendor/heroicons/24/outline/phone-arrow-up-right.svg +3 -0
- data/vendor/heroicons/24/outline/phone-x-mark.svg +3 -0
- data/vendor/heroicons/24/outline/phone.svg +3 -0
- data/vendor/heroicons/24/outline/photo.svg +3 -0
- data/vendor/heroicons/24/outline/play-circle.svg +4 -0
- data/vendor/heroicons/24/outline/play-pause.svg +3 -0
- data/vendor/heroicons/24/outline/play.svg +3 -0
- data/vendor/heroicons/24/outline/plus-circle.svg +3 -0
- data/vendor/heroicons/24/outline/plus-small.svg +3 -0
- data/vendor/heroicons/24/outline/plus.svg +3 -0
- data/vendor/heroicons/24/outline/power.svg +3 -0
- data/vendor/heroicons/24/outline/presentation-chart-bar.svg +3 -0
- data/vendor/heroicons/24/outline/presentation-chart-line.svg +3 -0
- data/vendor/heroicons/24/outline/printer.svg +3 -0
- data/vendor/heroicons/24/outline/puzzle-piece.svg +3 -0
- data/vendor/heroicons/24/outline/qr-code.svg +4 -0
- data/vendor/heroicons/24/outline/question-mark-circle.svg +3 -0
- data/vendor/heroicons/24/outline/queue-list.svg +3 -0
- data/vendor/heroicons/24/outline/radio.svg +3 -0
- data/vendor/heroicons/24/outline/receipt-percent.svg +3 -0
- data/vendor/heroicons/24/outline/receipt-refund.svg +3 -0
- data/vendor/heroicons/24/outline/rectangle-group.svg +3 -0
- data/vendor/heroicons/24/outline/rectangle-stack.svg +3 -0
- data/vendor/heroicons/24/outline/rocket-launch.svg +3 -0
- data/vendor/heroicons/24/outline/rss.svg +3 -0
- data/vendor/heroicons/24/outline/scale.svg +3 -0
- data/vendor/heroicons/24/outline/scissors.svg +3 -0
- data/vendor/heroicons/24/outline/server-stack.svg +3 -0
- data/vendor/heroicons/24/outline/server.svg +3 -0
- data/vendor/heroicons/24/outline/share.svg +3 -0
- data/vendor/heroicons/24/outline/shield-check.svg +3 -0
- data/vendor/heroicons/24/outline/shield-exclamation.svg +3 -0
- data/vendor/heroicons/24/outline/shopping-bag.svg +3 -0
- data/vendor/heroicons/24/outline/shopping-cart.svg +3 -0
- data/vendor/heroicons/24/outline/signal-slash.svg +3 -0
- data/vendor/heroicons/24/outline/signal.svg +3 -0
- data/vendor/heroicons/24/outline/slash.svg +3 -0
- data/vendor/heroicons/24/outline/sparkles.svg +3 -0
- data/vendor/heroicons/24/outline/speaker-wave.svg +3 -0
- data/vendor/heroicons/24/outline/speaker-x-mark.svg +3 -0
- data/vendor/heroicons/24/outline/square-2-stack.svg +3 -0
- data/vendor/heroicons/24/outline/square-3-stack-3d.svg +3 -0
- data/vendor/heroicons/24/outline/squares-2x2.svg +3 -0
- data/vendor/heroicons/24/outline/squares-plus.svg +3 -0
- data/vendor/heroicons/24/outline/star.svg +3 -0
- data/vendor/heroicons/24/outline/stop-circle.svg +4 -0
- data/vendor/heroicons/24/outline/stop.svg +3 -0
- data/vendor/heroicons/24/outline/strikethrough.svg +3 -0
- data/vendor/heroicons/24/outline/sun.svg +3 -0
- data/vendor/heroicons/24/outline/swatch.svg +3 -0
- data/vendor/heroicons/24/outline/table-cells.svg +3 -0
- data/vendor/heroicons/24/outline/tag.svg +4 -0
- data/vendor/heroicons/24/outline/ticket.svg +3 -0
- data/vendor/heroicons/24/outline/trash.svg +3 -0
- data/vendor/heroicons/24/outline/trophy.svg +3 -0
- data/vendor/heroicons/24/outline/truck.svg +3 -0
- data/vendor/heroicons/24/outline/tv.svg +3 -0
- data/vendor/heroicons/24/outline/underline.svg +3 -0
- data/vendor/heroicons/24/outline/user-circle.svg +3 -0
- data/vendor/heroicons/24/outline/user-group.svg +3 -0
- data/vendor/heroicons/24/outline/user-minus.svg +3 -0
- data/vendor/heroicons/24/outline/user-plus.svg +3 -0
- data/vendor/heroicons/24/outline/user.svg +3 -0
- data/vendor/heroicons/24/outline/users.svg +3 -0
- data/vendor/heroicons/24/outline/variable.svg +3 -0
- data/vendor/heroicons/24/outline/video-camera-slash.svg +3 -0
- data/vendor/heroicons/24/outline/video-camera.svg +3 -0
- data/vendor/heroicons/24/outline/view-columns.svg +3 -0
- data/vendor/heroicons/24/outline/viewfinder-circle.svg +3 -0
- data/vendor/heroicons/24/outline/wallet.svg +3 -0
- data/vendor/heroicons/24/outline/wifi.svg +3 -0
- data/vendor/heroicons/24/outline/window.svg +3 -0
- data/vendor/heroicons/24/outline/wrench-screwdriver.svg +3 -0
- data/vendor/heroicons/24/outline/wrench.svg +4 -0
- data/vendor/heroicons/24/outline/x-circle.svg +3 -0
- data/vendor/heroicons/24/outline/x-mark.svg +3 -0
- data/vendor/heroicons/24/solid/academic-cap.svg +5 -0
- data/vendor/heroicons/24/solid/adjustments-horizontal.svg +3 -0
- data/vendor/heroicons/24/solid/adjustments-vertical.svg +3 -0
- data/vendor/heroicons/24/solid/archive-box-arrow-down.svg +4 -0
- data/vendor/heroicons/24/solid/archive-box-x-mark.svg +4 -0
- data/vendor/heroicons/24/solid/archive-box.svg +4 -0
- data/vendor/heroicons/24/solid/arrow-down-circle.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-down-left.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-down-on-square-stack.svg +4 -0
- data/vendor/heroicons/24/solid/arrow-down-on-square.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-down-right.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-down-tray.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-down.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-left-circle.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-left-end-on-rectangle.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-left-on-rectangle.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-left-start-on-rectangle.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-left.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-long-down.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-long-left.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-long-right.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-long-up.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-path-rounded-square.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-path.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-right-circle.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-right-end-on-rectangle.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-right-on-rectangle.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-right-start-on-rectangle.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-right.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-small-down.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-small-left.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-small-right.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-small-up.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-top-right-on-square.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-trending-down.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-trending-up.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-turn-down-left.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-turn-down-right.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-turn-left-down.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-turn-left-up.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-turn-right-down.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-turn-right-up.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-turn-up-left.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-turn-up-right.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-up-circle.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-up-left.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-up-on-square-stack.svg +4 -0
- data/vendor/heroicons/24/solid/arrow-up-on-square.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-up-right.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-up-tray.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-up.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-uturn-down.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-uturn-left.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-uturn-right.svg +3 -0
- data/vendor/heroicons/24/solid/arrow-uturn-up.svg +3 -0
- data/vendor/heroicons/24/solid/arrows-pointing-in.svg +3 -0
- data/vendor/heroicons/24/solid/arrows-pointing-out.svg +3 -0
- data/vendor/heroicons/24/solid/arrows-right-left.svg +3 -0
- data/vendor/heroicons/24/solid/arrows-up-down.svg +3 -0
- data/vendor/heroicons/24/solid/at-symbol.svg +3 -0
- data/vendor/heroicons/24/solid/backspace.svg +3 -0
- data/vendor/heroicons/24/solid/backward.svg +3 -0
- data/vendor/heroicons/24/solid/banknotes.svg +5 -0
- data/vendor/heroicons/24/solid/bars-2.svg +3 -0
- data/vendor/heroicons/24/solid/bars-3-bottom-left.svg +3 -0
- data/vendor/heroicons/24/solid/bars-3-bottom-right.svg +3 -0
- data/vendor/heroicons/24/solid/bars-3-center-left.svg +3 -0
- data/vendor/heroicons/24/solid/bars-3.svg +3 -0
- data/vendor/heroicons/24/solid/bars-4.svg +3 -0
- data/vendor/heroicons/24/solid/bars-arrow-down.svg +3 -0
- data/vendor/heroicons/24/solid/bars-arrow-up.svg +3 -0
- data/vendor/heroicons/24/solid/battery-0.svg +3 -0
- data/vendor/heroicons/24/solid/battery-100.svg +3 -0
- data/vendor/heroicons/24/solid/battery-50.svg +4 -0
- data/vendor/heroicons/24/solid/beaker.svg +3 -0
- data/vendor/heroicons/24/solid/bell-alert.svg +4 -0
- data/vendor/heroicons/24/solid/bell-slash.svg +4 -0
- data/vendor/heroicons/24/solid/bell-snooze.svg +3 -0
- data/vendor/heroicons/24/solid/bell.svg +3 -0
- data/vendor/heroicons/24/solid/bold.svg +3 -0
- data/vendor/heroicons/24/solid/bolt-slash.svg +3 -0
- data/vendor/heroicons/24/solid/bolt.svg +3 -0
- data/vendor/heroicons/24/solid/book-open.svg +3 -0
- data/vendor/heroicons/24/solid/bookmark-slash.svg +3 -0
- data/vendor/heroicons/24/solid/bookmark-square.svg +3 -0
- data/vendor/heroicons/24/solid/bookmark.svg +3 -0
- data/vendor/heroicons/24/solid/briefcase.svg +4 -0
- data/vendor/heroicons/24/solid/bug-ant.svg +3 -0
- data/vendor/heroicons/24/solid/building-library.svg +5 -0
- data/vendor/heroicons/24/solid/building-office-2.svg +3 -0
- data/vendor/heroicons/24/solid/building-office.svg +3 -0
- data/vendor/heroicons/24/solid/building-storefront.svg +4 -0
- data/vendor/heroicons/24/solid/cake.svg +3 -0
- data/vendor/heroicons/24/solid/calculator.svg +3 -0
- data/vendor/heroicons/24/solid/calendar-date-range.svg +4 -0
- data/vendor/heroicons/24/solid/calendar-days.svg +4 -0
- data/vendor/heroicons/24/solid/calendar.svg +3 -0
- data/vendor/heroicons/24/solid/camera.svg +4 -0
- data/vendor/heroicons/24/solid/chart-bar-square.svg +3 -0
- data/vendor/heroicons/24/solid/chart-bar.svg +3 -0
- data/vendor/heroicons/24/solid/chart-pie.svg +4 -0
- data/vendor/heroicons/24/solid/chat-bubble-bottom-center-text.svg +3 -0
- data/vendor/heroicons/24/solid/chat-bubble-bottom-center.svg +3 -0
- data/vendor/heroicons/24/solid/chat-bubble-left-ellipsis.svg +3 -0
- data/vendor/heroicons/24/solid/chat-bubble-left-right.svg +4 -0
- data/vendor/heroicons/24/solid/chat-bubble-left.svg +3 -0
- data/vendor/heroicons/24/solid/chat-bubble-oval-left-ellipsis.svg +3 -0
- data/vendor/heroicons/24/solid/chat-bubble-oval-left.svg +3 -0
- data/vendor/heroicons/24/solid/check-badge.svg +3 -0
- data/vendor/heroicons/24/solid/check-circle.svg +3 -0
- data/vendor/heroicons/24/solid/check.svg +3 -0
- data/vendor/heroicons/24/solid/chevron-double-down.svg +4 -0
- data/vendor/heroicons/24/solid/chevron-double-left.svg +4 -0
- data/vendor/heroicons/24/solid/chevron-double-right.svg +4 -0
- data/vendor/heroicons/24/solid/chevron-double-up.svg +4 -0
- data/vendor/heroicons/24/solid/chevron-down.svg +3 -0
- data/vendor/heroicons/24/solid/chevron-left.svg +3 -0
- data/vendor/heroicons/24/solid/chevron-right.svg +3 -0
- data/vendor/heroicons/24/solid/chevron-up-down.svg +3 -0
- data/vendor/heroicons/24/solid/chevron-up.svg +3 -0
- data/vendor/heroicons/24/solid/circle-stack.svg +6 -0
- data/vendor/heroicons/24/solid/clipboard-document-check.svg +4 -0
- data/vendor/heroicons/24/solid/clipboard-document-list.svg +4 -0
- data/vendor/heroicons/24/solid/clipboard-document.svg +5 -0
- data/vendor/heroicons/24/solid/clipboard.svg +3 -0
- data/vendor/heroicons/24/solid/clock.svg +3 -0
- data/vendor/heroicons/24/solid/cloud-arrow-down.svg +3 -0
- data/vendor/heroicons/24/solid/cloud-arrow-up.svg +3 -0
- data/vendor/heroicons/24/solid/cloud.svg +3 -0
- data/vendor/heroicons/24/solid/code-bracket-square.svg +3 -0
- data/vendor/heroicons/24/solid/code-bracket.svg +3 -0
- data/vendor/heroicons/24/solid/cog-6-tooth.svg +3 -0
- data/vendor/heroicons/24/solid/cog-8-tooth.svg +3 -0
- data/vendor/heroicons/24/solid/cog.svg +4 -0
- data/vendor/heroicons/24/solid/command-line.svg +3 -0
- data/vendor/heroicons/24/solid/computer-desktop.svg +3 -0
- data/vendor/heroicons/24/solid/cpu-chip.svg +4 -0
- data/vendor/heroicons/24/solid/credit-card.svg +4 -0
- data/vendor/heroicons/24/solid/cube-transparent.svg +3 -0
- data/vendor/heroicons/24/solid/cube.svg +3 -0
- data/vendor/heroicons/24/solid/currency-bangladeshi.svg +3 -0
- data/vendor/heroicons/24/solid/currency-dollar.svg +4 -0
- data/vendor/heroicons/24/solid/currency-euro.svg +3 -0
- data/vendor/heroicons/24/solid/currency-pound.svg +3 -0
- data/vendor/heroicons/24/solid/currency-rupee.svg +3 -0
- data/vendor/heroicons/24/solid/currency-yen.svg +3 -0
- data/vendor/heroicons/24/solid/cursor-arrow-rays.svg +3 -0
- data/vendor/heroicons/24/solid/cursor-arrow-ripple.svg +3 -0
- data/vendor/heroicons/24/solid/device-phone-mobile.svg +4 -0
- data/vendor/heroicons/24/solid/device-tablet.svg +4 -0
- data/vendor/heroicons/24/solid/divide.svg +3 -0
- data/vendor/heroicons/24/solid/document-arrow-down.svg +4 -0
- data/vendor/heroicons/24/solid/document-arrow-up.svg +4 -0
- data/vendor/heroicons/24/solid/document-chart-bar.svg +4 -0
- data/vendor/heroicons/24/solid/document-check.svg +4 -0
- data/vendor/heroicons/24/solid/document-currency-bangladeshi.svg +3 -0
- data/vendor/heroicons/24/solid/document-currency-dollar.svg +3 -0
- data/vendor/heroicons/24/solid/document-currency-euro.svg +4 -0
- data/vendor/heroicons/24/solid/document-currency-pound.svg +3 -0
- data/vendor/heroicons/24/solid/document-currency-rupee.svg +3 -0
- data/vendor/heroicons/24/solid/document-currency-yen.svg +3 -0
- data/vendor/heroicons/24/solid/document-duplicate.svg +4 -0
- data/vendor/heroicons/24/solid/document-magnifying-glass.svg +5 -0
- data/vendor/heroicons/24/solid/document-minus.svg +4 -0
- data/vendor/heroicons/24/solid/document-plus.svg +4 -0
- data/vendor/heroicons/24/solid/document-text.svg +4 -0
- data/vendor/heroicons/24/solid/document.svg +4 -0
- data/vendor/heroicons/24/solid/ellipsis-horizontal-circle.svg +3 -0
- data/vendor/heroicons/24/solid/ellipsis-horizontal.svg +3 -0
- data/vendor/heroicons/24/solid/ellipsis-vertical.svg +3 -0
- data/vendor/heroicons/24/solid/envelope-open.svg +4 -0
- data/vendor/heroicons/24/solid/envelope.svg +4 -0
- data/vendor/heroicons/24/solid/equals.svg +3 -0
- data/vendor/heroicons/24/solid/exclamation-circle.svg +3 -0
- data/vendor/heroicons/24/solid/exclamation-triangle.svg +3 -0
- data/vendor/heroicons/24/solid/eye-dropper.svg +3 -0
- data/vendor/heroicons/24/solid/eye-slash.svg +5 -0
- data/vendor/heroicons/24/solid/eye.svg +4 -0
- data/vendor/heroicons/24/solid/face-frown.svg +3 -0
- data/vendor/heroicons/24/solid/face-smile.svg +3 -0
- data/vendor/heroicons/24/solid/film.svg +3 -0
- data/vendor/heroicons/24/solid/finger-print.svg +3 -0
- data/vendor/heroicons/24/solid/fire.svg +3 -0
- data/vendor/heroicons/24/solid/flag.svg +3 -0
- data/vendor/heroicons/24/solid/folder-arrow-down.svg +3 -0
- data/vendor/heroicons/24/solid/folder-minus.svg +3 -0
- data/vendor/heroicons/24/solid/folder-open.svg +3 -0
- data/vendor/heroicons/24/solid/folder-plus.svg +3 -0
- data/vendor/heroicons/24/solid/folder.svg +3 -0
- data/vendor/heroicons/24/solid/forward.svg +3 -0
- data/vendor/heroicons/24/solid/funnel.svg +3 -0
- data/vendor/heroicons/24/solid/gif.svg +3 -0
- data/vendor/heroicons/24/solid/gift-top.svg +4 -0
- data/vendor/heroicons/24/solid/gift.svg +3 -0
- data/vendor/heroicons/24/solid/globe-alt.svg +3 -0
- data/vendor/heroicons/24/solid/globe-americas.svg +3 -0
- data/vendor/heroicons/24/solid/globe-asia-australia.svg +4 -0
- data/vendor/heroicons/24/solid/globe-europe-africa.svg +3 -0
- data/vendor/heroicons/24/solid/h1.svg +3 -0
- data/vendor/heroicons/24/solid/h2.svg +3 -0
- data/vendor/heroicons/24/solid/h3.svg +3 -0
- data/vendor/heroicons/24/solid/hand-raised.svg +3 -0
- data/vendor/heroicons/24/solid/hand-thumb-down.svg +3 -0
- data/vendor/heroicons/24/solid/hand-thumb-up.svg +3 -0
- data/vendor/heroicons/24/solid/hashtag.svg +3 -0
- data/vendor/heroicons/24/solid/heart.svg +3 -0
- data/vendor/heroicons/24/solid/home-modern.svg +4 -0
- data/vendor/heroicons/24/solid/home.svg +4 -0
- data/vendor/heroicons/24/solid/identification.svg +3 -0
- data/vendor/heroicons/24/solid/inbox-arrow-down.svg +4 -0
- data/vendor/heroicons/24/solid/inbox-stack.svg +4 -0
- data/vendor/heroicons/24/solid/inbox.svg +3 -0
- data/vendor/heroicons/24/solid/information-circle.svg +3 -0
- data/vendor/heroicons/24/solid/italic.svg +3 -0
- data/vendor/heroicons/24/solid/key.svg +3 -0
- data/vendor/heroicons/24/solid/language.svg +3 -0
- data/vendor/heroicons/24/solid/lifebuoy.svg +3 -0
- data/vendor/heroicons/24/solid/light-bulb.svg +4 -0
- data/vendor/heroicons/24/solid/link-slash.svg +3 -0
- data/vendor/heroicons/24/solid/link.svg +3 -0
- data/vendor/heroicons/24/solid/list-bullet.svg +3 -0
- data/vendor/heroicons/24/solid/lock-closed.svg +3 -0
- data/vendor/heroicons/24/solid/lock-open.svg +3 -0
- data/vendor/heroicons/24/solid/magnifying-glass-circle.svg +4 -0
- data/vendor/heroicons/24/solid/magnifying-glass-minus.svg +3 -0
- data/vendor/heroicons/24/solid/magnifying-glass-plus.svg +3 -0
- data/vendor/heroicons/24/solid/magnifying-glass.svg +3 -0
- data/vendor/heroicons/24/solid/map-pin.svg +3 -0
- data/vendor/heroicons/24/solid/map.svg +3 -0
- data/vendor/heroicons/24/solid/megaphone.svg +3 -0
- data/vendor/heroicons/24/solid/microphone.svg +4 -0
- data/vendor/heroicons/24/solid/minus-circle.svg +3 -0
- data/vendor/heroicons/24/solid/minus-small.svg +3 -0
- data/vendor/heroicons/24/solid/minus.svg +3 -0
- data/vendor/heroicons/24/solid/moon.svg +3 -0
- data/vendor/heroicons/24/solid/musical-note.svg +3 -0
- data/vendor/heroicons/24/solid/newspaper.svg +4 -0
- data/vendor/heroicons/24/solid/no-symbol.svg +3 -0
- data/vendor/heroicons/24/solid/numbered-list.svg +3 -0
- data/vendor/heroicons/24/solid/paint-brush.svg +3 -0
- data/vendor/heroicons/24/solid/paper-airplane.svg +3 -0
- data/vendor/heroicons/24/solid/paper-clip.svg +3 -0
- data/vendor/heroicons/24/solid/pause-circle.svg +3 -0
- data/vendor/heroicons/24/solid/pause.svg +3 -0
- data/vendor/heroicons/24/solid/pencil-square.svg +4 -0
- data/vendor/heroicons/24/solid/pencil.svg +3 -0
- data/vendor/heroicons/24/solid/percent-badge.svg +3 -0
- data/vendor/heroicons/24/solid/phone-arrow-down-left.svg +4 -0
- data/vendor/heroicons/24/solid/phone-arrow-up-right.svg +4 -0
- data/vendor/heroicons/24/solid/phone-x-mark.svg +3 -0
- data/vendor/heroicons/24/solid/phone.svg +3 -0
- data/vendor/heroicons/24/solid/photo.svg +3 -0
- data/vendor/heroicons/24/solid/play-circle.svg +3 -0
- data/vendor/heroicons/24/solid/play-pause.svg +3 -0
- data/vendor/heroicons/24/solid/play.svg +3 -0
- data/vendor/heroicons/24/solid/plus-circle.svg +3 -0
- data/vendor/heroicons/24/solid/plus-small.svg +3 -0
- data/vendor/heroicons/24/solid/plus.svg +3 -0
- data/vendor/heroicons/24/solid/power.svg +3 -0
- data/vendor/heroicons/24/solid/presentation-chart-bar.svg +3 -0
- data/vendor/heroicons/24/solid/presentation-chart-line.svg +3 -0
- data/vendor/heroicons/24/solid/printer.svg +3 -0
- data/vendor/heroicons/24/solid/puzzle-piece.svg +3 -0
- data/vendor/heroicons/24/solid/qr-code.svg +3 -0
- data/vendor/heroicons/24/solid/question-mark-circle.svg +3 -0
- data/vendor/heroicons/24/solid/queue-list.svg +3 -0
- data/vendor/heroicons/24/solid/radio.svg +3 -0
- data/vendor/heroicons/24/solid/receipt-percent.svg +3 -0
- data/vendor/heroicons/24/solid/receipt-refund.svg +3 -0
- data/vendor/heroicons/24/solid/rectangle-group.svg +3 -0
- data/vendor/heroicons/24/solid/rectangle-stack.svg +3 -0
- data/vendor/heroicons/24/solid/rocket-launch.svg +4 -0
- data/vendor/heroicons/24/solid/rss.svg +3 -0
- data/vendor/heroicons/24/solid/scale.svg +3 -0
- data/vendor/heroicons/24/solid/scissors.svg +4 -0
- data/vendor/heroicons/24/solid/server-stack.svg +4 -0
- data/vendor/heroicons/24/solid/server.svg +4 -0
- data/vendor/heroicons/24/solid/share.svg +3 -0
- data/vendor/heroicons/24/solid/shield-check.svg +3 -0
- data/vendor/heroicons/24/solid/shield-exclamation.svg +3 -0
- data/vendor/heroicons/24/solid/shopping-bag.svg +3 -0
- data/vendor/heroicons/24/solid/shopping-cart.svg +3 -0
- data/vendor/heroicons/24/solid/signal-slash.svg +3 -0
- data/vendor/heroicons/24/solid/signal.svg +3 -0
- data/vendor/heroicons/24/solid/slash.svg +3 -0
- data/vendor/heroicons/24/solid/sparkles.svg +3 -0
- data/vendor/heroicons/24/solid/speaker-wave.svg +4 -0
- data/vendor/heroicons/24/solid/speaker-x-mark.svg +3 -0
- data/vendor/heroicons/24/solid/square-2-stack.svg +4 -0
- data/vendor/heroicons/24/solid/square-3-stack-3d.svg +5 -0
- data/vendor/heroicons/24/solid/squares-2x2.svg +3 -0
- data/vendor/heroicons/24/solid/squares-plus.svg +3 -0
- data/vendor/heroicons/24/solid/star.svg +3 -0
- data/vendor/heroicons/24/solid/stop-circle.svg +3 -0
- data/vendor/heroicons/24/solid/stop.svg +3 -0
- data/vendor/heroicons/24/solid/strikethrough.svg +3 -0
- data/vendor/heroicons/24/solid/sun.svg +3 -0
- data/vendor/heroicons/24/solid/swatch.svg +4 -0
- data/vendor/heroicons/24/solid/table-cells.svg +3 -0
- data/vendor/heroicons/24/solid/tag.svg +3 -0
- data/vendor/heroicons/24/solid/ticket.svg +3 -0
- data/vendor/heroicons/24/solid/trash.svg +3 -0
- data/vendor/heroicons/24/solid/trophy.svg +3 -0
- data/vendor/heroicons/24/solid/truck.svg +5 -0
- data/vendor/heroicons/24/solid/tv.svg +4 -0
- data/vendor/heroicons/24/solid/underline.svg +3 -0
- data/vendor/heroicons/24/solid/user-circle.svg +3 -0
- data/vendor/heroicons/24/solid/user-group.svg +4 -0
- data/vendor/heroicons/24/solid/user-minus.svg +3 -0
- data/vendor/heroicons/24/solid/user-plus.svg +3 -0
- data/vendor/heroicons/24/solid/user.svg +3 -0
- data/vendor/heroicons/24/solid/users.svg +3 -0
- data/vendor/heroicons/24/solid/variable.svg +3 -0
- data/vendor/heroicons/24/solid/video-camera-slash.svg +3 -0
- data/vendor/heroicons/24/solid/video-camera.svg +3 -0
- data/vendor/heroicons/24/solid/view-columns.svg +3 -0
- data/vendor/heroicons/24/solid/viewfinder-circle.svg +3 -0
- data/vendor/heroicons/24/solid/wallet.svg +3 -0
- data/vendor/heroicons/24/solid/wifi.svg +3 -0
- data/vendor/heroicons/24/solid/window.svg +3 -0
- data/vendor/heroicons/24/solid/wrench-screwdriver.svg +5 -0
- data/vendor/heroicons/24/solid/wrench.svg +3 -0
- data/vendor/heroicons/24/solid/x-circle.svg +3 -0
- data/vendor/heroicons/24/solid/x-mark.svg +3 -0
- data/vendor/heroicons/LICENSE +21 -0
- data/vendor/heroicons/VERSION +5 -0
- metadata +1664 -0
|
@@ -0,0 +1,800 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'digest'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
require 'set'
|
|
6
|
+
require 'shellwords'
|
|
7
|
+
|
|
8
|
+
begin
|
|
9
|
+
require 'vips'
|
|
10
|
+
rescue LoadError
|
|
11
|
+
# ruby-vips not installed (or libvips missing). The pipeline detects
|
|
12
|
+
# this in `detect_backend` and falls back to the shell processor.
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
module Abqari
|
|
16
|
+
# Build-time image optimisation. The pipeline resizes raster images
|
|
17
|
+
# into multiple widths, encodes them as AVIF + WebP + an optimised
|
|
18
|
+
# original-format fallback, and rewrites `<img>` tags into
|
|
19
|
+
# `<picture>` elements that serve the smallest variant the visitor's
|
|
20
|
+
# browser can decode.
|
|
21
|
+
#
|
|
22
|
+
# Two source locations are covered:
|
|
23
|
+
#
|
|
24
|
+
# 1. `app/assets/images/` — site-wide imagery used from ERB via
|
|
25
|
+
# `picture_tag('hero.jpg', alt: '…')`. Variants land at
|
|
26
|
+
# `_site/assets/images/<basename>-<width>.<hash>.<fmt>` and are
|
|
27
|
+
# shared across pages.
|
|
28
|
+
#
|
|
29
|
+
# 2. Bundle directories (`content/posts/<slug>/`, etc.) — hero
|
|
30
|
+
# and body images that live next to a post's `index.md`.
|
|
31
|
+
# Variants land at `_site/<page.url>/<basename>-<width>.<hash>.<fmt>`
|
|
32
|
+
# and the post body's `<img src="hero.jpg">` is rewritten to a
|
|
33
|
+
# `<picture>` element automatically during `post_process_html`.
|
|
34
|
+
#
|
|
35
|
+
# Defaults are tuned for the publisher case (camera photos, prose
|
|
36
|
+
# leds): AVIF first (~50% smaller than JPEG), WebP fallback (~30%
|
|
37
|
+
# smaller), JPEG passthrough for browsers without modern format
|
|
38
|
+
# support. Per-format quality lands at the standard perceptual-
|
|
39
|
+
# equivalence points — AVIF 50, WebP 80, JPEG 85. Metadata stripped
|
|
40
|
+
# by default (EXIF, GPS, embedded thumbnails — a 50–500 KB win and
|
|
41
|
+
# a privacy bonus).
|
|
42
|
+
#
|
|
43
|
+
# Upscaling is skipped. A 600-pixel-wide source with
|
|
44
|
+
# `widths: [400, 800, 1200]` produces variants at 400 and 600 (the
|
|
45
|
+
# source's native width) — the 800 and 1200 entries are dropped
|
|
46
|
+
# rather than emitted as upscaled-blurrier-and-larger files.
|
|
47
|
+
class ImagePipeline
|
|
48
|
+
# Raster sources the pipeline handles. SVG is excluded — vector,
|
|
49
|
+
# nothing to thumbnail.
|
|
50
|
+
IMAGE_EXTENSIONS = %w[.jpg .jpeg .png .gif .webp .avif].freeze
|
|
51
|
+
|
|
52
|
+
DEFAULTS = {
|
|
53
|
+
'optimise' => true,
|
|
54
|
+
'processor' => 'auto', # auto | vips-native | vips | imagemagick
|
|
55
|
+
# AVIF first so browsers pick it ahead of WebP — AVIF is 20–50%
|
|
56
|
+
# smaller at equal quality and has universal modern-browser
|
|
57
|
+
# support as of 2026 (Safari 16.4+, Chrome/Edge 85+, Firefox
|
|
58
|
+
# 113+). Older browsers fall through to WebP, then the original.
|
|
59
|
+
'formats' => %w[avif webp],
|
|
60
|
+
'widths' => [400, 800, 1200],
|
|
61
|
+
# Per-format quality. AVIF's quality scale is perceptually
|
|
62
|
+
# different from JPEG/WebP — 50 produces files smaller than WebP
|
|
63
|
+
# at 80 with comparable quality. Applying one number across all
|
|
64
|
+
# formats (the legacy behaviour) made AVIF files LARGER than
|
|
65
|
+
# WebP, defeating the format's purpose.
|
|
66
|
+
'quality' => { 'avif' => 50, 'webp' => 80, 'original' => 85 },
|
|
67
|
+
'sizes' => '(min-width: 768px) 50vw, 100vw',
|
|
68
|
+
'source_dir' => 'app/assets/images',
|
|
69
|
+
# Strip EXIF, GPS, embedded thumbnails, ICC profiles. Photographers
|
|
70
|
+
# who need to preserve metadata (portfolio sites that surface
|
|
71
|
+
# camera info) can flip this off.
|
|
72
|
+
'preserve_metadata' => false
|
|
73
|
+
}.freeze
|
|
74
|
+
|
|
75
|
+
HASH_LENGTH = 10
|
|
76
|
+
|
|
77
|
+
def initialize(site)
|
|
78
|
+
@site = site
|
|
79
|
+
@config = merged_config(site.config['images'])
|
|
80
|
+
@backend = nil
|
|
81
|
+
@variants_cache = {}
|
|
82
|
+
@cache_mutex = Mutex.new
|
|
83
|
+
@registry = {}
|
|
84
|
+
@registry_mutex = Mutex.new
|
|
85
|
+
@url_to_bundle_dir = nil
|
|
86
|
+
@config['formats'] = prune_unsupported_formats(@config['formats'])
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def enabled?
|
|
90
|
+
@config['optimise'] == true && backend
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def backend
|
|
94
|
+
@backend = detect_backend if @backend.nil? && !@backend_resolved
|
|
95
|
+
@backend_resolved = true
|
|
96
|
+
@backend
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Phase entry point — called from Site#build BEFORE render. Copies
|
|
100
|
+
# `app/assets/images/` originals through (always — needed for the
|
|
101
|
+
# passthrough case), then if optimisation is on, walks every known
|
|
102
|
+
# image source and registers it in the URL → variants map so
|
|
103
|
+
# `post_process_html` can rewrite `<img>` tags during render.
|
|
104
|
+
def process
|
|
105
|
+
copy_originals
|
|
106
|
+
return unless enabled?
|
|
107
|
+
|
|
108
|
+
build_url_to_bundle_dir_map
|
|
109
|
+
register_known_sources
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Resolve a URL to its on-disk source path, or nil if we don't
|
|
113
|
+
# recognise it. Used by post-processing to figure out which `<img
|
|
114
|
+
# src=...>` tags to rewrite.
|
|
115
|
+
def source_path_for(url)
|
|
116
|
+
@registry_mutex.synchronize { @registry[url]&.dig(:source_path) }
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# picture_tag — emits a `<picture>` block for an asset image. The
|
|
120
|
+
# `source` argument is resolved under `app/assets/images/` (the
|
|
121
|
+
# legacy ERB helper convention). For bundle images, prefer the
|
|
122
|
+
# automatic rewrite in `post_process_html` or the
|
|
123
|
+
# `bundle_picture_tag` helper.
|
|
124
|
+
def picture_tag(source, alt: '', sizes: nil, widths: nil)
|
|
125
|
+
sizes ||= @config['sizes']
|
|
126
|
+
requested_widths = (widths || @config['widths']).sort
|
|
127
|
+
|
|
128
|
+
source_path = absolute_source(source)
|
|
129
|
+
unless File.exist?(source_path)
|
|
130
|
+
# Source doesn't exist locally — emit a literal <img> with the
|
|
131
|
+
# source value as-is (allows authors to use full URLs).
|
|
132
|
+
return %(<img src="#{escape_attr(source)}" alt="#{escape_attr(alt)}" loading="lazy" decoding="async">)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
unless enabled?
|
|
136
|
+
copy_passthrough(source_path, '/assets/images/')
|
|
137
|
+
dims = dimension_attrs(source_path)
|
|
138
|
+
url = "/assets/images/#{File.basename(source_path)}"
|
|
139
|
+
return %(<img src="#{escape_attr(url)}" alt="#{escape_attr(alt)}"#{dims} loading="lazy" decoding="async">)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
url_prefix = '/assets/images/'
|
|
143
|
+
target_dir = File.join(@site.output_dir, 'assets', 'images')
|
|
144
|
+
register_source("#{url_prefix}#{File.basename(source_path)}", source_path, target_dir, url_prefix)
|
|
145
|
+
render_picture("#{url_prefix}#{File.basename(source_path)}", alt, sizes, requested_widths)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Emit a `<picture>` block for a URL that's been registered with
|
|
149
|
+
# the pipeline (asset or bundle). Returns nil if the URL isn't
|
|
150
|
+
# registered — callers fall back to whatever they were going to
|
|
151
|
+
# do otherwise (a plain `<img>`, the legacy sibling-lookup, etc.).
|
|
152
|
+
#
|
|
153
|
+
# Used by `bundle_picture_tag` in render_context to opt explicit
|
|
154
|
+
# layout-level uses into the pipeline.
|
|
155
|
+
def picture_tag_for_url(url, alt:, sizes: nil, widths: nil, attrs: {})
|
|
156
|
+
return nil unless enabled?
|
|
157
|
+
return nil unless source_path_for(canonical_url(url))
|
|
158
|
+
|
|
159
|
+
sizes ||= @config['sizes']
|
|
160
|
+
widths = (widths || @config['widths']).sort
|
|
161
|
+
render_picture(canonical_url(url), alt, sizes, widths, extra_attrs: attrs)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Rewrite `<img>` tags whose `src` we know how to optimise. Asset
|
|
165
|
+
# URLs and bundle URLs are both routed through the registry — any
|
|
166
|
+
# `<img src=...>` that maps to a registered source gets replaced
|
|
167
|
+
# with a `<picture>` element. Unknown images pass through
|
|
168
|
+
# unchanged.
|
|
169
|
+
#
|
|
170
|
+
# `page:` is accepted for symmetry with other post-processors but
|
|
171
|
+
# the registry lookup is URL-based (we already have a complete
|
|
172
|
+
# map by `Site#build` time) so the page itself isn't needed here.
|
|
173
|
+
def post_process_html(html, page: nil)
|
|
174
|
+
return html unless enabled?
|
|
175
|
+
|
|
176
|
+
html.gsub(/<img\s+([^>]*?)>/m) do |match|
|
|
177
|
+
attrs = ::Regexp.last_match(1)
|
|
178
|
+
src = attrs[/src="([^"]+)"/, 1]
|
|
179
|
+
next match unless src
|
|
180
|
+
|
|
181
|
+
registered_url = canonical_url(src)
|
|
182
|
+
next match unless source_path_for(registered_url)
|
|
183
|
+
|
|
184
|
+
alt = attrs[/alt="([^"]*)"/, 1] || ''
|
|
185
|
+
render_picture(registered_url, alt, @config['sizes'], @config['widths'].sort)
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
private
|
|
190
|
+
|
|
191
|
+
# ----------------------------------------------------------------
|
|
192
|
+
# Config + back-compat
|
|
193
|
+
# ----------------------------------------------------------------
|
|
194
|
+
|
|
195
|
+
# Apply DEFAULTS to user config, with care for the `quality` key —
|
|
196
|
+
# which can be a scalar (legacy: applies to all formats) or a hash
|
|
197
|
+
# (per-format). Scalar gets expanded to a per-format hash so call
|
|
198
|
+
# sites can do a uniform lookup.
|
|
199
|
+
def merged_config(user)
|
|
200
|
+
user ||= {}
|
|
201
|
+
result = DEFAULTS.merge(user)
|
|
202
|
+
if user.key?('quality')
|
|
203
|
+
q = user['quality']
|
|
204
|
+
result['quality'] = if q.is_a?(Hash)
|
|
205
|
+
DEFAULTS['quality'].merge(q.transform_keys(&:to_s))
|
|
206
|
+
elsif q.is_a?(Numeric)
|
|
207
|
+
{ 'avif' => q, 'webp' => q, 'original' => q }
|
|
208
|
+
else
|
|
209
|
+
DEFAULTS['quality']
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
result
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def quality_for(format)
|
|
216
|
+
@config['quality'][format.to_s] || @config['quality']['original'] || 80
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# ----------------------------------------------------------------
|
|
220
|
+
# Format capability pre-flight
|
|
221
|
+
# ----------------------------------------------------------------
|
|
222
|
+
|
|
223
|
+
# Probe each configured output format once per process. Drop the
|
|
224
|
+
# ones the host environment can't actually write — typically AVIF
|
|
225
|
+
# on stripped-down libvips builds that lack the libheif AV1 encoder
|
|
226
|
+
# plugin (the `Vips::Error: heifsave: Unsupported compression`
|
|
227
|
+
# symptom on Ubuntu 24.04 without `libheif-plugin-aomenc`).
|
|
228
|
+
#
|
|
229
|
+
# Without this guard, the pipeline raises mid-build for every
|
|
230
|
+
# image, the rendered site is corrupted, and the build fails. With
|
|
231
|
+
# it, we emit a one-line warning, drop the format from the formats
|
|
232
|
+
# list, and the pipeline produces a `<picture>` element with the
|
|
233
|
+
# remaining formats — site still ships, just without AVIF variants.
|
|
234
|
+
#
|
|
235
|
+
# Memoised at the class level so the actual probe runs at most
|
|
236
|
+
# once per process even when many ImagePipeline instances are
|
|
237
|
+
# created (e.g. across the test suite). The probe is cheap, but
|
|
238
|
+
# cold-start codec init for HEIF/AVIF can take a second or two —
|
|
239
|
+
# repeating it per-test would be slow.
|
|
240
|
+
def prune_unsupported_formats(formats)
|
|
241
|
+
# `vips_loaded?`, NOT `defined?(::Vips)`: ruby-vips installs fine
|
|
242
|
+
# without the libvips C library, and its require defines the Vips
|
|
243
|
+
# module BEFORE the FFI bind fails — so after our rescued
|
|
244
|
+
# LoadError the constant exists but Vips::Image/Vips::Error do
|
|
245
|
+
# not. The weak check sent the probe straight into a NameError
|
|
246
|
+
# and killed every build on a machine without libvips (the
|
|
247
|
+
# passthrough fallback's whole audience).
|
|
248
|
+
return formats unless vips_loaded?
|
|
249
|
+
formats.select do |fmt|
|
|
250
|
+
if format_writable?(fmt)
|
|
251
|
+
true
|
|
252
|
+
else
|
|
253
|
+
warn_unsupported_format(fmt)
|
|
254
|
+
false
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def format_writable?(format)
|
|
260
|
+
return true if format == 'original'
|
|
261
|
+
capability = ImagePipeline.format_capabilities[format.to_s]
|
|
262
|
+
return capability unless capability.nil?
|
|
263
|
+
ImagePipeline.format_capabilities[format.to_s] = probe_format(format)
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# The actual probe — a tiny in-memory write. Run at most once per
|
|
267
|
+
# format per process via the class-level memo above.
|
|
268
|
+
def probe_format(format)
|
|
269
|
+
# Belt-and-braces for the partial-load state described in
|
|
270
|
+
# prune_unsupported_formats: without real bindings, even this
|
|
271
|
+
# method's `rescue ::Vips::Error` clause raises NameError.
|
|
272
|
+
return true unless vips_loaded?
|
|
273
|
+
|
|
274
|
+
probe = ::Vips::Image.black(2, 2)
|
|
275
|
+
probe.write_to_buffer(".#{format}")
|
|
276
|
+
true
|
|
277
|
+
rescue ::Vips::Error
|
|
278
|
+
false
|
|
279
|
+
rescue StandardError
|
|
280
|
+
# Any non-Vips failure (load error, FFI weirdness): bias toward
|
|
281
|
+
# "yes, try it" — the real write will surface a clearer error
|
|
282
|
+
# than swallowing the probe failure here.
|
|
283
|
+
true
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def warn_unsupported_format(format)
|
|
287
|
+
return if ImagePipeline.warned_formats.include?(format.to_s)
|
|
288
|
+
ImagePipeline.warned_formats << format.to_s
|
|
289
|
+
Log.warn "Image pipeline: #{format.upcase} unsupported by this libvips " \
|
|
290
|
+
"build (likely missing codec plugin) — skipping #{format.upcase} variants. " \
|
|
291
|
+
"On Ubuntu 24.04 install `libheif1 libheif-plugin-aomenc` to enable AVIF."
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# Class-level caches for probe results + already-warned formats.
|
|
295
|
+
# Survives across ImagePipeline instances within a single Ruby
|
|
296
|
+
# process.
|
|
297
|
+
class << self
|
|
298
|
+
def format_capabilities
|
|
299
|
+
@format_capabilities ||= {}
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def warned_formats
|
|
303
|
+
@warned_formats ||= Set.new
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# The libvips C-library version, used as a cache-key component
|
|
308
|
+
# so a libvips upgrade invalidates variants automatically. Falls
|
|
309
|
+
# back to 'none' when ruby-vips isn't loaded (shell backend or
|
|
310
|
+
# passthrough) — in that case `backend.name` already varies
|
|
311
|
+
# between `shell-vips` / `shell-magick`, so the version
|
|
312
|
+
# component is supplementary.
|
|
313
|
+
def vips_version_tag
|
|
314
|
+
return 'none' unless defined?(::Vips) && ::Vips.respond_to?(:version_string)
|
|
315
|
+
::Vips.version_string
|
|
316
|
+
rescue StandardError
|
|
317
|
+
'none'
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
# ----------------------------------------------------------------
|
|
321
|
+
# Backend detection
|
|
322
|
+
# ----------------------------------------------------------------
|
|
323
|
+
|
|
324
|
+
def detect_backend
|
|
325
|
+
strip = !@config['preserve_metadata']
|
|
326
|
+
requested = @config['processor'].to_s
|
|
327
|
+
|
|
328
|
+
case requested
|
|
329
|
+
when 'vips-native'
|
|
330
|
+
return VipsBackend.new(strip: strip) if vips_loaded?
|
|
331
|
+
|
|
332
|
+
Log.warn 'Abqari: images.processor=vips-native but ruby-vips not loaded. Falling back to auto-detect.'
|
|
333
|
+
when 'vips'
|
|
334
|
+
return ShellBackend.new('vips', strip: strip) if which('vips')
|
|
335
|
+
|
|
336
|
+
Log.warn 'Abqari: images.processor=vips but vips CLI not on PATH. Falling back to auto-detect.'
|
|
337
|
+
when 'imagemagick'
|
|
338
|
+
return ShellBackend.new('magick', strip: strip) if which('magick')
|
|
339
|
+
|
|
340
|
+
Log.warn 'Abqari: images.processor=imagemagick but magick CLI not on PATH. Falling back to auto-detect.'
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
# Auto-detect order: native ruby-vips > shell vips > shell magick.
|
|
344
|
+
return VipsBackend.new(strip: strip) if vips_loaded?
|
|
345
|
+
return ShellBackend.new('vips', strip: strip) if which('vips')
|
|
346
|
+
return ShellBackend.new('magick', strip: strip) if which('magick')
|
|
347
|
+
|
|
348
|
+
Log.warn 'Abqari: images.optimise=true but no image processor available. ' \
|
|
349
|
+
"Install libvips (`brew install vips` / `apt install libvips42`) " \
|
|
350
|
+
"or imagemagick. Falling back to passthrough — originals only."
|
|
351
|
+
nil
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def vips_loaded?
|
|
355
|
+
defined?(::Vips) && ::Vips.respond_to?(:version_string)
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def which(cmd)
|
|
359
|
+
ENV['PATH'].to_s.split(File::PATH_SEPARATOR).any? do |dir|
|
|
360
|
+
File.executable?(File.join(dir, cmd))
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
# ----------------------------------------------------------------
|
|
365
|
+
# Source registration — populates the URL → source-path map so
|
|
366
|
+
# post_process_html can look up image references during render.
|
|
367
|
+
# ----------------------------------------------------------------
|
|
368
|
+
|
|
369
|
+
def build_url_to_bundle_dir_map
|
|
370
|
+
@url_to_bundle_dir = {}
|
|
371
|
+
@site.all_pages.each do |page|
|
|
372
|
+
next unless page.respond_to?(:bundle_dir) && page.bundle_dir
|
|
373
|
+
next unless page.respond_to?(:url) && page.url
|
|
374
|
+
|
|
375
|
+
@url_to_bundle_dir[page.url] = page.bundle_dir
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
def register_known_sources
|
|
380
|
+
register_dir(asset_source_dir, '/assets/images/',
|
|
381
|
+
File.join(@site.output_dir, 'assets', 'images'))
|
|
382
|
+
|
|
383
|
+
@url_to_bundle_dir.each do |page_url, bundle_dir|
|
|
384
|
+
target_dir = PathSafe.join_under(@site.output_dir, page_url.sub(%r{\A/}, '').chomp('/'))
|
|
385
|
+
register_dir(bundle_dir, page_url, target_dir)
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
register_flat_collection_sources
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
def register_flat_collection_sources
|
|
392
|
+
@site.collections.each do |_name, cfg|
|
|
393
|
+
next if cfg['bundle'] != false
|
|
394
|
+
next unless cfg.fetch('enabled', true)
|
|
395
|
+
|
|
396
|
+
source_dir = File.join(@site.site_root, cfg['source'].to_s)
|
|
397
|
+
next unless Dir.exist?(source_dir)
|
|
398
|
+
|
|
399
|
+
url_prefix = "/#{cfg['permalink'].to_s.sub(/:slug.*/, '').sub(%r{\A/}, '').chomp('/')}/"
|
|
400
|
+
next if url_prefix == '//'
|
|
401
|
+
|
|
402
|
+
target_dir = PathSafe.join_under(@site.output_dir, url_prefix.sub(%r{\A/}, '').chomp('/'))
|
|
403
|
+
register_dir(source_dir, url_prefix, target_dir)
|
|
404
|
+
end
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
def register_dir(source_dir, url_prefix, target_dir)
|
|
408
|
+
return unless Dir.exist?(source_dir)
|
|
409
|
+
|
|
410
|
+
Dir.glob(File.join(source_dir, '*')).each do |entry|
|
|
411
|
+
next if File.directory?(entry)
|
|
412
|
+
next if File.basename(entry).start_with?('.')
|
|
413
|
+
next unless IMAGE_EXTENSIONS.include?(File.extname(entry).downcase)
|
|
414
|
+
next if @site.external_symlink?(entry)
|
|
415
|
+
|
|
416
|
+
url = "#{url_prefix}#{File.basename(entry)}"
|
|
417
|
+
register_source(url, entry, target_dir, url_prefix)
|
|
418
|
+
end
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
def register_source(url, source_path, target_dir, url_prefix)
|
|
422
|
+
@registry_mutex.synchronize do
|
|
423
|
+
@registry[url] ||= {
|
|
424
|
+
source_path: source_path,
|
|
425
|
+
target_dir: target_dir,
|
|
426
|
+
url_prefix: url_prefix,
|
|
427
|
+
dimensions: nil
|
|
428
|
+
}
|
|
429
|
+
end
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
def asset_source_dir
|
|
433
|
+
File.join(@site.site_root, @config['source_dir'])
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
def absolute_source(name)
|
|
437
|
+
File.join(asset_source_dir, File.basename(name))
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
# Strip query/fragment + collapse internal `../` for lookup. URLs
|
|
441
|
+
# in rendered HTML are absolute and already normalised, so this is
|
|
442
|
+
# mainly a defensive trim.
|
|
443
|
+
def canonical_url(url)
|
|
444
|
+
url.split(/[?#]/, 2).first
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
# ----------------------------------------------------------------
|
|
448
|
+
# Picture-element rendering
|
|
449
|
+
# ----------------------------------------------------------------
|
|
450
|
+
|
|
451
|
+
def render_picture(url, alt, sizes, widths, extra_attrs: {})
|
|
452
|
+
entry = @registry_mutex.synchronize { @registry[url] }
|
|
453
|
+
source_path = entry[:source_path]
|
|
454
|
+
target_dir = entry[:target_dir]
|
|
455
|
+
url_prefix = entry[:url_prefix]
|
|
456
|
+
|
|
457
|
+
source_w, source_h = ensure_dimensions(url, source_path)
|
|
458
|
+
effective_widths = compute_effective_widths(widths, source_w)
|
|
459
|
+
return passthrough_img(url, alt, source_w, source_h, extra_attrs) if effective_widths.empty?
|
|
460
|
+
|
|
461
|
+
formats_to_emit = @config['formats']
|
|
462
|
+
source_tags = formats_to_emit.filter_map do |fmt|
|
|
463
|
+
srcset = build_srcset(source_path, target_dir, url_prefix, fmt, effective_widths)
|
|
464
|
+
next if srcset.empty?
|
|
465
|
+
|
|
466
|
+
%(<source type="image/#{fmt}" srcset="#{srcset}" sizes="#{sizes}">)
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
fallback_width = effective_widths.max
|
|
470
|
+
fallback_url = variant_for(source_path, target_dir, url_prefix, fallback_width, 'original')
|
|
471
|
+
width_attr = fallback_width
|
|
472
|
+
height_attr = source_w.positive? ? (fallback_width * source_h / source_w.to_f).round : nil
|
|
473
|
+
|
|
474
|
+
attrs = +%(src="#{escape_attr(fallback_url)}" alt="#{escape_attr(alt)}")
|
|
475
|
+
attrs << %( width="#{width_attr}") if width_attr
|
|
476
|
+
attrs << %( height="#{height_attr}") if height_attr
|
|
477
|
+
# Caller-supplied attrs (class, loading="eager", fetchpriority, etc.)
|
|
478
|
+
# are appended last so they win over defaults. `loading` and
|
|
479
|
+
# `decoding` default below; pass them in extra_attrs to override.
|
|
480
|
+
extra_attrs.each { |k, v| attrs << %( #{escape_attr(k.to_s)}="#{escape_attr(v.to_s)}") }
|
|
481
|
+
attrs << %( loading="lazy") unless extra_attrs.key?(:loading) || extra_attrs.key?('loading')
|
|
482
|
+
attrs << %( decoding="async") unless extra_attrs.key?(:decoding) || extra_attrs.key?('decoding')
|
|
483
|
+
|
|
484
|
+
<<~HTML.chomp
|
|
485
|
+
<picture>
|
|
486
|
+
#{source_tags.join("\n ")}
|
|
487
|
+
<img #{attrs}>
|
|
488
|
+
</picture>
|
|
489
|
+
HTML
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
# Emit a plain `<img>` for the passthrough case (no optimisation,
|
|
493
|
+
# or no widths survived the upscale guard).
|
|
494
|
+
def passthrough_img(url, alt, source_w, source_h, extra_attrs = {})
|
|
495
|
+
dims = source_w && source_h ? %( width="#{source_w}" height="#{source_h}") : ''
|
|
496
|
+
attrs = +%(src="#{escape_attr(url)}" alt="#{escape_attr(alt)}"#{dims})
|
|
497
|
+
extra_attrs.each { |k, v| attrs << %( #{escape_attr(k.to_s)}="#{escape_attr(v.to_s)}") }
|
|
498
|
+
attrs << %( loading="lazy") unless extra_attrs.key?(:loading) || extra_attrs.key?('loading')
|
|
499
|
+
attrs << %( decoding="async") unless extra_attrs.key?(:decoding) || extra_attrs.key?('decoding')
|
|
500
|
+
%(<img #{attrs}>)
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
def build_srcset(source_path, target_dir, url_prefix, format, widths)
|
|
504
|
+
widths.map do |w|
|
|
505
|
+
"#{variant_for(source_path, target_dir, url_prefix, w, format)} #{w}w"
|
|
506
|
+
end.join(', ')
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
# Skip-if-smaller logic. Drop any requested width that's >= source
|
|
510
|
+
# width (no point upscaling — bigger file, blurrier output), and
|
|
511
|
+
# add the source width itself so the largest variant is always
|
|
512
|
+
# the original's native resolution.
|
|
513
|
+
def compute_effective_widths(requested, source_w)
|
|
514
|
+
return requested if source_w.nil? || source_w.zero?
|
|
515
|
+
|
|
516
|
+
kept = requested.select { |w| w < source_w }
|
|
517
|
+
kept << source_w
|
|
518
|
+
kept.uniq.sort
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
# ----------------------------------------------------------------
|
|
522
|
+
# Variant generation
|
|
523
|
+
# ----------------------------------------------------------------
|
|
524
|
+
|
|
525
|
+
def variant_for(source_path, target_dir, url_prefix, width, format)
|
|
526
|
+
cache_key = [source_path, width, format].join('|')
|
|
527
|
+
|
|
528
|
+
@cache_mutex.synchronize do
|
|
529
|
+
return @variants_cache[cache_key] if @variants_cache[cache_key]
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
# Backend identity is part of the cache key so a switch from
|
|
533
|
+
# `vips-native` → `shell-magick` (or a libvips upgrade that
|
|
534
|
+
# changes a codec) invalidates old variants. Without this, the
|
|
535
|
+
# content-addressed filename stays stable while the bytes
|
|
536
|
+
# inside change, and CDN-cached old variants never get evicted.
|
|
537
|
+
# `Vips.version_string` is the underlying libvips C-library
|
|
538
|
+
# version (e.g. "8.18.2"); a codec upgrade in libvips ships as
|
|
539
|
+
# a new version string, so the hash flips automatically.
|
|
540
|
+
ext = format == 'original' ? File.extname(source_path).delete('.') : format
|
|
541
|
+
backend_name = backend ? backend.name : 'passthrough'
|
|
542
|
+
hash = Digest::SHA1.hexdigest(
|
|
543
|
+
"#{source_path}:#{width}:#{format}:#{quality_for(format)}:" \
|
|
544
|
+
"#{File.mtime(source_path).to_i}:#{backend_name}:#{vips_version_tag}"
|
|
545
|
+
)[0, HASH_LENGTH]
|
|
546
|
+
|
|
547
|
+
basename = File.basename(source_path, File.extname(source_path))
|
|
548
|
+
out_filename = "#{basename}-#{width}.#{hash}.#{ext}"
|
|
549
|
+
FileUtils.mkdir_p(target_dir)
|
|
550
|
+
out_path = File.join(target_dir, out_filename)
|
|
551
|
+
|
|
552
|
+
# Write a fresh variant to a sibling `.tmp` and rename into
|
|
553
|
+
# place. Under parallel render two workers can both miss the
|
|
554
|
+
# in-memory cache, both see `File.exist?(out_path)` return
|
|
555
|
+
# false, and both attempt to write to the SAME output path
|
|
556
|
+
# — a slow-disk race can produce a torn/corrupt file. With
|
|
557
|
+
# the tmp-and-rename pattern, the loser's rename either
|
|
558
|
+
# silently wins (overwriting a complete file with another
|
|
559
|
+
# complete file) or the file already exists and we skip the
|
|
560
|
+
# write entirely. Either way, no reader sees a half-written
|
|
561
|
+
# variant.
|
|
562
|
+
unless File.exist?(out_path)
|
|
563
|
+
# tmp filename keeps the original extension at the end so
|
|
564
|
+
# libvips' `write_to_file` (which infers format from the
|
|
565
|
+
# extension) doesn't reject "foo.avif.123.tmp" as unknown.
|
|
566
|
+
# The `pid + thread.object_id` prefix is load-bearing: two
|
|
567
|
+
# workers can both pass the cache_mutex check and both reach
|
|
568
|
+
# this branch for the same `out_filename`. Without unique
|
|
569
|
+
# tmp paths per thread, one would truncate the other's
|
|
570
|
+
# in-progress write, and the rename-after-rename would hit
|
|
571
|
+
# ENOENT on the loser.
|
|
572
|
+
tmp_path = File.join(
|
|
573
|
+
target_dir,
|
|
574
|
+
".#{Process.pid}.#{Thread.current.object_id}.#{out_filename}"
|
|
575
|
+
)
|
|
576
|
+
begin
|
|
577
|
+
backend.thumbnail(source_path, tmp_path, width, format, quality_for(format))
|
|
578
|
+
File.rename(tmp_path, out_path)
|
|
579
|
+
ensure
|
|
580
|
+
File.unlink(tmp_path) if File.exist?(tmp_path)
|
|
581
|
+
end
|
|
582
|
+
end
|
|
583
|
+
|
|
584
|
+
url = "#{url_prefix}#{out_filename}"
|
|
585
|
+
@cache_mutex.synchronize { @variants_cache[cache_key] = url }
|
|
586
|
+
url
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
def ensure_dimensions(url, source_path)
|
|
590
|
+
entry = @registry_mutex.synchronize { @registry[url] }
|
|
591
|
+
return entry[:dimensions] if entry[:dimensions]
|
|
592
|
+
|
|
593
|
+
dims = backend ? backend.dimensions(source_path) : read_dimensions(source_path)
|
|
594
|
+
dims ||= [0, 0]
|
|
595
|
+
@registry_mutex.synchronize { @registry[url][:dimensions] = dims if @registry[url] }
|
|
596
|
+
dims
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
# ----------------------------------------------------------------
|
|
600
|
+
# Pure-Ruby dimension reader — used as a fallback when no backend
|
|
601
|
+
# is available, and as the primary path for header-readable
|
|
602
|
+
# formats (avoids spawning a process just to read width/height).
|
|
603
|
+
# ----------------------------------------------------------------
|
|
604
|
+
|
|
605
|
+
def read_dimensions(path)
|
|
606
|
+
return nil unless File.exist?(path)
|
|
607
|
+
|
|
608
|
+
case File.extname(path).downcase
|
|
609
|
+
when '.png' then read_png_dimensions(path)
|
|
610
|
+
when '.jpg', '.jpeg' then read_jpeg_dimensions(path)
|
|
611
|
+
when '.gif' then read_gif_dimensions(path)
|
|
612
|
+
end
|
|
613
|
+
rescue StandardError
|
|
614
|
+
nil
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
def read_png_dimensions(path)
|
|
618
|
+
File.open(path, 'rb') do |f|
|
|
619
|
+
f.read(16) # 8-byte signature + 4-byte length + 4-byte 'IHDR'
|
|
620
|
+
f.read(8).unpack('NN') # width, height (4 bytes each, big-endian)
|
|
621
|
+
end
|
|
622
|
+
end
|
|
623
|
+
|
|
624
|
+
def read_jpeg_dimensions(path)
|
|
625
|
+
File.open(path, 'rb') do |f|
|
|
626
|
+
f.read(2) # SOI marker FF D8
|
|
627
|
+
loop do
|
|
628
|
+
marker = f.read(2)&.unpack1('n')
|
|
629
|
+
return nil unless marker
|
|
630
|
+
|
|
631
|
+
# SOFn frame markers (excluding DHT, DAC, JPG).
|
|
632
|
+
if (0xFFC0..0xFFCF).cover?(marker) && ![0xFFC4, 0xFFC8, 0xFFCC].include?(marker)
|
|
633
|
+
f.read(3) # segment length (2) + sample precision (1)
|
|
634
|
+
height, width = f.read(4).unpack('nn')
|
|
635
|
+
return [width, height]
|
|
636
|
+
end
|
|
637
|
+
|
|
638
|
+
length = f.read(2)&.unpack1('n')
|
|
639
|
+
return nil unless length
|
|
640
|
+
|
|
641
|
+
f.read(length - 2)
|
|
642
|
+
end
|
|
643
|
+
end
|
|
644
|
+
end
|
|
645
|
+
|
|
646
|
+
def read_gif_dimensions(path)
|
|
647
|
+
File.open(path, 'rb') do |f|
|
|
648
|
+
f.read(6) # "GIF87a" or "GIF89a"
|
|
649
|
+
f.read(4).unpack('vv') # width, height (2 bytes each, little-endian)
|
|
650
|
+
end
|
|
651
|
+
end
|
|
652
|
+
|
|
653
|
+
# ----------------------------------------------------------------
|
|
654
|
+
# Passthrough copy — for the no-optimisation case.
|
|
655
|
+
# ----------------------------------------------------------------
|
|
656
|
+
|
|
657
|
+
def copy_passthrough(source_path, _url_prefix)
|
|
658
|
+
dest_dir = File.join(@site.output_dir, 'assets', 'images')
|
|
659
|
+
FileUtils.mkdir_p(dest_dir)
|
|
660
|
+
FileUtils.cp(source_path, File.join(dest_dir, File.basename(source_path)))
|
|
661
|
+
end
|
|
662
|
+
|
|
663
|
+
def copy_originals
|
|
664
|
+
return unless Dir.exist?(asset_source_dir)
|
|
665
|
+
|
|
666
|
+
dest_dir = File.join(@site.output_dir, 'assets', 'images')
|
|
667
|
+
FileUtils.mkdir_p(dest_dir)
|
|
668
|
+
Dir.glob(File.join(asset_source_dir, '*')).sort.each do |entry|
|
|
669
|
+
next if File.directory?(entry) || File.basename(entry).start_with?('.')
|
|
670
|
+
|
|
671
|
+
FileUtils.cp(entry, File.join(dest_dir, File.basename(entry)))
|
|
672
|
+
end
|
|
673
|
+
end
|
|
674
|
+
|
|
675
|
+
def dimension_attrs(source_path)
|
|
676
|
+
dims = read_dimensions(source_path) || (backend && backend.dimensions(source_path))
|
|
677
|
+
return '' unless dims && dims[0]&.positive? && dims[1]&.positive?
|
|
678
|
+
|
|
679
|
+
%( width="#{dims[0]}" height="#{dims[1]}")
|
|
680
|
+
end
|
|
681
|
+
|
|
682
|
+
def escape_attr(text)
|
|
683
|
+
text.to_s.gsub('&', '&').gsub('"', '"').gsub('<', '<')
|
|
684
|
+
end
|
|
685
|
+
end
|
|
686
|
+
|
|
687
|
+
# ------------------------------------------------------------------
|
|
688
|
+
# Backends — strategy pattern. Each implements `thumbnail` and
|
|
689
|
+
# `dimensions`; ImagePipeline picks one in `detect_backend`.
|
|
690
|
+
# ------------------------------------------------------------------
|
|
691
|
+
|
|
692
|
+
class ImagePipeline
|
|
693
|
+
# Native libvips via ruby-vips. Same operations as the shell
|
|
694
|
+
# backend but no per-image process spawn — ~5–10× faster for
|
|
695
|
+
# variant generation. Requires the `vips` gem AND the libvips C
|
|
696
|
+
# library; if either is missing the pipeline falls back to the
|
|
697
|
+
# shell backend automatically.
|
|
698
|
+
class VipsBackend
|
|
699
|
+
def initialize(strip:)
|
|
700
|
+
@strip = strip
|
|
701
|
+
end
|
|
702
|
+
|
|
703
|
+
def name
|
|
704
|
+
'vips-native'
|
|
705
|
+
end
|
|
706
|
+
|
|
707
|
+
def thumbnail(src, dest, width, format, quality)
|
|
708
|
+
# `thumbnail` is libvips' high-level "load + resize + sharpen"
|
|
709
|
+
# operation. It uses the demand-driven pipeline so memory is
|
|
710
|
+
# bounded regardless of source resolution. EXIF / metadata
|
|
711
|
+
# stripping is handled by the `strip:` flag on the writer —
|
|
712
|
+
# no separate copy-and-clear pass needed.
|
|
713
|
+
# `thumbnail` with width only preserves aspect ratio. No
|
|
714
|
+
# height constraint needed; libvips computes the matching
|
|
715
|
+
# height from the source.
|
|
716
|
+
image = ::Vips::Image.thumbnail(src, width)
|
|
717
|
+
|
|
718
|
+
case format
|
|
719
|
+
when 'avif', 'webp'
|
|
720
|
+
image.write_to_file(dest, Q: quality, strip: @strip)
|
|
721
|
+
when 'original'
|
|
722
|
+
ext = File.extname(dest).delete('.').downcase
|
|
723
|
+
if %w[png gif].include?(ext)
|
|
724
|
+
# PNG / GIF don't take a quality flag.
|
|
725
|
+
image.write_to_file(dest, strip: @strip)
|
|
726
|
+
else
|
|
727
|
+
image.write_to_file(dest, Q: quality, strip: @strip)
|
|
728
|
+
end
|
|
729
|
+
else
|
|
730
|
+
raise "Unknown format: #{format}"
|
|
731
|
+
end
|
|
732
|
+
end
|
|
733
|
+
|
|
734
|
+
def dimensions(src)
|
|
735
|
+
image = ::Vips::Image.new_from_file(src, access: :sequential)
|
|
736
|
+
[image.width, image.height]
|
|
737
|
+
rescue ::Vips::Error
|
|
738
|
+
nil
|
|
739
|
+
end
|
|
740
|
+
end
|
|
741
|
+
|
|
742
|
+
# Shell-out to `vips` or `magick` CLI. Used when ruby-vips isn't
|
|
743
|
+
# available — slower per invocation (~150–300 ms each) but works
|
|
744
|
+
# without the FFI dep.
|
|
745
|
+
class ShellBackend
|
|
746
|
+
def initialize(cli, strip:)
|
|
747
|
+
@cli = cli # 'vips' or 'magick'
|
|
748
|
+
@strip = strip
|
|
749
|
+
end
|
|
750
|
+
|
|
751
|
+
def name
|
|
752
|
+
"shell-#{@cli}"
|
|
753
|
+
end
|
|
754
|
+
|
|
755
|
+
def thumbnail(src, dest, width, format, quality)
|
|
756
|
+
cmd = case @cli
|
|
757
|
+
when 'vips' then vips_cmd(src, dest, width, format, quality)
|
|
758
|
+
when 'magick' then magick_cmd(src, dest, width, format, quality)
|
|
759
|
+
end
|
|
760
|
+
|
|
761
|
+
success = system(*cmd, out: File::NULL, err: File::NULL)
|
|
762
|
+
raise "Image processing failed: #{cmd.shelljoin}" unless success
|
|
763
|
+
end
|
|
764
|
+
|
|
765
|
+
def dimensions(src)
|
|
766
|
+
case @cli
|
|
767
|
+
when 'vips'
|
|
768
|
+
w = `vipsheader -f width #{src.shellescape} 2>/dev/null`.strip.to_i
|
|
769
|
+
h = `vipsheader -f height #{src.shellescape} 2>/dev/null`.strip.to_i
|
|
770
|
+
[w, h] if w.positive? && h.positive?
|
|
771
|
+
when 'magick'
|
|
772
|
+
out = `magick identify -format "%w %h" #{src.shellescape} 2>/dev/null`.strip
|
|
773
|
+
dims = out.split.map(&:to_i)
|
|
774
|
+
dims.size == 2 && dims.all?(&:positive?) ? dims : nil
|
|
775
|
+
end
|
|
776
|
+
end
|
|
777
|
+
|
|
778
|
+
private
|
|
779
|
+
|
|
780
|
+
def vips_cmd(src, dest, width, format, quality)
|
|
781
|
+
# vips' load-option syntax accepts `[Q=…]` on the destination.
|
|
782
|
+
# `-strip` would be `--strip-metadata` but that's been deprecated
|
|
783
|
+
# in favour of explicit no-keep on the output writer; the
|
|
784
|
+
# `[strip=true]` suffix is the supported form.
|
|
785
|
+
opts = []
|
|
786
|
+
opts << "Q=#{quality}" unless format == 'original' && %w[png gif].include?(File.extname(dest).delete('.'))
|
|
787
|
+
opts << 'strip=true' if @strip
|
|
788
|
+
output = opts.empty? ? dest : "#{dest}[#{opts.join(',')}]"
|
|
789
|
+
['vips', 'thumbnail', src, output, width.to_s]
|
|
790
|
+
end
|
|
791
|
+
|
|
792
|
+
def magick_cmd(src, dest, width, _format, quality)
|
|
793
|
+
cmd = ['magick', src, '-resize', "#{width}x", '-quality', quality.to_s]
|
|
794
|
+
cmd << '-strip' if @strip
|
|
795
|
+
cmd << dest
|
|
796
|
+
cmd
|
|
797
|
+
end
|
|
798
|
+
end
|
|
799
|
+
end
|
|
800
|
+
end
|