diff --git a/_includes/intro.md b/_includes/intro.md
deleted file mode 100644
index 49c6e2a..0000000
--- a/_includes/intro.md
+++ /dev/null
@@ -1,47 +0,0 @@
-# {{ include.language }}: HTML/CSS to Image
-{: .no_toc }
-{: .fs-9 }
-
-Generate Images and PDFs with {{ include.language }}. Renders exactly like Google Chrome.
-{: .code-title }
-Render PNG, JPG, or WebP images + PDFs with {{ include.language }} from HTML/CSS or reusable dynamic templates.
-{: .code-subtitle}
-
-
-[Live demo](https://htmlcsstoimage.com/#demo){: .btn .btn-primary .fs-5 .mb-4 .mb-md-0 .mr-2 target="_blank" }
-[Get an API Key](https://htmlcsstoimage.com){: .btn .fs-5 .mb-4 .mb-md-0 target="_blank" }
-
-
-## Generating images with {{include.language}}
-1. The API takes your HTML/CSS and runs it inside a real instance of Google Chrome to **convert your HTML into an image**.
-2. Use {{include.language}} to send the API your HTML/CSS.
-3. You'll get back JSON with the URL to your generated image.
-
-For more details on how this works, see [Creating an image](/getting-started/using-the-api#creating-an-image).
-
-**Example API response:**
-```json
-{
- "url": "https://hcti.io/v1/image/be4c5118-fe19-462b-a49e-48cf72697a9d",
- "id": "be4c5118-fe19-462b-a49e-48cf72697a9d"
-}
-```
-
-{% cloudinary /assets/images/dog-rates-example.png sizes="500px" alt="Image generated with {{ include.language }}. Convert HTML to an image using {{ include.language }}." %}
-
-## Authentication with {{ include.language }}
-The API uses [HTTP Basic authentication](https://en.wikipedia.org/wiki/Basic_access_authentication).
-
-Your username is your **User ID** and your password is your **API Key**. Both of these are available from the [dashboard](https://htmlcsstoimage.com/dashboard). The {{ include.language }} code sample demonstrates how to authenticate your request.
-
-You can sign up for a free API key to get started.
-
-Free API Key for {{ include.language }}{: .btn .btn-blue .fs-5 .mb-4 .mb-md-0 }
-
-
-
-## {{ include.language }} example code
-
-This {{ include.language }} code example sends an HTTP POST to the `https://hcti.io/v1/image` API to convert your HTML/CSS to an image.
-
-For API ID/key terminology, scoped credentials, and access errors, see [authentication and API keys](/getting-started/using-the-api/api-keys/). Image creation requires `images:create`.
diff --git a/_includes/mcp_tools.md b/_includes/mcp_tools.md
deleted file mode 100644
index 9005112..0000000
--- a/_includes/mcp_tools.md
+++ /dev/null
@@ -1,32 +0,0 @@
-## Available tools
-
-HTML/CSS to Image (HCTI) supports these core MCP tools across clients. Access depends on the [permissions approved for your connection](/integrations/mcp/permissions/). Ask your assistant to use HCTI by name when you want it to render HTML/CSS or capture a website.
-
-### Account usage
-
-| Tool | What it does |
-|:-----|:------------|
-| **check_usage** | Check images used, the account's image allowance, the next reset date, and whether overages are enabled. Does not render an image. |
-
-### Screenshots and rendering
-
-| Tool | What it does |
-|:-----|:------------|
-| **create_image** | Render HTML and CSS as PNG, JPG, WebP, or PDF. Accepts rendering options such as `device_scale`, `viewport_width`, `viewport_height`, and `ms_delay`. |
-| **create_url_image** | Capture a public webpage or a specific element using a CSS selector. Supports viewport settings and cookie banner blocking. |
-| **create_templated_image** | Render a saved template using its `template_id` and a `template_values` object. |
-| **create_batch_images** | Render multiple variations in one request, with shared `default_options` and per-image `variations`. |
-| **get_max_batch_size** | Check the authorized account's current batch limit before preparing a batch. |
-
-### Template management
-
-| Tool | What it does |
-|:-----|:------------|
-| **create_template** | Save HTML and CSS as a reusable template with variables. |
-| **update_template** | Update an existing template by ID. |
-| **list_templates** | Find templates in the connected HCTI account. |
-| **list_template_versions** | Inspect the version history of a template. |
-
-See the [rendering parameters](/integrations/mcp/#image-parameters) for available options and the [Template Editor guide](/template-editor/) for designing reusable templates.
-
-More management capabilities are available for proxies, storage destinations, and Open Graph configurations. See the [complete MCP tools reference](/integrations/mcp/tools/) for all tools and required permissions.
diff --git a/_includes/nav.html b/_includes/nav.html
deleted file mode 100644
index 307b647..0000000
--- a/_includes/nav.html
+++ /dev/null
@@ -1,84 +0,0 @@
-
diff --git a/_includes/og_config_parameter_support.md b/_includes/og_config_parameter_support.md
deleted file mode 100644
index 65942d3..0000000
--- a/_includes/og_config_parameter_support.md
+++ /dev/null
@@ -1,4 +0,0 @@
-{% capture og_parameter_support_text -%}
-The dashboard provides the most common screenshot controls when you create an OG Image Config. See the [full list of supported `hcti:` page parameters](/getting-started/og-images/supported-parameters/) for every available page-level override.
-{% endcapture -%}
-{% include hint.md title="Parameter support" heading=false text=og_parameter_support_text %}
diff --git a/_includes/og_image_platform_grid.html b/_includes/og_image_platform_grid.html
deleted file mode 100644
index af64657..0000000
--- a/_includes/og_image_platform_grid.html
+++ /dev/null
@@ -1,73 +0,0 @@
-
-
diff --git a/_includes/operation-cards.html b/_includes/operation-cards.html
deleted file mode 100644
index 32408fb..0000000
--- a/_includes/operation-cards.html
+++ /dev/null
@@ -1,9 +0,0 @@
-{% comment %}
- Usage: {% include operation-cards.html resource="storage-destinations" %}
- Operation definitions live in _data/operations.json, keyed by resource.
-{% endcomment %}
-
- {% for operation in site.data.operations[include.resource] %}
- {% include operation-card.html operation=operation %}
- {% endfor %}
-
diff --git a/_includes/template-editor/Property_Opacity.md b/_includes/template-editor/Property_Opacity.md
deleted file mode 100644
index 4ba67c9..0000000
--- a/_includes/template-editor/Property_Opacity.md
+++ /dev/null
@@ -1,8 +0,0 @@
-{% capture opacity_details %}
-Opacity controls transparency for the whole block, including its content and styling. Lower opacity makes the block more transparent.
-
-{% include template-editor/templatable-properties.md items="Opacity" %}
-{% endcapture %}
-
-{% assign opacity_details_summary = include.details | default: "Transparency for the block" %}
-{% include template-editor/property.md name="Opacity" details=opacity_details_summary more=opacity_details templated=true %}
diff --git a/_includes/template-editor/Property_Padding.md b/_includes/template-editor/Property_Padding.md
deleted file mode 100644
index d2589c0..0000000
--- a/_includes/template-editor/Property_Padding.md
+++ /dev/null
@@ -1,22 +0,0 @@
-{% capture padding_details %}
-Padding creates space inside the block's edge before content is laid out.
-
-
-
-
Simple
-
Use one value when every side should have the same inset.
-
-
-
-
Advanced
-
Open Advanced Padding to set top, right, bottom, and left independently.
-
-
-
-{% if include.panel %}
-{% include hint.md title="Panel spacing" text="Panel padding is separate from gap and child margin. Padding creates space inside the panel edge; gap and child margin control spacing between child blocks." heading=false %}
-{% endif %}
-{% endcapture %}
-
-{% assign padding_details_summary = include.details | default: "Space inside the block edge" %}
-{% include template-editor/property.md name="Padding" details=padding_details_summary more=padding_details %}
diff --git a/_includes/template-editor/block-specific-options-intro.md b/_includes/template-editor/block-specific-options-intro.md
deleted file mode 100644
index f013efe..0000000
--- a/_includes/template-editor/block-specific-options-intro.md
+++ /dev/null
@@ -1 +0,0 @@
-## Block Specific Properties
diff --git a/_includes/template-editor/child-properties-intro.md b/_includes/template-editor/child-properties-intro.md
deleted file mode 100644
index e0be4b6..0000000
--- a/_includes/template-editor/child-properties-intro.md
+++ /dev/null
@@ -1,2 +0,0 @@
-{% assign child_panel_type = include.type | default: "panel" %}
-When a block is inside a {{ child_panel_type }} Panel, extra properties become available for how that block sits inside the panel and relates to the other children. Select the child block to edit these properties.
diff --git a/_includes/template-editor/common-block-options.md b/_includes/template-editor/common-block-options.md
deleted file mode 100644
index 0576c04..0000000
--- a/_includes/template-editor/common-block-options.md
+++ /dev/null
@@ -1,131 +0,0 @@
-## Common Properties
-
-Most blocks share a core set of properties. The right sidebar shows the properties available for the current selection, including controls added by the parent panel.
-
-{% assign extra_size = include.extra_size | default: include.extra_extra_size %}
-{% assign extra_arrange = include.extra_arrange | default: include.extra_extra_arrange %}
-{% assign extra_background = include.extra_background | default: include.extra_extra_background %}
-{% assign extra_border = include.extra_border | default: include.extra_extra_border %}
-{% assign extra_shadow = include.extra_shadow | default: include.extra_extra_shadow %}
-
-{% capture size_details %}
-
-
-
Width
-
Controls the block's horizontal space. Use a fixed value for a predictable footprint, `%` when the width should respond to the canvas or parent panel, or auto sizing when content or layout should decide the width.
-
-
-
-
Height
-
Controls the block's vertical space. Height can be fixed, flexible, or constrained with minimum and maximum values when a block needs room to grow without getting too large or too small.
-
-
-
-
Aspect Ratio
-
Keeps width and height connected so a block can scale without stretching. This is useful for images, shapes, and blocks that need to preserve a specific proportion.
Set the block's position on the canvas or inside a Free Panel. X controls horizontal placement; Y controls vertical placement. Positions can use fixed units or `%` values with anchors.
-
-
-
-
Alignment
-
Move a block to common canvas positions, or use anchors to decide how the block lines up to a percent-based X or Y value.
-
-
-
-
Rotation and Flip
-
Rotate the block or flip it horizontally or vertically without changing the block's content.
-
-
-
-
Lock and Layer Order
-
Lock a block to avoid accidental edits, or move it forward and backward when blocks overlap.
Assign a single color as the background of the block.
- {% include template-editor/templatable-properties.md items="Color" %}
-
-
-
-
Gradient
-
Use a linear, radial, or conic gradient. Gradients are built from stops, and each stop has a color and a position. Stops are ordered by position, so moving a stop changes how the colors blend across the block.
- {% include template-editor/templatable-properties.md items="Stop colors|Stop positions" %}
-
Linear and conic gradients also include an angle control.
-
-
-
-
Pattern
-
Choose a pattern, then customize the colors it exposes. Some patterns also include a size control. Different patterns use different numbers of colors, so the available controls change after you choose a pattern.
- {% include template-editor/templatable-properties.md items="Pattern colors|Pattern size" %}
-
-
-{% include hint.md title="Canvas backgrounds" text="Canvas and block backgrounds are separate. A transparent block can show the canvas background or the parent panel behind it." heading=false %}
-
-{% if extra_background %}
-{{ extra_background }}
-{% endif %}
-{% endcapture %}
-
-{% capture border_details %}
-Border controls are split into simple and advanced modes.
-
-
-
-
Simple
-
Use simple border controls when the same color, thickness, line style, and corner radius should apply everywhere.
-
-
-
-
Advanced
-
Open Advanced Border to edit sides and corners separately. Sides can have their own color, thickness, and line style. Corners can have their own radius.
-
-
-
-{% if extra_border %}
-{{ extra_border }}
-{% endif %}
-{% endcapture %}
-
-{% capture shadow_details %}
-Shadows can be layered. Use a regular shadow for depth, an inner shadow for inset effects, or a glow when the shadow should spread evenly around the block.
-
-{% if extra_shadow %}
-{{ extra_shadow }}
-{% endif %}
-{% endcapture %}
-
-
-
PropertyDetails
- {% include template-editor/property.md name="Size" details="Control how much space the block takes up" more=size_details templated=true %}
- {% include template-editor/property.md name="Arrange" details="Place and manage the block in its layout" more=arrange_details %}
- {% include template-editor/property.md name="Background" details="Style the surface behind the block's content" more=background_details templated=true %}
- {% include template-editor/property.md name="Border" details="Define the block's edge and corners" more=border_details %}
- {% include template-editor/property.md name="Shadow" details="Layer shadows to add depth to your blocks" more=shadow_details %}
-
diff --git a/_includes/template-editor/grid-tracks.md b/_includes/template-editor/grid-tracks.md
deleted file mode 100644
index c058303..0000000
--- a/_includes/template-editor/grid-tracks.md
+++ /dev/null
@@ -1,28 +0,0 @@
-## Grid Tracks
-
-Grid and Table Panels are built from tracks: columns and rows. Tracks can have their own size and background.
-
-
-
-
Selecting Tracks
-
Hover a column or row on the canvas and click its track selector to edit that track. The right sidebar switches to the selected column or row and shows track-specific controls.
-
When the panel itself is selected, the right sidebar also includes a collapsible Rows/Columns item. Open it to select a row or column without using the canvas hover target.
-
-
-
-
Track Size
-
Use track size to control how much space a row or column receives inside the panel.
-
-
Defined uses a value with fr, %, or px units.
-
Auto lets the track size itself from its contents.
-
Min Content sizes the track to the smallest content-based size it can use.
-
Max Content sizes the track around its largest content-based size.
-
-
-
-
-
Track Background
-
Track backgrounds apply to the selected row or column. Use them for bands, separators, highlighted columns, or table-like layouts.
-
A child block's own background is drawn above the track background. Transparent blocks let the track background show through.
-{% endif %}
diff --git a/_includes/template-editor/templating-note.md b/_includes/template-editor/templating-note.md
deleted file mode 100644
index 74a5229..0000000
--- a/_includes/template-editor/templating-note.md
+++ /dev/null
@@ -1,3 +0,0 @@
-Many properties can use [Variables](/template-editor/variables/) to assign their value when each image is created.
-
-Variables can be required, ignored, or given a fallback value. When you render the template, pass values with `template_values`.
diff --git a/_includes/template_additional_parameters.md b/_includes/template_additional_parameters.md
deleted file mode 100644
index 582365b..0000000
--- a/_includes/template_additional_parameters.md
+++ /dev/null
@@ -1 +0,0 @@
-{% include additional_parameters.md context="templates" %}
diff --git a/_includes/title.html b/_includes/title.html
deleted file mode 100644
index 8bd3fa8..0000000
--- a/_includes/title.html
+++ /dev/null
@@ -1,5 +0,0 @@
-{% if site.logo %}
-
-{% else %}
- {{ site.title }}
-{% endif %}
diff --git a/_includes/toc.html b/_includes/toc.html
deleted file mode 100644
index 904077b..0000000
--- a/_includes/toc.html
+++ /dev/null
@@ -1,93 +0,0 @@
-{% comment %}
- Table of Contents generator
- Extracts h2 and h3 headings from the page content and generates a TOC
-{% endcomment %}
-
-{% assign content_string = include.content %}
-{% assign headings_found = false %}
-
-{% comment %} Check if there are any h2 headings with id attributes {% endcomment %}
-{% if content_string contains '
-
On this page
-
- {% comment %} Split content by to get each h2 section {% endcomment %}
- {% assign sections = content_string | split: '' %}
-
- {% for section in sections %}
- {% comment %} Check if this section contains an h2 opening tag {% endcomment %}
- {% if section contains '
- {% if template_editor_templated_content != "" %}
- {{ template_editor_templated_content | markdownify }}
- {% endif %}
- {% capture template_editor_templating_note %}
- {% include template-editor/templating-note.md %}
- {% endcapture %}
- {{ template_editor_templating_note | markdownify }}
-
diff --git a/_plugins/jekyll_asset_pipeline.rb b/_plugins/jekyll_asset_pipeline.rb
deleted file mode 100644
index 38920bd..0000000
--- a/_plugins/jekyll_asset_pipeline.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-require 'jekyll_asset_pipeline'
-
-module JekyllAssetPipeline
- class CssTagTemplate < JekyllAssetPipeline::Template
- def self.filetype
- '.css'
- end
-
- def html
- file_path = "#{output_path}/#{@filename}"
-
- # Load all CSS files directly as stylesheets
- "\n"
- end
- end
-end
-
-module JekyllAssetPipeline
- class JsTagTemplate < JekyllAssetPipeline::Template
- def self.filetype
- '.js'
- end
-
- def html
- file_path = "#{output_path}/#{@filename}"
-
- if @filename.match? "highlighting"
- return "\n" \
- end
-
- "\n" \
- end
- end
-end
-
-module JekyllAssetPipeline
- class CssCompressor < JekyllAssetPipeline::Compressor
- require 'yui/compressor'
-
- def self.filetype
- '.css'
- end
-
- def compress
- return @content
- # return YUI::CssCompressor.new.compress(@content)
- end
- end
-
- class JavaScriptCompressor < JekyllAssetPipeline::Compressor
- require 'uglifier'
-
- def self.filetype
- '.js'
- end
-
- def compress
- Uglifier.compile(@content, harmony: true)
- end
- end
-end
diff --git a/_plugins/jeykll_html_css_to_image.rb b/_plugins/jeykll_html_css_to_image.rb
deleted file mode 100644
index 0ae24ea..0000000
--- a/_plugins/jeykll_html_css_to_image.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-require "json"
-require "htmlcsstoimage"
-
-module Jekyll
- class HtmlCssToImageTag < Liquid::Tag
- attr_reader :input, :template_params, :template_id
-
- def initialize(tag_name, input, tokens)
- super
- @input = input
- end
-
- def render(context)
- key = hcti_key(context)
- # Return placeholder for local development without API key
- return "https://via.placeholder.com/600x400?text=HCTI+Image+Placeholder" unless key
-
- input = parse_input(@input, context)
- @template_id = input["template_id"]
- @template_params = input
- @template_params.delete("template_id")
-
- client = HTMLCSSToImage.new(api_key: key)
- image = client.create_image_from_template(template_id, @template_params)
-
- image.url
- end
-
- private
-
- def hcti_key(context)
- env_var = ENV["HCTI_API_KEY"]
- return env_var if env_var
-
- hcti_config = context.registers[:site].config["hcti"]
- if hcti_config
- key = hcti_config["api_key"]
- end
-
- return key if key
- # Return nil for local development - the tag will return a placeholder
- nil
- end
-
- def parse_input(input, context)
- return nil if input.nil? || input.empty?
- # Allows the use of variables
- input = Liquid::Template.parse(input).render(context)
- JSON.parse(input)
- end
- end
-end
-
-Liquid::Template.register_tag('hcti_image_url', Jekyll::HtmlCssToImageTag)
diff --git a/_sass/color_schemes/hcti.scss b/_sass/color_schemes/hcti.scss
deleted file mode 100644
index ca79852..0000000
--- a/_sass/color_schemes/hcti.scss
+++ /dev/null
@@ -1,137 +0,0 @@
-//
-// Typography
-//
-
-$body-font-family: -apple-system, BlinkMacSystemFont, "helvetica neue",
- helvetica, roboto, noto, "segoe ui", arial, sans-serif !default;
-$mono-font-family: "SFMono-Regular", Menlo, Consolas, Monospace !default;
-$root-font-size: 16px !default; // Base font-size for rems
-$body-line-height: 1.4 !default;
-$content-line-height: 1.6 !default;
-$body-heading-line-height: 1.25 !default;
-
-//
-// Colors
-//
-
-$white: #fff !default;
-
-$grey-dk-000: #959396 !default;
-$grey-dk-100: #5c5962 !default;
-$grey-dk-200: #44434d !default;
-$grey-dk-250: #302d36 !default;
-$grey-dk-300: #27262b !default;
-
-$grey-lt-000: #f5f6fa !default;
-$grey-lt-100: #eeebee !default;
-$grey-lt-200: #ecebed !default;
-$grey-lt-300: #e6e1e8 !default;
-
-$purple-000: #7253ed !default;
-$purple-100: #5e41d0 !default;
-$purple-200: #4e26af !default;
-$purple-300: #381885 !default;
-
-$blue-000: #2c84fa !default;
-$blue-100: #2869e6 !default;
-$blue-200: #264caf !default;
-$blue-300: #183385 !default;
-
-$hcti-green: #018a59 !default; // Darker green for WCAG AA contrast (5.3:1 on white)
-$hcti-green-light: #02b875 !default; // Original lighter green for decorative use
-$green-000: #41d693 !default;
-$green-100: #11b584 !default;
-$green-200: #009c7b !default;
-$green-300: #026e57 !default;
-
-$yellow-000: #ffeb82 !default;
-$yellow-100: #fadf50 !default;
-$yellow-200: #f7d12e !default;
-$yellow-300: #e7af06 !default;
-
-$red-000: #f77e7e !default;
-$red-100: #f96e65 !default;
-$red-200: #e94c4c !default;
-$red-300: #dd2e2e !default;
-
-$body-background-color: $white !default;
-$sidebar-color: #343a40 !default;
-$search-background-color: $white !default;
-$table-background-color: $white !default;
-$code-background-color: $grey-lt-000 !default;
-$feedback-color: darken($sidebar-color, 3%) !default;
-
-$body-text-color: $grey-dk-100 !default;
-$body-heading-color: $grey-dk-300 !default;
-$search-result-preview-color: $grey-dk-000 !default;
-$nav-child-link-color: $grey-dk-100 !default;
-$link-color: $hcti-green;
-$btn-primary-color: $hcti-green;
-$base-button-color: #f7f7f7 !default;
-
-//
-// Spacing
-//
-
-$spacing-unit: 1rem; // 1rem == 16px
-
-$spacers: (
- sp-0: 0,
- sp-1: $spacing-unit * 0.25,
- sp-2: $spacing-unit * 0.5,
- sp-3: $spacing-unit * 0.75,
- sp-4: $spacing-unit,
- sp-5: $spacing-unit * 1.5,
- sp-6: $spacing-unit * 2,
- sp-7: $spacing-unit * 2.5,
- sp-8: $spacing-unit * 3,
- sp-9: $spacing-unit * 3.5,
- sp-10: $spacing-unit * 4,
-) !default;
-
-$sp-1: map-get($spacers, sp-1) !default; // 0.25 rem == 4px
-$sp-2: map-get($spacers, sp-2) !default; // 0.5 rem == 8px
-$sp-3: map-get($spacers, sp-3) !default; // 0.75 rem == 12px
-$sp-4: map-get($spacers, sp-4) !default; // 1 rem == 16px
-$sp-5: map-get($spacers, sp-5) !default; // 1.5 rem == 24px
-$sp-6: map-get($spacers, sp-6) !default; // 2 rem == 32px
-$sp-7: map-get($spacers, sp-7) !default; // 2.5 rem == 40px
-$sp-8: map-get($spacers, sp-8) !default; // 3 rem == 48px
-$sp-9: map-get($spacers, sp-9) !default; // 3.5 rem == 56px
-$sp-10: map-get($spacers, sp-10) !default; // 4 rem == 64px
-
-//
-// Borders
-//
-
-$border: 1px solid !default;
-$border-radius: 4px !default;
-$border-color: $grey-lt-100 !default;
-
-//
-// Grid system
-//
-
-$gutter-spacing: $sp-6 !default;
-$gutter-spacing-sm: $sp-4 !default;
-$nav-width: 264px !default;
-$nav-width-md: 248px !default;
-$nav-list-item-height: $sp-6 !default;
-$nav-list-item-height-sm: $sp-8 !default;
-$nav-list-expander-right: true;
-$content-width: 1600px !default;
-$header-height: 60px !default;
-$search-results-width: $content-width - $nav-width !default;
-$transition-duration: 400ms;
-
-//
-// Media queries in pixels
-//
-
-$media-queries: (
- xs: 320px,
- sm: 500px,
- md: $content-width,
- lg: $content-width + $nav-width,
- xl: 1400px,
-) !default;
diff --git a/_sass/custom/custom.scss b/_sass/custom/custom.scss
deleted file mode 100644
index aa934e1..0000000
--- a/_sass/custom/custom.scss
+++ /dev/null
@@ -1,1410 +0,0 @@
-body {
- background-color: $sidebar-color;
-}
-
-.main-content .operation-cards {
- display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
- gap: 0.75rem;
- margin: 1.25rem 0 2rem;
-}
-
-.main-content .operation-card {
- min-width: 0;
- padding: 1rem 1.25rem;
- border: 1px solid #e3e6eb;
- border-radius: 6px;
- background: #fff;
-
- h3 {
- margin: 0 0 0.5rem;
- font-size: 1rem;
- font-weight: 600;
- line-height: 1.4;
- }
-
- code {
- padding: 0;
- border: 0;
- background: transparent;
- font-size: 0.8125rem;
- color: #d63384;
- overflow-wrap: anywhere;
- }
-
- .operation-card__endpoint {
- display: flex;
- align-items: baseline;
- gap: 0.625rem;
-
- code {
- color: #292d35;
- }
- }
-
- .operation-card__method {
- flex-shrink: 0;
- padding: 0.125rem 0.375rem;
- border-radius: 3px;
- background: #eef1f5;
- color: #404854;
- font-size: 0.6875rem;
- font-weight: 700;
- line-height: 1.5;
- }
-
- .operation-card__method--get {
- background: #edf4ff;
- color: #245ca0;
- }
-
- .operation-card__method--post {
- background: #eaf7f0;
- color: #236344;
- }
-
- .operation-card__method--put {
- background: #fff5e6;
- color: #855510;
- }
-
- .operation-card__method--delete {
- background: #fceeee;
- color: #a03636;
- }
-
- .operation-card__details {
- display: grid;
- grid-template-columns: 1fr;
- gap: 0.375rem 1rem;
- margin: 0.75rem 0 0;
-
- div {
- min-width: 0;
- }
-
- dt {
- float: none;
- width: auto;
- text-align: left;
- margin: 0 0 0.125rem;
- color: #656b75;
- font-size: 0.75rem;
- font-weight: 400;
-
- &::after {
- content: none;
- }
- }
-
- dd {
- margin: 0;
- line-height: 1.4;
- }
- }
-}
-
-@media (max-width: 1000px) {
- .main-content .operation-cards {
- grid-template-columns: 1fr;
- }
-}
-
-@media (max-width: 480px) {
- .main-content .operation-card {
- padding: 0.875rem 1rem;
-
- .operation-card__details {
- grid-template-columns: 1fr;
- gap: 0.625rem;
- }
- }
-}
-
-.main-content .image-rate-limit-callout {
- margin: 1.5rem 0 2rem;
- padding: 1.25rem 1.5rem;
- background: #effaf5;
- border: 1px solid #d3eadd;
- border-left: 4px solid #02b875;
- border-radius: 6px;
-
- p {
- margin: 0;
- color: #23543f;
- font-size: clamp(1.125rem, 2vw, 1.375rem);
- font-weight: 600;
- line-height: 1.45;
- }
-
- strong {
- font-weight: inherit;
- }
-}
-
-// =============================================================================
-// Accessibility: Improved Contrast Fixes
-// =============================================================================
-
-// Override grey text utilities for better contrast (WCAG AA requires 4.5:1)
-// $grey-dk-000 (#959396) only has ~3.5:1 contrast on white, so we use darker colors
-.text-grey-dk-000 {
- color: $grey-dk-100 !important; // #5c5962 has ~6:1 contrast ratio
-}
-
-// DocSearch placeholder - improve contrast for search button
-.DocSearch-Button-Placeholder {
- color: #5c5962 !important; // Better contrast than default light grey
-}
-
-.DocSearch-Button {
- .DocSearch-Search-Icon {
- color: #5c5962 !important;
- }
-}
-
-// Site footer text contrast fix
-.site-footer {
- color: $grey-lt-100 !important; // Light color on dark background for sidebar
-}
-
-// Aux navigation buttons (Get an API Key, Demo) - improve link contrast
-// $hcti-green (#02b875) has ~3.6:1 contrast on white, need 4.5:1 for WCAG AA
-.aux-nav {
- .site-button {
- color: #018a59 !important; // Darker green with ~5.3:1 contrast ratio on white
-
- &:hover {
- color: #016847 !important; // Even darker on hover for clear feedback
- }
- }
-}
-
-.cloudinary-image {
- width: auto;
- height: auto;
-}
-
-.main {
- background-color: white;
- border-right: $border $border-color;
- height: 100%;
- margin-right: auto;
-}
-
-.site-button {
- &:hover {
- background-color: unset;
- text-decoration: underline;
- background-image: unset !important;
- }
-}
-
-img {
- vertical-align: bottom;
- margin: 0;
- padding: 0;
- border: 0;
-}
-
-.te-doc-figure {
- margin: 1.5rem 0 2rem;
-
- img {
- display: block;
- width: 100%;
- max-width: 100%;
- height: auto;
- background: white;
- border: $border $border-color;
- border-radius: 6px;
- box-shadow: 0 8px 24px rgba(31, 35, 40, 0.08);
- }
-}
-.te-doc-figure.centered img{
- margin-left: auto;
- margin-right: auto;
-}
-
-.te-doc-figure--sidebar {
- img {
- max-width: 430px;
- }
-}
-
-.te-doc-figure--natural {
- img {
- width: auto;
- max-width: 100%;
- }
-}
-
-.te-doc-section-with-aside {
- display: grid;
- grid-template-columns: minmax(0, 1fr) clamp(12rem, 26%, 18rem);
- gap: 1.25rem;
- align-items: start;
- margin-bottom: 1.5rem;
-}
-
-.te-doc-figure--inline {
- margin: 0.25rem 0 0;
-}
-
-.te-doc-figure--wide {
- margin-left: -0.5rem;
- margin-right: -0.5rem;
-}
-
-// =============================================================================
-// OG Image Platform Guide Cards
-// =============================================================================
-
-.cms-guide-grid {
- display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
- gap: 1rem;
- margin: 1.5rem 0 2rem;
-}
-
-.cms-guide-card {
- position: relative;
- display: flex;
- min-width: 0;
- min-height: 12rem;
- flex-direction: column;
- overflow: hidden;
- padding: 1.25rem 3.5rem 1.25rem 1.25rem;
- color: $grey-dk-300 !important;
- background: linear-gradient(145deg, white 65%, mix(white, $grey-lt-000, 35%));
- background-image: linear-gradient(145deg, white 65%, mix(white, $grey-lt-000, 35%)) !important;
- border: 1px solid $border-color;
- border-radius: 10px;
- box-shadow: 0 4px 14px rgba(31, 35, 40, 0.06);
- text-decoration: none !important;
- transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
-
- &:hover {
- color: $grey-dk-300 !important;
- background-image: linear-gradient(145deg, white 65%, mix(white, $grey-lt-000, 20%)) !important;
- border-color: $link-color;
- box-shadow: 0 10px 24px rgba(31, 35, 40, 0.1);
- text-decoration: none !important;
- transform: translateY(-2px);
-
- .cms-guide-card__arrow {
- color: white;
- background: $link-color;
- transform: translateX(2px);
- }
- }
-
- &:focus-visible {
- outline: 3px solid rgba($link-color, 0.35);
- outline-offset: 3px;
- }
-}
-
-.cms-guide-card__logo {
- display: flex;
- height: 3.5rem;
- align-items: center;
- margin-bottom: 1.1rem;
-
- img {
- display: block;
- width: auto;
- max-width: 3.5rem;
- height: auto;
- max-height: 3.5rem;
- border: 0 !important;
- object-fit: contain;
- }
-}
-
-.cms-guide-card__logo--wordmark img {
- max-width: 10.5rem;
- max-height: 3.25rem;
-}
-
-.cms-guide-card__logo--sitepress img {
- max-width: 3.75rem;
- max-height: 3.75rem;
-}
-
-.cms-guide-card__title {
- display: block;
- margin-bottom: 0.35rem;
- color: $grey-dk-300;
- font-size: 1.05rem;
- line-height: 1.3;
- white-space: normal;
-}
-
-.cms-guide-card__description {
- display: block;
- color: $grey-dk-100;
- font-size: 0.88rem;
- line-height: 1.45;
- white-space: normal;
-}
-
-.cms-guide-card__arrow {
- position: absolute;
- right: 1rem;
- bottom: 1rem;
- display: inline-flex;
- width: 2rem;
- height: 2rem;
- align-items: center;
- justify-content: center;
- color: $link-color;
- background: rgba($link-color, 0.1);
- border-radius: 50%;
- font-size: 1.15rem;
- line-height: 1;
- transition: color 0.15s ease, background-color 0.15s ease, transform 0.15s ease;
-}
-
-@media (max-width: 42rem) {
- .cms-guide-grid {
- grid-template-columns: minmax(0, 1fr);
- }
-
- .cms-guide-card {
- min-height: 11rem;
- }
-}
-
-@media (max-width: 50rem) {
- .te-doc-section-with-aside {
- display: block;
- }
-
- .te-doc-figure--inline {
- width: 100%;
- margin: 1rem 0 1.5rem;
- }
-}
-
-// Inline code styling (single backticks and raw inline ) - light pink text, no background
-code.language-plaintext.highlighter-rouge,
-p > code,
-li > code,
-td > code,
-dd > code {
- background-color: transparent !important;
- color: #d63384 !important; // Light pinkish color
- padding: 0.1em 0.2em !important;
- font-size: 0.9em;
- border: none !important;
- border-radius: 0 !important;
-}
-
-::selection {
- background: $blue-100 !important;
-}
-
-.code-toolbar {
- position: relative;
- pre {
- background-color: #1e1e2e !important;
- }
- code {
- border: unset;
- color: #cdd6f4 !important;
- }
-
- .toolbar-item {
- position: absolute;
- padding: 8px;
- top: 0px;
- right: 0px;
- color: $grey-lt-100;
-
- .success {
- svg {
- fill: $link-color;
- }
- }
- }
-}
-
-// Improved code block styling for better contrast
-pre[class*="language-"],
-code[class*="language-"] {
- background-color: #1e1e2e !important;
- color: #cdd6f4 !important;
- border: none !important;
-}
-
-pre[class*="language-"] {
- border-radius: 6px;
- padding: 1rem !important;
-}
-
-// Syntax highlighting colors (dark theme)
-.token.comment,
-.token.prolog,
-.token.doctype,
-.token.cdata {
- color: #6c7086 !important;
-}
-
-.token.string,
-.token.attr-value {
- color: #a6e3a1 !important;
-}
-
-.token.punctuation,
-.token.operator {
- color: #89dceb !important;
-}
-
-.token.property,
-.token.tag,
-.token.boolean,
-.token.number,
-.token.constant,
-.token.symbol {
- color: #fab387 !important;
-}
-
-.token.selector,
-.token.attr-name,
-.token.char,
-.token.builtin {
- color: #f9e2af !important;
-}
-
-.token.keyword,
-.token.atrule {
- color: #cba6f7 !important;
-}
-
-.token.function {
- color: #89b4fa !important;
-}
-
-.token.url,
-.token.variable {
- color: #f38ba8 !important;
-}
-
-// Bash/Shell specific syntax highlighting
-.language-bash,
-.language-shell,
-.language-sh {
- // Command names (curl, echo, etc.)
- .token.function {
- color: #89b4fa !important;
- font-weight: 600;
- }
-
- // Flags and parameters (-X, -d, -u, --data-urlencode)
- .token.parameter,
- .token.variable {
- color: #f9e2af !important;
- }
-
- // Strings (single and double quoted)
- .token.string {
- color: #a6e3a1 !important;
- }
-
- // URLs
- .token.url {
- color: #89dceb !important;
- }
-
- // Operators and punctuation
- .token.operator {
- color: #f38ba8 !important;
- }
-}
-
-// Additional bash-specific token styling for better visibility
-pre[class*="language-bash"],
-pre[class*="language-shell"],
-pre[class*="language-sh"] {
- .token.assign-left {
- color: #cba6f7 !important;
- }
-}
-
-.code-example {
- align-text: center;
- .hcti-container {
- margin: auto;
- display: inline-block;
- background-color: transparentize($blue-100, 0.75);
- border: 1px dashed $blue-100;
- }
- background-image: linear-gradient(0deg,rgba(225,228,232,0),rgba(225,228,232,0) 7px,#e1e4e8 ,rgba(225,228,232,0) 8px),linear-gradient(90deg,rgba(225,228,232,0),rgba(225,228,232,0) 7px,#e1e4e8 ,rgba(225,228,232,0) 8px);
- background-position: center center;
- background-size: 15px 15px;
-}
-
-.hint {
- svg {
- vertical-align: middle;
- margin-top: -2px;
- margin-right: 4px;
- fill: $blue-000;
- }
- .hint-title {
- color: $body-heading-color;
- font-weight: 700;
- line-height: 1.25;
- margin-bottom: 0.5rem;
- }
- p {
- margin-left: 28px;
- color: $grey-dk-200;
- }
- background: linear-gradient(140deg, rgba(245,246,250,0.30) 0%, rgba(245,246,250,1) 61%);
- border: 1px solid $grey-lt-100;
- border-radius: 4px;
- box-sizing: border-box;
- margin-bottom: .75rem;
- margin-top: .75rem;
- overflow: auto;
- padding: .75rem;
-}
-
-.http-method {
- span {
- background-color: $grey-lt-100;
- border-radius: 3px;
- box-sizing: border-box;
- color: black;
- padding: 4px 8px;
- text-transform: uppercase;
- }
- color: $grey-dk-200;
- border-color: none;
- border-left: solid $blue-100 4px;
- background-color: $code-background-color;
- background: linear-gradient(140deg, rgba(245,246,250,0.50) 0%, rgba(245,246,250,1) 61%);
- border-radius: 3px;
- box-sizing: border-box;
- line-height: 1.45;
- margin-bottom: 16px;
- margin-top: 10px;
- padding: 8px;
- padding-top: 16px;
- padding-bottom: 16px;
-}
-
-img {
- border: $border;
- border-color: $border-color;
-}
-
-.nav-list-link {
- font-weight: 500;
- color: #3b454e;
-
- &.child {
- font-weight: 400 !important;
- }
-}
-
-.nav-list-link.active {
- color: $link-color !important;
-}
-
-// =============================================================================
-// Right Sidebar Table of Contents
-// =============================================================================
-
-.site-container {
- display: flex;
-
- @media (max-width: 800px) {
- max-width: 100%;
- flex-direction: column;
- }
-
- @media (min-width: 1600px) {
- max-width: 1700px;
- margin-left: auto;
- margin-right: auto;
- }
-
- @media (min-width: 1900px) {
- max-width: 1900px;
- }
-
- @media (min-width: 2200px) {
- max-width: 2100px;
- }
-}
-
-@media (min-width: 1200px) {
- .main {
- max-width: 1200px !important;
- }
-
- .main-content-wrap {
- max-width: none !important;
- }
-}
-
-.content-with-toc {
- display: flex;
- gap: 2rem;
-
- @media (min-width: 1600px) {
- gap: 2.5rem;
- }
-}
-
-// Main content in flex container
-.content-with-toc > .main-content {
- flex: 1;
- min-width: 0;
-
-
- @media (max-width: 800px) {
- max-width: 100%;
- }
-}
-
-.toc-sidebar {
- flex: 0 0 200px;
- width: 200px;
-
- // Hide on medium screens
- @media (max-width: 1200px) {
- display: none;
- }
-
- // Wider on larger screens
- @media (min-width: 1600px) {
- flex: 0 0 240px;
- width: 240px;
- }
-}
-
-.toc-nav {
- position: sticky;
- top: 2rem;
- max-height: calc(100vh - 4rem);
- overflow-y: auto;
- padding-bottom: 2rem;
-}
-
-.toc-title {
- font-size: 0.75rem;
- font-weight: 600;
- text-transform: uppercase;
- letter-spacing: 0.05em;
- color: $grey-dk-000;
- margin-bottom: 0.75rem;
- padding-left: 0.75rem;
-}
-
-.toc-list {
- list-style: none;
- padding: 0;
- margin: 0;
- border-left: 1px solid $grey-lt-200;
-}
-
-.toc-item {
- margin: 0;
-
- &.toc-h2 {
- margin-top: 0.25rem;
- }
-
- &.toc-h3 {
- margin-top: 0.125rem;
- }
-}
-
-.toc-link {
- display: block;
- padding: 0.25rem 0.75rem;
- font-size: 0.8125rem;
- line-height: 1.4;
- color: $grey-dk-100;
- text-decoration: none;
- border-left: 2px solid transparent;
- margin-left: -1px;
- transition: color 0.15s ease, border-color 0.15s ease, font-weight 0.15s ease;
-
- &:hover {
- color: $link-color;
- border-left-color: $grey-lt-100;
- }
-
- // Active state for scroll-spy
- &.toc-active {
- color: $link-color !important;
- border-left-color: $link-color !important;
- font-weight: 600;
- background-color: rgba($link-color, 0.08);
- }
-}
-
-.toc-h3 .toc-link {
- padding-left: 1.25rem;
- font-size: 0.75rem;
- color: $grey-dk-000;
-
- &.toc-active {
- color: $link-color;
- font-weight: 600;
- }
-}
-
-.toc-sublist {
- list-style: none;
- padding: 0;
- margin: 0;
-}
-
-// Hide the inline markdown-toc when we have the sidebar
-// Only show inline TOC on smaller screens where sidebar is hidden
-#markdown-toc {
- @media (min-width: 1201px) {
- display: none;
- }
-}
-
-@media (min-width: 1201px) {
- // Hide inline TOC elements when the sidebar TOC is visible.
- .main-content > .text-delta:has(+ #markdown-toc),
- .main-content > hr:has(+ #markdown-toc),
- .main-content > hr:has(+ .text-delta + #markdown-toc) {
- display: none;
- }
-}
-
-// =============================================================================
-// Copy Page Dropdown
-// =============================================================================
-
-.breadcrumb-with-actions {
- display: flex;
- justify-content: space-between;
- align-items: center;
- gap: 1rem;
- margin-bottom: 0.5rem;
-
- .breadcrumb-nav, .breadcrumb-nav-list {
- flex: 1;
- margin-bottom: 0 !important;
- }
-}
-
-.page-header-actions {
- display: flex;
- justify-content: flex-end;
- margin-bottom: 1rem;
- margin-top: -0.5rem;
-}
-
-.copy-page-dropdown {
- position: relative;
- display: inline-block;
-
- @media (max-width: 800px) {
- display: none; // Hide on mobile to save space
- }
-}
-
-.copy-page-btn {
- display: flex;
- align-items: center;
- gap: 0.375rem;
- padding: 0.375rem 0.75rem;
- font-size: 0.875rem;
- font-weight: 500;
- color: $grey-dk-200;
- background-color: white;
- border: 1px solid $grey-lt-200;
- border-radius: 6px;
- cursor: pointer;
- transition: all 0.15s ease;
- white-space: nowrap;
-
- &:hover {
- background-color: $grey-lt-000;
- border-color: $grey-lt-100;
- color: $grey-dk-300;
- }
-
- &:focus {
- outline: 2px solid $link-color;
- outline-offset: 2px;
- }
-
- &[aria-expanded="true"] {
- background-color: $grey-lt-000;
- border-color: $grey-lt-100;
-
- .dropdown-arrow {
- transform: rotate(180deg);
- }
- }
-
- svg {
- flex-shrink: 0;
- }
-
- .dropdown-arrow {
- transition: transform 0.2s ease;
- }
-}
-
-.copy-page-menu {
- position: absolute;
- top: calc(100% + 0.5rem);
- right: 0;
- min-width: 280px;
- background-color: white;
- border: 1px solid $grey-lt-200;
- border-radius: 8px;
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1), 0 0 1px rgba(0, 0, 0, 0.1);
- z-index: 1000;
- opacity: 0;
- visibility: hidden;
- transform: translateY(-8px);
- transition: opacity 0.2s ease, transform 0.2s ease, visibility 0.2s ease;
- overflow: hidden;
-
- &.show {
- opacity: 1;
- visibility: visible;
- transform: translateY(0);
- }
-}
-
-.copy-page-menu-item {
- display: flex;
- align-items: flex-start;
- gap: 0.875rem;
- padding: 0.875rem 1rem;
- width: 100%;
- background: none;
- border: none;
- text-align: left;
- cursor: pointer;
- transition: background-color 0.15s ease;
- text-decoration: none;
- color: inherit;
-
- &:hover {
- background-color: $grey-lt-000;
- }
-
- &:active {
- background-color: $grey-lt-100;
- }
-
- &:not(:last-child) {
- border-bottom: 1px solid $grey-lt-100;
- }
-
- svg {
- flex-shrink: 0;
- margin-top: 0.1875rem;
- color: $grey-dk-100;
- }
-
- .ai-icon {
- width: 16px;
- height: 16px;
- }
-}
-
-.menu-item-text {
- flex: 1;
- min-width: 0;
-}
-
-.menu-item-title {
- font-size: 0.875rem;
- font-weight: 500;
- color: $grey-dk-300;
- margin-bottom: 0.25rem;
-}
-
-.menu-item-subtitle {
- font-size: 0.8125rem;
- color: $grey-dk-100;
- line-height: 1.4;
-}
-
-// =============================================================================
-// Template Editor Property Lists
-// =============================================================================
-
-.te-property-list {
- margin: 1rem 0 1.5rem;
- border: 1px solid $border-color;
- border-radius: 8px;
- overflow: hidden;
-}
-
-.te-property-list__header {
- display: grid;
- grid-template-columns: 1rem minmax(8rem, 0.36fr) 1fr;
- gap: 1rem;
- padding: 0.65rem 0.9rem;
- background: $grey-lt-000;
- border-bottom: 1px solid $border-color;
- color: $grey-dk-200;
- font-size: 0.72rem;
- font-weight: 700;
- letter-spacing: 0.08em;
- text-transform: uppercase;
-
- &::before {
- content: "";
- }
-}
-
-.te-property {
- margin: 0;
- border-bottom: 1px solid $border-color;
- background: white;
-
- &:last-child {
- border-bottom: 0;
- }
-}
-
-.te-property__summary {
- display: grid;
- grid-template-columns: 1rem minmax(8rem, 0.36fr) 1fr;
- gap: 1rem;
- align-items: center;
- padding: 0.85rem 0.9rem;
-}
-
-details.te-property > .te-property__summary {
- cursor: pointer;
- list-style: none;
- position: relative;
- padding-right: 2.25rem;
- -webkit-user-select: none;
- user-select: none;
-
- &::-webkit-details-marker {
- display: none;
- }
-
- &::after {
- position: absolute;
- top: 0.95rem;
- right: 1rem;
- width: 0.45rem;
- height: 0.45rem;
- border-right: 2px solid $grey-dk-100;
- border-bottom: 2px solid $grey-dk-100;
- content: "";
- transform: rotate(45deg);
- transform-origin: center;
- }
-
- &:hover {
- background: $grey-lt-000;
- }
-}
-
-details.te-property[open] > .te-property__summary {
- border-bottom: 1px solid $border-color;
-
- &::after {
- top: 1.1rem;
- transform: rotate(225deg);
- }
-}
-
-.te-property__variable {
- display: flex;
- align-items: flex-start;
- justify-content: center;
- min-height: 1rem;
-}
-
-.te-property__name {
- color: $grey-dk-300;
- font-weight: 700;
-}
-
-.te-property__name-text {
- min-width: 0;
-}
-
-.te-property__templated-icon {
- width: 0.9rem;
- height: 0.9rem;
- flex: 0 0 auto;
- opacity: 0.72;
- margin-top: 0.1rem;
- border: none;
-}
-
-.te-property__details {
- color: $grey-dk-100;
- line-height: 1.45;
-
- p {
- margin: 0;
- }
-}
-
-.te-property__more {
- display: grid;
- grid-template-columns: 1rem minmax(8rem, 0.36fr) 1fr;
- gap: 1rem;
- padding: 0 0.9rem;
- color: $grey-dk-200;
- background: mix(white, $grey-lt-000, 45%);
-
- .anchor-heading {
- display: none !important;
- }
-
- .hint {
- margin: 0.85rem 0 0;
- padding: 0.6rem 0.7rem;
- overflow: visible;
- background: rgba($grey-lt-000, 0.55);
- border-color: mix($border-color, white, 72%);
- border-left: 1px solid mix($border-color, white, 72%);
- border-radius: 4px;
-
- h3,
- .hint-title {
- margin: 0 0 0.2rem;
- color: $grey-dk-300;
- font-size: 0.84rem;
- font-weight: 700;
- letter-spacing: 0;
- line-height: 1.3;
- text-transform: none;
- }
-
- p {
- margin: 0;
- color: $grey-dk-100;
- font-size: 0.86rem;
- line-height: 1.5;
- }
-
- svg {
- width: 0.78rem;
- height: 0.78rem;
- margin-top: -0.12rem;
- fill: $grey-dk-100;
- }
- }
-
- p {
- margin: 0 0 0.75rem;
- line-height: 1.55;
- }
-
- h3 {
- margin: 0.95rem 0 0.25rem;
- font-size: 0.92rem;
- }
-
- > :first-child {
- margin-top: 0;
- }
-
- > :last-child {
- margin-bottom: 0;
- }
-}
-
-.te-property__more-inner {
- grid-column: 2 / 4;
- max-width: 44rem;
- padding: 1rem 0 1.05rem;
-}
-
-.te-property__sections {
- margin: 0.85rem 0;
-}
-
-.te-property__section {
- padding: 0.8rem 0;
- margin: 0;
- border-top: 1px solid $border-color;
-
- &:first-child {
- padding-top: 0;
- border-top: 0;
- }
-
- &:last-child {
- padding-bottom: 0;
- }
-}
-
-.te-property__subhead {
- margin-bottom: 0.25rem;
- color: $grey-dk-300;
- font-size: 0.77rem;
- font-weight: 700;
- letter-spacing: 0.06em;
- line-height: 1.3;
- text-transform: uppercase;
-}
-
-.te-property__meta {
- color: $grey-dk-100;
- font-size: 0.88rem;
-}
-
-.te-property-templatable {
- margin: 0.55rem 0 0.75rem;
-}
-
-.te-property-templatable__title {
- color: $grey-dk-100;
- font-size: 0.76rem;
- font-weight: 700;
- letter-spacing: 0.06em;
- line-height: 1.3;
- text-transform: uppercase;
-}
-
-.te-property-templatable__list {
- margin: 0.3rem 0 0;
- padding: 0;
- color: $grey-dk-200;
- font-size: 0.9rem;
- list-style: none;
- line-height: 1.45;
-
- li {
- display: flex;
- align-items: baseline;
- gap: 0.5rem;
- margin: 0.12rem 0;
- padding-left: 0;
- list-style: none;
-
- &::before {
- display: none !important;
- content: none !important;
- }
-
- &::marker {
- content: "";
- }
- }
-}
-
-.te-property-templatable__marker {
- width: 0.22rem;
- height: 0.22rem;
- flex: 0 0 0.22rem;
- background: $grey-dk-000;
- border-radius: 50%;
- transform: translateY(-0.16rem);
-}
-
-.te-compact-list {
- margin: 0.4rem 0 0;
- padding-left: 1.05rem;
-
- li {
- margin: 0.15rem 0;
- }
-}
-
-.te-variable-modes-grid {
- display: grid;
- grid-template-columns: 2.25rem max-content minmax(0, 1fr);
- margin: 0.85rem 0 1rem;
- overflow: hidden;
- color: $grey-dk-100;
- border: 1px solid $border-color;
- border-radius: 4px;
-
- > * {
- min-height: 2.45rem;
- padding: 0.65rem 0.75rem;
- border-top: 1px solid $border-color;
- }
-
- > :nth-child(-n + 3) {
- min-height: auto;
- padding-top: 0.55rem;
- padding-bottom: 0.55rem;
- color: $grey-dk-100;
- font-size: 0.82rem;
- font-weight: 700;
- background: mix(white, $grey-lt-000, 45%);
- border-top: 0;
- }
-
- > :nth-child(3n + 1) {
- display: flex;
- align-items: center;
- justify-content: center;
- padding-right: 0.45rem;
- padding-left: 0.45rem;
- }
-}
-
-.te-variable-modes-grid__icon svg {
- display: inline-block;
-}
-
-@media (max-width: 42rem) {
- .te-property-list__header {
- display: none;
- }
-
- .te-property__summary {
- grid-template-columns: 1rem 1fr;
- gap: 0.25rem 0.75rem;
- }
-
- .te-property__details {
- grid-column: 2;
- }
-
- .te-property__more {
- grid-template-columns: 1rem 1fr;
- gap: 0.25rem 0.75rem;
- }
-
- .te-property__more-inner {
- grid-column: 2;
- }
-}
-
-// =============================================================================
-// Cursor MCP Install Button
-// =============================================================================
-
-.cursor-install-btn {
- display: inline-block;
- margin-bottom: 1rem;
- transition: opacity 0.15s ease, transform 0.15s ease;
-
- &:hover {
- opacity: 0.85;
- transform: translateY(-1px);
- }
-
- img {
- border: none !important;
- vertical-align: middle;
- height: 38px !important;
- width: auto !important;
- }
-}
-
-// =============================================================================
-// Mobile Responsive Fixes
-// =============================================================================
-
-@media (max-width: 900px) {
- // Fix horizontal overflow on mobile
- .main {
- max-width: 100% !important;
- width: 100%;
- overflow-x: hidden;
- }
-
- .main-content-wrap {
- max-width: 100% !important;
- padding-left: 15px;
- padding-right: 15px;
- }
-
- .content-with-toc {
- max-width: 100%;
- }
-
- .main-content table {
- display: block;
- overflow-x: auto;
- -webkit-overflow-scrolling: touch;
- max-width: 100%;
- }
-
- // Code blocks on mobile - ensure they stay within container and scroll
- div.highlight {
- max-width: 100%;
- overflow-x: auto;
- -webkit-overflow-scrolling: touch;
-
- pre {
- max-width: 100%;
- }
-
- code {
- white-space: pre;
- }
- }
-
- // Hide copy button on mobile
- .copy-btn {
- display: none !important;
- }
-}
-
-.code-title{
- font-size: 1.5rem;
- font-weight: 300;
- margin-bottom: 0rem;
- margin-top: 0.5rem;
-}
-.code-subtitle{
- font-size: 0.9rem;
- color: $grey-dk-200;
- font-weight: 200;
- margin-top: 0.25rem;
-}
-
-.main-content > h1:first-of-type{
- margin-top: 0 !important;
-}
-
-// Give client walkthroughs a quieter title and clear separation between steps.
-.main-content.mcp-client-guide {
- > h1 { font-size: 2rem; line-height: 1.25; margin-bottom: 1rem; }
- > .mcp-lead { font-size: 1.125rem; line-height: 1.6; }
- > h2 { margin-top: 3rem; margin-bottom: 1rem; }
- > ol > li { margin-bottom: 0.75rem; }
-
- .mcp-config {
- margin: 1.5rem 0 2rem;
- padding: 1rem 1.25rem;
- border: 1px solid $grey-lt-200;
- border-radius: 6px;
-
- summary { cursor: pointer; font-weight: 500; }
- &[open] summary { margin-bottom: 1rem; }
- }
-
- @media (max-width: 600px) {
- > h1 { font-size: 1.625rem; }
- }
-}
-
-// Prose prompts should stay readable at every viewport width.
-.main-content .language-text.highlighter-rouge {
- .highlight { min-width: 0; }
-
- pre,
- pre code {
- white-space: pre-wrap;
- overflow-wrap: anywhere;
- word-break: normal;
- }
-}
diff --git a/_sass/layout.scss b/_sass/layout.scss
deleted file mode 100644
index 2cd1585..0000000
--- a/_sass/layout.scss
+++ /dev/null
@@ -1,209 +0,0 @@
-//
-// The basic two column layout
-//
-
-.side-bar {
- z-index: 0;
- flex-wrap: wrap;
- background-color: $sidebar-color;
- @include mq(md){
- margin-left: auto;
- }
-
- // @include mq(md) {
- // flex-wrap: nowrap;
- // position: fixed;
- // width: $nav-width-md;
- // height: 100%;
- // flex-direction: column;
- // border-right: $border $border-color;
- // align-items: flex-end;
- // }
-}
-.side-bar-inner{
- @include mq(md) {
- position: sticky;
- top:0;
- }
-}
-
-.main {
- flex-grow: 1;
- @include mq(md) {
- position: relative;
- max-width: $content-width;
- // margin-left: $nav-width-md;
- }
-
- @include mq(lg) {
- // margin-left: calc(
- // (100% - #{$nav-width + $content-width}) / 2 + #{$nav-width}
- // );
- }
-}
-
-.main-content-wrap {
- @include container;
- padding-top: $gutter-spacing-sm;
- padding-bottom: $gutter-spacing-sm;
-
- @include mq(md) {
- padding-top: $gutter-spacing;
- padding-bottom: $gutter-spacing;
- }
-}
-
-.main-header {
- z-index: 0;
- display: none;
- background-color: $sidebar-color;
-
- @include mq(md) {
- display: flex;
- justify-content: space-between;
- height: $header-height;
- background-color: $body-background-color;
- border-bottom: $border $border-color;
- }
-
- &.nav-open {
- display: block;
-
- @include mq(md) {
- display: flex;
- }
- }
-}
-
-.site-nav,
-.site-header,
-.site-footer {
- width: 100%;
-
- @include mq(lg) {
- width: $nav-width;
- }
-}
-
-.site-nav {
- display: none;
-
- &.nav-open {
- display: block;
- }
-
- @include mq(md) {
- display: block;
- padding-top: $sp-8;
- padding-bottom: $gutter-spacing-sm;
- overflow-y: auto;
- flex: 1 1 auto;
- }
-}
-
-.site-header {
- display: flex;
- min-height: $header-height;
- align-items: center;
-
- @include mq(md) {
- height: $header-height;
- max-height: $header-height;
- border-bottom: $border $border-color;
- }
-}
-
-.site-title {
- @include container;
- flex-grow: 1;
- display: flex;
- height: 100%;
- align-items: center;
- padding-top: $sp-3;
- padding-bottom: $sp-3;
- color: $body-heading-color;
- @include fs-6;
-
- @include mq(md) {
- padding-top: $sp-2;
- padding-bottom: $sp-2;
- }
-}
-
-@if variable-exists(logo) {
- .site-logo {
- width: 100%;
- height: 100%;
- background-image: url($logo);
- background-repeat: no-repeat;
- background-position: left center;
- background-size: contain;
- }
-}
-
-.site-button {
- display: flex;
- height: 100%;
- padding: $gutter-spacing-sm;
- align-items: center;
-}
-
-@include mq(md) {
- .site-header .site-button {
- display: none;
- }
-}
-
-.site-title:hover {
- background-image: linear-gradient(
- -90deg,
- rgba($feedback-color, 1) 0%,
- rgba($feedback-color, 0.8) 80%,
- rgba($feedback-color, 0) 100%
- );
-}
-
-.site-button:hover {
- background-image: linear-gradient(
- -90deg,
- rgba($feedback-color, 1) 0%,
- rgba($feedback-color, 0.8) 100%
- );
-}
-
-// stylelint-disable selector-max-type
-
-body {
- position: relative;
- padding-bottom: $sp-10;
- overflow-y: scroll;
-
- @include mq(md) {
- position: static;
- padding-bottom: 0;
- }
-}
-
-// stylelint-enable selector-max-type
-
-.site-footer {
- @include container;
- position: absolute;
- bottom: 0;
- left: 0;
- padding-top: $sp-4;
- padding-bottom: $sp-4;
- color: $grey-dk-000;
- @include fs-2;
-
- @include mq(md) {
- position: static;
- justify-self: end;
- }
-}
-
-.icon {
- width: $sp-5;
- height: $sp-5;
- color: $link-color;
-}
diff --git a/_sass/modules.scss b/_sass/modules.scss
deleted file mode 100644
index d82591b..0000000
--- a/_sass/modules.scss
+++ /dev/null
@@ -1,20 +0,0 @@
-//
-// Import external dependencies
-//
-@import "./vendor/normalize.scss/normalize.scss";
-
-//
-// Modules
-//
-@import "./base";
-@import "./layout";
-@import "./content";
-@import "./navigation";
-@import "./typography";
-@import "./labels";
-@import "./buttons";
-@import "./search";
-@import "./tables";
-@import "./code";
-@import "./utilities/utilities";
-@import "./print";
diff --git a/assets/css/search.css b/assets/css/search.css
deleted file mode 100644
index 9c44f5e..0000000
--- a/assets/css/search.css
+++ /dev/null
@@ -1,8 +0,0 @@
-.search {
- margin-top: 10px;
-}
-
-.DocSearch-Button {
- width: 50%;
- min-width: 150px;
-}
diff --git a/assets/css/syntax.css b/assets/css/syntax.css
deleted file mode 100644
index 54d90b7..0000000
--- a/assets/css/syntax.css
+++ /dev/null
@@ -1,286 +0,0 @@
-/**
- * Rouge Syntax Highlighting - One Dark Theme
- * Build-time highlighting - no JavaScript required
- */
-
-/* Code block container */
-.highlight {
- background: #1e1e2e;
- border-radius: 6px;
- margin: 1em 0;
- overflow-x: auto;
-}
-
-.highlight pre {
- margin: 0;
- padding: 1em;
- overflow-x: auto;
- line-height: 1.5;
-}
-
-.highlight code {
- background: transparent;
- border: none;
- padding: 0;
- font-family: "SF Mono", "Fira Code", "Consolas", "Monaco", monospace;
- font-size: 0.9em;
- color: #cdd6f4;
-}
-
-/* Inline code */
-code {
- background: #313244;
- border-radius: 4px;
- padding: 0.2em 0.4em;
- font-size: 0.9em;
- color: #cdd6f4;
-}
-
-pre code {
- background: transparent;
- padding: 0;
-}
-
-/* Rouge token colors - One Dark / Catppuccin inspired */
-
-/* Comments */
-.highlight .c, /* Comment */
-.highlight .c1, /* Comment.Single */
-.highlight .cm, /* Comment.Multiline */
-.highlight .cp, /* Comment.Preproc */
-.highlight .cs, /* Comment.Special */
-.highlight .ch /* Comment.Hashbang */
-{
- color: #6c7086;
- font-style: italic;
-}
-
-/* Strings */
-.highlight .s, /* String */
-.highlight .s1, /* String.Single */
-.highlight .s2, /* String.Double */
-.highlight .sb, /* String.Backtick */
-.highlight .sc, /* String.Char */
-.highlight .sd, /* String.Doc */
-.highlight .se, /* String.Escape */
-.highlight .sh, /* String.Heredoc */
-.highlight .si, /* String.Interpol */
-.highlight .sr, /* String.Regex */
-.highlight .ss, /* String.Symbol */
-.highlight .sx /* String.Other */
-{
- color: #a6e3a1;
-}
-
-/* Numbers */
-.highlight .m, /* Number */
-.highlight .mf, /* Number.Float */
-.highlight .mh, /* Number.Hex */
-.highlight .mi, /* Number.Integer */
-.highlight .mo, /* Number.Oct */
-.highlight .mb, /* Number.Bin */
-.highlight .mx, /* Number.Other */
-.highlight .il /* Number.Integer.Long */
-{
- color: #fab387;
-}
-
-/* Keywords */
-.highlight .k, /* Keyword */
-.highlight .kc, /* Keyword.Constant */
-.highlight .kd, /* Keyword.Declaration */
-.highlight .kn, /* Keyword.Namespace */
-.highlight .kp, /* Keyword.Pseudo */
-.highlight .kr, /* Keyword.Reserved */
-.highlight .kt /* Keyword.Type */
-{
- color: #cba6f7;
-}
-
-/* Names/Identifiers */
-.highlight .n /* Name */
-{
- color: #cdd6f4;
-}
-
-.highlight .na /* Name.Attribute */
-{
- color: #89b4fa;
-}
-
-.highlight .nb /* Name.Builtin */
-{
- color: #f38ba8;
-}
-
-.highlight .nc /* Name.Class */
-{
- color: #f9e2af;
-}
-
-.highlight .nd /* Name.Decorator */
-{
- color: #cba6f7;
-}
-
-.highlight .nf, /* Name.Function */
-.highlight .fm /* Name.Function.Magic */
-{
- color: #89b4fa;
-}
-
-.highlight .ni /* Name.Entity */
-{
- color: #fab387;
-}
-
-.highlight .nl /* Name.Label */
-{
- color: #89dceb;
-}
-
-.highlight .nn /* Name.Namespace */
-{
- color: #f9e2af;
-}
-
-.highlight .no /* Name.Constant */
-{
- color: #fab387;
-}
-
-.highlight .nt /* Name.Tag */
-{
- color: #f38ba8;
-}
-
-.highlight .nv, /* Name.Variable */
-.highlight .vc, /* Name.Variable.Class */
-.highlight .vg, /* Name.Variable.Global */
-.highlight .vi, /* Name.Variable.Instance */
-.highlight .vm /* Name.Variable.Magic */
-{
- color: #f38ba8;
-}
-
-/* Operators */
-.highlight .o, /* Operator */
-.highlight .ow /* Operator.Word */
-{
- color: #89dceb;
-}
-
-/* Punctuation */
-.highlight .p /* Punctuation */
-{
- color: #cdd6f4;
-}
-
-/* Generic */
-.highlight .gd /* Generic.Deleted */
-{
- color: #f38ba8;
- background: rgba(243, 139, 168, 0.1);
-}
-
-.highlight .ge /* Generic.Emph */
-{
- font-style: italic;
-}
-
-.highlight .gh /* Generic.Heading */
-{
- color: #89b4fa;
- font-weight: bold;
-}
-
-.highlight .gi /* Generic.Inserted */
-{
- color: #a6e3a1;
- background: rgba(166, 227, 161, 0.1);
-}
-
-.highlight .gs /* Generic.Strong */
-{
- font-weight: bold;
-}
-
-.highlight .gu /* Generic.Subheading */
-{
- color: #89dceb;
-}
-
-/* Errors */
-.highlight .err /* Error */
-{
- color: #f38ba8;
-}
-
-/* Text/Whitespace */
-.highlight .w /* Text.Whitespace */
-{
- color: #6c7086;
-}
-
-/* Shell/Bash specific - these get good highlighting with Rouge */
-.highlight .nb /* Builtin commands like curl, echo, cd */
-{
- color: #89b4fa;
- font-weight: 500;
-}
-
-.highlight .nv /* Variables like $HOME */
-{
- color: #f38ba8;
-}
-
-/* Selection */
-.highlight ::selection {
- background: #45475a;
-}
-
-.highlight ::-moz-selection {
- background: #45475a;
-}
-
-/* Copy button */
-.copy-btn {
- position: absolute;
- top: 0.5em;
- right: 0.5em;
- background: #45475a;
- color: #cdd6f4;
- border: none;
- border-radius: 4px;
- padding: 0.3em 0.7em;
- font-size: 0.75em;
- cursor: pointer;
- opacity: 0;
- transition: opacity 0.2s ease, background 0.2s ease;
-}
-
-.highlight:hover .copy-btn {
- opacity: 1;
-}
-
-.copy-btn:hover {
- background: #585b70;
-}
-
-/* Mobile Responsive Styles */
-@media (max-width: 800px) {
- /* Hide copy button on mobile (no hover on touch devices) */
- .copy-btn {
- display: none;
- }
-
- /* Fix code block overflow */
- .highlight {
- max-width: 100%;
- }
-
- .highlight pre {
- max-width: 100%;
- }
-}
-
diff --git a/assets/js/analytics.js b/assets/js/analytics.js
deleted file mode 100644
index 3d50aed..0000000
--- a/assets/js/analytics.js
+++ /dev/null
@@ -1,2649 +0,0 @@
-(function() {
- /*
-
- Copyright The Closure Library Authors.
- SPDX-License-Identifier: Apache-2.0
-*/
- var l = this || self
- , m = function(a, b) {
- a = a.split(".");
- var c = l;
- a[0]in c || "undefined" == typeof c.execScript || c.execScript("var " + a[0]);
- for (var d; a.length && (d = a.shift()); )
- a.length || void 0 === b ? c = c[d] && c[d] !== Object.prototype[d] ? c[d] : c[d] = {} : c[d] = b
- };
- var q = function(a, b) {
- for (var c in b)
- b.hasOwnProperty(c) && (a[c] = b[c])
- }
- , r = function(a) {
- for (var b in a)
- if (a.hasOwnProperty(b))
- return !0;
- return !1
- };
- var t = window
- , u = document
- , v = function(a, b) {
- u.addEventListener ? u.addEventListener(a, b, !1) : u.attachEvent && u.attachEvent("on" + a, b)
- };
- var w = /^(?:(?:https?|mailto|ftp):|[^:/?#]*(?:[/?#]|$))/i;
- var x = {}
- , y = function() {
- x.TAGGING = x.TAGGING || [];
- x.TAGGING[1] = !0
- };
- var z = /:[0-9]+$/
- , B = function(a, b) {
- b && (b = String(b).toLowerCase());
- if ("protocol" === b || "port" === b)
- a.protocol = A(a.protocol) || A(t.location.protocol);
- "port" === b ? a.port = String(Number(a.hostname ? a.port : t.location.port) || ("http" == a.protocol ? 80 : "https" == a.protocol ? 443 : "")) : "host" === b && (a.hostname = (a.hostname || t.location.hostname).replace(z, "").toLowerCase());
- var c = A(a.protocol);
- b && (b = String(b).toLowerCase());
- switch (b) {
- case "url_no_fragment":
- b = "";
- a && a.href && (b = a.href.indexOf("#"),
- b = 0 > b ? a.href : a.href.substr(0, b));
- a = b;
- break;
- case "protocol":
- a = c;
- break;
- case "host":
- a = a.hostname.replace(z, "").toLowerCase();
- break;
- case "port":
- a = String(Number(a.port) || ("http" == c ? 80 : "https" == c ? 443 : ""));
- break;
- case "path":
- a.pathname || a.hostname || y();
- a = "/" == a.pathname.substr(0, 1) ? a.pathname : "/" + a.pathname;
- a = a.split("/");
- a: if (b = [],
- c = a[a.length - 1],
- Array.prototype.indexOf)
- b = b.indexOf(c),
- b = "number" == typeof b ? b : -1;
- else {
- for (var d = 0; d < b.length; d++)
- if (b[d] === c) {
- b = d;
- break a
- }
- b = -1
- }
- 0 <= b && (a[a.length - 1] = "");
- a = a.join("/");
- break;
- case "query":
- a = a.search.replace("?", "");
- break;
- case "extension":
- a = a.pathname.split(".");
- a = 1 < a.length ? a[a.length - 1] : "";
- a = a.split("/")[0];
- break;
- case "fragment":
- a = a.hash.replace("#", "");
- break;
- default:
- a = a && a.href
- }
- return a
- }
- , A = function(a) {
- return a ? a.replace(":", "").toLowerCase() : ""
- }
- , C = function(a) {
- var b = u.createElement("a");
- a && (b.href = a);
- var c = b.pathname;
- "/" !== c[0] && (a || y(),
- c = "/" + c);
- a = b.hostname.replace(z, "");
- return {
- href: b.href,
- protocol: b.protocol,
- host: b.host,
- hostname: a,
- pathname: c,
- search: b.search,
- hash: b.hash,
- port: b.port
- }
- };
- function D() {
- for (var a = E, b = {}, c = 0; c < a.length; ++c)
- b[a[c]] = c;
- return b
- }
- function F() {
- var a = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
- a += a.toLowerCase() + "0123456789-_";
- return a + "."
- }
- var E, G;
- function H(a) {
- E = E || F();
- G = G || D();
- for (var b = [], c = 0; c < a.length; c += 3) {
- var d = c + 1 < a.length
- , f = c + 2 < a.length
- , e = a.charCodeAt(c)
- , g = d ? a.charCodeAt(c + 1) : 0
- , h = f ? a.charCodeAt(c + 2) : 0
- , k = e >> 2;
- e = (e & 3) << 4 | g >> 4;
- g = (g & 15) << 2 | h >> 6;
- h &= 63;
- f || (h = 64,
- d || (g = 64));
- b.push(E[k], E[e], E[g], E[h])
- }
- return b.join("")
- }
- function I(a) {
- function b(k) {
- for (; d < a.length; ) {
- var n = a.charAt(d++)
- , p = G[n];
- if (null != p)
- return p;
- if (!/^[\s\xa0]*$/.test(n))
- throw Error("Unknown base64 encoding at char: " + n);
- }
- return k
- }
- E = E || F();
- G = G || D();
- for (var c = "", d = 0; ; ) {
- var f = b(-1)
- , e = b(0)
- , g = b(64)
- , h = b(64);
- if (64 === h && -1 === f)
- return c;
- c += String.fromCharCode(f << 2 | e >> 4);
- 64 != g && (c += String.fromCharCode(e << 4 & 240 | g >> 2),
- 64 != h && (c += String.fromCharCode(g << 6 & 192 | h)))
- }
- }
- ;var J;
- var N = function() {
- var a = K
- , b = L
- , c = M()
- , d = function(g) {
- a(g.target || g.srcElement || {})
- }
- , f = function(g) {
- b(g.target || g.srcElement || {})
- };
- if (!c.init) {
- v("mousedown", d);
- v("keyup", d);
- v("submit", f);
- var e = HTMLFormElement.prototype.submit;
- HTMLFormElement.prototype.submit = function() {
- b(this);
- e.call(this)
- }
- ;
- c.init = !0
- }
- }
- , O = function(a, b, c) {
- for (var d = M().decorators, f = {}, e = 0; e < d.length; ++e) {
- var g = d[e], h;
- if (h = !c || g.forms)
- a: {
- h = g.domains;
- var k = a
- , n = !!g.sameHost;
- if (h && (n || k !== u.location.hostname))
- for (var p = 0; p < h.length; p++)
- if (h[p]instanceof RegExp) {
- if (h[p].test(k)) {
- h = !0;
- break a
- }
- } else if (0 <= k.indexOf(h[p]) || n && 0 <= h[p].indexOf(k)) {
- h = !0;
- break a
- }
- h = !1
- }
- h && (h = g.placement,
- void 0 == h && (h = g.fragment ? 2 : 1),
- h === b && q(f, g.callback()))
- }
- return f
- }
- , M = function() {
- var a = {};
- var b = t.google_tag_data;
- t.google_tag_data = void 0 === b ? a : b;
- a = t.google_tag_data;
- b = a.gl;
- b && b.decorators || (b = {
- decorators: []
- },
- a.gl = b);
- return b
- };
- var P = /(.*?)\*(.*?)\*(.*)/
- , aa = /([^?#]+)(\?[^#]*)?(#.*)?/;
- function Q(a) {
- return new RegExp("(.*?)(^|&)" + a + "=([^&]*)&?(.*)")
- }
- var S = function(a) {
- var b = [], c;
- for (c in a)
- if (a.hasOwnProperty(c)) {
- var d = a[c];
- void 0 !== d && d === d && null !== d && "[object Object]" !== d.toString() && (b.push(c),
- b.push(H(String(d))))
- }
- a = b.join("*");
- return ["1", R(a), a].join("*")
- }
- , R = function(a, b) {
- a = [window.navigator.userAgent, (new Date).getTimezoneOffset(), window.navigator.userLanguage || window.navigator.language, Math.floor((new Date).getTime() / 60 / 1E3) - (void 0 === b ? 0 : b), a].join("*");
- if (!(b = J)) {
- b = Array(256);
- for (var c = 0; 256 > c; c++) {
- for (var d = c, f = 0; 8 > f; f++)
- d = d & 1 ? d >>> 1 ^ 3988292384 : d >>> 1;
- b[c] = d
- }
- }
- J = b;
- b = 4294967295;
- for (c = 0; c < a.length; c++)
- b = b >>> 8 ^ J[(b ^ a.charCodeAt(c)) & 255];
- return ((b ^ -1) >>> 0).toString(36)
- }
- , ca = function(a) {
- return function(b) {
- var c = C(t.location.href)
- , d = c.search.replace("?", "");
- a: {
- var f = d.split("&");
- for (var e = 0; e < f.length; e++) {
- var g = f[e].split("=");
- if ("_gl" === decodeURIComponent(g[0]).replace(/\+/g, " ")) {
- f = g.slice(1).join("=");
- break a
- }
- }
- f = void 0
- }
- b.query = T(f || "") || {};
- f = B(c, "fragment");
- e = f.match(Q("_gl"));
- b.fragment = T(e && e[3] || "") || {};
- a && ba(c, d, f)
- }
- };
- function U(a, b) {
- if (a = Q(a).exec(b)) {
- var c = a[2]
- , d = a[4];
- b = a[1];
- d && (b = b + c + d)
- }
- return b
- }
- var ba = function(a, b, c) {
- function d(e, g) {
- e = U("_gl", e);
- e.length && (e = g + e);
- return e
- }
- if (t.history && t.history.replaceState) {
- var f = Q("_gl");
- if (f.test(b) || f.test(c))
- a = B(a, "path"),
- b = d(b, "?"),
- c = d(c, "#"),
- t.history.replaceState({}, void 0, "" + a + b + c)
- }
- }
- , T = function(a) {
- var b = void 0 === b ? 3 : b;
- try {
- if (a) {
- a: {
- for (var c = 0; 3 > c; ++c) {
- var d = P.exec(a);
- if (d) {
- var f = d;
- break a
- }
- a = decodeURIComponent(a)
- }
- f = void 0
- }
- if (f && "1" === f[1]) {
- var e = f[2]
- , g = f[3];
- a: {
- for (f = 0; f < b; ++f)
- if (e === R(g, f)) {
- var h = !0;
- break a
- }
- h = !1
- }
- if (h) {
- b = {};
- var k = g ? g.split("*") : [];
- for (g = 0; g < k.length; g += 2)
- b[k[g]] = I(k[g + 1]);
- return b
- }
- }
- }
- } catch (n) {}
- };
- function V(a, b, c, d) {
- function f(k) {
- k = U(a, k);
- var n = k.charAt(k.length - 1);
- k && "&" !== n && (k += "&");
- return k + h
- }
- d = void 0 === d ? !1 : d;
- var e = aa.exec(c);
- if (!e)
- return "";
- c = e[1];
- var g = e[2] || "";
- e = e[3] || "";
- var h = a + "=" + b;
- d ? e = "#" + f(e.substring(1)) : g = "?" + f(g.substring(1));
- return "" + c + g + e
- }
- function W(a, b) {
- var c = "FORM" === (a.tagName || "").toUpperCase()
- , d = O(b, 1, c)
- , f = O(b, 2, c);
- b = O(b, 3, c);
- r(d) && (d = S(d),
- c ? X("_gl", d, a) : Y("_gl", d, a, !1));
- !c && r(f) && (c = S(f),
- Y("_gl", c, a, !0));
- for (var e in b)
- b.hasOwnProperty(e) && Z(e, b[e], a)
- }
- function Z(a, b, c, d) {
- if (c.tagName) {
- if ("a" === c.tagName.toLowerCase())
- return Y(a, b, c, d);
- if ("form" === c.tagName.toLowerCase())
- return X(a, b, c)
- }
- if ("string" == typeof c)
- return V(a, b, c, d)
- }
- function Y(a, b, c, d) {
- c.href && (a = V(a, b, c.href, void 0 === d ? !1 : d),
- w.test(a) && (c.href = a))
- }
- function X(a, b, c) {
- if (c && c.action) {
- var d = (c.method || "").toLowerCase();
- if ("get" === d) {
- d = c.childNodes || [];
- for (var f = !1, e = 0; e < d.length; e++) {
- var g = d[e];
- if (g.name === a) {
- g.setAttribute("value", b);
- f = !0;
- break
- }
- }
- f || (d = u.createElement("input"),
- d.setAttribute("type", "hidden"),
- d.setAttribute("name", a),
- d.setAttribute("value", b),
- c.appendChild(d))
- } else
- "post" === d && (a = V(a, b, c.action),
- w.test(a) && (c.action = a))
- }
- }
- var K = function(a) {
- try {
- a: {
- for (var b = 100; a && 0 < b; ) {
- if (a.href && a.nodeName.match(/^a(?:rea)?$/i)) {
- var c = a;
- break a
- }
- a = a.parentNode;
- b--
- }
- c = null
- }
- if (c) {
- var d = c.protocol;
- "http:" !== d && "https:" !== d || W(c, c.hostname)
- }
- } catch (f) {}
- }
- , L = function(a) {
- try {
- if (a.action) {
- var b = B(C(a.action), "host");
- W(a, b)
- }
- } catch (c) {}
- };
- m("google_tag_data.glBridge.auto", function(a, b, c, d) {
- N();
- c = "fragment" === c ? 2 : 1;
- a = {
- callback: a,
- domains: b,
- fragment: 2 === c,
- placement: c,
- forms: !!d,
- sameHost: !1
- };
- M().decorators.push(a)
- });
- m("google_tag_data.glBridge.decorate", function(a, b, c) {
- a = S(a);
- return Z("_gl", a, b, !!c)
- });
- m("google_tag_data.glBridge.generate", S);
- m("google_tag_data.glBridge.get", function(a, b) {
- var c = ca(!!b);
- b = M();
- b.data || (b.data = {
- query: {},
- fragment: {}
- },
- c(b.data));
- c = {};
- if (b = b.data)
- q(c, b.query),
- a && q(c, b.fragment);
- return c
- });
-}
-)(window);
-(function() {
- function La(a) {
- var b = 1, c;
- if (a)
- for (b = 0,
- c = a.length - 1; 0 <= c; c--) {
- var d = a.charCodeAt(c);
- b = (b << 6 & 268435455) + d + (d << 14);
- d = b & 266338304;
- b = 0 != d ? b ^ d >> 21 : b
- }
- return b
- }
- ;/*
-
- Copyright The Closure Library Authors.
- SPDX-License-Identifier: Apache-2.0
-*/
- var $c = function(a) {
- this.C = a || []
- };
- $c.prototype.set = function(a) {
- this.C[a] = !0
- }
- ;
- $c.prototype.encode = function() {
- for (var a = [], b = 0; b < this.C.length; b++)
- this.C[b] && (a[Math.floor(b / 6)] ^= 1 << b % 6);
- for (b = 0; b < a.length; b++)
- a[b] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".charAt(a[b] || 0);
- return a.join("") + "~"
- }
- ;
- var ha = window.GoogleAnalyticsObject, F;
- if (F = void 0 != ha)
- F = -1 < (ha.constructor + "").indexOf("String");
- var wa;
- if (wa = F) {
- var Ea = window.GoogleAnalyticsObject;
- wa = Ea ? Ea.replace(/^[\s\xa0]+|[\s\xa0]+$/g, "") : ""
- }
- var gb = wa || "ga"
- , jd = /^(?:utma\.)?\d+\.\d+$/
- , kd = /^amp-[\w.-]{22,64}$/
- , Ba = !1;
- var vd = new $c;
- function J(a) {
- vd.set(a)
- }
- var Td = function(a) {
- a = Dd(a);
- a = new $c(a);
- for (var b = vd.C.slice(), c = 0; c < a.C.length; c++)
- b[c] = b[c] || a.C[c];
- return (new $c(b)).encode()
- }
- , Dd = function(a) {
- a = a.get(Gd);
- ka(a) || (a = []);
- return a
- };
- var ea = function(a) {
- return "function" == typeof a
- }
- , ka = function(a) {
- return "[object Array]" == Object.prototype.toString.call(Object(a))
- }
- , qa = function(a) {
- return void 0 != a && -1 < (a.constructor + "").indexOf("String")
- }
- , D = function(a, b) {
- return 0 == a.indexOf(b)
- }
- , sa = function(a) {
- return a ? a.replace(/^[\s\xa0]+|[\s\xa0]+$/g, "") : ""
- }
- , ra = function() {
- for (var a = O.navigator.userAgent + (M.cookie ? M.cookie : "") + (M.referrer ? M.referrer : ""), b = a.length, c = O.history.length; 0 < c; )
- a += c-- ^ b++;
- return [hd() ^ La(a) & 2147483647, Math.round((new Date).getTime() / 1E3)].join(".")
- }
- , ta = function(a) {
- var b = M.createElement("img");
- b.width = 1;
- b.height = 1;
- b.src = a;
- return b
- }
- , ua = function() {}
- , K = function(a) {
- if (encodeURIComponent instanceof Function)
- return encodeURIComponent(a);
- J(28);
- return a
- }
- , L = function(a, b, c, d) {
- try {
- a.addEventListener ? a.addEventListener(b, c, !!d) : a.attachEvent && a.attachEvent("on" + b, c)
- } catch (e) {
- J(27)
- }
- }
- , f = /^[\w\-:/.?=&%!\[\]]+$/
- , Nd = /^[\w+/_-]+[=]{0,2}$/
- , be = function(a, b) {
- return E(M.location[b ? "href" : "search"], a)
- }
- , E = function(a, b) {
- return (a = a.match("(?:&|#|\\?)" + K(b).replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1") + "=([^]*)")) && 2 == a.length ? a[1] : ""
- }
- , xa = function() {
- var a = "" + M.location.hostname;
- return 0 == a.indexOf("www.") ? a.substring(4) : a
- }
- , de = function(a, b) {
- var c = a.indexOf(b);
- if (5 == c || 6 == c)
- if (a = a.charAt(c + b.length),
- "/" == a || "?" == a || "" == a || ":" == a)
- return !0;
- return !1
- }
- , ya = function(a, b) {
- var c = M.referrer;
- if (/^(https?|android-app):\/\//i.test(c)) {
- if (a)
- return c;
- a = "//" + M.location.hostname;
- if (!de(c, a))
- return b && (b = a.replace(/\./g, "-") + ".cdn.ampproject.org",
- de(c, b)) ? void 0 : c
- }
- }
- , za = function(a, b) {
- if (1 == b.length && null != b[0] && "object" === typeof b[0])
- return b[0];
- for (var c = {}, d = Math.min(a.length + 1, b.length), e = 0; e < d; e++)
- if ("object" === typeof b[e]) {
- for (var g in b[e])
- b[e].hasOwnProperty(g) && (c[g] = b[e][g]);
- break
- } else
- e < a.length && (c[a[e]] = b[e]);
- return c
- };
- var ee = function() {
- this.keys = [];
- this.values = {};
- this.m = {}
- };
- ee.prototype.set = function(a, b, c) {
- this.keys.push(a);
- c ? this.m[":" + a] = b : this.values[":" + a] = b
- }
- ;
- ee.prototype.get = function(a) {
- return this.m.hasOwnProperty(":" + a) ? this.m[":" + a] : this.values[":" + a]
- }
- ;
- ee.prototype.map = function(a) {
- for (var b = 0; b < this.keys.length; b++) {
- var c = this.keys[b]
- , d = this.get(c);
- d && a(c, d)
- }
- }
- ;
- var O = window
- , M = document
- , va = function(a, b) {
- return setTimeout(a, b)
- };
- var Qa = window
- , Za = document
- , G = function(a) {
- var b = Qa._gaUserPrefs;
- if (b && b.ioo && b.ioo() || a && !0 === Qa["ga-disable-" + a])
- return !0;
- try {
- var c = Qa.external;
- if (c && c._gaUserPrefs && "oo" == c._gaUserPrefs)
- return !0
- } catch (g) {}
- a = [];
- b = String(Za.cookie).split(";");
- for (c = 0; c < b.length; c++) {
- var d = b[c].split("=")
- , e = d[0].replace(/^\s*|\s*$/g, "");
- e && "AMP_TOKEN" == e && ((d = d.slice(1).join("=").replace(/^\s*|\s*$/g, "")) && (d = decodeURIComponent(d)),
- a.push(d))
- }
- for (b = 0; b < a.length; b++)
- if ("$OPT_OUT" == a[b])
- return !0;
- return Za.getElementById("__gaOptOutExtension") ? !0 : !1
- };
- var Ca = function(a) {
- var b = []
- , c = M.cookie.split(";");
- a = new RegExp("^\\s*" + a + "=\\s*(.*?)\\s*$");
- for (var d = 0; d < c.length; d++) {
- var e = c[d].match(a);
- e && b.push(e[1])
- }
- return b
- }
- , zc = function(a, b, c, d, e, g, ca) {
- e = G(e) ? !1 : eb.test(M.location.hostname) || "/" == c && vc.test(d) ? !1 : !0;
- if (!e)
- return !1;
- b && 1200 < b.length && (b = b.substring(0, 1200));
- c = a + "=" + b + "; path=" + c + "; ";
- g && (c += "expires=" + (new Date((new Date).getTime() + g)).toGMTString() + "; ");
- d && "none" !== d && (c += "domain=" + d + ";");
- ca && (c += ca + ";");
- d = M.cookie;
- M.cookie = c;
- if (!(d = d != M.cookie))
- a: {
- a = Ca(a);
- for (d = 0; d < a.length; d++)
- if (b == a[d]) {
- d = !0;
- break a
- }
- d = !1
- }
- return d
- }
- , Cc = function(a) {
- return encodeURIComponent ? encodeURIComponent(a).replace(/\(/g, "%28").replace(/\)/g, "%29") : a
- }
- , vc = /^(www\.)?google(\.com?)?(\.[a-z]{2})?$/
- , eb = /(^|\.)doubleclick\.net$/i;
- var oc, Id = /^.*Version\/?(\d+)[^\d].*$/i, ne = function() {
- if (void 0 !== O.__ga4__)
- return O.__ga4__;
- if (void 0 === oc) {
- var a = O.navigator.userAgent;
- if (a) {
- var b = a;
- try {
- b = decodeURIComponent(a)
- } catch (c) {}
- if (a = !(0 <= b.indexOf("Chrome")) && !(0 <= b.indexOf("CriOS")) && (0 <= b.indexOf("Safari/") || 0 <= b.indexOf("Safari,")))
- b = Id.exec(b),
- a = 11 <= (b ? Number(b[1]) : -1);
- oc = a
- } else
- oc = !1
- }
- return oc
- };
- var Fa, Ga, fb, Ab, ja = /^https?:\/\/[^/]*cdn\.ampproject\.org\//, Ue = /^(?:www\.|m\.|amp\.)+/, Ub = [], da = function(a) {
- if (ye(a[Kd])) {
- if (void 0 === Ab) {
- var b;
- if (b = (b = De.get()) && b._ga || void 0)
- Ab = b,
- J(81)
- }
- if (void 0 !== Ab)
- return a[Q] || (a[Q] = Ab),
- !1
- }
- if (a[Kd]) {
- J(67);
- if (a[ac] && "cookie" != a[ac])
- return !1;
- if (void 0 !== Ab)
- a[Q] || (a[Q] = Ab);
- else {
- a: {
- b = String(a[W] || xa());
- var c = String(a[Yb] || "/")
- , d = Ca(String(a[U] || "_ga"));
- b = na(d, b, c);
- if (!b || jd.test(b))
- b = !0;
- else if (b = Ca("AMP_TOKEN"),
- 0 == b.length)
- b = !0;
- else {
- if (1 == b.length && (b = decodeURIComponent(b[0]),
- "$RETRIEVING" == b || "$OPT_OUT" == b || "$ERROR" == b || "$NOT_FOUND" == b)) {
- b = !0;
- break a
- }
- b = !1
- }
- }
- if (b && tc(ic, String(a[Na])))
- return !0
- }
- }
- return !1
- }, ic = function() {
- Z.D([ua])
- }, tc = function(a, b) {
- var c = Ca("AMP_TOKEN");
- if (1 < c.length)
- return J(55),
- !1;
- c = decodeURIComponent(c[0] || "");
- if ("$OPT_OUT" == c || "$ERROR" == c || G(b))
- return J(62),
- !1;
- if (!ja.test(M.referrer) && "$NOT_FOUND" == c)
- return J(68),
- !1;
- if (void 0 !== Ab)
- return J(56),
- va(function() {
- a(Ab)
- }, 0),
- !0;
- if (Fa)
- return Ub.push(a),
- !0;
- if ("$RETRIEVING" == c)
- return J(57),
- va(function() {
- tc(a, b)
- }, 1E4),
- !0;
- Fa = !0;
- c && "$" != c[0] || (xc("$RETRIEVING", 3E4),
- setTimeout(Mc, 3E4),
- c = "");
- return Pc(c, b) ? (Ub.push(a),
- !0) : !1
- }, Pc = function(a, b, c) {
- if (!window.JSON)
- return J(58),
- !1;
- var d = O.XMLHttpRequest;
- if (!d)
- return J(59),
- !1;
- var e = new d;
- if (!("withCredentials"in e))
- return J(60),
- !1;
- e.open("POST", (c || "https://ampcid.google.com/v1/publisher:getClientId") + "?key=AIzaSyA65lEHUEizIsNtlbNo-l2K18dT680nsaM", !0);
- e.withCredentials = !0;
- e.setRequestHeader("Content-Type", "text/plain");
- e.onload = function() {
- Fa = !1;
- if (4 == e.readyState) {
- try {
- 200 != e.status && (J(61),
- Qc("", "$ERROR", 3E4));
- var g = JSON.parse(e.responseText);
- g.optOut ? (J(63),
- Qc("", "$OPT_OUT", 31536E6)) : g.clientId ? Qc(g.clientId, g.securityToken, 31536E6) : !c && g.alternateUrl ? (Ga && clearTimeout(Ga),
- Fa = !0,
- Pc(a, b, g.alternateUrl)) : (J(64),
- Qc("", "$NOT_FOUND", 36E5))
- } catch (ca) {
- J(65),
- Qc("", "$ERROR", 3E4)
- }
- e = null
- }
- }
- ;
- d = {
- originScope: "AMP_ECID_GOOGLE"
- };
- a && (d.securityToken = a);
- e.send(JSON.stringify(d));
- Ga = va(function() {
- J(66);
- Qc("", "$ERROR", 3E4)
- }, 1E4);
- return !0
- }, Mc = function() {
- Fa = !1
- }, xc = function(a, b) {
- if (void 0 === fb) {
- fb = "";
- for (var c = id(), d = 0; d < c.length; d++) {
- var e = c[d];
- if (zc("AMP_TOKEN", encodeURIComponent(a), "/", e, "", b)) {
- fb = e;
- return
- }
- }
- }
- zc("AMP_TOKEN", encodeURIComponent(a), "/", fb, "", b)
- }, Qc = function(a, b, c) {
- Ga && clearTimeout(Ga);
- b && xc(b, c);
- Ab = a;
- b = Ub;
- Ub = [];
- for (c = 0; c < b.length; c++)
- b[c](a)
- }, ye = function(a) {
- a: {
- if (ja.test(M.referrer)) {
- var b = M.location.hostname.replace(Ue, "");
- b: {
- var c = M.referrer;
- c = c.replace(/^https?:\/\//, "");
- var d = c.replace(/^[^/]+/, "").split("/")
- , e = d[2];
- d = (d = "s" == e ? d[3] : e) ? decodeURIComponent(d) : d;
- if (!d) {
- if (0 == c.indexOf("xn--")) {
- c = "";
- break b
- }
- (c = c.match(/(.*)\.cdn\.ampproject\.org\/?$/)) && 2 == c.length && (d = c[1].replace(/-/g, ".").replace(/\.\./g, "-"))
- }
- c = d ? d.replace(Ue, "") : ""
- }
- (d = b === c) || (c = "." + c,
- d = b.substring(b.length - c.length, b.length) === c);
- if (d) {
- b = !0;
- break a
- } else
- J(78)
- }
- b = !1
- }
- return b && !1 !== a
- };
- var bd = function(a) {
- return (a ? "https:" : Ba || "https:" == M.location.protocol ? "https:" : "http:") + "//www.google-analytics.com"
- }
- , Da = function(a) {
- this.name = "len";
- this.message = a + "-8192"
- }
- , ba = function(a, b, c) {
- c = c || ua;
- if (2036 >= b.length)
- wc(a, b, c);
- else if (8192 >= b.length)
- x(a, b, c) || wd(a, b, c) || wc(a, b, c);
- else
- throw ge("len", b.length),
- new Da(b.length);
- }
- , pe = function(a, b, c, d) {
- d = d || ua;
- wd(a + "?" + b, "", d, c)
- }
- , wc = function(a, b, c) {
- var d = ta(a + "?" + b);
- d.onload = d.onerror = function() {
- d.onload = null;
- d.onerror = null;
- c()
- }
- }
- , wd = function(a, b, c, d) {
- var e = O.XMLHttpRequest;
- if (!e)
- return !1;
- var g = new e;
- if (!("withCredentials"in g))
- return !1;
- a = a.replace(/^http:/, "https:");
- g.open("POST", a, !0);
- g.withCredentials = !0;
- g.setRequestHeader("Content-Type", "text/plain");
- g.onreadystatechange = function() {
- if (4 == g.readyState) {
- if (d)
- try {
- var ca = g.responseText;
- if (1 > ca.length)
- ge("xhr", "ver", "0"),
- c();
- else if ("1" != ca.charAt(0))
- ge("xhr", "ver", String(ca.length)),
- c();
- else if (3 < d.count++)
- ge("xhr", "tmr", "" + d.count),
- c();
- else if (1 == ca.length)
- c();
- else {
- var l = ca.charAt(1);
- if ("d" == l)
- pe("https://stats.g.doubleclick.net/j/collect", d.U, d, c);
- else if ("g" == l) {
- wc("https://www.google.%/ads/ga-audiences".replace("%", "com"), d.google, c);
- var k = ca.substring(2);
- k && (/^[a-z.]{1,6}$/.test(k) ? wc("https://www.google.%/ads/ga-audiences".replace("%", k), d.google, ua) : ge("tld", "bcc", k))
- } else
- ge("xhr", "brc", l),
- c()
- }
- } catch (w) {
- ge("xhr", "rsp"),
- c()
- }
- else
- c();
- g = null
- }
- }
- ;
- g.send(b);
- return !0
- }
- , x = function(a, b, c) {
- return O.navigator.sendBeacon ? O.navigator.sendBeacon(a, b) ? (c(),
- !0) : !1 : !1
- }
- , ge = function(a, b, c) {
- 1 <= 100 * Math.random() || G("?") || (a = ["t=error", "_e=" + a, "_v=j83", "sr=1"],
- b && a.push("_f=" + b),
- c && a.push("_m=" + K(c.substring(0, 100))),
- a.push("aip=1"),
- a.push("z=" + hd()),
- wc(bd(!0) + "/u/d", a.join("&"), ua))
- };
- var qc = function() {
- return O.gaData = O.gaData || {}
- }
- , h = function(a) {
- var b = qc();
- return b[a] = b[a] || {}
- };
- var Ha = function() {
- this.M = []
- };
- Ha.prototype.add = function(a) {
- this.M.push(a)
- }
- ;
- Ha.prototype.D = function(a) {
- try {
- for (var b = 0; b < this.M.length; b++) {
- var c = a.get(this.M[b]);
- c && ea(c) && c.call(O, a)
- }
- } catch (d) {}
- b = a.get(Ia);
- b != ua && ea(b) && (a.set(Ia, ua, !0),
- setTimeout(b, 10))
- }
- ;
- function Ja(a) {
- if (100 != a.get(Ka) && La(P(a, Q)) % 1E4 >= 100 * R(a, Ka))
- throw "abort";
- }
- function Ma(a) {
- if (G(P(a, Na)))
- throw "abort";
- }
- function Oa() {
- var a = M.location.protocol;
- if ("http:" != a && "https:" != a)
- throw "abort";
- }
- function Pa(a) {
- try {
- O.navigator.sendBeacon ? J(42) : O.XMLHttpRequest && "withCredentials"in new O.XMLHttpRequest && J(40)
- } catch (c) {}
- a.set(ld, Td(a), !0);
- a.set(Ac, R(a, Ac) + 1);
- var b = [];
- ue.map(function(c, d) {
- d.F && (c = a.get(c),
- void 0 != c && c != d.defaultValue && ("boolean" == typeof c && (c *= 1),
- b.push(d.F + "=" + K("" + c))))
- });
- !1 === a.get(xe) && b.push("npa=1");
- b.push("z=" + Bd());
- a.set(Ra, b.join("&"), !0)
- }
- function Sa(a) {
- var b = P(a, fa);
- !b && a.get(Vd) && (b = "beacon");
- var c = P(a, gd)
- , d = P(a, oe)
- , e = c || (d || bd(!1) + "") + "/collect";
- switch (P(a, ad)) {
- case "d":
- e = c || (d || bd(!1) + "") + "/j/collect";
- b = a.get(qe) || void 0;
- pe(e, P(a, Ra), b, a.Z(Ia));
- break;
- case "b":
- e = c || (d || bd(!1) + "") + "/r/collect";
- default:
- b ? (c = P(a, Ra),
- d = (d = a.Z(Ia)) || ua,
- "image" == b ? wc(e, c, d) : "xhr" == b && wd(e, c, d) || "beacon" == b && x(e, c, d) || ba(e, c, d)) : ba(e, P(a, Ra), a.Z(Ia))
- }
- e = P(a, Na);
- e = h(e);
- b = e.hitcount;
- e.hitcount = b ? b + 1 : 1;
- e.first_hit || (e.first_hit = (new Date).getTime());
- e = P(a, Na);
- delete h(e).pending_experiments;
- a.set(Ia, ua, !0)
- }
- function Hc(a) {
- qc().expId && a.set(Nc, qc().expId);
- qc().expVar && a.set(Oc, qc().expVar);
- var b = P(a, Na);
- if (b = h(b).pending_experiments) {
- var c = [];
- for (d in b)
- b.hasOwnProperty(d) && b[d] && c.push(encodeURIComponent(d) + "." + encodeURIComponent(b[d]));
- var d = c.join("!")
- } else
- d = void 0;
- d && ((b = a.get(m)) && (d = b + "!" + d),
- a.set(m, d, !0))
- }
- function cd() {
- if (O.navigator && "preview" == O.navigator.loadPurpose)
- throw "abort";
- }
- function yd(a) {
- var b = O.gaDevIds || [];
- if (ka(b)) {
- var c = a.get("&did");
- qa(c) && 0 < c.length && (b = b.concat(c.split(",")));
- c = [];
- for (var d = 0; d < b.length; d++) {
- var e;
- a: {
- for (e = 0; e < c.length; e++)
- if (b[d] == c[e]) {
- e = !0;
- break a
- }
- e = !1
- }
- e || c.push(b[d])
- }
- 0 != c.length && a.set("&did", c.join(","), !0)
- }
- }
- function vb(a) {
- if (!a.get(Na))
- throw "abort";
- }
- ;var hd = function() {
- return Math.round(2147483647 * Math.random())
- }
- , Bd = function() {
- try {
- var a = new Uint32Array(1);
- O.crypto.getRandomValues(a);
- return a[0] & 2147483647
- } catch (b) {
- return hd()
- }
- };
- function Ta(a) {
- var b = R(a, Ua);
- 500 <= b && J(15);
- var c = P(a, Va);
- if ("transaction" != c && "item" != c) {
- c = R(a, Wa);
- var d = (new Date).getTime()
- , e = R(a, Xa);
- 0 == e && a.set(Xa, d);
- e = Math.round(2 * (d - e) / 1E3);
- 0 < e && (c = Math.min(c + e, 20),
- a.set(Xa, d));
- if (0 >= c)
- throw "abort";
- a.set(Wa, --c)
- }
- a.set(Ua, ++b)
- }
- ;var Ya = function() {
- this.data = new ee
- };
- Ya.prototype.get = function(a) {
- var b = $a(a)
- , c = this.data.get(a);
- b && void 0 == c && (c = ea(b.defaultValue) ? b.defaultValue() : b.defaultValue);
- return b && b.Z ? b.Z(this, a, c) : c
- }
- ;
- var P = function(a, b) {
- a = a.get(b);
- return void 0 == a ? "" : "" + a
- }
- , R = function(a, b) {
- a = a.get(b);
- return void 0 == a || "" === a ? 0 : Number(a)
- };
- Ya.prototype.Z = function(a) {
- return (a = this.get(a)) && ea(a) ? a : ua
- }
- ;
- Ya.prototype.set = function(a, b, c) {
- if (a)
- if ("object" == typeof a)
- for (var d in a)
- a.hasOwnProperty(d) && ab(this, d, a[d], c);
- else
- ab(this, a, b, c)
- }
- ;
- var ab = function(a, b, c, d) {
- if (void 0 != c)
- switch (b) {
- case Na:
- wb.test(c)
- }
- var e = $a(b);
- e && e.o ? e.o(a, b, c, d) : a.data.set(b, c, d)
- };
- var ue = new ee
- , ve = []
- , bb = function(a, b, c, d, e) {
- this.name = a;
- this.F = b;
- this.Z = d;
- this.o = e;
- this.defaultValue = c
- }
- , $a = function(a) {
- var b = ue.get(a);
- if (!b)
- for (var c = 0; c < ve.length; c++) {
- var d = ve[c]
- , e = d[0].exec(a);
- if (e) {
- b = d[1](e);
- ue.set(b.name, b);
- break
- }
- }
- return b
- }
- , yc = function(a) {
- var b;
- ue.map(function(c, d) {
- d.F == a && (b = d)
- });
- return b && b.name
- }
- , S = function(a, b, c, d, e) {
- a = new bb(a,b,c,d,e);
- ue.set(a.name, a);
- return a.name
- }
- , cb = function(a, b) {
- ve.push([new RegExp("^" + a + "$"), b])
- }
- , T = function(a, b, c) {
- return S(a, b, c, void 0, db)
- }
- , db = function() {};
- var hb = T("apiVersion", "v")
- , ib = T("clientVersion", "_v");
- S("anonymizeIp", "aip");
- var jb = S("adSenseId", "a")
- , Va = S("hitType", "t")
- , Ia = S("hitCallback")
- , Ra = S("hitPayload");
- S("nonInteraction", "ni");
- S("currencyCode", "cu");
- S("dataSource", "ds");
- var Vd = S("useBeacon", void 0, !1)
- , fa = S("transport");
- S("sessionControl", "sc", "");
- S("sessionGroup", "sg");
- S("queueTime", "qt");
- var Ac = S("_s", "_s");
- S("screenName", "cd");
- var kb = S("location", "dl", "")
- , lb = S("referrer", "dr")
- , mb = S("page", "dp", "");
- S("hostname", "dh");
- var nb = S("language", "ul")
- , ob = S("encoding", "de");
- S("title", "dt", function() {
- return M.title || void 0
- });
- cb("contentGroup([0-9]+)", function(a) {
- return new bb(a[0],"cg" + a[1])
- });
- var pb = S("screenColors", "sd")
- , qb = S("screenResolution", "sr")
- , rb = S("viewportSize", "vp")
- , sb = S("javaEnabled", "je")
- , tb = S("flashVersion", "fl");
- S("campaignId", "ci");
- S("campaignName", "cn");
- S("campaignSource", "cs");
- S("campaignMedium", "cm");
- S("campaignKeyword", "ck");
- S("campaignContent", "cc");
- var ub = S("eventCategory", "ec")
- , xb = S("eventAction", "ea")
- , yb = S("eventLabel", "el")
- , zb = S("eventValue", "ev")
- , Bb = S("socialNetwork", "sn")
- , Cb = S("socialAction", "sa")
- , Db = S("socialTarget", "st")
- , Eb = S("l1", "plt")
- , Fb = S("l2", "pdt")
- , Gb = S("l3", "dns")
- , Hb = S("l4", "rrt")
- , Ib = S("l5", "srt")
- , Jb = S("l6", "tcp")
- , Kb = S("l7", "dit")
- , Lb = S("l8", "clt")
- , Ve = S("l9", "_gst")
- , We = S("l10", "_gbt")
- , Xe = S("l11", "_cst")
- , Ye = S("l12", "_cbt")
- , Mb = S("timingCategory", "utc")
- , Nb = S("timingVar", "utv")
- , Ob = S("timingLabel", "utl")
- , Pb = S("timingValue", "utt");
- S("appName", "an");
- S("appVersion", "av", "");
- S("appId", "aid", "");
- S("appInstallerId", "aiid", "");
- S("exDescription", "exd");
- S("exFatal", "exf");
- var Nc = S("expId", "xid")
- , Oc = S("expVar", "xvar")
- , m = S("exp", "exp")
- , Rc = S("_utma", "_utma")
- , Sc = S("_utmz", "_utmz")
- , Tc = S("_utmht", "_utmht")
- , Ua = S("_hc", void 0, 0)
- , Xa = S("_ti", void 0, 0)
- , Wa = S("_to", void 0, 20);
- cb("dimension([0-9]+)", function(a) {
- return new bb(a[0],"cd" + a[1])
- });
- cb("metric([0-9]+)", function(a) {
- return new bb(a[0],"cm" + a[1])
- });
- S("linkerParam", void 0, void 0, Bc, db);
- var Ze = T("_cd2l", void 0, !1)
- , ld = S("usage", "_u")
- , Gd = S("_um");
- S("forceSSL", void 0, void 0, function() {
- return Ba
- }, function(a, b, c) {
- J(34);
- Ba = !!c
- });
- var ed = S("_j1", "jid")
- , ia = S("_j2", "gjid");
- cb("\\&(.*)", function(a) {
- var b = new bb(a[0],a[1])
- , c = yc(a[0].substring(1));
- c && (b.Z = function(d) {
- return d.get(c)
- }
- ,
- b.o = function(d, e, g, ca) {
- d.set(c, g, ca)
- }
- ,
- b.F = void 0);
- return b
- });
- var Qb = T("_oot")
- , dd = S("previewTask")
- , Rb = S("checkProtocolTask")
- , md = S("validationTask")
- , Sb = S("checkStorageTask")
- , Uc = S("historyImportTask")
- , Tb = S("samplerTask")
- , Vb = S("_rlt")
- , Wb = S("buildHitTask")
- , Xb = S("sendHitTask")
- , Vc = S("ceTask")
- , zd = S("devIdTask")
- , Cd = S("timingTask")
- , Ld = S("displayFeaturesTask")
- , oa = S("customTask")
- , ze = S("fpsCrossDomainTask")
- , V = T("name")
- , Q = T("clientId", "cid")
- , n = T("clientIdTime")
- , xd = T("storedClientId")
- , Ad = S("userId", "uid")
- , Na = T("trackingId", "tid")
- , U = T("cookieName", void 0, "_ga")
- , W = T("cookieDomain")
- , Yb = T("cookiePath", void 0, "/")
- , Zb = T("cookieExpires", void 0, 63072E3)
- , Hd = T("cookieUpdate", void 0, !0)
- , Be = T("cookieFlags", void 0, "")
- , $b = T("legacyCookieDomain")
- , Wc = T("legacyHistoryImport", void 0, !0)
- , ac = T("storage", void 0, "cookie")
- , bc = T("allowLinker", void 0, !1)
- , cc = T("allowAnchor", void 0, !0)
- , Ka = T("sampleRate", "sf", 100)
- , dc = T("siteSpeedSampleRate", void 0, 1)
- , ec = T("alwaysSendReferrer", void 0, !1)
- , I = T("_gid", "_gid")
- , la = T("_gcn")
- , Kd = T("useAmpClientId")
- , ce = T("_gclid")
- , fe = T("_gt")
- , he = T("_ge", void 0, 7776E6)
- , ie = T("_gclsrc")
- , je = T("storeGac", void 0, !0)
- , oe = S("_x_19")
- , Ae = S("_fplc", "_fplc")
- , gd = S("transportUrl")
- , Md = S("_r", "_r")
- , qe = S("_dp")
- , ad = S("_jt", void 0, "n")
- , Ud = S("allowAdFeatures", void 0, !0)
- , xe = S("allowAdPersonalizationSignals", void 0, !0);
- function X(a, b, c, d) {
- b[a] = function() {
- try {
- return d && J(d),
- c.apply(this, arguments)
- } catch (e) {
- throw ge("exc", a, e && e.name),
- e;
- }
- }
- }
- ;var Od = function() {
- this.V = 100;
- this.$ = this.fa = !1;
- this.oa = "detourexp";
- this.groups = 1
- }
- , Ed = function(a) {
- var b = new Od, c;
- if (b.fa && b.$)
- return 0;
- b.$ = !0;
- if (a) {
- if (b.oa && void 0 !== a.get(b.oa))
- return R(a, b.oa);
- if (0 == a.get(dc))
- return 0
- }
- if (0 == b.V)
- return 0;
- void 0 === c && (c = Bd());
- return 0 == c % b.V ? Math.floor(c / b.V) % b.groups + 1 : 0
- };
- function fc() {
- var a, b;
- if ((b = (b = O.navigator) ? b.plugins : null) && b.length)
- for (var c = 0; c < b.length && !a; c++) {
- var d = b[c];
- -1 < d.name.indexOf("Shockwave Flash") && (a = d.description)
- }
- if (!a)
- try {
- var e = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7");
- a = e.GetVariable("$version")
- } catch (g) {}
- if (!a)
- try {
- e = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6"),
- a = "WIN 6,0,21,0",
- e.AllowScriptAccess = "always",
- a = e.GetVariable("$version")
- } catch (g) {}
- if (!a)
- try {
- e = new ActiveXObject("ShockwaveFlash.ShockwaveFlash"),
- a = e.GetVariable("$version")
- } catch (g) {}
- a && (e = a.match(/[\d]+/g)) && 3 <= e.length && (a = e[0] + "." + e[1] + " r" + e[2]);
- return a || void 0
- }
- ;var Ee = function() {
- var a = Ca("FPLC");
- return 0 < a.length ? a[0] : void 0
- }
- , Fe = function(a) {
- P(a, oe) && a.get(Ze) && a.set(Ae, Ee() || "0")
- };
- var aa = function(a) {
- var b = Math.min(R(a, dc), 100);
- return La(P(a, Q)) % 100 >= b ? !1 : !0
- }
- , gc = function(a) {
- var b = {};
- if (Ec(b) || Fc(b)) {
- var c = b[Eb];
- void 0 == c || Infinity == c || isNaN(c) || (0 < c ? (Y(b, Gb),
- Y(b, Jb),
- Y(b, Ib),
- Y(b, Fb),
- Y(b, Hb),
- Y(b, Kb),
- Y(b, Lb),
- Y(b, Ve),
- Y(b, We),
- Y(b, Xe),
- Y(b, Ye),
- va(function() {
- a(b)
- }, 10)) : L(O, "load", function() {
- gc(a)
- }, !1))
- }
- }
- , Ec = function(a) {
- var b = O.performance || O.webkitPerformance;
- b = b && b.timing;
- if (!b)
- return !1;
- var c = b.navigationStart;
- if (0 == c)
- return !1;
- a[Eb] = b.loadEventStart - c;
- a[Gb] = b.domainLookupEnd - b.domainLookupStart;
- a[Jb] = b.connectEnd - b.connectStart;
- a[Ib] = b.responseStart - b.requestStart;
- a[Fb] = b.responseEnd - b.responseStart;
- a[Hb] = b.fetchStart - c;
- a[Kb] = b.domInteractive - c;
- a[Lb] = b.domContentLoadedEventStart - c;
- a[Ve] = N.L - c;
- a[We] = N.ya - c;
- O.google_tag_manager && O.google_tag_manager._li && (b = O.google_tag_manager._li,
- a[Xe] = b.cst,
- a[Ye] = b.cbt);
- return !0
- }
- , Fc = function(a) {
- if (O.top != O)
- return !1;
- var b = O.external
- , c = b && b.onloadT;
- b && !b.isValidLoadTime && (c = void 0);
- 2147483648 < c && (c = void 0);
- 0 < c && b.setPageReadyTime();
- if (void 0 == c)
- return !1;
- a[Eb] = c;
- return !0
- }
- , Y = function(a, b) {
- var c = a[b];
- if (isNaN(c) || Infinity == c || 0 > c)
- a[b] = void 0
- }
- , Fd = function(a) {
- return function(b) {
- if ("pageview" == b.get(Va) && !a.I) {
- a.I = !0;
- var c = aa(b)
- , d = 0 < E(P(b, kb), "gclid").length;
- (c || d) && gc(function(e) {
- c && a.send("timing", e);
- d && a.send("adtiming", e)
- })
- }
- }
- };
- var hc = !1
- , mc = function(a) {
- if ("cookie" == P(a, ac)) {
- if (a.get(Hd) || P(a, xd) != P(a, Q)) {
- var b = 1E3 * R(a, Zb);
- ma(a, Q, U, b);
- a.data.set(xd, P(a, Q))
- }
- (a.get(Hd) || uc(a) != P(a, I)) && ma(a, I, la, 864E5);
- if (a.get(je)) {
- var c = P(a, ce);
- if (c) {
- var d = Math.min(R(a, he), 1E3 * R(a, Zb));
- d = Math.min(d, 1E3 * R(a, fe) + d - (new Date).getTime());
- a.data.set(he, d);
- b = {};
- var e = P(a, fe)
- , g = P(a, ie)
- , ca = kc(P(a, Yb))
- , l = lc(P(a, W))
- , k = P(a, Na)
- , w = P(a, Be);
- g && "aw.ds" != g ? b && (b.ua = !0) : (c = ["1", e, Cc(c)].join("."),
- 0 < d && (b && (b.ta = !0),
- zc("_gac_" + Cc(k), c, ca, l, k, d, w)));
- le(b)
- }
- } else
- J(75);
- (a = P(a, Ae)) && zc("FPLC", a, "/", "none", "", void 0, "Secure")
- }
- }
- , ma = function(a, b, c, d) {
- var e = nd(a, b);
- if (e) {
- c = P(a, c);
- var g = kc(P(a, Yb))
- , ca = lc(P(a, W))
- , l = P(a, Be)
- , k = P(a, Na);
- if ("auto" != ca)
- zc(c, e, g, ca, k, d, l) && (hc = !0);
- else {
- J(32);
- for (var w = id(), Ce = 0; Ce < w.length; Ce++)
- if (ca = w[Ce],
- a.data.set(W, ca),
- e = nd(a, b),
- zc(c, e, g, ca, k, d, l)) {
- hc = !0;
- return
- }
- a.data.set(W, "auto")
- }
- }
- }
- , uc = function(a) {
- var b = Ca(P(a, la));
- return Xd(a, b)
- }
- , nc = function(a) {
- if ("cookie" == P(a, ac) && !hc && (mc(a),
- !hc))
- throw "abort";
- }
- , Yc = function(a) {
- if (a.get(Wc)) {
- var b = P(a, W)
- , c = P(a, $b) || xa()
- , d = Xc("__utma", c, b);
- d && (J(19),
- a.set(Tc, (new Date).getTime(), !0),
- a.set(Rc, d.R),
- (b = Xc("__utmz", c, b)) && d.hash == b.hash && a.set(Sc, b.R))
- }
- }
- , nd = function(a, b) {
- b = Cc(P(a, b));
- var c = lc(P(a, W)).split(".").length;
- a = jc(P(a, Yb));
- 1 < a && (c += "-" + a);
- return b ? ["GA1", c, b].join(".") : ""
- }
- , Xd = function(a, b) {
- return na(b, P(a, W), P(a, Yb))
- }
- , na = function(a, b, c) {
- if (!a || 1 > a.length)
- J(12);
- else {
- for (var d = [], e = 0; e < a.length; e++) {
- var g = a[e];
- var ca = g.split(".");
- var l = ca.shift();
- ("GA1" == l || "1" == l) && 1 < ca.length ? (g = ca.shift().split("-"),
- 1 == g.length && (g[1] = "1"),
- g[0] *= 1,
- g[1] *= 1,
- ca = {
- H: g,
- s: ca.join(".")
- }) : ca = kd.test(g) ? {
- H: [0, 0],
- s: g
- } : void 0;
- ca && d.push(ca)
- }
- if (1 == d.length)
- return J(13),
- d[0].s;
- if (0 == d.length)
- J(12);
- else {
- J(14);
- d = Gc(d, lc(b).split(".").length, 0);
- if (1 == d.length)
- return d[0].s;
- d = Gc(d, jc(c), 1);
- 1 < d.length && J(41);
- return d[0] && d[0].s
- }
- }
- }
- , Gc = function(a, b, c) {
- for (var d = [], e = [], g, ca = 0; ca < a.length; ca++) {
- var l = a[ca];
- l.H[c] == b ? d.push(l) : void 0 == g || l.H[c] < g ? (e = [l],
- g = l.H[c]) : l.H[c] == g && e.push(l)
- }
- return 0 < d.length ? d : e
- }
- , lc = function(a) {
- return 0 == a.indexOf(".") ? a.substr(1) : a
- }
- , id = function() {
- var a = []
- , b = xa().split(".");
- if (4 == b.length) {
- var c = b[b.length - 1];
- if (parseInt(c, 10) == c)
- return ["none"]
- }
- for (c = b.length - 2; 0 <= c; c--)
- a.push(b.slice(c).join("."));
- b = M.location.hostname;
- eb.test(b) || vc.test(b) || a.push("none");
- return a
- }
- , kc = function(a) {
- if (!a)
- return "/";
- 1 < a.length && a.lastIndexOf("/") == a.length - 1 && (a = a.substr(0, a.length - 1));
- 0 != a.indexOf("/") && (a = "/" + a);
- return a
- }
- , jc = function(a) {
- a = kc(a);
- return "/" == a ? 1 : a.split("/").length
- }
- , le = function(a) {
- a.ta && J(77);
- a.na && J(74);
- a.pa && J(73);
- a.ua && J(69)
- };
- function Xc(a, b, c) {
- "none" == b && (b = "");
- var d = []
- , e = Ca(a);
- a = "__utma" == a ? 6 : 2;
- for (var g = 0; g < e.length; g++) {
- var ca = ("" + e[g]).split(".");
- ca.length >= a && d.push({
- hash: ca[0],
- R: e[g],
- O: ca
- })
- }
- if (0 != d.length)
- return 1 == d.length ? d[0] : Zc(b, d) || Zc(c, d) || Zc(null, d) || d[0]
- }
- function Zc(a, b) {
- if (null == a)
- var c = a = 1;
- else
- c = La(a),
- a = La(D(a, ".") ? a.substring(1) : "." + a);
- for (var d = 0; d < b.length; d++)
- if (b[d].hash == c || b[d].hash == a)
- return b[d]
- }
- ;var Jc = new RegExp(/^https?:\/\/([^\/:]+)/)
- , De = O.google_tag_data.glBridge
- , Kc = /(.*)([?])(?:_ga=[^]*)(?:&?)(.*)/
- , od = /(.*)([?])(?:_gac=[^]*)(?:&?)(.*)/;
- function Bc(a) {
- if (a.get(Ze))
- return J(35),
- De.generate($e(a));
- var b = P(a, Q)
- , c = P(a, I) || "";
- b = "_ga=2." + K(pa(c + b, 0) + "." + c + "-" + b);
- (a = af(a)) ? (J(44),
- a = "&_gac=1." + K([pa(a.qa, 0), a.timestamp, a.qa].join("."))) : a = "";
- return b + a
- }
- function Ic(a, b) {
- var c = new Date
- , d = O.navigator
- , e = d.plugins || [];
- a = [a, d.userAgent, c.getTimezoneOffset(), c.getYear(), c.getDate(), c.getHours(), c.getMinutes() + b];
- for (b = 0; b < e.length; ++b)
- a.push(e[b].description);
- return La(a.join("."))
- }
- function pa(a, b) {
- var c = new Date
- , d = O.navigator
- , e = c.getHours() + Math.floor((c.getMinutes() + b) / 60);
- return La([a, d.userAgent, d.language || "", c.getTimezoneOffset(), c.getYear(), c.getDate() + Math.floor(e / 24), (24 + e) % 24, (60 + c.getMinutes() + b) % 60].join("."))
- }
- var Dc = function(a) {
- J(48);
- this.target = a;
- this.T = !1
- };
- Dc.prototype.ca = function(a, b) {
- if (a) {
- if (this.target.get(Ze))
- return De.decorate($e(this.target), a, b);
- if (a.tagName) {
- if ("a" == a.tagName.toLowerCase()) {
- a.href && (a.href = qd(this, a.href, b));
- return
- }
- if ("form" == a.tagName.toLowerCase())
- return rd(this, a)
- }
- if ("string" == typeof a)
- return qd(this, a, b)
- }
- }
- ;
- var qd = function(a, b, c) {
- var d = Kc.exec(b);
- d && 3 <= d.length && (b = d[1] + (d[3] ? d[2] + d[3] : ""));
- (d = od.exec(b)) && 3 <= d.length && (b = d[1] + (d[3] ? d[2] + d[3] : ""));
- a = a.target.get("linkerParam");
- var e = b.indexOf("?");
- d = b.indexOf("#");
- c ? b += (-1 == d ? "#" : "&") + a : (c = -1 == e ? "?" : "&",
- b = -1 == d ? b + (c + a) : b.substring(0, d) + c + a + b.substring(d));
- b = b.replace(/&+_ga=/, "&_ga=");
- return b = b.replace(/&+_gac=/, "&_gac=")
- }
- , rd = function(a, b) {
- if (b && b.action)
- if ("get" == b.method.toLowerCase()) {
- a = a.target.get("linkerParam").split("&");
- for (var c = 0; c < a.length; c++) {
- var d = a[c].split("=")
- , e = d[1];
- d = d[0];
- for (var g = b.childNodes || [], ca = !1, l = 0; l < g.length; l++)
- if (g[l].name == d) {
- g[l].setAttribute("value", e);
- ca = !0;
- break
- }
- ca || (g = M.createElement("input"),
- g.setAttribute("type", "hidden"),
- g.setAttribute("name", d),
- g.setAttribute("value", e),
- b.appendChild(g))
- }
- } else
- "post" == b.method.toLowerCase() && (b.action = qd(a, b.action))
- };
- Dc.prototype.S = function(a, b, c) {
- function d(g) {
- try {
- g = g || O.event;
- a: {
- var ca = g.target || g.srcElement;
- for (g = 100; ca && 0 < g; ) {
- if (ca.href && ca.nodeName.match(/^a(?:rea)?$/i)) {
- var l = ca;
- break a
- }
- ca = ca.parentNode;
- g--
- }
- l = {}
- }
- ("http:" == l.protocol || "https:" == l.protocol) && sd(a, l.hostname || "") && l.href && (l.href = qd(e, l.href, b))
- } catch (k) {
- J(26)
- }
- }
- var e = this;
- this.target.get(Ze) ? De.auto(function() {
- return $e(e.target)
- }, a, b ? "fragment" : "", c) : (this.T || (this.T = !0,
- L(M, "mousedown", d, !1),
- L(M, "keyup", d, !1)),
- c && L(M, "submit", function(g) {
- g = g || O.event;
- if ((g = g.target || g.srcElement) && g.action) {
- var ca = g.action.match(Jc);
- ca && sd(a, ca[1]) && rd(e, g)
- }
- }))
- }
- ;
- function sd(a, b) {
- if (b == M.location.hostname)
- return !1;
- for (var c = 0; c < a.length; c++)
- if (a[c]instanceof RegExp) {
- if (a[c].test(b))
- return !0
- } else if (0 <= b.indexOf(a[c]))
- return !0;
- return !1
- }
- function ke(a, b) {
- return b != Ic(a, 0) && b != Ic(a, -1) && b != Ic(a, -2) && b != pa(a, 0) && b != pa(a, -1) && b != pa(a, -2)
- }
- function $e(a) {
- var b = af(a)
- , c = {};
- return c._ga = a.get(Q),
- c._gid = a.get(I) || void 0,
- c._gac = b ? [b.qa, b.timestamp].join(".") : void 0,
- c._fplc = Ee(),
- c
- }
- function af(a) {
- function b(e) {
- return void 0 == e || "" === e ? 0 : Number(e)
- }
- var c = a.get(ce);
- if (c && a.get(je)) {
- var d = b(a.get(fe));
- if (1E3 * d + b(a.get(he)) <= (new Date).getTime())
- J(76);
- else
- return {
- timestamp: d,
- qa: c
- }
- }
- }
- ;var p = /^(GTM|OPT)-[A-Z0-9]+$/
- , q = /;_gaexp=[^;]*/g
- , r = /;((__utma=)|([^;=]+=GAX?\d+\.))[^;]*/g
- , Aa = /^https?:\/\/[\w\-.]+\.google.com(:\d+)?\/optimize\/opt-launch\.html\?.*$/
- , t = function(a) {
- function b(d, e) {
- e && (c += "&" + d + "=" + K(e))
- }
- var c = "https://www.google-analytics.com/gtm/js?id=" + K(a.id);
- "dataLayer" != a.B && b("l", a.B);
- b("t", a.target);
- b("cid", a.clientId);
- b("cidt", a.ka);
- b("gac", a.la);
- b("aip", a.ia);
- a.sync && b("m", "sync");
- b("cycle", a.G);
- a.qa && b("gclid", a.qa);
- Aa.test(M.referrer) && b("cb", String(hd()));
- return c
- };
- var Jd = function(a, b, c) {
- this.aa = b;
- (b = c) || (b = (b = P(a, V)) && "t0" != b ? Wd.test(b) ? "_gat_" + Cc(P(a, Na)) : "_gat_" + Cc(b) : "_gat");
- this.Y = b;
- this.ra = null
- }
- , Rd = function(a, b) {
- var c = b.get(Wb);
- b.set(Wb, function(e) {
- Pd(a, e, ed);
- Pd(a, e, ia);
- var g = c(e);
- Qd(a, e);
- return g
- });
- var d = b.get(Xb);
- b.set(Xb, function(e) {
- var g = d(e);
- if (se(e)) {
- if (ne() !== H(a, e)) {
- J(80);
- var ca = {
- U: re(a, e, 1),
- google: re(a, e, 2),
- count: 0
- };
- pe("https://stats.g.doubleclick.net/j/collect", ca.U, ca)
- } else
- ta(re(a, e, 0));
- e.set(ed, "", !0)
- }
- return g
- })
- }
- , Pd = function(a, b, c) {
- !1 === b.get(Ud) || b.get(c) || ("1" == Ca(a.Y)[0] ? b.set(c, "", !0) : b.set(c, "" + hd(), !0))
- }
- , Qd = function(a, b) {
- se(b) && zc(a.Y, "1", P(b, Yb), P(b, W), P(b, Na), 6E4, P(b, Be))
- }
- , se = function(a) {
- return !!a.get(ed) && !1 !== a.get(Ud)
- }
- , re = function(a, b, c) {
- var d = new ee
- , e = function(ca) {
- $a(ca).F && d.set($a(ca).F, b.get(ca))
- };
- e(hb);
- e(ib);
- e(Na);
- e(Q);
- e(ed);
- if (0 == c || 1 == c)
- e(Ad),
- e(ia),
- e(I);
- d.set($a(ld).F, Td(b));
- var g = "";
- d.map(function(ca, l) {
- g += K(ca) + "=";
- g += K("" + l) + "&"
- });
- g += "z=" + hd();
- 0 == c ? g = a.aa + g : 1 == c ? g = "t=dc&aip=1&_r=3&" + g : 2 == c && (g = "t=sr&aip=1&_r=4&slf_rd=1&" + g);
- return g
- }
- , H = function(a, b) {
- null === a.ra && (a.ra = 1 === Ed(b),
- a.ra && J(33));
- return a.ra
- }
- , Wd = /^gtm\d+$/;
- var fd = function(a, b) {
- a = a.b;
- if (!a.get("dcLoaded")) {
- var c = new $c(Dd(a));
- c.set(29);
- a.set(Gd, c.C);
- b = b || {};
- var d;
- b[U] && (d = Cc(b[U]));
- b = new Jd(a,"https://stats.g.doubleclick.net/r/collect?t=dc&aip=1&_r=3&",d);
- Rd(b, a);
- a.set("dcLoaded", !0)
- }
- };
- var Sd = function(a) {
- if (!a.get("dcLoaded") && "cookie" == a.get(ac)) {
- var b = new Jd(a);
- Pd(b, a, ed);
- Pd(b, a, ia);
- Qd(b, a);
- if (se(a)) {
- var c = ne() !== H(b, a);
- a.set(Md, 1, !0);
- c ? (J(79),
- a.set(ad, "d", !0),
- a.set(qe, {
- U: re(b, a, 1),
- google: re(b, a, 2),
- count: 0
- }, !0)) : a.set(ad, "b", !0)
- }
- }
- };
- var Lc = function() {
- var a = O.gaGlobal = O.gaGlobal || {};
- return a.hid = a.hid || hd()
- };
- var wb = /^(UA|YT|MO|GP)-(\d+)-(\d+)$/
- , pc = function(a) {
- function b(e, g) {
- d.b.data.set(e, g)
- }
- function c(e, g) {
- b(e, g);
- d.filters.add(e)
- }
- var d = this;
- this.b = new Ya;
- this.filters = new Ha;
- b(V, a[V]);
- b(Na, sa(a[Na]));
- b(U, a[U]);
- b(W, a[W] || xa());
- b(Yb, a[Yb]);
- b(Zb, a[Zb]);
- b(Hd, a[Hd]);
- b(Be, a[Be]);
- b($b, a[$b]);
- b(Wc, a[Wc]);
- b(bc, a[bc]);
- b(cc, a[cc]);
- b(Ka, a[Ka]);
- b(dc, a[dc]);
- b(ec, a[ec]);
- b(ac, a[ac]);
- b(Ad, a[Ad]);
- b(n, a[n]);
- b(Kd, a[Kd]);
- b(je, a[je]);
- b(Ze, a[Ze]);
- b(oe, a[oe]);
- b(hb, 1);
- b(ib, "j83");
- c(Qb, Ma);
- c(oa, ua);
- c(dd, cd);
- c(Rb, Oa);
- c(md, vb);
- c(Sb, nc);
- c(Uc, Yc);
- c(Tb, Ja);
- c(Vb, Ta);
- c(Vc, Hc);
- c(zd, yd);
- c(Ld, Sd);
- c(ze, Fe);
- c(Wb, Pa);
- c(Xb, Sa);
- c(Cd, Fd(this));
- pd(this.b);
- td(this.b, a[Q]);
- this.b.set(jb, Lc())
- }
- , td = function(a, b) {
- var c = P(a, U);
- a.data.set(la, "_ga" == c ? "_gid" : c + "_gid");
- if ("cookie" == P(a, ac)) {
- hc = !1;
- c = Ca(P(a, U));
- c = Xd(a, c);
- if (!c) {
- c = P(a, W);
- var d = P(a, $b) || xa();
- c = Xc("__utma", d, c);
- void 0 != c ? (J(10),
- c = c.O[1] + "." + c.O[2]) : c = void 0
- }
- c && (hc = !0);
- if (d = c && !a.get(Hd))
- if (d = c.split("."),
- 2 != d.length)
- d = !1;
- else if (d = Number(d[1])) {
- var e = R(a, Zb);
- d = d + e < (new Date).getTime() / 1E3
- } else
- d = !1;
- d && (c = void 0);
- c && (a.data.set(xd, c),
- a.data.set(Q, c),
- (c = uc(a)) && a.data.set(I, c));
- if (a.get(je) && (c = a.get(ce),
- d = a.get(ie),
- !c || d && "aw.ds" != d)) {
- c = {};
- if (M) {
- d = [];
- e = M.cookie.split(";");
- for (var g = /^\s*_gac_(UA-\d+-\d+)=\s*(.+?)\s*$/, ca = 0; ca < e.length; ca++) {
- var l = e[ca].match(g);
- l && d.push({
- ja: l[1],
- value: l[2]
- })
- }
- e = {};
- if (d && d.length)
- for (g = 0; g < d.length; g++)
- (ca = d[g].value.split("."),
- "1" != ca[0] || 3 != ca.length) ? c && (c.na = !0) : ca[1] && (e[d[g].ja] ? c && (c.pa = !0) : e[d[g].ja] = [],
- e[d[g].ja].push({
- timestamp: ca[1],
- qa: ca[2]
- }));
- d = e
- } else
- d = {};
- d = d[P(a, Na)];
- le(c);
- d && 0 != d.length && (c = d[0],
- a.data.set(fe, c.timestamp),
- a.data.set(ce, c.qa))
- }
- }
- if (a.get(Hd) && (c = be("_ga", !!a.get(cc)),
- g = be("_gl", !!a.get(cc)),
- d = De.get(a.get(cc)),
- e = d._ga,
- g && 0 < g.indexOf("_ga*") && !e && J(30),
- g = d.gclid,
- ca = d._gac,
- c || e || g || ca))
- if (c && e && J(36),
- a.get(bc) || ye(a.get(Kd))) {
- e && (J(38),
- a.data.set(Q, e),
- d._gid && (J(51),
- a.data.set(I, d._gid)));
- g ? (J(82),
- a.data.set(ce, g),
- d.gclsrc && a.data.set(ie, d.gclsrc)) : ca && (e = ca.split(".")) && 2 === e.length && (J(37),
- a.data.set(ce, e[0]),
- a.data.set(fe, e[1]));
- if (d = d._fplc)
- J(83),
- a.data.set(Ae, d);
- if (c)
- b: if (d = c.indexOf("."),
- -1 == d)
- J(22);
- else {
- e = c.substring(0, d);
- g = c.substring(d + 1);
- d = g.indexOf(".");
- c = g.substring(0, d);
- g = g.substring(d + 1);
- if ("1" == e) {
- if (d = g,
- ke(d, c)) {
- J(23);
- break b
- }
- } else if ("2" == e) {
- d = g.indexOf("-");
- e = "";
- 0 < d ? (e = g.substring(0, d),
- d = g.substring(d + 1)) : d = g.substring(1);
- if (ke(e + d, c)) {
- J(53);
- break b
- }
- e && (J(2),
- a.data.set(I, e))
- } else {
- J(22);
- break b
- }
- J(11);
- a.data.set(Q, d);
- if (c = be("_gac", !!a.get(cc)))
- c = c.split("."),
- "1" != c[0] || 4 != c.length ? J(72) : ke(c[3], c[1]) ? J(71) : (a.data.set(ce, c[3]),
- a.data.set(fe, c[2]),
- J(70))
- }
- } else
- J(21);
- b && (J(9),
- a.data.set(Q, K(b)));
- a.get(Q) || (b = (b = O.gaGlobal) && b.from_cookie && "cookie" !== P(a, ac) ? void 0 : (b = b && b.vid) && -1 !== b.search(jd) ? b : void 0,
- b ? (J(17),
- a.data.set(Q, b)) : (J(8),
- a.data.set(Q, ra())));
- a.get(I) || (J(3),
- a.data.set(I, ra()));
- mc(a);
- b = O.gaGlobal = O.gaGlobal || {};
- c = P(a, Q);
- a = c === P(a, xd);
- if (void 0 == b.vid || a && !b.from_cookie)
- b.vid = c,
- b.from_cookie = a
- }
- , pd = function(a) {
- var b = O.navigator
- , c = O.screen
- , d = M.location;
- a.set(lb, ya(!!a.get(ec), !!a.get(Kd)));
- if (d) {
- var e = d.pathname || "";
- "/" != e.charAt(0) && (J(31),
- e = "/" + e);
- a.set(kb, d.protocol + "//" + d.hostname + e + d.search)
- }
- c && a.set(qb, c.width + "x" + c.height);
- c && a.set(pb, c.colorDepth + "-bit");
- c = M.documentElement;
- var g = (e = M.body) && e.clientWidth && e.clientHeight
- , ca = [];
- c && c.clientWidth && c.clientHeight && ("CSS1Compat" === M.compatMode || !g) ? ca = [c.clientWidth, c.clientHeight] : g && (ca = [e.clientWidth, e.clientHeight]);
- c = 0 >= ca[0] || 0 >= ca[1] ? "" : ca.join("x");
- a.set(rb, c);
- a.set(tb, fc());
- a.set(ob, M.characterSet || M.charset);
- a.set(sb, b && "function" === typeof b.javaEnabled && b.javaEnabled() || !1);
- a.set(nb, (b && (b.language || b.browserLanguage) || "").toLowerCase());
- a.data.set(ce, be("gclid", !0));
- a.data.set(ie, be("gclsrc", !0));
- a.data.set(fe, Math.round((new Date).getTime() / 1E3));
- if (d && a.get(cc) && (b = M.location.hash)) {
- b = b.split(/[?]+/);
- d = [];
- for (c = 0; c < b.length; ++c)
- (D(b[c], "utm_id") || D(b[c], "utm_campaign") || D(b[c], "utm_source") || D(b[c], "utm_medium") || D(b[c], "utm_term") || D(b[c], "utm_content") || D(b[c], "gclid") || D(b[c], "dclid") || D(b[c], "gclsrc")) && d.push(b[c]);
- 0 < d.length && (b = "#" + d.join("&"),
- a.set(kb, a.get(kb) + b))
- }
- };
- pc.prototype.get = function(a) {
- return this.b.get(a)
- }
- ;
- pc.prototype.set = function(a, b) {
- this.b.set(a, b)
- }
- ;
- var me = {
- pageview: [mb],
- event: [ub, xb, yb, zb],
- social: [Bb, Cb, Db],
- timing: [Mb, Nb, Pb, Ob]
- };
- pc.prototype.send = function(a) {
- if (!(1 > arguments.length)) {
- if ("string" === typeof arguments[0]) {
- var b = arguments[0];
- var c = [].slice.call(arguments, 1)
- } else
- b = arguments[0] && arguments[0][Va],
- c = arguments;
- b && (c = za(me[b] || [], c),
- c[Va] = b,
- this.b.set(c, void 0, !0),
- this.filters.D(this.b),
- this.b.data.m = {})
- }
- }
- ;
- pc.prototype.ma = function(a, b) {
- var c = this;
- u(a, c, b) || (v(a, function() {
- u(a, c, b)
- }),
- y(String(c.get(V)), a, void 0, b, !0))
- }
- ;
- var rc = function(a) {
- if ("prerender" == M.visibilityState)
- return !1;
- a();
- return !0
- }
- , z = function(a) {
- if (!rc(a)) {
- J(16);
- var b = !1
- , c = function() {
- if (!b && rc(a)) {
- b = !0;
- var d = c
- , e = M;
- e.removeEventListener ? e.removeEventListener("visibilitychange", d, !1) : e.detachEvent && e.detachEvent("onvisibilitychange", d)
- }
- };
- L(M, "visibilitychange", c)
- }
- };
- var te = /^(?:(\w+)\.)?(?:(\w+):)?(\w+)$/
- , sc = function(a) {
- if (ea(a[0]))
- this.u = a[0];
- else {
- var b = te.exec(a[0]);
- null != b && 4 == b.length && (this.c = b[1] || "t0",
- this.K = b[2] || "",
- this.methodName = b[3],
- this.a = [].slice.call(a, 1),
- this.K || (this.A = "create" == this.methodName,
- this.i = "require" == this.methodName,
- this.g = "provide" == this.methodName,
- this.ba = "remove" == this.methodName),
- this.i && (3 <= this.a.length ? (this.X = this.a[1],
- this.W = this.a[2]) : this.a[1] && (qa(this.a[1]) ? this.X = this.a[1] : this.W = this.a[1])));
- b = a[1];
- a = a[2];
- if (!this.methodName)
- throw "abort";
- if (this.i && (!qa(b) || "" == b))
- throw "abort";
- if (this.g && (!qa(b) || "" == b || !ea(a)))
- throw "abort";
- if (ud(this.c) || ud(this.K))
- throw "abort";
- if (this.g && "t0" != this.c)
- throw "abort";
- }
- };
- function ud(a) {
- return 0 <= a.indexOf(".") || 0 <= a.indexOf(":")
- }
- ;var Yd, Zd, $d, A;
- Yd = new ee;
- $d = new ee;
- A = new ee;
- Zd = {
- ec: 45,
- ecommerce: 46,
- linkid: 47
- };
- var u = function(a, b, c) {
- b == N || b.get(V);
- var d = Yd.get(a);
- if (!ea(d))
- return !1;
- b.plugins_ = b.plugins_ || new ee;
- if (b.plugins_.get(a))
- return !0;
- b.plugins_.set(a, new d(b,c || {}));
- return !0
- }
- , y = function(a, b, c, d, e) {
- if (!ea(Yd.get(b)) && !$d.get(b)) {
- Zd.hasOwnProperty(b) && J(Zd[b]);
- a = N.j(a);
- if (p.test(b)) {
- J(52);
- if (!a)
- return !0;
- c = d || {};
- d = {
- id: b,
- B: c.dataLayer || "dataLayer",
- ia: !!a.get("anonymizeIp"),
- sync: e,
- G: !1
- };
- a.get(">m") == b && (d.G = !0);
- var g = String(a.get("name"));
- "t0" != g && (d.target = g);
- G(String(a.get("trackingId"))) || (d.clientId = String(a.get(Q)),
- d.ka = Number(a.get(n)),
- c = c.palindrome ? r : q,
- c = (c = M.cookie.replace(/^|(; +)/g, ";").match(c)) ? c.sort().join("").substring(1) : void 0,
- d.la = c,
- d.qa = E(a.b.get(kb) || "", "gclid"));
- c = d.B;
- g = (new Date).getTime();
- O[c] = O[c] || [];
- g = {
- "gtm.start": g
- };
- e || (g.event = "gtm.js");
- O[c].push(g);
- c = t(d)
- }
- !c && Zd.hasOwnProperty(b) ? (J(39),
- c = b + ".js") : J(43);
- if (c) {
- if (a) {
- var ca = a.get(oe);
- qa(ca) || (ca = void 0)
- }
- c && 0 <= c.indexOf("/") || (c = (ca || bd(!1)) + "/plugins/ua/" + c);
- ca = ae(c);
- a = ca.protocol;
- d = M.location.protocol;
- if (("https:" == a || a == d || ("http:" != a ? 0 : "http:" == d)) && B(ca)) {
- if (ca = ca.url)
- a = (a = M.querySelector && M.querySelector("script[nonce]") || null) ? a.nonce || a.getAttribute && a.getAttribute("nonce") || "" : "",
- e ? (e = "",
- a && Nd.test(a) && (e = ' nonce="' + a + '"'),
- f.test(ca) && M.write("
-
-The signing key must be enabled and grant [`images:create`](/getting-started/using-the-api/permissions/). See [API key management](/management-api/api-keys/) when replacing or disabling signing credentials.
diff --git a/integrations/zapier.md b/integrations/zapier.md
deleted file mode 100644
index 471e0e9..0000000
--- a/integrations/zapier.md
+++ /dev/null
@@ -1,88 +0,0 @@
----
-layout: page
-title: Zapier integration
-nav_title: Zapier
-permalink: /integrations/zapier/
-parent: Integrations
-nav_order: 1
-description: >-
- Convert HTML/CSS to an image (jpg, png or webp) with Zapier. Renders images
- exactly like Google Chrome.
----
-# Zapier: HTML/CSS to Image
-{: .no_toc }
-{: .fs-9 }
-
-Generate images on Zapier with HTML/CSS to Image. Renders exactly like Google Chrome.
-{: .fs-4 .fw-300 }
-
-[Zapier integration](https://zapier.com/apps/htmlcss-to-image/integrations){: .btn .btn-primary .fs-5 .mb-4 .mb-md-0 .mr-2 target="_blank" }
-[Get an API Key](https://htmlcsstoimage.com){: .btn .fs-5 .mb-4 .mb-md-0 target="_blank" }
-
-
-Connect HTML/CSS to Image with thousands of integrations on Zapier.
-
-
-{% cloudinary /assets/images/zapier.png alt="Connect html css to image with Zapier" %}
-
-
-## Available Actions
-
-Our Zapier integration includes two actions.
-
-- Create images from HTML/CSS
-- Create images from a URL
-
-## Authentication
-
-When connecting the integration to your account, you can retrieve your **User ID** and **API Key** from the dashboard.
-
-
-[Get your free API Key](https://htmlcsstoimage.com/dashboard){: .btn .fs-5 .mb-4 .mb-md-0 .btn-blue target="_blank" }
-
-## Create images from HTML/CSS
-
-The Zapier integration makes it simple to create an image with the API. When creating an image you will see these options:
-
-HTML is the only required value. The rest are optional.
-
-| Name | Type | Description |
-|:-------------|:------------------|:------|
-| **html*** | `String` | This is the HTML you want to render. You can send an HTML snippet \(`
Your content
`\) or an entire webpage. |
-| **css** | `String` | The CSS for your image. |
-| **google_fonts** | `String` | [Google fonts](/guides/using-google-fonts/) to be loaded. Example: `Roboto`. Multiple fonts can be loaded like this: `Roboto|Open Sans` |
-| **ms_delay** | `Integer` | The number of milliseconds the API should delay before generating the image. This is useful when waiting for JavaScript. We recommend starting with `500`. Large values slow down the initial render time.|
-| **device_scale** | `Double` | This adjusts the pixel ratio for the screenshot. Minimum: `1`, Maximum: `3`. |
-
-{% include hint.md title="Variables in your HTML" text="When creating your HTML, the power of Zapier is using values from previous steps. You can integrate in values from Twitter, Airtable, Wordpress or any other of the Zapier integrations. Make your images unique and customized for the situation." %}
-
-## Create images from a URL
-With this Zapier integration, you can generate a screenshot of any website.
-
-The only required value is `URL` the rest are optional.
-
-
-| Name | Type | Description |
-|:-------------|:------------------|:------|
-| **url*** | `String` | The fully qualified URL to a public webpage. Such as `https://htmlcsstoimage.com`. When passed this will override the html param and will generate a screenshot of the url. |
-| **device_scale** | `Double` | This adjusts the pixel ratio for the screenshot. Minimum: `1`, Maximum: `3`. |
-| **ms_delay** | `Integer` | The number of milliseconds the API should delay before generating the image. This is useful when waiting for JavaScript. We recommend starting with `500`. Large values slow down the initial render time.|
-| **viewport_width** | `Integer` | Set the width of Chrome's viewport. This will disable automatic cropping. Both height and width parameters must be set if using either. |
-| **viewport_height** | `Integer` | Set the height of Chrome's viewport. This will disable automatic cropping. Both height and width parameters must be set if using either. |
-| **selector** | `String` | A CSS selector for an element on the webpage. We'll crop the image to this specific element. For example: `section#complete-toolkit.container-lg` |
-
-## Troubleshooting
-
-If you have trouble with the Zapier integration, please send us an email: support@htmlcsstoimage.com and we'll help you out.
-
-### Designing images with HTML/CSS
-Creating your image design can be the most challenging part. We suggest mocking up your image using [Codepen](https://pen.new). It allows you to see your HTML/CSS and edit it live.
-
-### 429 error
-If you receive this error, it means you have exceeded your plan limit for creating images this month. Take a look at your [Dashboard](https://htmlcsstoimage.com/dashboard) to check your usage and upgrade your plan.
-
-### Blank images
-When taking a screenshot of a URL, if you see a blank image, this often means that the screenshot was taken before the page was able to render anything. This can sometimes happen with complex pages that use a lot of JavaScript. To fix it, try adjusting the `ms_delay` value to slow down taking the screenshot.
-
-
-{% include code_footer.md version=1 %}
diff --git a/llms-full.txt b/llms-full.txt
deleted file mode 100644
index 0e0c7f7..0000000
--- a/llms-full.txt
+++ /dev/null
@@ -1,529 +0,0 @@
-# HTML/CSS to Image API - Complete Documentation
-
-> API for converting HTML and CSS to high-quality images (PNG, JPG, WebP, PDF). Renders exactly like Google Chrome.
-
-Website: https://htmlcsstoimage.com
-Documentation: https://docs.htmlcsstoimage.com
-Dashboard: https://htmlcsstoimage.com/dashboard
-
----
-
-## Authentication
-
-The API uses HTTP Basic authentication.
-
-- Username: Your **API ID** (`api_id`) from the dashboard
-- Password: Your **API Key** from the dashboard
-
-Get credentials at: https://htmlcsstoimage.com/dashboard
-
----
-
-## API Endpoints
-
-### Create Image
-
-```
-POST https://hcti.io/v1/image
-```
-
-Creates an image from HTML/CSS or screenshots a URL.
-
-**Required Parameters (one of):**
-
-| Parameter | Type | Description |
-|-----------|------|-------------|
-| `html` | String | HTML to render. Can be a snippet or full page. |
-| `url` | String | URL to screenshot. Must be publicly accessible. |
-
-**Optional Parameters:**
-
-| Parameter | Type | Description |
-|-----------|------|-------------|
-| `css` | String | CSS styles for your HTML |
-| `google_fonts` | String | Google Fonts to load. Multiple fonts: `Roboto|Open Sans` |
-| `device_scale` | Double | Pixel ratio (0.1-3). Default: 2 (retina) |
-| `viewport_width` | Integer | Browser viewport width in pixels |
-| `viewport_height` | Integer | Browser viewport height in pixels |
-| `viewport_mobile` | Boolean | Emulate a mobile viewport |
-| `viewport_landscape` | Boolean | Render the viewport in landscape orientation |
-| `viewport_touch` | Boolean | Enable touch support in the viewport |
-| `media_type` | String | Emulate `screen` or `print` CSS media |
-| `headers` | Object | Flat object of custom HTTP header names and values for URL screenshots. Up to 20 unique, case-insensitive names. |
-| `additional_header_origins` | Array | Additional exact HTTP(S) origins allowed to receive custom headers. Up to 20 unique origins. |
-| `include_headers_on_subrequests` | Boolean | Also send custom headers with subrequests to the requested URL's origin and any additional header origins. Default: false. |
-| `identify_as_hcti` | Boolean | Add `X-HCTI-SCREENSHOT: 1` to the top-level URL request. |
-| `selector` | String | CSS selector to capture specific element |
-| `ms_delay` | Integer | Milliseconds to wait before capture (0-10000) |
-| `max_wait_ms` | Integer | Maximum wait time cap (500-10000) |
-| `render_when_ready` | Boolean | Wait for ScreenshotReady() JS call |
-| `full_screen` | Boolean | Capture full scrollable page height |
-| `block_consent_banners` | Boolean | Block cookie consent popups |
-| `color_scheme` | String | `light` or `dark` mode |
-| `timezone` | String | IANA timezone, e.g. `America/New_York` |
-| `disable_twemoji` | Boolean | Use native emoji instead of Twemoji |
-| `transparent_background` | Boolean | Render with a transparent background. Use PNG output. |
-| `proxy_id` | String | Route the render's outbound traffic through one of your dashboard-configured HTTP proxies. Available on the 10k images/month plan or higher. See https://docs.htmlcsstoimage.com/guides/advanced/proxies/ |
-| `storage_destination_id` | String | Save rendered files to one of your organization's configured storage destinations. Available on the 10,000 images/month plan or higher. See https://docs.htmlcsstoimage.com/guides/advanced/storage-destinations/ |
-
-**Example Request:**
-
-```bash
-curl -X POST https://hcti.io/v1/image \
- -u 'your-user-id:your-api-key' \
- -H 'Content-Type: application/json' \
- -d '{
- "html": "
" }
- ]
-}
-```
-
----
-
-### Batch Delete Images
-
-```
-DELETE https://hcti.io/v1/image/batch
-```
-
-```json
-{
- "ids": ["abc123", "def456", "ghi789"]
-}
-```
-
----
-
-### List Images
-
-```
-GET https://hcti.io/v1/images
-```
-
-**Query Parameters:**
-
-| Parameter | Type | Description |
-|-----------|------|-------------|
-| `count` | Integer | Number to return (max 50) |
-| `page_token` | String | Pagination token |
-
----
-
-### Check Usage
-
-```
-GET https://hcti.io/v1/usage
-```
-
-Returns image creation counts by hour, day, month, and billing period.
-
----
-
-## Templates
-
-Templates allow reusable image designs with variable substitution. Templates can be created by sending HTML/CSS to the Templates API or by building visually in the dashboard with the Template Editor.
-
-Template Editor docs:
-- Overview: https://docs.htmlcsstoimage.com/template-editor/
-- Quick Start: https://docs.htmlcsstoimage.com/template-editor/quick-start/
-- Canvas: https://docs.htmlcsstoimage.com/template-editor/canvas/
-- Variables: https://docs.htmlcsstoimage.com/template-editor/variables/
-- Blocks: https://docs.htmlcsstoimage.com/template-editor/blocks/
-- Advanced: https://docs.htmlcsstoimage.com/template-editor/advanced/
-
-For editor templates, use the Variables guide to decide which values should be passed with `template_values`. Rich Text and HTML blocks can discover Handlebars variables from their content. Other supported properties can be connected to variables in the editor.
-
-### Create Template
-
-```
-POST https://hcti.io/v1/template
-```
-
-### Create Image from Template
-
-```
-POST https://hcti.io/v1/image
-```
-
-```json
-{
- "template_id": "t-your-template-id",
- "template_values": {
- "title": "My Title",
- "author": "John Doe"
- }
-}
-```
-
----
-
-## Automatic Open Graph Images
-
-OG Image Configs generate social cards for existing public websites, stores, CMSs, and static sites without requiring an API request or HMAC signature for every page.
-
-An exact website origin is connected to a domain ID. The path after that domain ID maps to the same path on the configured origin:
-
-| Public page | HCTI image URL |
-|-------------|----------------|
-| `https://example.com/` | `https://hcti.io/v1/og/DOMAIN_ID/` |
-| `https://example.com/articles/hello` | `https://hcti.io/v1/og/DOMAIN_ID/articles/hello` |
-
-Rendering modes:
-- **Page Screenshot:** Capture the page viewport or an element selected with `hcti:selector`.
-- **Template Values:** Render an HCTI template using mapped page metadata or explicit `html:tv:VARIABLE_NAME` meta tags.
-
-Behavior:
-- Dashboard defaults can be overridden with supported `hcti:` page metadata. Page metadata takes precedence.
-- Refresh intervals control when HCTI checks the source page for new metadata.
-- A numeric `hcti:content_version` gives changed content at the same page path a new image identity.
-- Query strings are ignored for source-page selection and cache busting.
-- Image sizing can use one universal image, adapt one render to platform-specific bounds, or render each platform size separately.
-
-Use an OG Image Config when images correspond to public page paths. Use the image API, templates, or signed image URLs when arbitrary values must be passed at request time.
-
-Documentation:
-- Setup: https://docs.htmlcsstoimage.com/getting-started/og-images/
-- Supported page parameters: https://docs.htmlcsstoimage.com/getting-started/og-images/supported-parameters/
-- Platform and CMS guides: https://docs.htmlcsstoimage.com/guides/og-images/
-- Caching and refreshes: https://docs.htmlcsstoimage.com/guides/debugging/og-image-caching/
-- Signed Image URLs: https://docs.htmlcsstoimage.com/getting-started/create-and-render/
-
----
-
-## HTTP Proxies
-
-Route a render's outbound traffic through your own HTTP proxy. Useful for restricting access to internal content, controlling the egress IP, or getting past bot detection.
-
-- Configure proxies in the dashboard at https://htmlcsstoimage.com/dashboard/proxies (URL, port, optional auth, optional bypass hosts).
-- Use a proxy by passing `proxy_id` on the create image request.
-- Disabling a proxy does not affect images that have already been generated.
-- Available on the 10,000 images/month plan or higher.
-
-Full guide: https://docs.htmlcsstoimage.com/guides/advanced/proxies/
-
----
-
-## Storage Destinations
-
-Save rendered files to an Amazon S3 or S3-compatible bucket owned by your organization.
-
-- Configure and test destinations at https://htmlcsstoimage.com/dashboard/storage-destinations
-- Supported providers: Amazon S3, Cloudflare R2, Backblaze B2, DigitalOcean Spaces, Wasabi, Google Cloud Storage, and other S3-compatible services.
-- Cloudflare R2 includes an optional **Data jurisdiction** setting. Choose **Default** for unrestricted and location-hint buckets, **European Union** for EU jurisdiction buckets, or **FedRAMP** for FedRAMP jurisdiction buckets. Changing it requires another connection test.
-- Pass `storage_destination_id` on HTML/CSS, URL, batch, or template create requests.
-- By default, HCTI retains its normal copy and also writes to your bucket.
-- **Disable HCTI Storage** keeps rendered files out of HCTI storage and the HCTI CDN.
-- When you choose to **Disable HCTI Storage**, the image create response contains a `/v1/store/...` URL. Send an authenticated `PUT` request to that URL using valid API credentials from the same organization as the image. Missing or invalid credentials return HTTP 401.
-- HTTP 200 and 424 store responses return a JSON `StoreImageResult` with `outcome`, `baseResult`, `transformationResult`, `success`, `message`, and `statusCode`. Incomplete results also include `error` and `referenceId`; other failures use the standard API error format.
-- `outcome` is `complete` (HTTP 200), `partial` (HTTP 424; base stored but transformation failed), or `failed` (HTTP 424; base not stored).
-- Each object result reports `status` (`stored`, `already_stored`, `failed`, or `not_attempted`), `target` (`hcti_storage` or `storage_destination`), `bucket`, and `key`.
-- Objects use the access settings configured on the destination bucket; HCTI does not make them public. Deleting an image or destination in HCTI does not delete rendered objects already written to the bucket.
-- Available on the 10,000 images/month plan or higher.
-
-Full guide: https://docs.htmlcsstoimage.com/guides/advanced/storage-destinations/
-
-Provider setup guides:
-
-- Amazon S3: https://docs.htmlcsstoimage.com/guides/advanced/storage-destinations/s3/
-- Cloudflare R2: https://docs.htmlcsstoimage.com/guides/advanced/storage-destinations/r2/
-- Backblaze B2: https://docs.htmlcsstoimage.com/guides/advanced/storage-destinations/backblaze-b2/
-- DigitalOcean Spaces: https://docs.htmlcsstoimage.com/guides/advanced/storage-destinations/digitalocean-spaces/
-- Wasabi: https://docs.htmlcsstoimage.com/guides/advanced/storage-destinations/wasabi/
-- Google Cloud Storage: https://docs.htmlcsstoimage.com/guides/advanced/storage-destinations/google-cloud-storage/
-- Other S3-compatible services: https://docs.htmlcsstoimage.com/guides/advanced/storage-destinations/s3-compatible/
-
----
-
-## MCP Server Integration
-
-For AI assistants like Cursor, Claude Desktop, Windsurf, Cline, and Zed.
-
-**Server URL:** `https://mcp.hcti.io`
-
-**Available Tools:**
-- `create_image` - Generate image from HTML/CSS
-- `create_url_image` - Screenshot a URL
-- `create_templated_image` - Use a template
-- `create_batch_images` - Create multiple images
-- `create_template` - Save a template
-- `list_templates` - View templates
-
-**Cursor Setup:**
-
-```json
-{
- "mcpServers": {
- "hcti": {
- "type": "http",
- "url": "https://mcp.hcti.io"
- }
- }
-}
-```
-
----
-
-## n8n Integration
-
-Use the HTML/CSS to Image integration to generate images, website screenshots, PDFs, and signed render-on-demand URLs in n8n workflows.
-
-**Package:** `@html-css-to-image/n8n-nodes-html-css-to-image`
-
-**Docs:** https://docs.htmlcsstoimage.com/integrations/n8n/
-
-**npm:** https://www.npmjs.com/package/@html-css-to-image/n8n-nodes-html-css-to-image
-
-**Available operations:**
-- Create an image or PDF from HTML and CSS
-- Take a screenshot of a webpage URL
-- Render a saved template with dynamic values
-- Generate signed template or webpage URLs
-- Return URL metadata, a binary PNG/JPG/WebP/PDF file, or both
-
----
-
-## Official TypeScript Client (npm)
-
-Use the official npm package for typed requests and helper methods in Node.js/TypeScript projects.
-
-**Package:** `@html-css-to-image/client`
-**Install:** `npm install @html-css-to-image/client`
-**Docs:** https://docs.htmlcsstoimage.com/example-code/typescript/
-**npm:** https://www.npmjs.com/package/@html-css-to-image/client
-
----
-
-## Official Python Client (PyPI)
-
-Use the official Python package for typed requests and responses, configurable HTTPX transport, and signed URL helpers.
-
-- **Package:** `html-css-to-image`
-- **Install:** `pip install html-css-to-image`
-- **Docs:** https://docs.htmlcsstoimage.com/example-code/python/
-- **PyPI:** https://pypi.org/project/html-css-to-image/
-- **GitHub:** https://github.com/htmlcsstoimage/python-client
-
----
-
-## Official PHP Client (Packagist)
-
-Use the official Composer package for typed requests and responses, injectable PSR-18 transport, and signed URL helpers.
-
-- **Package:** `html-css-to-image/client`
-- **Install:** `composer require html-css-to-image/client`
-- **Docs:** https://docs.htmlcsstoimage.com/example-code/php/
-- **Packagist:** https://packagist.org/packages/html-css-to-image/client
-- **GitHub:** https://github.com/htmlcsstoimage/php-client
-
----
-
-## Code Examples
-
-### Python
-
-```python
-import requests
-
-HCTI_API_ENDPOINT = "https://hcti.io/v1/image"
-HCTI_API_USER_ID = 'your-user-id'
-HCTI_API_KEY = 'your-api-key'
-
-data = {
- 'html': "
"}'
-```
-
----
-
-## Common Use Cases
-
-1. **Social Cards (OG Images)** - Use path-based OG Image Configs for public sites and CMSs, or templates and the image API for application data
-2. **Website Screenshots** - Capture any public URL
-3. **Dynamic Images** - Certificates, receipts, personalized graphics
-4. **Email Images** - Generate images for email campaigns
-
----
-
-## FAQ
-
-**Is there a rate limit?**
-Image creation has no per-second or per-minute rate limit and uses plan image credits. Management resources have separate limits of 100 reads and 20 writes per minute per resource family and organization, shared across REST and MCP.
-
-**How long do image URLs last?**
-Forever, as long as your account is active.
-
-**What file formats are supported?**
-PNG (default), JPG, WebP, and PDF.
-
-**Do you support custom fonts?**
-Yes, via `google_fonts` parameter or embedding font links in HTML.
-
----
-
-## Support
-
-Email: support@htmlcsstoimage.com
-
-We're experts at debugging HTML rendering issues and love helping developers succeed.
-
-## Management API, credentials, and permissions
-
-Base URL: https://hcti.io/v1. Same Basic authentication as image generation: api_id is the username, api_key is the secret password. A key's resource id is used in management URLs, not authentication.
-
-- Management overview: https://docs.htmlcsstoimage.com/management-api/
-- Interactive API reference: https://htmlcsstoimage.com/api-docs
-- API keys and authentication: https://docs.htmlcsstoimage.com/getting-started/using-the-api/api-keys/
-- Permission reference: https://docs.htmlcsstoimage.com/getting-started/using-the-api/permissions/
-- Rate limits: https://docs.htmlcsstoimage.com/getting-started/using-the-api/rate-limits/
-- Key management: https://docs.htmlcsstoimage.com/management-api/api-keys/
-- Usage: https://docs.htmlcsstoimage.com/management-api/usage/
-- Proxies: https://docs.htmlcsstoimage.com/management-api/proxies/
-- Storage destinations: https://docs.htmlcsstoimage.com/management-api/storage-destinations/
-- OG configurations: https://docs.htmlcsstoimage.com/management-api/og-configs/
-- Complete MCP tools: https://docs.htmlcsstoimage.com/integrations/mcp/tools/
-- MCP authorization: https://docs.htmlcsstoimage.com/integrations/mcp/permissions/
-
-Resource endpoints: /api-keys, /proxies, /storage-destinations, /og-configs. Create with POST, list/get with GET, delete with DELETE. Updates replace configuration and use POST. Lists use count (1–100, default 10) and page_start; follow pagination.next_page_start until null. Lists include disabled resources except keys, which require include_disabled=true.
-
-API key creation returns the secret once; subsequent public API responses omit it. DELETE disables a key and can be reversed through update. The caller cannot grant permissions beyond its own authority, and must have authority over both existing and replacement grants when updating a key. Explicit current permissions do not grant future permissions; all_future_permissions is a separate opt-in unavailable to MCP.
-
-Permissions: images:create, images:read, images:delete, images:store; templates:read, templates:create_update, templates:delete; usage:read; and read/create_update/delete for proxies, storage_destinations, og_configs, and api_keys. Management permissions are separate from using existing resources in image creation. api_keys permissions are REST-only.
-
-REST historical usage at GET /v1/usage requires usage:read. Hour/day/month series are overlapping views, not additive. MCP check_usage returns current usage/allowance/overages/reset, and get_max_batch_size returns the batch limit (0 means no batching). Both require usage:read.
-
-Management limits: 100 reads/minute and 20 writes/minute per organization for each resource family, shared across all credentials and REST/MCP. Rate-limit groups are api-key:read/write, proxy:read/write, storage-destination:read/write, and og-config:read/write. GET /storage-destinations/aws-external-id and MCP get_aws_storage_external_id require storage_destinations:create_update but use the storage read bucket. REST responses include RateLimit-Policy, RateLimit when available, and Retry-After when available on rejection. Respect Retry-After or wait 60 seconds for management throttles. MCP returns a tool error instructing a 60-second wait. Image, template, and usage endpoints do not use these management buckets. Image-credit exhaustion is a different 429; waiting a minute does not restore credits.
-
-MCP management tools: create_proxy, update_proxy, get_proxy, list_proxies, delete_proxy; create_storage_destination, update_storage_destination, get_storage_destination, list_storage_destinations, delete_storage_destination, get_aws_storage_external_id; create_og_config, update_og_config, get_og_config, list_og_configs, delete_og_config. Create/update use content (plus id on update); get/delete use id; list uses count/page_start. Management results wrap the resource or list in item with success/error. Permission or throttle failures can be direct tool errors.
-
-MCP uses OAuth. Tool discovery is not a permission grant. Reconnect and complete new authorization to add permissions; refresh does not expand grants. Do not retry denied operations until access changes. Never ask users to paste API keys or OAuth tokens into chat. No MCP tools manage API keys, delete images/templates, or change billing/membership.
-
-Proxy responses return a top-level username (null means no authentication; an empty string is a valid username), never passwords. Proxy REST/MCP updates retain a password only with authentication.retain_password=true, an unchanged username, and an omitted/null password. Otherwise supply a password; an empty string replaces it with an empty password. Omitted/null authentication removes authentication.
-
-Access-key storage REST/MCP updates retain a secret only with connection_info.retain_secret_access_key=true, unchanged provider/access_key_id, and an omitted/null secret_access_key. Otherwise supply the secret, including when disabling. Creating resources or changing credential identity requires the secret. Neither retention flag can be true alongside a supplied secret. Retention flags are request-only; Amazon S3 uses IAM roles instead.
diff --git a/llms.txt b/llms.txt
deleted file mode 100644
index 7e97ffb..0000000
--- a/llms.txt
+++ /dev/null
@@ -1,154 +0,0 @@
-# HTML/CSS to Image API
-
-> API for converting HTML and CSS to high-quality images (PNG, JPG, WebP, PDF). Renders exactly like Google Chrome.
-
-## Quick Start
-
-- **Endpoint:** `POST https://hcti.io/v1/image`
-- **Authentication:** HTTP Basic Auth (API ID + API Key)
-- **Get credentials:** https://htmlcsstoimage.com/dashboard
-
-## Example Request
-
-```bash
-curl -X POST https://hcti.io/v1/image \
- -u 'your-user-id:your-api-key' \
- -H 'Content-Type: application/json' \
- -d '{"html": "
Generate PNG, JPG, WebP, and PDF output with {language}, using HTML/CSS or reusable templates rendered in Google Chrome.
+
Generating images with {language}
+
Send your HTML/CSS to the API.
The API renders it in Google Chrome.
Read the generated image URL from the JSON response.
+
See Creating an image for request parameters. A response includes the image's url and id.
+
+
Authentication with {language}
+
Use HTTP Basic authentication with your API ID as the username and API key as the password. Find both in the dashboard. Keep credentials in server-side configuration or environment variables.
+
{language} example code
+
The example sends a POST request to https://hcti.io/v1/image. Image creation requires images:create. See authentication and API keys for scoped credentials and access errors.
Most blocks share these controls in the right sidebar. Selecting a child also shows controls for its parent layout.
+
+
Property
Details
+
+
Size
Width can be fixed, percentage-based, or automatic. Height can be fixed, flexible, or constrained by minimum and maximum values. Lock the aspect ratio to preserve proportions. Size values support variables.
+
Arrange
X and Y position blocks on the canvas or inside Free Panels, using fixed values or percentage anchors. Alignment controls place blocks at common positions. Rotate, flip, lock, or change the layer order.
+
Background
Choose a solid color, a linear, radial, or conic gradient, or a pattern. Colors support variables. Gradient stops have colors and positions and are sorted by position; linear and conic gradients also have an angle. Pattern colors and size support variables, with controls varying by pattern.
+
Border
Simple mode shares color, thickness, style, and radius across all sides. Advanced mode sets each side's color, thickness, and style and each corner's radius independently.
+
Shadow
Add layers of regular shadow, inner shadow, or glow, subject to the block-specific limits below.
+
+
+
Canvas and block backgrounds are separate: transparent areas reveal the parent or canvas. Patterns are based on CSS Pattern. See Sizing and positioning for sizing modes and position anchors.
+{cell &&
Auto size to grid cell makes a child fill its assigned area. Disable it when the child should keep its own width or height.
}
+{kind === 'text' &&
Text blocks support drop shadows and glow, without inner shadows or spread.
}
+{kind === 'image' &&
With Contain or Scale Down, the border and shadow follow the rendered image.
}
+{kind === 'shape' &&
Rectangles and circles support solid, gradient, and pattern backgrounds and advanced borders. Stars and triangles support solid colors and linear or radial gradients, simple borders, and drop shadows or glow; they do not support patterns, conic gradients, inner shadows, or shadow spread.
+
+
diff --git a/src/components/Footer.astro b/src/components/Footer.astro
new file mode 100644
index 0000000..a623541
--- /dev/null
+++ b/src/components/Footer.astro
@@ -0,0 +1,10 @@
+---
+import EditLink from '@astrojs/starlight/components/EditLink.astro';
+import ChangelogNavigation from './ChangelogNavigation.astro';
+---
+{Astro.locals.starlightRoute.entry.data.changelog && }
+
+
diff --git a/src/components/GridTracks.astro b/src/components/GridTracks.astro
new file mode 100644
index 0000000..2e4c14d
--- /dev/null
+++ b/src/components/GridTracks.astro
@@ -0,0 +1,12 @@
+
Grid tracks
+
Each column and row has its own size and background. Select a track using its hover selector or the collapsible Rows and Columns sections in the panel sidebar.
+
+
Size mode
Behavior
+
+
Defined
Use fr, %, or px.
+
Auto
Size to the content.
+
Min Content
Use the smallest intrinsic size.
+
Max Content
Use the largest intrinsic size.
+
+
+
Track backgrounds create bands or highlights. Child backgrounds appear above them; transparent children let the track background show through.
+ A 400 × 200 CSS pixel element{margin ? ' with a 20px margin on each side' : ''}. At the default device scale of 2, the output is {margin ? '880 × 480' : '800 × 400'} image pixels.
+
+
diff --git a/src/components/TemplatedContent.astro b/src/components/TemplatedContent.astro
new file mode 100644
index 0000000..87697b7
--- /dev/null
+++ b/src/components/TemplatedContent.astro
@@ -0,0 +1,3 @@
+
Templated content
+
+
Many properties can use variables to assign their value when each image is created. Variables can be required, ignored, or given a fallback value. Pass values in template_values when rendering the template.
+
diff --git a/src/components/TwoColumnContent.astro b/src/components/TwoColumnContent.astro
new file mode 100644
index 0000000..22ab0f4
--- /dev/null
+++ b/src/components/TwoColumnContent.astro
@@ -0,0 +1,30 @@
+---
+import DefaultTwoColumnContent from '@astrojs/starlight/components/TwoColumnContent.astro';
+---
+
+
+
+
+
+
+
diff --git a/src/content.config.ts b/src/content.config.ts
new file mode 100644
index 0000000..5286771
--- /dev/null
+++ b/src/content.config.ts
@@ -0,0 +1,19 @@
+import { defineCollection, z } from 'astro:content';
+import { docsLoader, i18nLoader } from '@astrojs/starlight/loaders';
+import { docsSchema, i18nSchema } from '@astrojs/starlight/schema';
+
+// Native Markdown/MDX only. Jekyll sources are never loaded by Astro.
+export const collections = { docs: defineCollection({
+ loader: docsLoader(),
+ schema: docsSchema({ extend: z.object({
+ page_title: z.string().trim().min(1).optional(),
+ og_title: z.string().trim().min(1).optional(),
+ changelog: z.object({
+ date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).refine(value => {
+ const parsed = new Date(`${value}T00:00:00Z`);
+ return !Number.isNaN(parsed.valueOf()) && parsed.toISOString().slice(0, 10) === value;
+ }, 'Use a valid YYYY-MM-DD date'),
+ anchor: z.string().regex(/^[a-z0-9_-]+$/),
+ }).optional(),
+ }) }),
+}), i18n: defineCollection({ loader: i18nLoader(), schema: i18nSchema() }) };
diff --git a/src/content/docs/404.mdx b/src/content/docs/404.mdx
new file mode 100644
index 0000000..3b0d225
--- /dev/null
+++ b/src/content/docs/404.mdx
@@ -0,0 +1,19 @@
+---
+title: Page not found
+description: Find your way back to the HTML/CSS to Image documentation.
+# Starlight reads this entry directly for /404.html; don't also publish /404/.
+draft: true
+editUrl: false
+tableOfContents: false
+sidebar:
+ hidden: true
+head:
+ - tag: meta
+ attrs:
+ name: robots
+ content: noindex, nofollow
+---
+
+We couldn't find that page. It may have moved, or the link may be incorrect.
+
+Try searching the docs, or head back to the [documentation homepage](/).
diff --git a/src/content/docs/changelog.mdx b/src/content/docs/changelog.mdx
new file mode 100644
index 0000000..9c5261f
--- /dev/null
+++ b/src/content/docs/changelog.mdx
@@ -0,0 +1,14 @@
+---
+title: Changelog
+slug: changelog
+description: The latest improvements to HTML/CSS to Image, with release notes and product updates.
+tableOfContents: false
+---
+
+import ChangelogList from '../../components/ChangelogList.astro';
+
+
+
+Product updates, new features, and improvements. Have a request? [Tell us](https://htmlcsstoimage.com/contact).
+
+
diff --git a/src/content/docs/changelog/2019_11_29__integromat-integration.md b/src/content/docs/changelog/2019_11_29__integromat-integration.md
new file mode 100644
index 0000000..ee8ef55
--- /dev/null
+++ b/src/content/docs/changelog/2019_11_29__integromat-integration.md
@@ -0,0 +1,16 @@
+---
+title: "Integromat integration"
+slug: changelog/2019-11-29-integromat-integration
+description: "Connect image generation to other apps through Integromat."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2019-11-29'
+ anchor: integromat-integration
+sidebar:
+ hidden: true
+---
+
+Our [Integromat integration](/integrations/make/) is now available. You can use the integration to connect HTML/CSS to Image with the thousands of integrations available on Integromat.
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2019_12_12__deleting-images.md b/src/content/docs/changelog/2019_12_12__deleting-images.md
new file mode 100644
index 0000000..083804e
--- /dev/null
+++ b/src/content/docs/changelog/2019_12_12__deleting-images.md
@@ -0,0 +1,16 @@
+---
+title: "Deleting images"
+slug: changelog/2019-12-12-deleting-images
+description: "Delete an image through the API; subsequent requests return HTTP 410."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2019-12-12'
+ anchor: deleting-images
+sidebar:
+ hidden: true
+---
+
+We have added the [Delete endpoint](/getting-started/using-the-api/#deleting-an-image). You can now delete an image and it will be completely removed from our servers. Requests to the image URL will return 410.
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2020_02_12__ms_delay-parameter.md b/src/content/docs/changelog/2020_02_12__ms_delay-parameter.md
new file mode 100644
index 0000000..7e38209
--- /dev/null
+++ b/src/content/docs/changelog/2020_02_12__ms_delay-parameter.md
@@ -0,0 +1,16 @@
+---
+title: "ms_delay parameter"
+slug: changelog/2020-02-12-ms_delay-parameter
+description: "Add a rendering delay for HTML with assets or JavaScript that need more time."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2020-02-12'
+ anchor: ms_delay-parameter
+sidebar:
+ hidden: true
+---
+
+We have added the `ms_delay` parameter. Allowing you to specify how long you want the rendered to wait before generating the image. Useful for HTML with many assets or JavaScript that needs extra time to load.
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2020_03_27__url-to-image.md b/src/content/docs/changelog/2020_03_27__url-to-image.md
new file mode 100644
index 0000000..6d5f7ff
--- /dev/null
+++ b/src/content/docs/changelog/2020_03_27__url-to-image.md
@@ -0,0 +1,16 @@
+---
+title: "URL to Image"
+slug: changelog/2020-03-27-url-to-image
+description: "Generate an image from a webpage URL."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2020-03-27'
+ anchor: url-to-image
+sidebar:
+ hidden: true
+---
+
+We've added the ability to create images from URL's. We're calling this URL to Image. [Learn more](/getting-started/url-to-image).
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2020_07_05__brand-new-docs-site.md b/src/content/docs/changelog/2020_07_05__brand-new-docs-site.md
new file mode 100644
index 0000000..bcc7f4b
--- /dev/null
+++ b/src/content/docs/changelog/2020_07_05__brand-new-docs-site.md
@@ -0,0 +1,17 @@
+---
+title: "Brand new docs site"
+slug: changelog/2020-07-05-brand-new-docs-site
+description: "A reorganized documentation site makes guides easier to find."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2020-07-05'
+ anchor: brand-new-docs-site
+sidebar:
+ hidden: true
+---
+
+We've released a new version of our documentation website. It's now simpler, faster & we've improved the organization of all the content.
+Please send us any feedback.
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2020_07_07__introducing-the-us-east-region.md b/src/content/docs/changelog/2020_07_07__introducing-the-us-east-region.md
new file mode 100644
index 0000000..0943af6
--- /dev/null
+++ b/src/content/docs/changelog/2020_07_07__introducing-the-us-east-region.md
@@ -0,0 +1,19 @@
+---
+title: "Introducing the US-EAST region"
+slug: changelog/2020-07-07-introducing-the-us-east-region
+description: "US-EAST rendering improves regional latency and failover."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2020-07-07'
+ anchor: introducing-the-us-east-region
+sidebar:
+ hidden: true
+---
+
+We have enabled our US-EAST region. Your API requests will now be automatically routed to either US-WEST or US-EAST based on where you ae located.
+This will improve latency for requests as well as reliability of the API. We will automatically fail over to the other region is one is unhealthy.
+
+We plan to introduce an EU region in the future.
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2020_07_17__url-to-image-performance-improvements.md b/src/content/docs/changelog/2020_07_17__url-to-image-performance-improvements.md
new file mode 100644
index 0000000..663af5b
--- /dev/null
+++ b/src/content/docs/changelog/2020_07_17__url-to-image-performance-improvements.md
@@ -0,0 +1,16 @@
+---
+title: "URL to Image performance improvements"
+slug: changelog/2020-07-17-url-to-image-performance-improvements
+description: "More reliable screenshots of pages with many requests and scripts."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2020-07-17'
+ anchor: url-to-image-performance-improvements
+sidebar:
+ hidden: true
+---
+
+We have been working on performance improvements to URL to Image. We've improved reliability when screenshotting webpages with large amounts of network requests and JavaScript.
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2020_07_22__new-zapier-templates.md b/src/content/docs/changelog/2020_07_22__new-zapier-templates.md
new file mode 100644
index 0000000..f12564c
--- /dev/null
+++ b/src/content/docs/changelog/2020_07_22__new-zapier-templates.md
@@ -0,0 +1,16 @@
+---
+title: "New Zapier templates"
+slug: changelog/2020-07-22-new-zapier-templates
+description: "New ready-made workflows for the Zapier integration."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2020-07-22'
+ anchor: new-zapier-templates
+sidebar:
+ hidden: true
+---
+
+We have added additional Zapier templates to the integration. You can see them here: [HTML/CSS to Image Templates](https://zapier.com/apps/htmlcss-to-image/integrations#zap-template-list).
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2020_07_26__render-when-ready.md b/src/content/docs/changelog/2020_07_26__render-when-ready.md
new file mode 100644
index 0000000..d1e8f4c
--- /dev/null
+++ b/src/content/docs/changelog/2020_07_26__render-when-ready.md
@@ -0,0 +1,20 @@
+---
+title: "Render when ready"
+slug: changelog/2020-07-26-render-when-ready
+description: "Control capture timing with render_when_ready and ScreenshotReady()."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2020-07-26'
+ anchor: render-when-ready
+sidebar:
+ hidden: true
+---
+
+We have added the [`render_when_ready` parameter to image creation](/parameters/render_when_ready/). This gives you control over when the image is rendered.
+
+By setting `render_when_ready` to `true`, we will wait to generate the image until your HTML calls the `ScreenshotReady()` function in JavaScript.
+
+This is useful for images that have complex JS and you need control over when it's ready to have an image created.
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2020_07_27__template-api.md b/src/content/docs/changelog/2020_07_27__template-api.md
new file mode 100644
index 0000000..aed508c
--- /dev/null
+++ b/src/content/docs/changelog/2020_07_27__template-api.md
@@ -0,0 +1,19 @@
+---
+title: "Template API"
+slug: changelog/2020-07-27-template-api
+description: "Create reusable HTML templates with values supplied at render time."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2020-07-27'
+ anchor: template-api
+sidebar:
+ hidden: true
+---
+
+The Template API is now available. With templates, you can create re-usable HTML that contains variables.
+These variables can be replaced when creating an image, making for even simpler image generation.
+
+- [HTML/CSS to Image Template API](/getting-started/templates/)
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2020_08_03__integromat-updates.md b/src/content/docs/changelog/2020_08_03__integromat-updates.md
new file mode 100644
index 0000000..21d1ce7
--- /dev/null
+++ b/src/content/docs/changelog/2020_08_03__integromat-updates.md
@@ -0,0 +1,18 @@
+---
+title: "Integromat updates"
+slug: changelog/2020-08-03-integromat-updates
+description: "Integromat adds selector, ms_delay, and device_scale options."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2020-08-03'
+ anchor: integromat-updates
+sidebar:
+ hidden: true
+---
+
+We've added additional parameters to the Integromat integration. You can now use `selector`, `ms_delay` and `device_scale`.
+
+- [HTML/CSS to Image Integromat Integration](/integrations/make/)
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2020_08_07__ruby-client-released.md b/src/content/docs/changelog/2020_08_07__ruby-client-released.md
new file mode 100644
index 0000000..ffb2b16
--- /dev/null
+++ b/src/content/docs/changelog/2020_08_07__ruby-client-released.md
@@ -0,0 +1,18 @@
+---
+title: "Ruby Client released"
+slug: changelog/2020-08-07-ruby-client-released
+description: "The first official Ruby client is available."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2020-08-07'
+ anchor: ruby-client-released
+sidebar:
+ hidden: true
+---
+
+We have released the first version of our new ruby client. This allows for easier integration with the API using Ruby.
+
+- [HTML/CSS to Image Ruby Client](https://github.com/htmlcsstoimage/ruby-client)
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2020_08_16__new-live-editor.md b/src/content/docs/changelog/2020_08_16__new-live-editor.md
new file mode 100644
index 0000000..830e0cc
--- /dev/null
+++ b/src/content/docs/changelog/2020_08_16__new-live-editor.md
@@ -0,0 +1,19 @@
+---
+title: "New Live Editor"
+slug: changelog/2020-08-16-new-live-editor
+description: "Preview HTML/CSS changes live before generating an image."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2020-08-16'
+ anchor: new-live-editor
+sidebar:
+ hidden: true
+---
+
+We've rebuilt the live demo. Now you can preview your HTML/CSS as you edit it. Then click the *image* button to see it converted into an image.
+This makes it much easier to prototype your images inside a browser.
+
+- [HTML/CSS to Image Demo](https://htmlcsstoimage.com/#demo)
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2020_08_31__zapier-plugin-released-from-beta.md b/src/content/docs/changelog/2020_08_31__zapier-plugin-released-from-beta.md
new file mode 100644
index 0000000..d253ac8
--- /dev/null
+++ b/src/content/docs/changelog/2020_08_31__zapier-plugin-released-from-beta.md
@@ -0,0 +1,19 @@
+---
+title: "Zapier plugin released from beta!"
+slug: changelog/2020-08-31-zapier-plugin-released-from-beta
+description: "The Zapier integration graduates from beta."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2020-08-31'
+ anchor: zapier-plugin-released-from-beta
+sidebar:
+ hidden: true
+---
+
+The HTML/CSS to Image Zapier integration has graduated from beta. Zapier wrote up a [nice blog post about the integration](https://zapier.com/blog/updates/2370/new-integration-htmlcss-image).
+It's become very popular quickly and we've seen many inventive ways people are improving their businesses through automated image generation.
+
+- [HTML/CSS to Image Zapier integration](https://docs.htmlcsstoimage.com/integrations/zapier)
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2020_09_10__various-performance-improvements-released.md b/src/content/docs/changelog/2020_09_10__various-performance-improvements-released.md
new file mode 100644
index 0000000..83fb7cc
--- /dev/null
+++ b/src/content/docs/changelog/2020_09_10__various-performance-improvements-released.md
@@ -0,0 +1,17 @@
+---
+title: "Various performance improvements released"
+slug: changelog/2020-09-10-various-performance-improvements-released
+description: "Faster initial rendering and improved image caching."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2020-09-10'
+ anchor: various-performance-improvements-released
+sidebar:
+ hidden: true
+---
+
+We've been working on further improving the performance of the API. Initial image renders are now ~30% faster. We have also improved caching of images.
+When cached, you can expect an average of 30-50ms when downloading an image.
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2020_09_30__font-rendering-improvements.md b/src/content/docs/changelog/2020_09_30__font-rendering-improvements.md
new file mode 100644
index 0000000..464cbc7
--- /dev/null
+++ b/src/content/docs/changelog/2020_09_30__font-rendering-improvements.md
@@ -0,0 +1,19 @@
+---
+title: "Font rendering improvements"
+slug: changelog/2020-09-30-font-rendering-improvements
+description: "Additional character sets improve rendering across languages."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2020-09-30'
+ anchor: font-rendering-improvements
+sidebar:
+ hidden: true
+---
+
+We have released a large update to the fonts available to the API. Previously, we were missing some character sets and some images would show an
+empty square instead of the correct character. Our goal is to be able to perfectly render text from any language.
+
+If you notice any more missing characters, please send us the image URL so that we can investigate it.
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2020_10_02__ruby-client-update.md b/src/content/docs/changelog/2020_10_02__ruby-client-update.md
new file mode 100644
index 0000000..499f61c
--- /dev/null
+++ b/src/content/docs/changelog/2020_10_02__ruby-client-update.md
@@ -0,0 +1,17 @@
+---
+title: "Ruby client update"
+slug: changelog/2020-10-02-ruby-client-update
+description: "A Ruby client patch fixes loading of the addressable dependency."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2020-10-02'
+ anchor: ruby-client-update
+sidebar:
+ hidden: true
+---
+
+We released a patch update to the [HTML/CSS to Image Ruby Client](https://github.com/htmlcsstoimage/ruby-client). It fixes an issue with
+`addressable` not being loaded.
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2020_11_01__additional-font-rendering-improvements.md b/src/content/docs/changelog/2020_11_01__additional-font-rendering-improvements.md
new file mode 100644
index 0000000..8c7be0c
--- /dev/null
+++ b/src/content/docs/changelog/2020_11_01__additional-font-rendering-improvements.md
@@ -0,0 +1,16 @@
+---
+title: "Additional font rendering improvements"
+slug: changelog/2020-11-01-additional-font-rendering-improvements
+description: "Improved character widths when using Helvetica Neue."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2020-11-01'
+ anchor: additional-font-rendering-improvements
+sidebar:
+ hidden: true
+---
+
+We released another improvement to default font rendering. Fixing character widths when "Helvetica Neue" is specified as the default font family.
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2020_11_13__full-screen-parameter-added-to-integromat.md b/src/content/docs/changelog/2020_11_13__full-screen-parameter-added-to-integromat.md
new file mode 100644
index 0000000..3fa2cf5
--- /dev/null
+++ b/src/content/docs/changelog/2020_11_13__full-screen-parameter-added-to-integromat.md
@@ -0,0 +1,16 @@
+---
+title: "Full screen parameter added to Integromat"
+slug: changelog/2020-11-13-full-screen-parameter-added-to-integromat
+description: "Capture a page’s full scrollable height through Integromat."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2020-11-13'
+ anchor: full-screen-parameter-added-to-integromat
+sidebar:
+ hidden: true
+---
+
+Integromat now has the full_screen parameter available. When set to true the entire height of the page will be included in the image.
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2020_12_13__improvements-to-image-deletion.md b/src/content/docs/changelog/2020_12_13__improvements-to-image-deletion.md
new file mode 100644
index 0000000..8c75bfc
--- /dev/null
+++ b/src/content/docs/changelog/2020_12_13__improvements-to-image-deletion.md
@@ -0,0 +1,17 @@
+---
+title: "Improvements to image deletion"
+slug: changelog/2020-12-13-improvements-to-image-deletion
+description: "Deleted images are removed from caches more quickly."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2020-12-13'
+ anchor: improvements-to-image-deletion
+sidebar:
+ hidden: true
+---
+
+We've improved image deletion. Previously, images might take several minutes to be uncached and deleted everywhere. Now they are removed almost instantly. For more information, learn how to
+[delete an image](/getting-started/using-the-api/#deleting-an-image).
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2020_12_20__zapier-integration-updated-to-107.md b/src/content/docs/changelog/2020_12_20__zapier-integration-updated-to-107.md
new file mode 100644
index 0000000..fa8a6e0
--- /dev/null
+++ b/src/content/docs/changelog/2020_12_20__zapier-integration-updated-to-107.md
@@ -0,0 +1,19 @@
+---
+title: "Zapier integration updated to 1.0.7"
+slug: changelog/2020-12-20-zapier-integration-updated-to-107
+description: "Zapier adds CSS for URL screenshots and selector-based cropping."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2020-12-20'
+ anchor: zapier-integration-updated-to-107
+sidebar:
+ hidden: true
+---
+
+We've released a new version of the Zapier integration. It now gives you even more configuration options when creating images. For URL images, you'll now see a CSS field. This allows you to inject CSS into the webpage.
+Making it easy to customize how the page looks.
+
+We have also added the `selector` field to the normal Create image action. This allows you to target a specific CSS selector in your HTML and we'll crop the image to that element.
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2020_12_22__inject-custom-css-into-a-webpage.md b/src/content/docs/changelog/2020_12_22__inject-custom-css-into-a-webpage.md
new file mode 100644
index 0000000..259e2fe
--- /dev/null
+++ b/src/content/docs/changelog/2020_12_22__inject-custom-css-into-a-webpage.md
@@ -0,0 +1,17 @@
+---
+title: "Inject custom CSS into a webpage"
+slug: changelog/2020-12-22-inject-custom-css-into-a-webpage
+description: "Inject CSS before capturing a webpage to customize its appearance."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2020-12-22'
+ anchor: inject-custom-css-into-a-webpage
+sidebar:
+ hidden: true
+---
+
+When using [URL to Image](/getting-started/url-to-image), you can now inject CSS into the page. This is useful for cases where you want to customize your screenshot of the page. Allowing you to do things like hide specific elements on the page
+(such as annoying popups or cookie banners). To get started with it, pass along the css param when using URL to image. This is now available on Zapier and soon will also be working for Integromat.
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2021_01_06__new-params-added-to-zapier.md b/src/content/docs/changelog/2021_01_06__new-params-added-to-zapier.md
new file mode 100644
index 0000000..79f20ed
--- /dev/null
+++ b/src/content/docs/changelog/2021_01_06__new-params-added-to-zapier.md
@@ -0,0 +1,22 @@
+---
+title: "New params added to Zapier"
+slug: changelog/2021-01-06-new-params-added-to-zapier
+description: "Zapier adds viewport dimensions, rendering delays, and improved errors."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2021-01-06'
+ anchor: new-params-added-to-zapier
+sidebar:
+ hidden: true
+---
+
+A new version (1.0.10) of our Zapier plugin has been released. It includes some great new parameters for better control over your images.
+
+- viewport_height
+- viewport_width
+- ms_delay
+
+We also improved the error messages for when you run into a problem.
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2021_01_16__integromat-integration-updated.md b/src/content/docs/changelog/2021_01_16__integromat-integration-updated.md
new file mode 100644
index 0000000..ea6d8f1
--- /dev/null
+++ b/src/content/docs/changelog/2021_01_16__integromat-integration-updated.md
@@ -0,0 +1,19 @@
+---
+title: "Integromat integration updated"
+slug: changelog/2021-01-16-integromat-integration-updated
+description: "Integromat adds CSS injection and CSS selector support."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2021-01-16'
+ anchor: integromat-integration-updated
+sidebar:
+ hidden: true
+---
+
+We released new params to the Integromat integration.
+
+- CSS injection available for URLs.
+- `selector` for specifying a CSS selector.
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2021_02_04__demo-and-example-pages-now-include-an-options-tab.md b/src/content/docs/changelog/2021_02_04__demo-and-example-pages-now-include-an-options-tab.md
new file mode 100644
index 0000000..35ab10b
--- /dev/null
+++ b/src/content/docs/changelog/2021_02_04__demo-and-example-pages-now-include-an-options-tab.md
@@ -0,0 +1,18 @@
+---
+title: "Demo and example pages now include an options tab"
+slug: changelog/2021-02-04-demo-and-example-pages-now-include-an-options-tab
+description: "Configure rendering options while trying the live demo and examples."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2021-02-04'
+ anchor: demo-and-example-pages-now-include-an-options-tab
+sidebar:
+ hidden: true
+---
+
+We updated the live demo to be even more configurable. Now you can adjust all the options on an image when testing.
+
+We have also been releasing more examples which can be played with inside of the live demo. Give them a try here: HTML/CSS to Image Examples.
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2021_02_16__improvements-to-transparency-rendering.md b/src/content/docs/changelog/2021_02_16__improvements-to-transparency-rendering.md
new file mode 100644
index 0000000..e801d6a
--- /dev/null
+++ b/src/content/docs/changelog/2021_02_16__improvements-to-transparency-rendering.md
@@ -0,0 +1,24 @@
+---
+title: "Improvements to transparency rendering"
+slug: changelog/2021-02-16-improvements-to-transparency-rendering
+description: "Transparent backgrounds are now supported for URL screenshots."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2021-02-16'
+ anchor: improvements-to-transparency-rendering
+sidebar:
+ hidden: true
+---
+
+Background transparency now also works for images generated from URLs. To do it, pass the following code in your CSS param.
+
+```
+body {
+ background-color: transparent;
+}
+```
+
+Note: this will only work for PNG's. Jpeg's do not support transparency.
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2021_03_31__image-console-on-the-dashboard.md b/src/content/docs/changelog/2021_03_31__image-console-on-the-dashboard.md
new file mode 100644
index 0000000..9a1b6d6
--- /dev/null
+++ b/src/content/docs/changelog/2021_03_31__image-console-on-the-dashboard.md
@@ -0,0 +1,18 @@
+---
+title: "Image console on the dashboard"
+slug: changelog/2021-03-31-image-console-on-the-dashboard
+description: "Create HTML/CSS images and URL screenshots from the dashboard console."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2021-03-31'
+ anchor: image-console-on-the-dashboard
+sidebar:
+ hidden: true
+---
+
+We've made a bunch of improvements to the Dashboard. You can now create images in your own account using the Image Console tab.
+
+You can also create images using URL's from the console. Click the Options tab to see the new url field.
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2021_04_24__hebrew-character-set-updated.md b/src/content/docs/changelog/2021_04_24__hebrew-character-set-updated.md
new file mode 100644
index 0000000..48b7eb3
--- /dev/null
+++ b/src/content/docs/changelog/2021_04_24__hebrew-character-set-updated.md
@@ -0,0 +1,16 @@
+---
+title: "Hebrew character set updated"
+slug: changelog/2021-04-24-hebrew-character-set-updated
+description: "Updated default fonts fix Hebrew character rendering."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2021-04-24'
+ anchor: hebrew-character-set-updated
+sidebar:
+ hidden: true
+---
+
+We released an update to our default fonts to fix a bug where hebrew characters were not rendering properly.
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2021_05_15__chrome-version-updated.md b/src/content/docs/changelog/2021_05_15__chrome-version-updated.md
new file mode 100644
index 0000000..aab08f0
--- /dev/null
+++ b/src/content/docs/changelog/2021_05_15__chrome-version-updated.md
@@ -0,0 +1,16 @@
+---
+title: "Chrome version updated"
+slug: changelog/2021-05-15-chrome-version-updated
+description: "The renderer receives an updated Chrome version."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2021-05-15'
+ anchor: chrome-version-updated
+sidebar:
+ hidden: true
+---
+
+We updated the image renderer to use the newest build of Chrome. This means all of the latest features in Chrome will also render the same through the API.
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2021_06_12__zapier-integration-updated.md b/src/content/docs/changelog/2021_06_12__zapier-integration-updated.md
new file mode 100644
index 0000000..37e3df2
--- /dev/null
+++ b/src/content/docs/changelog/2021_06_12__zapier-integration-updated.md
@@ -0,0 +1,16 @@
+---
+title: "Zapier integration updated"
+slug: changelog/2021-06-12-zapier-integration-updated
+description: "Security and performance improvements for the Zapier integration."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2021-06-12'
+ anchor: zapier-integration-updated
+sidebar:
+ hidden: true
+---
+
+Released a new version of the Zapier integration. Making security + performance improvements.
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2021_07_23__large-image-performance-improvements.md b/src/content/docs/changelog/2021_07_23__large-image-performance-improvements.md
new file mode 100644
index 0000000..14e88aa
--- /dev/null
+++ b/src/content/docs/changelog/2021_07_23__large-image-performance-improvements.md
@@ -0,0 +1,16 @@
+---
+title: "Large image performance improvements"
+slug: changelog/2021-07-23-large-image-performance-improvements
+description: "Faster rendering for large images."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2021-07-23'
+ anchor: large-image-performance-improvements
+sidebar:
+ hidden: true
+---
+
+We made a handful of performance improvements for large image rendering speed.
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2021_10_29__dashboard-improvements-1.md b/src/content/docs/changelog/2021_10_29__dashboard-improvements-1.md
new file mode 100644
index 0000000..d1b51f7
--- /dev/null
+++ b/src/content/docs/changelog/2021_10_29__dashboard-improvements-1.md
@@ -0,0 +1,16 @@
+---
+title: "Dashboard improvements"
+slug: changelog/2021-10-29-dashboard-improvements-1
+description: "Dashboard and invoice improvements based on customer feedback."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2021-10-29'
+ anchor: dashboard-improvements-1
+sidebar:
+ hidden: true
+---
+
+We released several improvements to the dashboard and invoices based on your feedback.
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2021_11_21__dashboard-improvements.md b/src/content/docs/changelog/2021_11_21__dashboard-improvements.md
new file mode 100644
index 0000000..7a2bc7d
--- /dev/null
+++ b/src/content/docs/changelog/2021_11_21__dashboard-improvements.md
@@ -0,0 +1,16 @@
+---
+title: "Dashboard improvements"
+slug: changelog/2021-11-21-dashboard-improvements
+description: "Inspect hourly, daily, and monthly image-usage rollups."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2021-11-21'
+ anchor: dashboard-improvements
+sidebar:
+ hidden: true
+---
+
+We have added a `usage` endpoint to the API. This exposes the amount of images your account has created in hourly, daily and monthly rollups. [Learn more](/getting-started/using-the-api#checking-account-usage).
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2022_05_18__ms_delay-improvements.md b/src/content/docs/changelog/2022_05_18__ms_delay-improvements.md
new file mode 100644
index 0000000..2c520be
--- /dev/null
+++ b/src/content/docs/changelog/2022_05_18__ms_delay-improvements.md
@@ -0,0 +1,21 @@
+---
+title: "ms_delay improvements"
+slug: changelog/2022-05-18-ms_delay-improvements
+description: "Rendering delays now support up to 20 seconds, with additional credits above five seconds."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2022-05-18'
+ anchor: ms_delay-improvements
+sidebar:
+ hidden: true
+---
+
+This has been a much requested feature!
+
+We have increased the `ms_delay` limit to 20 seconds (20,000ms). Usage above 5000ms does cost an additional image credit.
+For example, using `ms_delay` of 10000ms on an image will consume 2 image credits.
+
+This is useful for pages with very slow load times, such as metrics dashboards. We hope you find it useful!
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2022_06_01__integromat-integration-is-now-called-make.md b/src/content/docs/changelog/2022_06_01__integromat-integration-is-now-called-make.md
new file mode 100644
index 0000000..5becd6d
--- /dev/null
+++ b/src/content/docs/changelog/2022_06_01__integromat-integration-is-now-called-make.md
@@ -0,0 +1,16 @@
+---
+title: "Integromat integration is now called Make"
+slug: changelog/2022-06-01-integromat-integration-is-now-called-make
+description: "The Integromat integration is now named Make."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2022-06-01'
+ anchor: integromat-integration-is-now-called-make
+sidebar:
+ hidden: true
+---
+
+FYI - Integromat has rebranded to Make. We have updated all our links and documentation to now point to Make.
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2022_06_18__invoice-improvements.md b/src/content/docs/changelog/2022_06_18__invoice-improvements.md
new file mode 100644
index 0000000..f760663
--- /dev/null
+++ b/src/content/docs/changelog/2022_06_18__invoice-improvements.md
@@ -0,0 +1,17 @@
+---
+title: "Invoice improvements"
+slug: changelog/2022-06-18-invoice-improvements
+description: "View invoices, download PDFs, and pay overdue invoices from the dashboard."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2022-06-18'
+ anchor: invoice-improvements
+sidebar:
+ hidden: true
+---
+
+The invoice page has been upgraded. You can now view all of your invoices, download a PDF, and make payments
+on any overdue invoices.
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2023_04_12__getting-account-usage.md b/src/content/docs/changelog/2023_04_12__getting-account-usage.md
new file mode 100644
index 0000000..57560f5
--- /dev/null
+++ b/src/content/docs/changelog/2023_04_12__getting-account-usage.md
@@ -0,0 +1,16 @@
+---
+title: "Getting account usage"
+slug: changelog/2023-04-12-getting-account-usage
+description: "Read account usage through the usage endpoint."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2023-04-12'
+ anchor: getting-account-usage
+sidebar:
+ hidden: true
+---
+
+We've added a new page explaining how to [grab your account usage](/management-api/usage/) via the API.
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2023_11_19__new-ms_delay-and-device_scale-guides.md b/src/content/docs/changelog/2023_11_19__new-ms_delay-and-device_scale-guides.md
new file mode 100644
index 0000000..d6531d3
--- /dev/null
+++ b/src/content/docs/changelog/2023_11_19__new-ms_delay-and-device_scale-guides.md
@@ -0,0 +1,19 @@
+---
+title: "New ms_delay and device_scale guides"
+slug: changelog/2023-11-19-new-ms_delay-and-device_scale-guides
+description: "New guides explain rendering delays and device scale."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2023-11-19'
+ anchor: new-ms_delay-and-device_scale-guides
+sidebar:
+ hidden: true
+---
+
+We've added new pages with detailed information on how to better use ms_delay and device_scale.
+
+1. [ms_delay](/parameters/ms_delay/)
+2. [device_scale](/parameters/device_scale/)
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2023_12_20__learn-how-to-make-a-downloadable-image.md b/src/content/docs/changelog/2023_12_20__learn-how-to-make-a-downloadable-image.md
new file mode 100644
index 0000000..deef721
--- /dev/null
+++ b/src/content/docs/changelog/2023_12_20__learn-how-to-make-a-downloadable-image.md
@@ -0,0 +1,16 @@
+---
+title: "Learn how to make a downloadable image"
+slug: changelog/2023-12-20-learn-how-to-make-a-downloadable-image
+description: "Use the dl parameter to make an image URL download its file."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2023-12-20'
+ anchor: learn-how-to-make-a-downloadable-image
+sidebar:
+ hidden: true
+---
+
+Did you know? You can make your image downloadable by adding `?dl=1` to the end of the URL. Learn more here: [dl param](/parameters/dl/)
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2024_12_18__dpi-query-param.md b/src/content/docs/changelog/2024_12_18__dpi-query-param.md
new file mode 100644
index 0000000..bf611ca
--- /dev/null
+++ b/src/content/docs/changelog/2024_12_18__dpi-query-param.md
@@ -0,0 +1,17 @@
+---
+title: "DPI query param"
+slug: changelog/2024-12-18-dpi-query-param
+description: "Set an image’s DPI metadata with the dpi query parameter."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2024-12-18'
+ anchor: dpi-query-param
+sidebar:
+ hidden: true
+---
+
+We've added a new query param `dpi` which allows you to set the DPI metadata tag on your generated image. For example `hcti.io/v1/image/123abc?dpi=300`.
+This only sets the metadata tag, you must still create an image large enough for the needed DPI.
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2025_06_15__single-request-image-generation-endpoint.md b/src/content/docs/changelog/2025_06_15__single-request-image-generation-endpoint.md
new file mode 100644
index 0000000..226d1f6
--- /dev/null
+++ b/src/content/docs/changelog/2025_06_15__single-request-image-generation-endpoint.md
@@ -0,0 +1,24 @@
+---
+title: "Single-Request Image Generation endpoint"
+slug: changelog/2025-06-15-single-request-image-generation-endpoint
+description: "Render images on demand through signed create-and-render URLs."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2025-06-15'
+ anchor: single-request-image-generation-endpoint
+sidebar:
+ hidden: true
+---
+
+We've released a powerful new endpoint that allows you to generate images from HTML/CSS in a single request using signed URLs. This new [create-and-render endpoint](/getting-started/create-and-render/) eliminates the need for the traditional two-step process of creating an image and then fetching it.
+
+Key benefits include:
+- **Client-side friendly**: Generate image URLs without exposing your API key
+- **No POST requests**: Skip the image creation step and go straight to rendering
+- **HMAC authentication**: Secure signed URLs prevent unauthorized usage
+- **Direct image response**: URLs return images immediately when accessed
+
+This endpoint is perfect for scenarios where you need to generate image URLs from client-side code or want to simplify your image generation workflow. The endpoint uses HMAC SHA256 authentication to ensure security while keeping your API key private.
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2025_06_18__automatic-cookie-consent-banner-blocking.md b/src/content/docs/changelog/2025_06_18__automatic-cookie-consent-banner-blocking.md
new file mode 100644
index 0000000..7296f1e
--- /dev/null
+++ b/src/content/docs/changelog/2025_06_18__automatic-cookie-consent-banner-blocking.md
@@ -0,0 +1,20 @@
+---
+title: "Automatic cookie consent banner blocking"
+slug: changelog/2025-06-18-automatic-cookie-consent-banner-blocking
+description: "Automatically hide common cookie-consent banners in URL screenshots."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2025-06-18'
+ anchor: automatic-cookie-consent-banner-blocking
+sidebar:
+ hidden: true
+---
+
+We've added a new `block_consent_banners` parameter that automatically blocks cookie consent popups and banners on websites. When set to `true`, the API will detect and hide common consent frameworks like OneTrust, Cookiebot, Quantcast Choice, TrustArc, and many others.
+
+This feature is especially useful for URL screenshots where cookie banners can interfere with the desired content. Instead of manually writing CSS to hide specific banners, you can now use this single parameter to handle most cases automatically.
+
+The automatic blocking is maintained and updated regularly to support new consent frameworks as they emerge. For custom implementations not covered by the automatic blocking, you can still use the existing CSS injection method.
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2025_12_29__mcp-server-for-ai-assistants.md b/src/content/docs/changelog/2025_12_29__mcp-server-for-ai-assistants.md
new file mode 100644
index 0000000..03c2afc
--- /dev/null
+++ b/src/content/docs/changelog/2025_12_29__mcp-server-for-ai-assistants.md
@@ -0,0 +1,26 @@
+---
+title: "MCP Server for AI Assistants"
+slug: changelog/2025-12-29-mcp-server-for-ai-assistants
+description: "Create images and manage templates directly from compatible AI assistants."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2025-12-29'
+ anchor: mcp-server-for-ai-assistants
+sidebar:
+ hidden: true
+---
+
+We've launched an MCP (Model Context Protocol) server that lets you generate images directly from AI coding assistants. Simply describe what you want, and the AI will create it for you using the HTML/CSS to Image API.
+
+The MCP server supports:
+- **Image creation** from HTML/CSS or URLs
+- **Template management** for reusable image designs
+- **Batch image generation** for creating multiple images at once
+- **OAuth authentication** for seamless, secure access
+
+Works with Cursor, Claude Code, Claude Desktop, Windsurf, Cline, Zed, OpenCode, and automation platforms like Zapier and Pipedream.
+
+[Learn more about the MCP Server integration](/integrations/mcp/)
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2026_01_10__official-net--c-nuget-packages.md b/src/content/docs/changelog/2026_01_10__official-net--c-nuget-packages.md
new file mode 100644
index 0000000..9237be5
--- /dev/null
+++ b/src/content/docs/changelog/2026_01_10__official-net--c-nuget-packages.md
@@ -0,0 +1,31 @@
+---
+title: "Official .NET / C# NuGet Packages"
+slug: changelog/2026-01-10-official-net--c-nuget-packages
+description: "Official .NET packages for API access, dependency injection, Blazor, and Razor."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2026-01-10'
+ anchor: official-net--c-nuget-packages
+sidebar:
+ hidden: true
+---
+
+We've released official NuGet packages for .NET developers! The new packages make it easy to integrate HTML/CSS to Image into your .NET applications with strongly-typed models and full async support.
+
+| Package | Description |
+|:--------|:------------|
+| [HtmlCssToImage](https://www.nuget.org/packages/HtmlCssToImage) | Core client library |
+| [HtmlCssToImage.DependencyInjection](https://www.nuget.org/packages/HtmlCssToImage.DependencyInjection) | ASP.NET Core dependency injection |
+| [HtmlCssToImage.Blazor](https://www.nuget.org/packages/HtmlCssToImage.Blazor) | Open Graph image tags for Blazor |
+| [HtmlCssToImage.TagHelpers](https://www.nuget.org/packages/HtmlCssToImage.TagHelpers) | Razor tag helpers for MVC/Razor Pages |
+
+Key features:
+- **Strongly-typed requests** for HTML/CSS, URL screenshots, and templates
+- **Batch image creation** for generating multiple images in one call
+- **Signed URL generation** for create-and-render workflows
+- **Native AOT support** for .NET 9+
+
+[View the C# documentation](/example-code/c/) or check out the [GitHub repository](https://github.com/htmlcsstoimage/dotnet-client).
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2026_02_28__organizations-invites-and-user-permissions.md b/src/content/docs/changelog/2026_02_28__organizations-invites-and-user-permissions.md
new file mode 100644
index 0000000..5c90ca7
--- /dev/null
+++ b/src/content/docs/changelog/2026_02_28__organizations-invites-and-user-permissions.md
@@ -0,0 +1,29 @@
+---
+title: "Organizations, invites, and user permissions"
+slug: changelog/2026-02-28-organizations-invites-and-user-permissions
+description: "Collaborate through organizations, invitations, and member roles."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2026-02-28'
+ anchor: organizations-invites-and-user-permissions
+sidebar:
+ hidden: true
+---
+
+We've added **organizations**, **invites**, and **user permissions** so you can collaborate on an account with your team.
+
+- **Organizations** — Group API keys, usage, and billing under a single organization. You can create multiple organizations and switch between them.
+- **Invites** — Owners and admins can invite people to join an organization by email. Invitees can accept or decline; accepted invites assign a role (Owner, Admin, or User).
+- **User permissions** — Three roles control what members can do:
+ - **Owner** — Full control: manage billing, API keys, members, invites, and organization metadata.
+ - **Admin** — Manage billing, API keys, and users (invite members, change roles, remove members). Cannot assign or invite as Owner.
+ - **User** — View organization resources and create images. Cannot manage billing, API keys, or other members.
+
+**Organization settings** (members, invites, usage, billing): [https://htmlcsstoimage.com/dashboard/organization-settings](https://htmlcsstoimage.com/dashboard/organization-settings)
+
+**Account settings** (your email, organizations, switching orgs, pending invites): [https://htmlcsstoimage.com/account/settings](https://htmlcsstoimage.com/account/settings)
+
+For more detail, see [Organization settings](/guides/account/organization-settings/) and [Account settings](/guides/account/account-settings/) in the guides.
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2026_04_18__route-renders-through-your-own-http-proxy.md b/src/content/docs/changelog/2026_04_18__route-renders-through-your-own-http-proxy.md
new file mode 100644
index 0000000..6f0e6bd
--- /dev/null
+++ b/src/content/docs/changelog/2026_04_18__route-renders-through-your-own-http-proxy.md
@@ -0,0 +1,22 @@
+---
+title: "Route renders through your own HTTP proxy"
+slug: changelog/2026-04-18-route-renders-through-your-own-http-proxy
+description: "Configure your own HTTP proxies and select them for image rendering."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2026-04-18'
+ anchor: route-renders-through-your-own-http-proxy
+sidebar:
+ hidden: true
+---
+
+You can now configure HTTP proxies in the dashboard and route image renders through them. All outbound traffic for a render will go through the proxy you select. This is useful for restricting access to internal content, controlling the egress IP, or getting past bot detection.
+
+- **Configure proxies** at [https://htmlcsstoimage.com/dashboard/proxies](https://htmlcsstoimage.com/dashboard/proxies). Set a URL, port, optional auth, and optional **Bypass Hosts** (hostnames that should skip the proxy).
+- **Use a proxy** by passing `proxy_id` on the create image request.
+- **Disable safely** — turning a proxy off does not affect images that have already been generated. Only new renders that try to use that `proxy_id` will be rejected.
+
+Available on the **10,000 images/month plan or higher**. [Read the full guide](/guides/advanced/proxies/).
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2026_04_23__jumbo-images--render-up-to-80000px-without-quality-loss.md b/src/content/docs/changelog/2026_04_23__jumbo-images--render-up-to-80000px-without-quality-loss.md
new file mode 100644
index 0000000..d76aae1
--- /dev/null
+++ b/src/content/docs/changelog/2026_04_23__jumbo-images--render-up-to-80000px-without-quality-loss.md
@@ -0,0 +1,25 @@
+---
+title: "Jumbo images — render up to 80,000px without quality loss"
+slug: changelog/2026-04-23-jumbo-images--render-up-to-80000px-without-quality-loss
+description: "Render large images at full resolution with tiled rendering and jumbo dimensions."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2026-04-23'
+ anchor: jumbo-images--render-up-to-80000px-without-quality-loss
+sidebar:
+ hidden: true
+---
+
+You can now render images far beyond Chrome's usual ~8000px limit by setting `jumbo_max_width` and `jumbo_max_height`. The renderer splits the output into 8000 x 8000 tiles, renders each natively, and stitches them into a single image. Preserving your full `device_scale` so the result stays sharp at any size.
+
+- **Render up to 80,000px on each side** (subject to a 400,000,000-pixel total area cap).
+- **Quality is preserved.** Each tile renders at full resolution and the final image keeps its `device_scale`. No more blurry downscaled output when your render naturally exceeds 8000px.
+- **Both params required together.** Pass `jumbo_max_width` and `jumbo_max_height` on the create image request. Works with both `html`/`css` and `url` renders.
+- **Billed per tile.** Each 8000 x 8000 tile consumes one image credit.
+
+Without jumbo, a render larger than ~8000px is automatically scaled down to fit, which usually produces a blurry image. If you're rendering big, set the jumbo params.
+
+[Read the full Jumbo Images guide](/guides/advanced/jumbo-images/).
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2026_06_24__template-editor-for-visual-template-design.md b/src/content/docs/changelog/2026_06_24__template-editor-for-visual-template-design.md
new file mode 100644
index 0000000..55b5f3f
--- /dev/null
+++ b/src/content/docs/changelog/2026_06_24__template-editor-for-visual-template-design.md
@@ -0,0 +1,25 @@
+---
+title: "Template Editor for visual template design"
+slug: changelog/2026-06-24-template-editor-for-visual-template-design
+description: "Build reusable image templates visually with blocks, variables, and previews."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2026-06-24'
+ anchor: template-editor-for-visual-template-design
+sidebar:
+ hidden: true
+---
+
+We've introduced the Template Editor, a visual way to build saved image templates in the dashboard.
+
+Instead of writing every template by hand in HTML and CSS, you can build reusable image designs from blocks, style them visually, connect template variables, preview with test data, and render the saved template through the API.
+
+- **Visual blocks** for text, images, shapes, HTML, and layout panels.
+- **Canvas controls** for image size, background, and render settings.
+- **Template variables** for text, image URLs, colors, numbers, and other render-time values.
+- **Preview and export tools** to test your template before using it in production.
+
+Templates built in the editor are saved alongside HTML/CSS templates and render through the existing Templates API. See the [Template Editor docs](/template-editor/) to get started.
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2026_06_29__template-editor-context-menu--input-improvements.md b/src/content/docs/changelog/2026_06_29__template-editor-context-menu--input-improvements.md
new file mode 100644
index 0000000..90e3ab8
--- /dev/null
+++ b/src/content/docs/changelog/2026_06_29__template-editor-context-menu--input-improvements.md
@@ -0,0 +1,18 @@
+---
+title: "Template Editor: Context Menu & Input Improvements"
+slug: changelog/2026-06-29-template-editor-context-menu--input-improvements
+description: "New context-menu actions, gradient controls, and independent X/Y positioning."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2026-06-29'
+ anchor: template-editor-context-menu--input-improvements
+sidebar:
+ hidden: true
+---
+
+- You can now right-click on blocks and have quick access to set a subset of their properties.
+- The gradient input now has a more intuitive slider interface and interactive preview.
+- The quick position popover now lets you adjust a block's X and Y position independently, so you can center it horizontally without changing its vertical position, or vice versa.
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2026_07_16__n8n-integration.md b/src/content/docs/changelog/2026_07_16__n8n-integration.md
new file mode 100644
index 0000000..dfafb24
--- /dev/null
+++ b/src/content/docs/changelog/2026_07_16__n8n-integration.md
@@ -0,0 +1,24 @@
+---
+title: "n8n integration"
+slug: changelog/2026-07-16-n8n-integration
+description: "Generate images, PDFs, screenshots, and signed URLs in n8n workflows."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2026-07-16'
+ anchor: n8n-integration
+sidebar:
+ hidden: true
+---
+
+The HTML/CSS to Image integration for n8n is now available. It supports:
+
+- Creating images and PDFs from HTML/CSS
+- Taking screenshots of webpage URLs
+- Rendering saved templates with dynamic values
+- Generating signed render-on-demand URLs
+- Returning image metadata, binary PNG/JPG/WebP/PDF files, or both
+
+[Read the n8n integration guide](/integrations/n8n/).
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2026_07_19__htmlcsstoimage-net-client-v060.md b/src/content/docs/changelog/2026_07_19__htmlcsstoimage-net-client-v060.md
new file mode 100644
index 0000000..278d3be
--- /dev/null
+++ b/src/content/docs/changelog/2026_07_19__htmlcsstoimage-net-client-v060.md
@@ -0,0 +1,19 @@
+---
+title: "HtmlCssToImage .NET Client v0.6.0"
+slug: changelog/2026-07-19-htmlcsstoimage-net-client-v060
+description: "The .NET client adds render-time resizing, DPI adjustments, and cropping."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2026-07-19'
+ anchor: htmlcsstoimage-net-client-v060
+sidebar:
+ hidden: true
+---
+
+Our C#/.NET client was updated with improved performance for building urls and new `ImageRenderOptions` to support render-time:
+- Resizing (`height` and `width`)
+- DPI adjustments
+- Full cropping support
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2026_07_20__transparent-backgrounds-with-one-parameter.md b/src/content/docs/changelog/2026_07_20__transparent-backgrounds-with-one-parameter.md
new file mode 100644
index 0000000..9e83418
--- /dev/null
+++ b/src/content/docs/changelog/2026_07_20__transparent-backgrounds-with-one-parameter.md
@@ -0,0 +1,20 @@
+---
+title: "Transparent backgrounds with one parameter"
+slug: changelog/2026-07-20-transparent-backgrounds-with-one-parameter
+description: "Set transparent_background to render a transparent PNG with one option."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2026-07-20'
+ anchor: transparent-backgrounds-with-one-parameter
+sidebar:
+ hidden: true
+---
+
+You can now render a transparent PNG by setting `transparent_background: true` on the create image request. It works with both HTML/CSS and URL-based images, as well as saved templates and image batches.
+
+The existing CSS-based transparent background method remains supported. The official clients expose the new option as `TransparentBackground` in .NET and `transparent_background` in TypeScript.
+
+[Read the transparent background guide](/guides/styling/transparent-background/).
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2026_07_21__image-deletion-in-net-v080-and-typescript-v050.md b/src/content/docs/changelog/2026_07_21__image-deletion-in-net-v080-and-typescript-v050.md
new file mode 100644
index 0000000..43418ce
--- /dev/null
+++ b/src/content/docs/changelog/2026_07_21__image-deletion-in-net-v080-and-typescript-v050.md
@@ -0,0 +1,23 @@
+---
+title: "Image deletion in .NET v0.8.0 and TypeScript v0.5.0"
+slug: changelog/2026-07-21-image-deletion-in-net-v080-and-typescript-v050
+description: "Delete individual images or batches with the official .NET and TypeScript clients."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2026-07-21'
+ anchor: image-deletion-in-net-v080-and-typescript-v050
+sidebar:
+ hidden: true
+---
+
+The official .NET and TypeScript clients now support deleting images through the API:
+
+- Delete one image with `DeleteImageAsync` in .NET or `deleteImage` in TypeScript.
+- Delete multiple images in one request with `DeleteImageBatchAsync` in .NET or `deleteImageBatch` in TypeScript.
+
+The .NET client's `ApiResult` now implements `IDisposable` because it owns the raw `HttpResponseMessage`. The raw response remains available when you need headers, the originating request, or other transport details. C# callers should use `using` or `using var` when reading API results.
+
+[View the C# examples](/example-code/c/) or [TypeScript examples](/example-code/typescript/).
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2026_07_27__store-rendered-images-in-your-own-bucket.md b/src/content/docs/changelog/2026_07_27__store-rendered-images-in-your-own-bucket.md
new file mode 100644
index 0000000..a373a08
--- /dev/null
+++ b/src/content/docs/changelog/2026_07_27__store-rendered-images-in-your-own-bucket.md
@@ -0,0 +1,25 @@
+---
+title: "Store rendered images in your own bucket"
+slug: changelog/2026-07-27-store-rendered-images-in-your-own-bucket
+description: "Write rendered images to S3, R2, and other supported storage destinations."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2026-07-27'
+ anchor: store-rendered-images-in-your-own-bucket
+sidebar:
+ hidden: true
+---
+
+You can now configure storage destinations for Amazon S3, Cloudflare R2, Backblaze B2, DigitalOcean Spaces, Wasabi, Google Cloud Storage, and other S3-compatible services.
+
+- Add and test destinations from the dashboard, with credentials scoped to your bucket and optional key prefix.
+- Select the default, EU, or FedRAMP data jurisdiction when connecting a Cloudflare R2 bucket.
+- Pass `storage_destination_id` when creating an HTML/CSS image, URL screenshot, image batch, or template.
+- Keep the normal HTML/CSS to Image copy while also writing to your bucket, or choose to **Disable HCTI Storage** for files that should live only in your storage.
+- Authenticate `PUT /v1/store/...` requests with your user ID and API key, and use the structured response to see the outcome, object status, bucket, and key for the base image and any transformation.
+- Use `storage_destination_id` in the official TypeScript client or `StorageDestinationId` in the .NET client.
+
+Storage destinations are available on the 10,000 images/month plan or higher. [Read the Storage Destinations guide](/guides/advanced/storage-destinations/).
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2026_07_31__custom-request-headers-for-url-screenshots.md b/src/content/docs/changelog/2026_07_31__custom-request-headers-for-url-screenshots.md
new file mode 100644
index 0000000..e42f9ec
--- /dev/null
+++ b/src/content/docs/changelog/2026_07_31__custom-request-headers-for-url-screenshots.md
@@ -0,0 +1,25 @@
+---
+title: "Custom request headers for URL screenshots"
+slug: changelog/2026-07-31-custom-request-headers-for-url-screenshots
+description: "Send custom headers when capturing authenticated pages and preview environments."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2026-07-31'
+ anchor: custom-request-headers-for-url-screenshots
+sidebar:
+ hidden: true
+---
+
+URL screenshots can now include custom HTTP headers for authenticated pages, preview environments, origin-specific behavior, and other controlled workflows.
+
+- Pass `headers` as a flat JSON object, or repeat `headers=name:value` in form and create-and-render requests.
+- Custom headers are restricted to top-level navigations on the requested URL's origin by default.
+- Set `include_headers_on_subrequests: true` when same-origin CSS, images, JavaScript, or API requests also require the headers.
+- Use `additional_header_origins` to explicitly allow exact cross-origin schemes, hosts, and ports. Headers are never sent to origins outside this allowlist.
+- Set `identify_as_hcti: true` to add `X-HCTI-SCREENSHOT: 1` when your application only needs to identify a screenshot request. This predictable value should not be used for authentication.
+- Use the new options with the official .NET client v0.10.0 and TypeScript client v0.7.0, including signed create-and-render URLs.
+
+Avoid long-lived credentials and do not put secrets in signed URLs. [Read the custom headers documentation](/parameters/headers/) or see how to [allow authorized renders through Cloudflare](/guides/debugging/cloudflare-challenges/).
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2026_08_05__control-duplicate-image-detection-with-dedupe_duration_s.md b/src/content/docs/changelog/2026_08_05__control-duplicate-image-detection-with-dedupe_duration_s.md
new file mode 100644
index 0000000..0e7986d
--- /dev/null
+++ b/src/content/docs/changelog/2026_08_05__control-duplicate-image-detection-with-dedupe_duration_s.md
@@ -0,0 +1,24 @@
+---
+title: "Control duplicate image detection with dedupe_duration_s"
+slug: changelog/2026-08-05-control-duplicate-image-detection-with-dedupe_duration_s
+description: "Control how long matching image requests can reuse an existing image."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2026-08-05'
+ anchor: control-duplicate-image-detection-with-dedupe_duration_s
+sidebar:
+ hidden: true
+---
+
+You can now set `dedupe_duration_s` when creating an HTML/CSS image or URL screenshot. When an image with the same content and parameters was created within that lookback window, the API can return its existing image ID and URL without consuming another image credit.
+
+- URL screenshots default to a 0-second lookback.
+- HTML/CSS images default to 30 days on Free and Basic plans, 180 days on Pro, and 365 days on Scale.
+- Free and Basic plans support either `0` or the plan default. Pro and Scale plans support any value from `0` through their plan maximum.
+- The option is available for standard single-image POST requests and MCP-created images. It does not apply to batches, templates, or signed create-and-render URLs.
+- The official TypeScript client v0.8.0 and .NET client v0.11.0 now expose the option when creating images. It is not included in generated create-and-render URLs or batch requests.
+
+Read the [`dedupe_duration_s` parameter docs](/parameters/dedupe_duration_s/) or the [duplicate detection guide](/guides/advanced/duplicate-detection/).
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2026_08_16__automatic-open-graph-images-for-websites-and-cmss.md b/src/content/docs/changelog/2026_08_16__automatic-open-graph-images-for-websites-and-cmss.md
new file mode 100644
index 0000000..73615fa
--- /dev/null
+++ b/src/content/docs/changelog/2026_08_16__automatic-open-graph-images-for-websites-and-cmss.md
@@ -0,0 +1,25 @@
+---
+title: "Automatic Open Graph images for websites and CMSs"
+slug: changelog/2026-08-16-automatic-open-graph-images-for-websites-and-cmss
+description: "Give every public page a matching social image with one OG Image Config."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2026-08-16'
+ anchor: automatic-open-graph-images-for-websites-and-cmss
+sidebar:
+ hidden: true
+---
+
+OG Image Configs provide one path-based image URL pattern for every public page on a website, store, CMS, or static site.
+
+- Connect an exact website origin that you own or administer.
+- Use **Page Screenshot** to capture the page or a selected element, or use **Template Values** to render page metadata in an HCTI template.
+- Choose one universal image, adapt one render to platform-specific bounds, or render each platform size separately.
+- Control how often HCTI refreshes page metadata and use `hcti:content_version` when content at the same path needs a new image identity.
+- Add page-specific screenshot controls with supported `hcti:` metadata.
+- Follow dedicated setup guides for Shopify, Squarespace, Webflow, Wix, WordPress, Jekyll, Astro, and Sitepress.
+
+[Set up automatic Open Graph images](/getting-started/og-images/) or choose a [platform and CMS guide](/guides/og-images/).
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2026_08_21__ruby-client-v020.md b/src/content/docs/changelog/2026_08_21__ruby-client-v020.md
new file mode 100644
index 0000000..672e8ea
--- /dev/null
+++ b/src/content/docs/changelog/2026_08_21__ruby-client-v020.md
@@ -0,0 +1,27 @@
+---
+title: "Ruby client v0.2.0"
+slug: changelog/2026-08-21-ruby-client-v020
+description: "Ruby gains signed URLs, batch operations, and support for additional rendering options."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2026-08-21'
+ anchor: ruby-client-v020
+sidebar:
+ hidden: true
+---
+
+Version 0.2.0 of the official [HTML/CSS to Image Ruby client](https://github.com/htmlcsstoimage/ruby-client) is now available.
+
+- Generate signed create-and-render URLs for webpage screenshots and saved templates.
+- Create and delete images in batches.
+- Create images from saved templates, list template versions, and create new template versions.
+- Use current parameters including `dedupe_duration_s`, `storage_destination_id`, transparent backgrounds, custom request headers, proxies, jumbo images, and consent-banner blocking.
+
+This release changes how signed template URL tokens are generated. Ruby client 0.1.x URL-decoded the query string before calculating its HMAC. Version 0.2.0 signs the exact encoded query string included after `?`, as required by the current API.
+
+Regenerating a template URL with the same inputs after upgrading therefore produces a different token. URLs previously generated by 0.1.x continue to work.
+
+[See the Ruby examples and upgrade notes](/example-code/ruby/).
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2026_08_27__choose-the-returned-file-format-in-image-creation-requests.md b/src/content/docs/changelog/2026_08_27__choose-the-returned-file-format-in-image-creation-requests.md
new file mode 100644
index 0000000..ff1c010
--- /dev/null
+++ b/src/content/docs/changelog/2026_08_27__choose-the-returned-file-format-in-image-creation-requests.md
@@ -0,0 +1,31 @@
+---
+title: "Choose the returned file format in image creation requests"
+slug: changelog/2026-08-27-choose-the-returned-file-format-in-image-creation-requests
+description: "Choose PNG, JPG, WebP, or PDF in image creation requests."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2026-08-27'
+ anchor: choose-the-returned-file-format-in-image-creation-requests
+sidebar:
+ hidden: true
+---
+
+Image creation requests can now include `format` in the request body. This parameter selects the file extension included in the returned URL; it does not change the stored image definition.
+
+- Set `format` to `png`, `jpg`, `webp`, or `pdf` for an HTML/CSS image, URL screenshot, saved-template image, or image batch.
+- Raster images are rendered and stored as PNGs. JPG and WebP URLs convert that stored PNG when requested.
+- PDFs are rendered and saved separately when a PDF URL is requested.
+- The same image ID can still be requested later with a different supported extension.
+- Batch requests can set `format` in `default_options` or override it in an individual variation.
+- When omitted, the API returns its default extensionless URL, which renders as PNG.
+
+The official clients now expose the body parameter and apply it to generated signed URL paths:
+
+- [.NET client v0.12.0](https://github.com/htmlcsstoimage/dotnet-client) with `RenderImageFormat.PDF` support.
+- [TypeScript client v0.9.0](https://github.com/htmlcsstoimage/ts-client).
+- [Ruby client v0.3.0](https://github.com/htmlcsstoimage/ruby-client).
+
+[Read the `format` parameter documentation](/parameters/format/) or compare output types in the [file formats guide](/guides/styling/file-formats/).
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2026_09_02__htmlcss-to-image-is-now-verified-by-n8n.md b/src/content/docs/changelog/2026_09_02__htmlcss-to-image-is-now-verified-by-n8n.md
new file mode 100644
index 0000000..897ac93
--- /dev/null
+++ b/src/content/docs/changelog/2026_09_02__htmlcss-to-image-is-now-verified-by-n8n.md
@@ -0,0 +1,27 @@
+---
+title: "HTML/CSS to Image is now verified by n8n"
+slug: changelog/2026-09-02-htmlcss-to-image-is-now-verified-by-n8n
+description: "Install the verified HCTI integration directly from the n8n Nodes panel."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2026-09-02'
+ anchor: htmlcss-to-image-is-now-verified-by-n8n
+sidebar:
+ hidden: true
+---
+
+The official HTML/CSS to Image integration is now verified by n8n. Instance owners and admins can find and install it directly from the n8n Nodes panel on n8n Cloud or a self-hosted instance.
+
+Use the integration to:
+
+- Create images and PDFs from HTML/CSS.
+- Take screenshots of webpage URLs.
+- Render saved templates with dynamic values.
+- Generate signed render-on-demand URLs.
+- Delete images by ID.
+- Return image metadata, binary PNG/JPG/WebP/PDF files, or both.
+
+[View HTML/CSS to Image on n8n](https://n8n.io/integrations/htmlcss-to-image-api/) or [read the integration guide](/integrations/n8n/).
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2026_09_03__official-python-and-php-clients.md b/src/content/docs/changelog/2026_09_03__official-python-and-php-clients.md
new file mode 100644
index 0000000..b4df00d
--- /dev/null
+++ b/src/content/docs/changelog/2026_09_03__official-python-and-php-clients.md
@@ -0,0 +1,21 @@
+---
+title: "Official Python and PHP clients"
+slug: changelog/2026-09-03-official-python-and-php-clients
+description: "Official Python and PHP packages with typed requests and signed URL helpers."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2026-09-03'
+ anchor: official-python-and-php-clients
+sidebar:
+ hidden: true
+---
+
+Official HTML/CSS to Image clients are now available for Python and PHP. Each package provides typed requests and responses, configurable HTTP transport, and helpers for templates, batches, render options, image deletion, and signed URLs.
+
+- Python: [`html-css-to-image` on PyPI](https://pypi.org/project/html-css-to-image/) and the [Python examples](/example-code/python/).
+- PHP: [`html-css-to-image/client` on Packagist](https://packagist.org/packages/html-css-to-image/client) and the [PHP examples](/example-code/php/).
+
+The complete package documentation is maintained in the [Python](https://github.com/htmlcsstoimage/python-client) and [PHP](https://github.com/htmlcsstoimage/php-client) repositories.
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2026_09_14__management-api-and-scoped-permissions.md b/src/content/docs/changelog/2026_09_14__management-api-and-scoped-permissions.md
new file mode 100644
index 0000000..6bb7213
--- /dev/null
+++ b/src/content/docs/changelog/2026_09_14__management-api-and-scoped-permissions.md
@@ -0,0 +1,20 @@
+---
+title: "Management API and scoped permissions"
+slug: changelog/2026-09-14-management-api-and-scoped-permissions
+description: "Manage organization resources through REST and MCP with scoped permissions."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2026-09-14'
+ anchor: management-api-and-scoped-permissions
+sidebar:
+ hidden: true
+---
+
+Manage API keys, proxies, storage destinations, and Open Graph configurations through the [Management API](/management-api/). Create application-specific credentials with explicit permissions, monitor [usage](/management-api/usage/), and automate resource configuration.
+
+MCP adds proxy, storage destination, and OG configuration management tools. Approve the access you need through [OAuth authorization](/integrations/mcp/permissions/); API key management remains available through REST and the dashboard. See the [complete tools reference](/integrations/mcp/tools/).
+
+Management resource operations have per-organization rate limits shared across REST and MCP: 100 reads and 20 writes per minute for each resource family. These limits are separate from image credits. See [rate limits](/getting-started/using-the-api/rate-limits/).
+
+[All updates](/changelog/)
diff --git a/src/content/docs/changelog/2026_09_15__official-go-client.md b/src/content/docs/changelog/2026_09_15__official-go-client.md
new file mode 100644
index 0000000..7f384af
--- /dev/null
+++ b/src/content/docs/changelog/2026_09_15__official-go-client.md
@@ -0,0 +1,18 @@
+---
+title: "Official Go client"
+slug: changelog/2026-09-15-official-go-client
+description: "Typed Go requests, responses, and helpers for images, templates, batches, and signed URLs."
+section: Changelog
+tableOfContents: false
+changelog:
+ date: '2026-09-15'
+ anchor: official-go-client
+sidebar:
+ hidden: true
+---
+
+The official HTML/CSS to Image Go client is now available for Go 1.21+. It provides typed requests and responses for HTML/CSS images, URL screenshots, and templates, plus helpers for Google Fonts, PDF options, batches, image deletion, resizing and cropping, and signed URLs.
+
+Install it with `go get github.com/htmlcsstoimage/go-client`. See the [Go examples](/example-code/go/), [client repository](https://github.com/htmlcsstoimage/go-client), and [package documentation on pkg.go.dev](https://pkg.go.dev/github.com/htmlcsstoimage/go-client).
+
+[All updates](/changelog/)
diff --git a/example-code/c.md b/src/content/docs/example-code/c.mdx
similarity index 93%
rename from example-code/c.md
rename to src/content/docs/example-code/c.mdx
index 214921a..af90655 100644
--- a/example-code/c.md
+++ b/src/content/docs/example-code/c.mdx
@@ -1,22 +1,19 @@
---
-layout: page
title: C# / .NET
-parent: Example code
-permalink: /example-code/c/
+slug: example-code/c
description: >-
Convert HTML to an image (png, jpg or webp) with C# / .NET + the HTML/CSS to Image
API. Official NuGet package for .NET 6+.
---
-# C# / .NET: HTML/CSS to Image
-{: .no_toc }
-{: .fs-9 }
+import Help from '../../../components/Help.astro';
+import { LinkButton } from '@astrojs/starlight/components';
-Generate png, jpg or webp images with C# and .NET. Renders exactly like Google Chrome.
-{: .fs-6 .fw-300 }
+
-[Live demo](https://htmlcsstoimage.com/#demo){: .btn .btn-primary .fs-5 .mb-4 .mb-md-0 .mr-2 target="_blank" }
-[Get an API Key](https://htmlcsstoimage.com){: .btn .fs-5 .mb-4 .mb-md-0 target="_blank" }
-
+Generate PNG, JPG, or WebP images with C# and .NET, rendered in Google Chrome.
+
+Live demo
+Get an API key
## Official .NET Package
@@ -31,7 +28,6 @@ We provide an official NuGet package for .NET that makes it easy to generate ima
For full documentation, see the [GitHub repository](https://github.com/htmlcsstoimage/dotnet-client).
-
## Quick Start
@@ -77,7 +73,6 @@ if (result.Success)
API calls return an `ApiResult` that owns the raw `HttpResponseMessage`. The raw response lets you inspect headers, the originating request, and other HTTP details when needed. Use `using` or `using var` so it is disposed after you finish reading the result.
-
## Creating Images from a URL
@@ -98,7 +93,6 @@ if (result.Success)
}
```
-
## Using Templates
@@ -118,7 +112,6 @@ var request = new CreateTemplatedImageRequest
using var result = await client.CreateImageAsync(request);
```
-
## ASP.NET Core Integration
@@ -159,7 +152,6 @@ public class ImageService
}
```
-
## Blazor Integration
@@ -169,7 +161,6 @@ For Blazor applications, use the `HtmlCssToImage.Blazor` package to easily gener
dotnet add package HtmlCssToImage.Blazor
```
-
## Razor Pages / MVC Tag Helpers
@@ -179,7 +170,6 @@ For ASP.NET Core Razor Pages and MVC applications, use the `HtmlCssToImage.TagHe
dotnet add package HtmlCssToImage.TagHelpers
```
-
## Batch Image Creation
@@ -204,7 +194,6 @@ if (result.Success)
}
```
-
## Signed URLs (Create and Render)
@@ -224,13 +213,11 @@ Console.WriteLine($"Signed URL: {url}");
This is useful when you have content that may never be viewed, allowing you to generate images on-demand without using image credits upfront.
-
## Performance & Native AOT
The official .NET client is built with performance in mind and fully supports Native AOT (Ahead-of-Time) compilation in .NET 9+. The client uses source-generated JSON serialization internally.
-
## Plain HTTP Example
@@ -267,7 +254,6 @@ Console.WriteLine(json);
// {"url":"https://hcti.io/v1/image/be4c5118-fe19-462b-a49e-48cf72697a9d"}
```
-
## Legacy WebClient Example
@@ -300,4 +286,4 @@ Console.WriteLine(resultString);
// {"url":"https://hcti.io/v1/image/404ed70d-dcb9-4778-9be4-fad912321d5b"}
```
-{% include code_footer.md version=2 %}
+
diff --git a/example-code/curl.md b/src/content/docs/example-code/curl.mdx
similarity index 70%
rename from example-code/curl.md
rename to src/content/docs/example-code/curl.mdx
index 6a03a05..ae5d404 100644
--- a/example-code/curl.md
+++ b/src/content/docs/example-code/curl.mdx
@@ -1,22 +1,19 @@
---
-layout: page
title: Curl
-parent: Example code
-permalink: /example-code/curl/
+slug: example-code/curl
description: >-
Convert HTML to an image (png, jpg or webp) with Curl + the HTML/CSS to Image
API. Renders exactly like Google Chrome.
---
-# Curl
-{: .no_toc }
-{: .fs-9 }
+import Help from '../../../components/Help.astro';
+import { LinkButton } from '@astrojs/starlight/components';
-Generate a png, jpg or webp images from your terminal with cURL.
-{: .fs-4 .fw-300 }
+
-[Live demo](https://htmlcsstoimage.com/#demo){: .btn .btn-primary .fs-5 .mb-4 .mb-md-0 .mr-2 target="_blank" }
-[Get an API Key](https://htmlcsstoimage.com){: .btn .fs-5 .mb-4 .mb-md-0 target="_blank" }
-
+Generate PNG, JPG, or WebP images from your terminal with cURL.
+
+Live demo
+Get an API key
## Generate an image with cURL
@@ -24,7 +21,7 @@ Run this command in your terminal to generate an image using the API.
For more details on how this works, see [Creating an image](/getting-started/using-the-api#creating-an-image).
-```ruby
+```bash
# Replace UserID and APIKey with your credentials from the dashboard https://htmlcsstoimage.com/dashboard
curl -X POST https://hcti.io/v1/image -u 'UserID:APIKey'\
-d html="
Pong ✅
" \
@@ -35,14 +32,14 @@ curl -X POST https://hcti.io/v1/image -u 'UserID:APIKey'\
The API will return a json payload with the URL to your new image.
-```javascript
+```json
{ "url":"https://hcti.io/v1/image/1eecf460-e2e5-4db1-9db6-cf862c34a744" }
```
## Advanced example
Here we use additional parameters. Note that each line ends with a `\`.
-```ruby
+```bash
# Replace UserID and APIKey with your credentials from the dashboard https://htmlcsstoimage.com/dashboard
curl -X POST https://hcti.io/v1/image -u 'UserID:APIKey'\
-d html="
Pong ✅
" \
@@ -55,13 +52,13 @@ curl -X POST https://hcti.io/v1/image -u 'UserID:APIKey'\
In this example, we grab the contents of a local HTML file and pass them in to the `html` param.
-The `cat` command outputs the content of the file. By wrapping this in `$()` we are able to get the HTMl included in the payload.
+Use `--data-urlencode html@path/to/file.html` to read and encode the HTML file directly.
-```ruby
+```bash
curl -X POST https://hcti.io/v1/image -u 'UserID:APIKey'\
- -d html="$(cat path/to/file.html)" \
+ --data-urlencode html@path/to/file.html \
-d css=".ping { padding: 20px; font-family: 'sans-serif'; }"
```
-{% include code_footer.md version=3 %}
+
diff --git a/example-code/elixir.md b/src/content/docs/example-code/elixir.mdx
similarity index 92%
rename from example-code/elixir.md
rename to src/content/docs/example-code/elixir.mdx
index dd8962c..1fa9a21 100644
--- a/example-code/elixir.md
+++ b/src/content/docs/example-code/elixir.mdx
@@ -1,14 +1,14 @@
---
-layout: page
title: Elixir - HTML to Image Example
-nav_title: Elixir
-parent: Example code
-permalink: /example-code/elixir/
+slug: example-code/elixir
description: >-
Convert HTML to an image (png, jpg or webp) with Elixir + the HTML/CSS to Image
API. Renders exactly like Google Chrome.
---
-{% include intro.md language="Elixir" %}
+import CodeIntro from '../../../components/CodeIntro.astro';
+import Help from '../../../components/Help.astro';
+
+
This example sends JSON using [`Jason`](https://hexdocs.pm/jason/) for encoding and Erlang/OTP's built-in `:httpc` client for the HTTP request.
@@ -22,7 +22,6 @@ defp deps do
end
```
-{% raw %}
```elixir
:inets.start()
:ssl.start()
@@ -65,9 +64,6 @@ end
# {"url":"https://hcti.io/v1/image/1113184e-419f-49f1-b231-2069942a186f"}
```
-{% endraw %}
-
-
## Phoenix example with Req
@@ -86,7 +82,6 @@ end
Store your credentials in config or environment variables, then call the API from a context or service module.
-{% raw %}
```elixir
defmodule MyApp.Images.HtmlCssToImage do
@endpoint "https://hcti.io/v1/image"
@@ -114,6 +109,5 @@ defmodule MyApp.Images.HtmlCssToImage do
end
end
```
-{% endraw %}
-{% include code_footer.md version=1 %}
+
diff --git a/example-code/go.md b/src/content/docs/example-code/go.mdx
similarity index 94%
rename from example-code/go.md
rename to src/content/docs/example-code/go.mdx
index f31e6a2..32702f7 100644
--- a/example-code/go.md
+++ b/src/content/docs/example-code/go.mdx
@@ -1,14 +1,14 @@
---
-layout: page
title: Go - HTML to Image Example
-nav_title: Go
-parent: Example code
-permalink: /example-code/go/
+slug: example-code/go
description: >-
Convert HTML to an image (png, jpg or webp) with Go + the HTML/CSS to Image
API. Official Go client for Go 1.21+.
---
-{% include intro.md language="Go" %}
+import CodeIntro from '../../../components/CodeIntro.astro';
+import Help from '../../../components/Help.astro';
+
+
## Official Go client
@@ -48,7 +48,6 @@ The module's package name is `hcti`. Alternatively, use `hcti.NewClientFromEnv()
The client also supports URL screenshots, PDF options, templates, batches, image deletion, resizing and cropping, and signed URLs. See the [Go client repository](https://github.com/htmlcsstoimage/go-client) for usage and runnable examples, and the [package documentation on pkg.go.dev](https://pkg.go.dev/github.com/htmlcsstoimage/go-client) for the API reference.
-
## Direct HTTP example
@@ -106,4 +105,4 @@ func main() {
}
```
-{% include code_footer.md version=2 %}
+
diff --git a/example-code/google-apps-script.md b/src/content/docs/example-code/google-apps-script.mdx
similarity index 71%
rename from example-code/google-apps-script.md
rename to src/content/docs/example-code/google-apps-script.mdx
index 16a616d..b155a92 100644
--- a/example-code/google-apps-script.md
+++ b/src/content/docs/example-code/google-apps-script.mdx
@@ -1,22 +1,19 @@
---
-layout: page
title: Google Apps Script
-parent: Example code
-permalink: /example-code/google-apps-script/
+slug: example-code/google-apps-script
description: >-
Convert HTML to an image (png, jpg or webp) with Google Apps Script + the HTML/CSS to Image
API. Renders exactly like Google Chrome.
---
-# Google Apps Script: HTML/CSS to Image
-{: .no_toc }
-{: .fs-9 }
+import Help from '../../../components/Help.astro';
+import { LinkButton } from '@astrojs/starlight/components';
-Generate a png, jpg or webp images with Google Apps Script.
-{: .fs-4 .fw-300 }
+
-[Live demo](https://htmlcsstoimage.com/#demo){: .btn .btn-primary .fs-5 .mb-4 .mb-md-0 .mr-2 target="_blank" }
-[Get an API Key](https://htmlcsstoimage.com){: .btn .fs-5 .mb-4 .mb-md-0 target="_blank" }
-
+Generate PNG, JPG, or WebP images with Google Apps Script.
+
+Live demo
+Get an API key
## Generate an image with Google Apps Script
With [Google Apps Script](https://developers.google.com/apps-script), you can build add-ons for Google. It is based on JavaScript, but includes built in functions that you'll need to use to work with the HTML/CSS to Image API.
@@ -29,6 +26,10 @@ This shows you how to authenticate with the API and pass parameters in a POST re
```javascript
function createImage(html, css) {
+ const properties = PropertiesService.getScriptProperties();
+ const username = properties.getProperty('HCTI_API_ID');
+ const password = properties.getProperty('HCTI_API_KEY');
+ if (!username || !password) throw new Error('Set HCTI_API_ID and HCTI_API_KEY in Script Properties');
let formData = {
'html': html,
'css': css,
@@ -42,7 +43,7 @@ function createImage(html, css) {
// Replace username with your User ID and password with your API Key
options.headers = {"Authorization": "Basic " + Utilities.base64Encode(username + ":" + password)}
- JSON.parse(UrlFetchApp.fetch("https://hcti.io/v1/image", options)).url
+ return JSON.parse(UrlFetchApp.fetch("https://hcti.io/v1/image", options).getContentText()).url
}
function testCreateImage() {
@@ -73,4 +74,4 @@ var css = "div { color: red; }"
google.script.run.withSuccessHandler(onSuccess).withFailureHandler(onFailure).createImage(html, css);
```
-{% include code_footer.md version=2 %}
+
diff --git a/example-code/index.md b/src/content/docs/example-code/index.mdx
similarity index 83%
rename from example-code/index.md
rename to src/content/docs/example-code/index.mdx
index 5f33937..61edce5 100644
--- a/example-code/index.md
+++ b/src/content/docs/example-code/index.mdx
@@ -1,26 +1,20 @@
---
-layout: default
title: Example code
-permalink: /example-code/
-nav_order: 10
-has_children: true
-has_toc: false
+slug: example-code
description: >-
HTML to Image and PDF code examples for Python, PHP, JavaScript, TypeScript, Ruby, Go, C#/.NET, Java, Rust, Kotlin, Elixir, and more. Copy-paste ready API integration code.
---
-# Example code
-{: .no_toc }
-{: .fs-9 }
+import ParameterTable from '../../../components/ParameterTable.astro';
+import { Aside, LinkButton } from '@astrojs/starlight/components';
-Use these examples to render HTML/CSS, webpage screenshots, PDFs, and reusable templates from your application.
-{: .fs-4 .fw-300 }
+
-[Live demo](https://htmlcsstoimage.com/#demo){: .btn .btn-primary .fs-5 .mb-4 .mb-md-0 .mr-2 }
-[Get an API Key](https://htmlcsstoimage.com){: .btn .fs-5 .mb-4 .mb-md-0 }
+Use these examples to render HTML/CSS, webpage screenshots, PDFs, and reusable templates from your application.
-{% include hint.md title="Using an AI coding assistant?" text="Skip writing code entirely. Connect our [MCP Server](/integrations/mcp/) to generate images directly from Cursor or Claude Code." %}
+Live demo
+Get an API key
-
+
## Works with any programming language
@@ -128,15 +122,15 @@ The examples send JSON. The API also accepts form data; when using form data, ne
| Name | Type | Description |
|:-----|:-----|:------------|
-| **html**† | `String` | HTML to render. Send a snippet or a full HTML document. |
-| **css** | `String` | CSS for your HTML. When used with `url`, the CSS is injected into the page. |
-| **url**† | `String` | Fully qualified public URL to screenshot. When passed, it overrides `html`. |
+| `html`† | `String` | HTML to render. Send a snippet or a full HTML document. |
+| `css` | `String` | CSS for your HTML. When used with `url`, the CSS is injected into the page. |
+| `url`† | `String` | Fully qualified public URL to screenshot. When passed, it overrides `html`. |
-{% include hint.md title="Required params" text="† Either `html` OR `url` is required, but not both. `css` is optional." %}
+
### Rendering options
-{% include additional_parameters.md %}
+
When rendering templated images, send a `POST` request to `https://hcti.io/v1/image/:template_id` with `template_values` as JSON:
diff --git a/example-code/java.md b/src/content/docs/example-code/java.mdx
similarity index 92%
rename from example-code/java.md
rename to src/content/docs/example-code/java.mdx
index 3aac726..2f3506d 100644
--- a/example-code/java.md
+++ b/src/content/docs/example-code/java.mdx
@@ -1,14 +1,14 @@
---
-layout: page
title: Java - HTML to Image Example
-nav_title: Java
-parent: Example code
-permalink: /example-code/java/
+slug: example-code/java
description: >-
Convert HTML to an image (png, jpg or webp) with Java + the HTML/CSS to Image
API. Renders exactly like Google Chrome.
---
-{% include intro.md language="Java" %}
+import CodeIntro from '../../../components/CodeIntro.astro';
+import Help from '../../../components/Help.astro';
+
+
Java includes an HTTP client in the standard library, but not a JSON encoder. This example uses the built-in Java 11+ `HttpClient` with [`Jackson`](https://github.com/FasterXML/jackson) to encode the JSON request body.
@@ -69,4 +69,4 @@ public class HctiExample {
}
```
-{% include code_footer.md version=1 %}
+
diff --git a/example-code/javascript.md b/src/content/docs/example-code/javascript.mdx
similarity index 60%
rename from example-code/javascript.md
rename to src/content/docs/example-code/javascript.mdx
index cb58118..ffeb885 100644
--- a/example-code/javascript.md
+++ b/src/content/docs/example-code/javascript.mdx
@@ -1,21 +1,24 @@
---
-layout: default
title: JavaScript - HTML to Image Example
-nav_title: JavaScript
-parent: Example code
-permalink: /example-code/javascript/
+slug: example-code/javascript
description: >-
Convert HTML to an image (png, jpg or webp) with JavaScript + the HTML/CSS to Image
API. Renders exactly like Google Chrome.
---
-{% include intro.md language="JavaScript" %}
+import CodeIntro from '../../../components/CodeIntro.astro';
+import Help from '../../../components/Help.astro';
+import { Aside } from '@astrojs/starlight/components';
+
+
Looking for the official npm client? See the **Official npm client** section on the [TypeScript example page](/example-code/typescript/).
-This example uses the [Request client](https://github.com/request/request-promise). Install with `npm install request`.
+This example uses Node.js's built-in `fetch`. Run it as an ES module on Node.js 18 or newer.
```javascript
-const request = require('request')
+const API_ID = process.env.HCTI_API_ID;
+const API_KEY = process.env.HCTI_API_KEY;
+if (!API_ID || !API_KEY) throw new Error('Set HCTI_API_ID and HCTI_API_KEY');
// Define your HTML/CSS
const data = {
@@ -26,20 +29,23 @@ const data = {
// Create an image by sending a POST to the API.
// Retrieve your api_id and api_key from the Dashboard. https://htmlcsstoimage.com/dashboard
-request.post({ url: 'https://hcti.io/v1/image', form: data})
- .auth(API_ID, API_KEY)
- .on('data', function(data) {
- console.log(JSON.parse(data))
- })
+const response = await fetch('https://hcti.io/v1/image', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': 'Basic ' + Buffer.from(`${API_ID}:${API_KEY}`).toString('base64'),
+ },
+ body: JSON.stringify(data),
+});
+if (!response.ok) throw new Error(`API request failed: ${response.status}`);
+console.log(await response.json());
// {"url": "https://hcti.io/v1/image/1113184e-419f-49f1-b231-2069942a186f"}
```
To see all of the available parameters, see: [Creating an image](/getting-started/using-the-api/#creating-an-image).
-{% include hint.md title="Can I use this in a browser?" text="We recommend only using the API server-side. This is important because it keeps your API key secret. If you expose them in the browser, they can be used by anyone." %}
-
-
+
## JavaScript example - async/await
@@ -73,7 +79,6 @@ async function createImage() {
createImage();
```
-
## Plain JavaScript \(Node.js\) example
@@ -99,16 +104,23 @@ const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
- 'Authorization': 'Basic ' + new Buffer(apiId + ':' + apiKey).toString('base64')
+ 'Authorization': 'Basic ' + Buffer.from(apiId + ':' + apiKey).toString('base64')
}
}
const req = https.request(options, (res) => {
console.log(`statusCode: ${res.statusCode}`)
- res.on('data', (d) => {
- const image = JSON.parse(d)
- console.log(image["url"])
+ let body = '';
+ res.setEncoding('utf8');
+ res.on('data', (chunk) => { body += chunk; });
+ res.on('end', () => {
+ if (res.statusCode < 200 || res.statusCode >= 300) {
+ console.error(`API request failed: ${res.statusCode}`, body);
+ return;
+ }
+ try { console.log(JSON.parse(body).url); }
+ catch (error) { console.error('Invalid JSON response', error); }
})
})
@@ -120,10 +132,11 @@ req.write(data)
req.end()
```
-## Client side JavaScript example with Fetch API
-- Use the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) to make an HTTP POST request to the API
-- Supported by modern browsers
-- Recommended for internal applications only to keep your API key safe
+
+
+## Browser integration with Fetch
+
+Use the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) to call your own backend. The backend validates the request and calls HCTI with server-side credentials using the example above. This keeps your API key out of browser code, including internal applications.
```javascript
const json = {
@@ -131,19 +144,16 @@ const json = {
css: ".test { background-color: green; }"
};
-const username = "user-id";
-const password = "api-key";
-
const options = {
method: 'POST',
body: JSON.stringify(json),
headers: {
- 'Content-Type': 'application/json',
- 'Authorization': 'Basic ' + btoa(username + ":" + password)
+ 'Content-Type': 'application/json'
}
}
-fetch('https://hcti.io/v1/image', options)
+// Implement this route in your backend and return the HCTI JSON response.
+fetch('/api/create-image', options)
.then(res => {
if (res.ok) {
return res.json();
@@ -158,4 +168,4 @@ fetch('https://hcti.io/v1/image', options)
.catch(err => console.error(err));
```
-{% include code_footer.md version=1 %}
+
diff --git a/example-code/kotlin.md b/src/content/docs/example-code/kotlin.mdx
similarity index 90%
rename from example-code/kotlin.md
rename to src/content/docs/example-code/kotlin.mdx
index f7de592..4d4c9c9 100644
--- a/example-code/kotlin.md
+++ b/src/content/docs/example-code/kotlin.mdx
@@ -1,14 +1,14 @@
---
-layout: page
title: Kotlin - HTML to Image Example
-nav_title: Kotlin
-parent: Example code
-permalink: /example-code/kotlin/
+slug: example-code/kotlin
description: >-
Convert HTML to an image (png, jpg or webp) with Kotlin + the HTML/CSS to Image
API. Renders exactly like Google Chrome.
---
-{% include intro.md language="Kotlin" %}
+import CodeIntro from '../../../components/CodeIntro.astro';
+import Help from '../../../components/Help.astro';
+
+
This example uses the built-in Java 11+ `HttpClient` from Kotlin and [`kotlinx.serialization`](https://github.com/Kotlin/kotlinx.serialization) to encode the JSON request body.
@@ -64,4 +64,4 @@ fun main() {
}
```
-{% include code_footer.md version=1 %}
+
diff --git a/example-code/php.md b/src/content/docs/example-code/php.mdx
similarity index 73%
rename from example-code/php.md
rename to src/content/docs/example-code/php.mdx
index 4fc4995..8d6d7da 100644
--- a/example-code/php.md
+++ b/src/content/docs/example-code/php.mdx
@@ -1,14 +1,14 @@
---
-layout: page
title: PHP - HTML to Image Example
-nav_title: PHP
-parent: Example code
-permalink: /example-code/php/
+slug: example-code/php
description: >-
Convert HTML to an image (png, jpg or webp) with PHP + the HTML/CSS to Image
API. Official Composer package for PHP 8.2+.
---
-{% include intro.md language="PHP" %}
+import CodeIntro from '../../../components/CodeIntro.astro';
+import Help from '../../../components/Help.astro';
+
+
## Official Composer client
@@ -48,12 +48,31 @@ if ($result instanceof CreateImageSuccessResponse) {
The client also supports URL screenshots, templates, batches, image deletion, render options, and signed URLs. See the [PHP client repository](https://github.com/htmlcsstoimage/php-client) for complete usage and API documentation.
-
## Direct HTTP example
```php
-{% include_snippet everything from snippets/plain_php.php %}
+ true,
+ CURLOPT_POST => true,
+ CURLOPT_USERPWD => 'your-api-id:your-api-key',
+ CURLOPT_POSTFIELDS => http_build_query([
+ 'html' => "
";
$css = ".ping { padding: 20px; font-family: 'sans-serif'; }";
-$client = new GuzzleHttpClient();
+$client = new \GuzzleHttp\Client();
// Retrieve your user_id and api_key from https://htmlcsstoimage.com/dashboard
$res = $client->request('POST', 'https://hcti.io/v1/image', [
'auth' => ['user_id', 'api_key'],
@@ -87,4 +106,4 @@ The code turns out to be a bit more readable and less complex when using Guzzle.
When running this script on a Windows machine, it's possible you'll get an SSL error. The fix for this [is here](https://stackoverflow.com/questions/28858351/php-ssl-certificate-error-unable-to-get-local-issuer-certificate/32095378#32095378).
-{% include code_footer.md version=2 %}
+
diff --git a/example-code/python.md b/src/content/docs/example-code/python.mdx
similarity index 83%
rename from example-code/python.md
rename to src/content/docs/example-code/python.mdx
index 8fe7d26..d7fdc41 100644
--- a/example-code/python.md
+++ b/src/content/docs/example-code/python.mdx
@@ -1,14 +1,14 @@
---
-layout: page
title: Python - HTML to Image Example
-nav_title: Python
-parent: Example code
-permalink: /example-code/python/
+slug: example-code/python
description: >-
Convert HTML to an image (png, jpg or webp) with Python + the HTML/CSS to Image
API. Official PyPI package for Python 3.10+.
---
-{% include intro.md language="Python" %}
+import CodeIntro from '../../../components/CodeIntro.astro';
+import Help from '../../../components/Help.astro';
+
+
## Official PyPI client
@@ -37,7 +37,6 @@ else:
The client also supports URL screenshots, templates, batches, image deletion, render options, and signed URLs. See the [Python client repository](https://github.com/htmlcsstoimage/python-client) for complete usage and API documentation.
-
## Direct HTTP example
@@ -54,10 +53,11 @@ data = { 'html': "
Hello, world!
",
'css': ".box { color: white; background-color: #0f79b9; padding: 10px; font-family: Roboto }",
'google_fonts': "Roboto" }
-image = requests.post(url = HCTI_API_ENDPOINT, data = data, auth=(HCTI_API_USER_ID, HCTI_API_KEY))
+image = requests.post(url=HCTI_API_ENDPOINT, data=data, auth=(HCTI_API_USER_ID, HCTI_API_KEY), timeout=60)
+image.raise_for_status()
print("Your image URL is: %s"%image.json()['url'])
# https://hcti.io/v1/image/7ed741b8-f012-431e-8282-7eedb9910b32
```
-{% include code_footer.md version=1 %}
+
diff --git a/example-code/ruby.md b/src/content/docs/example-code/ruby.mdx
similarity index 84%
rename from example-code/ruby.md
rename to src/content/docs/example-code/ruby.mdx
index aeb40a6..9906a88 100644
--- a/example-code/ruby.md
+++ b/src/content/docs/example-code/ruby.mdx
@@ -1,23 +1,19 @@
---
-layout: page
title: Ruby - HTML to Image Example
-nav_title: Ruby
-parent: Example code
-permalink: /example-code/ruby/
+slug: example-code/ruby
description: >-
Convert HTML to an image (png, jpg or webp) with Ruby + the HTML/CSS to Image
API. Renders exactly like Google Chrome.
---
-# Ruby: HTML/CSS to Image
-{: .no_toc }
-{: .fs-9 }
+import Help from '../../../components/Help.astro';
+import { Aside, LinkButton } from '@astrojs/starlight/components';
-Generate a png, jpg or webp images with Ruby. Renders exactly like Google Chrome.
-{: .fs-6 .fw-300 }
+
-[Live demo](https://htmlcsstoimage.com/#demo){: .btn .btn-primary .fs-5 .mb-4 .mb-md-0 .mr-2 target="_blank" }
-[Get an API Key](https://htmlcsstoimage.com){: .btn .fs-5 .mb-4 .mb-md-0 target="_blank" }
-
+Generate PNG, JPG, or WebP images with Ruby, rendered in Google Chrome.
+
+Live demo
+Get an API key
## Generating images with Ruby
The API takes your HTML/CSS and runs it inside a real instance of Google Chrome to convert your html into an image.
@@ -159,7 +155,7 @@ This example uses Rails built in caching.
require "htmlcsstoimage"
def self.fetch_url(html:, css: nil, google_fonts: nil)
- cache_key = "htmlcssimage/#{html}/#{css}/#{google_fonts}"
+ cache_key = "htmlcssimage/#{Digest::SHA256.hexdigest([html, css, google_fonts].to_json)}"
cached_url = Rails.cache.read(cache_key)
return cached_url if cached_url.present?
@@ -168,7 +164,7 @@ def self.fetch_url(html:, css: nil, google_fonts: nil)
image = client.create_image(html, css: css, google_fonts: google_fonts)
if image.url
- Rails.cache.write(cache_key, image.url, expires_in: CACHE_EXPIRATION)
+ Rails.cache.write(cache_key, image.url, expires_in: 24.hours)
end
image.url
@@ -187,7 +183,7 @@ html = render_to_string("path/to/view", formats: :html, layout: false)
redirect_to fetch_url(html: html, google_fonts: "Roboto|Roboto+Condensed"), status: :found
```
-{% include hint.md title="Real world example" text="Take a look at [dev.to](https://dev.to)'s source code to see how they implemented this for generating social cards. [Code on GitHub.](https://github.com/thepracticaldev/dev.to/blob/9442fde9e799e0eaf82723550f29e0a677a0db2b/app/controllers/social_previews_controller.rb#L61)" %}
+
-{% include code_footer.md version=1 %}
+
diff --git a/example-code/rust.md b/src/content/docs/example-code/rust.mdx
similarity index 87%
rename from example-code/rust.md
rename to src/content/docs/example-code/rust.mdx
index 482c819..5022700 100644
--- a/example-code/rust.md
+++ b/src/content/docs/example-code/rust.mdx
@@ -1,14 +1,14 @@
---
-layout: page
title: Rust - HTML to Image Example
-nav_title: Rust
-parent: Example code
-permalink: /example-code/rust/
+slug: example-code/rust
description: >-
Convert HTML to an image (png, jpg or webp) with Rust + the HTML/CSS to Image
API. Renders exactly like Google Chrome.
---
-{% include intro.md language="Rust" %}
+import CodeIntro from '../../../components/CodeIntro.astro';
+import Help from '../../../components/Help.astro';
+
+
Rust's standard library does not include an HTTP client. This example uses [`reqwest`](https://docs.rs/reqwest/) for HTTP and [`serde_json`](https://docs.rs/serde_json/) to build the JSON request body.
@@ -50,4 +50,4 @@ fn main() -> Result<(), Box> {
}
```
-{% include code_footer.md version=1 %}
+
diff --git a/example-code/typescript.md b/src/content/docs/example-code/typescript.mdx
similarity index 90%
rename from example-code/typescript.md
rename to src/content/docs/example-code/typescript.mdx
index ae06362..93c30f4 100644
--- a/example-code/typescript.md
+++ b/src/content/docs/example-code/typescript.mdx
@@ -1,14 +1,15 @@
---
-layout: default
title: TypeScript - HTML to Image Example
-nav_title: TypeScript
-parent: Example code
-permalink: /example-code/typescript/
+slug: example-code/typescript
description: >-
Convert HTML to an image (png, jpg or webp) with TypeScript + the HTML/CSS to Image
API. Renders exactly like Google Chrome.
---
-{% include intro.md language="TypeScript" %}
+import CodeIntro from '../../../components/CodeIntro.astro';
+import Help from '../../../components/Help.astro';
+import { Aside } from '@astrojs/starlight/components';
+
+
## Official npm client
@@ -39,7 +40,7 @@ if (result.success) {
You can also generate signed URLs, render templates, and create image batches. See the [TypeScript client repository](https://github.com/htmlcsstoimage/ts-client) for full usage details.
-
+## Axios example
This example uses the [axios package](https://www.npmjs.com/package/axios). Install with `npm install axios`.
@@ -90,13 +91,11 @@ createImage()
To see all of the available parameters, see: [Creating an image](/getting-started/using-the-api/#creating-an-image).
-{% include hint.md title="Can I use this in a browser?" text="We recommend only using the API server-side. This is important because it keeps your API key secret. If you expose them in the browser, they can be used by anyone." %}
-
-
+
## TypeScript example with Fetch API
-Using the built-in Fetch API (available in Node.js 18+ and modern browsers).
+Use the built-in Fetch API in server-side Node.js 18 or newer.
```typescript
interface ImageRequest {
@@ -138,7 +137,6 @@ createImage({ html, css })
.catch(error => console.error(error));
```
-
## TypeScript with full type definitions
@@ -153,11 +151,11 @@ export interface HtmlCssToImageRequest {
css?: string;
/** The URL of a webpage to screenshot */
url?: string;
- /** Google fonts to load (comma separated) */
+ /** Google fonts to load (pipe separated) */
google_fonts?: string;
/** Delay in milliseconds before capturing */
ms_delay?: number;
- /** Device scale factor (1-3) */
+ /** Device scale factor */
device_scale?: number;
/** Whether to render the full page */
full_screen?: boolean;
@@ -219,7 +217,6 @@ const image = await client.createImage({
console.log(image.url);
```
-
## URL to Image with TypeScript
@@ -242,6 +239,7 @@ async function screenshotUrl(request: ScreenshotRequest): Promise {
}
});
+ if (!response.ok) throw new Error(`API request failed: ${response.status}`);
const data = await response.json();
return data.url;
}
@@ -256,4 +254,4 @@ const imageUrl = await screenshotUrl({
console.log(imageUrl);
```
-{% include code_footer.md version=1 %}
+
diff --git a/example-code/vb.net.md b/src/content/docs/example-code/vb.net.mdx
similarity index 78%
rename from example-code/vb.net.md
rename to src/content/docs/example-code/vb.net.mdx
index 45c00b8..0410731 100644
--- a/example-code/vb.net.md
+++ b/src/content/docs/example-code/vb.net.mdx
@@ -1,22 +1,19 @@
---
-layout: page
title: VB.NET
-parent: Example code
-permalink: /example-code/vb.net/
+slug: example-code/vb.net
description: >-
Convert HTML to an image (png, jpg or webp) with VB.NET + the HTML/CSS to Image
API. Renders exactly like Google Chrome.
---
-# VB.NET: HTML/CSS to Image
-{: .no_toc }
-{: .fs-9 }
+import Help from '../../../components/Help.astro';
+import { LinkButton } from '@astrojs/starlight/components';
-Generate a png, jpg or webp images from your terminal with VB.NET
-{: .fs-4 .fw-300 }
+
-[Live demo](https://htmlcsstoimage.com/#demo){: .btn .btn-primary .fs-5 .mb-4 .mb-md-0 .mr-2 target="_blank" }
-[Get an API Key](https://htmlcsstoimage.com){: .btn .fs-5 .mb-4 .mb-md-0 target="_blank" }
-
+Generate PNG, JPG, or WebP images with VB.NET.
+
+Live demo
+Get an API key
## Generate an image with VB.NET
The API takes your HTML/CSS and runs it in an instance of Google Chrome to convert your html into an image.
@@ -57,4 +54,4 @@ End Module
```
-{% include code_footer.md version=1 %}
+
diff --git a/faq.md b/src/content/docs/faq.md
similarity index 93%
rename from faq.md
rename to src/content/docs/faq.md
index 6924941..4d1ae96 100644
--- a/faq.md
+++ b/src/content/docs/faq.md
@@ -1,16 +1,11 @@
---
-layout: page
title: FAQ
-permalink: /faq/
-nav_order: 50
+slug: faq
description: Frequently asked questions about the HTML/CSS to Image API - file formats, pricing, rate limits, and more.
---
-# Frequently Asked Questions
-{: .no_toc }
-{: .fs-9 }
+
Not finding your answer here? Please email us: support@htmlcsstoimage.com. We enjoy helping people debug their HTML.
-{: .fs-4 .fw-300 }
@@ -22,7 +17,7 @@ PNG (default), JPG, WebP, and PDF. Simply change the file extension on your imag
### What are the maximum image dimensions?
-There's no strict maximum size. The [`viewport_width`](/parameters/viewport/#viewport-width-and-height) limit is 6000 pixels. For height, there's no set limit—`full_screen` screenshots can reach ~10,000 pixels tall.
+Standard renders that exceed the renderer's normal size limit can be downscaled. For larger output, use [Jumbo Images](/guides/advanced/jumbo-images/), which support up to 80,000 pixels per side within a 400,000,000-pixel total area limit. Viewport settings and final output dimensions are different; see [viewport](/parameters/viewport/) and [device scale](/parameters/device_scale/).
Keep in mind images render at 2x by default, so the actual output resolution is double the viewport size. Smaller images will render faster and perform better.
@@ -112,7 +107,7 @@ Yes, set `full_screen: true` to capture the entire scrollable height of a page,
No. Our rendering servers scale dynamically on AWS, so we are unable provide a static IP allowlist.
-If you're taking screenshots of protected URLs, we recommend adding a custom query parameter to the URL (for example `?hcti=1`). Then create a firewall rule (such as in Cloudflare) that allows requests when that query parameter is present.
+For protected URLs you control, use [custom request headers](/parameters/headers/) or a [configured proxy](/guides/advanced/proxies/). A predictable query parameter such as `?hcti=1` is not authentication. See [Cloudflare access guidance](/guides/debugging/cloudflare-challenges/).
### How do I block cookie consent banners?
@@ -136,7 +131,7 @@ Yes! Change the file extension to `.pdf` on any image URL. You can also customiz
### How do I generate social media images (OG images)?
-Create a 1200x630px template with your branding, then generate unique images for each page by passing dynamic content. Many companies like Dev.to use this for automated social cards.
+Create a 1200x630px template with your branding, then generate unique images for each page by passing dynamic content.
[See our social cards guide](/use-cases/social-cards/).
@@ -250,7 +245,7 @@ Email us! **support@htmlcsstoimage.com**. We'd love to hear from you. We're expe
"name": "What are the maximum image dimensions?",
"acceptedAnswer": {
"@type": "Answer",
- "text": "There's no strict maximum size. The viewport_width limit is 6000 pixels. For height, there's no set limit—full_screen screenshots can reach ~10,000 pixels tall. Images render at 2x by default, so actual output resolution is double the viewport size."
+ "text": "Standard renders can be downscaled when they exceed the renderer's size limit. Jumbo Images support up to 80,000 pixels per side within a 400,000,000-pixel total area limit. Viewport size and final output dimensions are different."
}
},
{
@@ -322,7 +317,7 @@ Email us! **support@htmlcsstoimage.com**. We'd love to hear from you. We're expe
"name": "Do you have a list of IP addresses I can allowlist?",
"acceptedAnswer": {
"@type": "Answer",
- "text": "No. Our rendering servers scale dynamically on AWS, so we don't provide a static IP allowlist. If you're taking screenshots of protected URLs, add a custom query parameter to the URL (for example ?hcti=1) and create a firewall rule (such as in Cloudflare) that allows requests when that query parameter is present."
+ "text": "No. Our rendering servers scale dynamically, so we do not provide a static IP allowlist. For protected URLs you control, use custom request headers or a configured proxy. A predictable query parameter is not authentication."
}
},
{
diff --git a/getting-started/create-and-render.md b/src/content/docs/getting-started/create-and-render.mdx
similarity index 71%
rename from getting-started/create-and-render.md
rename to src/content/docs/getting-started/create-and-render.mdx
index ab5a672..04e6f77 100644
--- a/getting-started/create-and-render.md
+++ b/src/content/docs/getting-started/create-and-render.mdx
@@ -1,55 +1,50 @@
---
-layout: page
-title: Signed Image URLs
-permalink: /getting-started/create-and-render/
-parent: Getting started
-nav_order: 6
+title: Signed image URLs
+slug: getting-started/create-and-render
description: >-
Generate signed image URLs from HTML, public webpages, or template values with a single authenticated GET request.
---
-# Single-Request Image Generation
-{: .no_toc }
-{: .fs-9 }
+import { Aside } from '@astrojs/starlight/components';
+import Endpoint from '../../../components/Endpoint.astro';
+import ParameterTable from '../../../components/ParameterTable.astro';
+import Help from '../../../components/Help.astro';
-Generate images from HTML/CSS, URLs, or templates in a single request with signed URLs.
-{: .fs-6 .fw-300 }
+
-
+Generate images from HTML/CSS, URLs, or templates in a single request with signed URLs.
-{% include hint.md title="Recommendation" text="We recommend using the [standard API endpoint](/getting-started/using-the-api/) for most use cases. Only use this endpoint if you specifically need to generate image URLs from client-side code or need to avoid the two-step create-then-fetch process." %}
+
-{% include hint.md title="Publishing social cards?" text="If each image corresponds to a public page on your website or CMS, an [OG Image Config](/getting-started/og-images/) is usually simpler. It maps page paths to stable image URLs and does not require an HMAC token for every page." %}
+
## Key benefits
This endpoint allows you to generate image URLs that directly render images when accessed:
-- **No POST request needed**: Generate image URLs client-side without making API calls
-- **Client-side friendly**: Use signed URLs to keep your API Key secure
-- **Simplified workflow**: Skip the image creation step and go straight to image rendering
+- **No POST request needed**: Generate signed URLs locally on your server or during a build, without an API call
+- **Browser-ready URLs**: Embed the finished URL without exposing your API Key
+- **On-demand rendering**: The first image request triggers generation
- **Template ready**: Create reusable signed image URLs from template values
Unlike the standard endpoint that requires a POST request followed by using the returned URL, this endpoint lets you construct a signed URL that will generate and return the image when accessed.
Each API key has an associated API ID (public) and API Key (secret). The token is generated by creating an HMAC SHA256 hash of the query string (without the `?`) using your API Key as the secret. For HTML/CSS and URL renders, the signed URL includes your API ID. For templated image URLs, the signed URL uses the `template_id` instead.
-{% include hint.md title="Security note" text="Never expose your API Key in client-side code. The API ID and generated token are safe to use client-side." %}
+
## Creating an image
To generate an image with a signed URL, construct a URL with your API ID and token:
-
- get https://hcti.io/v1/image/create-and-render/:api_id/:token/:format
-
+
-### URL Components
+### URL components
| Component | Description |
|:-------------|:------------------|
-| **api_id** | Your public API ID from the dashboard |
-| **token** | HMAC SHA256 hash of the query string using your API Key (see below for how to generate) |
-| **format** | Optional file format: `png` (default), `jpg`, `webp`, or `pdf` |
+| `api_id` | Your public API ID from the dashboard |
+| `token` | HMAC SHA256 hash of the query string using your API Key (see below for how to generate) |
+| `format` | Optional file format: `png` (default), `jpg`, `webp`, or `pdf` |
### Parameters
@@ -57,37 +52,31 @@ The parameters are the same as the [standard API endpoint](/getting-started/usin
| Name | Type | Description |
|:-------------|:------------------|:------|
-| **html**† | `String` | This is the HTML you want to render. You can send an HTML snippet \(`
Your content
`\) or an entire webpage. |
-| **css** | `String` | The CSS for your image. When using with `url` it will be injected into the page. |
-| **url**† | `String` | The fully qualified URL to a public webpage. When passed this will override the html param and will generate a screenshot of the url. |
-
-{% include hint.md title="Required params" text="† Either `url` OR `html` is required, but not both. `css` is optional." %}
+| `html`† | `String` | This is the HTML you want to render. You can send an HTML snippet \(`
Your content
`\) or an entire webpage. |
+| `css` | `String` | The CSS for your image. When using with `url` it will be injected into the page. |
+| `url`† | `String` | The fully qualified URL to a public webpage. When passed this will override the html param and will generate a screenshot of the url. |
-
+
### Additional parameters
Optional parameters for greater control over your image.
-{% include additional_parameters.md create_and_render=true %}
-
-
+
## Creating a templated image URL
To generate an image from a template with a signed URL, construct a URL with your `template_id` and token. You do not need to include your API ID in the path.
-
- get https://hcti.io/v1/image/:template_id/:token/:format?
-
+
-### URL Components
+### URL components
| Component | Description |
|:-------------|:------------------|
-| **template_id** | The template ID returned by the [template API](/getting-started/templates/#creating-a-template) |
-| **token** | HMAC SHA256 hash of the query string using your API Key |
-| **format** | Optional file format: `png` (default), `jpg`, `webp`, or `pdf`. |
+| `template_id` | The template ID returned by the [template API](/getting-started/templates/#creating-a-template) |
+| `token` | HMAC SHA256 hash of the query string using your API Key |
+| `format` | Optional file format: `png` (default), `jpg`, `webp`, or `pdf`. |
### Parameters
@@ -95,14 +84,14 @@ Template values are passed as query string parameters. Each query parameter name
| Name | Type | Description |
|:-------------|:------------------|:------|
-| **template values** | `String`, `Number`, `Boolean`, or `JSON` | Values for the variables in your template. For editor templates, see the [Variables guide](/template-editor/variables/). |
-| **template_version** | `Integer` | Optional. Render a specific version of the template. Include this in the query string before generating the token. |
+| `template values` | `String`, `Number`, `Boolean`, or `JSON` | Values for the variables in your template. For editor templates, see the [Variables guide](/template-editor/variables/). |
+| `template_version` | `Integer` | Optional. Render a specific version of the template. Include this in the query string before generating the token. |
Nested objects and arrays should be serialized as JSON and URL encoded. The token must be generated from the exact encoded query string you put after `?`.
For example, these template values:
-```javascript
+```json
{
"title": "Launch",
"author": {
@@ -119,9 +108,7 @@ author=%7B%22name%22%3A%22Jeff%22%7D&title=%22Launch%22
The `author` value decodes to `{"name":"Jeff"}`. The `title` value decodes to `"Launch"`.
-{% include hint.md title="Use an official client" text="The [TypeScript](/example-code/typescript/), [.NET](/example-code/c/), [Python](/example-code/python/), and [PHP](/example-code/php/) clients include signed URL helpers so you do not need to hand-build the query string or HMAC token." %}
-
-
+
## Understanding HMAC authentication
@@ -153,15 +140,13 @@ Your final URL would be:
https://hcti.io/v1/image/create-and-render/your-api-id-here/ac5553c5a9031e09f4580101080045e7e4cbd1734aa1b53a94f1006c3496ca21/png?html=%3Cdiv%3EHello%3C%2Fdiv%3E
```
-
-
## Examples
### Official client helpers
The official clients generate the signed URL for you and keep the API Key on your server.
-#### [TypeScript Client](/example-code/typescript/#official-npm-client)
+#### [TypeScript client](/example-code/typescript/#official-npm-client)
```typescript
import { HtmlCssToImageClient } from '@html-css-to-image/client';
@@ -174,7 +159,7 @@ const imageUrl = client.generateTemplatedImageUrl('t-b0354248-e7f6-4cca-81c6-2b4
});
```
-#### [.Net Client](/example-code/c/#official-net-package)
+#### [.NET client](/example-code/c/#official-net-package)
```csharp
using HtmlCssToImage;
@@ -197,7 +182,7 @@ var imageUrl = client.CreateTemplatedImageUrl(
});
```
-#### [Python Client](/example-code/python/#official-pypi-client)
+#### [Python client](/example-code/python/#official-pypi-client)
```python
from html_css_to_image import HtmlCssToImageClient
@@ -212,7 +197,7 @@ with HtmlCssToImageClient.from_env() as client:
)
```
-#### [PHP Client](/example-code/php/#official-composer-client)
+#### [PHP client](/example-code/php/#official-composer-client)
```php
use HtmlCssToImage\HtmlCssToImageClient;
@@ -346,11 +331,9 @@ echo "Templated image URL: $templatedImageUrl\n";
// echo '';
```
-
-
### Example response
-When you access a valid signed URL, the API will return the generated image directly with the appropriate content type header (`image/png`, `image/jpeg`, or `image/webp` depending on the format).
+When you access a valid signed URL, the API returns the generated file directly with the appropriate content type: `image/png`, `image/jpeg`, `image/webp`, or `application/pdf`.
If there's an error, you'll receive a JSON response:
@@ -358,7 +341,7 @@ If there's an error, you'll receive a JSON response:
STATUS: 400 BAD REQUEST
```
-```javascript
+```json
{
"error": "Bad Request",
"statusCode": 400,
@@ -370,7 +353,7 @@ STATUS: 400 BAD REQUEST
STATUS: 401 UNAUTHORIZED
```
-```javascript
+```json
{
"error": "Unauthorized",
"statusCode": 401,
@@ -380,4 +363,4 @@ STATUS: 401 UNAUTHORIZED
The signing key must be enabled and grant [`images:create`](/getting-started/using-the-api/permissions/). See [API key management](/management-api/api-keys/) when replacing or disabling signing credentials.
-{% include code_footer.md version=1 %}
+
diff --git a/getting-started/height-and-width.md b/src/content/docs/getting-started/height-and-width.mdx
similarity index 76%
rename from getting-started/height-and-width.md
rename to src/content/docs/getting-started/height-and-width.mdx
index f112cf9..924ccee 100644
--- a/getting-started/height-and-width.md
+++ b/src/content/docs/getting-started/height-and-width.mdx
@@ -1,40 +1,29 @@
---
-layout: page
title: Setting height and width
-permalink: /getting-started/setting-height-and-width/
-parent: Getting started
-nav_order: 5
+slug: getting-started/setting-height-and-width
description: >-
For height and width you have two options. Setting it in your HTML or using
the API to crop your images on the fly.
---
-# Setting height and width
-{: .no_toc }
-{: .fs-9 }
+import DocImage from '../../../components/DocImage.astro';
+import SizingExample from '../../../components/SizingExample.astro';
+import Help from '../../../components/Help.astro';
-Learn how to set the height and width for your images with the API.
-{: .fs-6 .fw-300 }
+
-
+Learn how to set the height and width for your images with the API.
## Setting height & width in HTML
When sending an HTML snippet to the API it will auto crop your image to the height/width of the **outermost HTML element**.
-- The following example will generate a screenshot that is 400px wide and 200px tall.
+- The following example uses an element that is 400 CSS pixels wide and 200 CSS pixels tall. At the default `device_scale: 2`, the output is 800 × 400 image pixels.
**Note**: _This works for HTML snippets only. Full HTML pages (has `` and `` tags) will not automatically crop._
-
-
-
-
Hello, world
-
-
-
-
+
```html
-
+
Hello, world
```
@@ -45,22 +34,14 @@ See how in the code sample we have set the `height: 200px; width: 400px;` in the
Margins are respected by the auto crop. To add some space around your image, you can add `margin: 20px` to the outermost element.
-
-
-
-
-
Height and width example
-
-
-
-
+
```html
-
+
Height and width example
```
-## Setting the Viewport
+## Setting the viewport
When rendering your image, we use a live instance of Google Chrome running on one of our servers. In Chrome, the viewport is the total viewable area rendered
by the browser.
@@ -93,11 +74,11 @@ Every image generated by the API can also be adjusted on the fly with the `heigh
**?width=400**
-{% cloudinary /assets/images/w400.jpeg sizes="200px" alt="Auto adjust the width of your image" %}
+
**?height=300**
-{% cloudinary /assets/images/h300.jpeg sizes="300px" alt="Auto adjust the height of your image" %}
+
### Advanced cropping
@@ -107,7 +88,7 @@ For more precise control over your images, you can use advanced cropping paramet
Images generated by the API can also be used for print. Learn how to convert pixels to DPI (dots per inch).
-- **Inches to pixels Formula:**`(DPI * (size in inches)) / 2 = pixels`
+- **Inches to CSS pixels, at `device_scale: 2`:** `(DPI * size in inches) / 2 = CSS pixels`. For a different device scale, divide by that scale instead.
- DPI = Dots Per Inch. For a high quality print, you will want 300 DPI.
### A4 paper example
@@ -131,8 +112,8 @@ Once you have determined your height and width in pixels, you can then set your
If you need the DPI metadata tag set on your image, you can do this by adding the query param to your image url. For example `hcti.io/v1/image/123abc?dpi=300`.
Please note, you will still need to use the calculations above to create an image large enough to have the correct DPI when printed.
-### Jumbo Images
+### Jumbo images
If you need to render really large images without sacrificing quality, you'll want to set the `jumbo_max_width` and `jumbo_max_height` parameters. [Learn more](/guides/advanced/jumbo-images).
-{% include code_footer.md version=3 %}
+
diff --git a/getting-started/index.md b/src/content/docs/getting-started/index.mdx
similarity index 90%
rename from getting-started/index.md
rename to src/content/docs/getting-started/index.mdx
index 13289d2..51f58f5 100644
--- a/getting-started/index.md
+++ b/src/content/docs/getting-started/index.mdx
@@ -1,24 +1,18 @@
---
-layout: default
title: Getting started
-permalink: /getting-started/
-nav_order: 2
-expanded: true
-has_children: true
+slug: getting-started
description: >-
Get started with the HTML/CSS to Image API. Quick start guide with examples for Python, PHP, JavaScript, Ruby, Go, C#, Rust, TypeScript and more.
---
-# Getting started
-{: .no_toc }
-{: .fs-9 }
+import { LinkButton } from '@astrojs/starlight/components';
+
+
Start here to learn how to use the HTML/CSS to Image API.
-{: .fs-6 .fw-300 }
-[Live demo](https://htmlcsstoimage.com/#demo){: .btn .btn-primary .fs-5 .mb-4 .mb-md-0 .mr-2 }
-[Get an API Key](https://htmlcsstoimage.com){: .btn .fs-5 .mb-4 .mb-md-0 }
+Live demo
+Get an API Key
-
## Quick start in 3 steps
@@ -53,7 +47,6 @@ The API returns a URL to your generated image. Use it anywhere - in emails, soci
}
```
-
## Choose your path
@@ -69,7 +62,6 @@ The API returns a URL to your generated image. Use it anywhere - in emails, soci
| Manage API keys and organization resources | [Management API](/management-api/) |
| Set up API credentials and permissions | [Authentication and API keys](/getting-started/using-the-api/api-keys/) |
-
## Example code
@@ -89,7 +81,6 @@ Get started quickly with code samples in your language:
- [Elixir](/example-code/elixir)
- [Google Apps Script](/example-code/google-apps-script)
-
## Automation and no-code options
@@ -100,7 +91,6 @@ Don't want to write code? We've got you covered:
- **[Make](/integrations/make/)** - Build powerful automation workflows
- **[MCP Server](/integrations/mcp/)** - Generate images from AI assistants like Cursor, Claude, and more
-
## Need help?
diff --git a/getting-started/og-images/index.md b/src/content/docs/getting-started/og-images/index.mdx
similarity index 93%
rename from getting-started/og-images/index.md
rename to src/content/docs/getting-started/og-images/index.mdx
index cb6e265..29a1ca7 100644
--- a/getting-started/og-images/index.md
+++ b/src/content/docs/getting-started/og-images/index.mdx
@@ -1,23 +1,18 @@
---
-layout: page
title: Open Graph Images
-permalink: /getting-started/og-images/
-parent: Getting started
-nav_order: 3
+slug: getting-started/og-images
description: >-
Automatically generate Open Graph images and social cards for every page on a website, CMS, store, or static site with one URL pattern.
---
-# Automatic Open Graph images for every page
-{: .no_toc }
-{: .fs-9 }
+import { Aside, LinkButton } from '@astrojs/starlight/components';
+import OgPlatformGuides from '../../../../components/OgPlatformGuides.astro';
-Give every page on your site an up-to-date social card with one stable image URL pattern.
-{: .fs-6 .fw-300 }
+
-[Create an OG Image Config](https://htmlcsstoimage.com/dashboard/og-configs/new){: .btn .btn-primary .fs-5 .mb-4 .mb-md-0 .mr-2 }
-[Preview a page](https://htmlcsstoimage.com/tools/social-card-previewer){: .btn .fs-5 .mb-4 .mb-md-0 }
+Give every page on your site an up-to-date social card with one stable image URL pattern.
-
+Create an OG Image Config
+Preview a page
## What is an Open Graph image?
@@ -87,7 +82,7 @@ See [OG Image caching and refreshes](/guides/debugging/og-image-caching/) for th
### 3. Choose what to render
-#### Page Screenshot
+#### Page screenshot
Use **Page Screenshot** to capture the source page itself. You can capture the viewport or target one element with a CSS selector. This works well when the page already contains a social-card component or when the page itself is the desired preview.
@@ -111,13 +106,13 @@ Common page-level controls include:
| [`hcti:transparent_background`](/parameters/transparent_background/) | Preserve a transparent page background |
| [`hcti:content_version`](/guides/debugging/og-image-caching/#force-a-new-content-identity) | Change the image identity when content at the same path changes |
-{% include og_config_parameter_support.md %}
+
You can use either the `property` or `name` attribute for `hcti:` meta tags.
Keep **Parse meta tags for each path** enabled for page-level controls.
-#### Template Values
+#### Template values
Use **Template Values** when every image should follow a reusable design. Select an HCTI template and either use its latest version or pin a specific version.
@@ -147,6 +142,8 @@ Different apps prefer different aspect ratios. The optimization setting decides
HCTI currently recognizes these platform targets:
+
+
Most platforms use Open Graph metadata. Twitter prefers `twitter:image` and falls back to `og:image`; Slack and Snapchat can also fall back between Open Graph and Twitter metadata. For reliable coverage, point both tags at the same HCTI URL:
```html
@@ -196,7 +195,7 @@ Incrementing this integer gives the render a new content identity without changi
Each guide shows where to put the tags and how to use that platform's page variables:
-{% include og_image_platform_grid.html %}
+
## Verify the result
diff --git a/getting-started/og-images/supported-parameters.md b/src/content/docs/getting-started/og-images/supported-parameters.mdx
similarity index 71%
rename from getting-started/og-images/supported-parameters.md
rename to src/content/docs/getting-started/og-images/supported-parameters.mdx
index 938f6d3..1898acc 100644
--- a/getting-started/og-images/supported-parameters.md
+++ b/src/content/docs/getting-started/og-images/supported-parameters.mdx
@@ -1,18 +1,14 @@
---
-layout: page
title: Supported OG Image parameters
-permalink: /getting-started/og-images/supported-parameters/
-parent: Open Graph Images
-nav_exclude: true
+slug: getting-started/og-images/supported-parameters
description: >-
The complete list of hcti: page metadata supported by HTML/CSS to Image OG Image Configs.
---
-# Supported OG Image parameters
-{: .no_toc }
-{: .fs-9 }
+import ParameterTable from '../../../../components/ParameterTable.astro';
+
+
Control how an individual page is rendered by adding `hcti:` metadata to its HTML.
-{: .fs-6 .fw-300 }
The most common screenshot controls are provided in the dashboard when you create or edit an OG Image Config. Use page-level metadata when a particular page needs to override those defaults.
@@ -28,12 +24,6 @@ Add a supported parameter to the page's `` using either the `property` or
Page metadata takes precedence over the corresponding defaults in the dashboard. Keep **Parse meta tags for each path** enabled for these overrides to be read.
-| Metadata | Type | Description |
-|:---------|:-----|:------------|
-{% for parameter in site.data.parameters.parameters -%}
- {% if parameter.support.og_config -%}
-| [`hcti:{{ parameter.name }}`]({{ parameter.link }}) | `{{ parameter.type }}` | {{ parameter.description }} |
- {% endif -%}
-{% endfor %}
+
For parameters used outside OG Image Configs, see the [complete API parameter reference](/parameters/).
diff --git a/getting-started/templates.md b/src/content/docs/getting-started/templates.mdx
similarity index 58%
rename from getting-started/templates.md
rename to src/content/docs/getting-started/templates.mdx
index 0638dac..a60d931 100644
--- a/getting-started/templates.md
+++ b/src/content/docs/getting-started/templates.mdx
@@ -1,24 +1,22 @@
---
-layout: page
-title: Image Templates
-permalink: /getting-started/templates/
+title: Image templates
+slug: getting-started/templates
description: >-
Create reusable templates for your HTML & CSS to make image generation easy.
-parent: Getting started
-nav_order: 4
---
-# Image Templates
-{: .no_toc }
-{: .fs-9 }
+import { Aside, LinkButton } from '@astrojs/starlight/components';
+import DocImage from '../../../components/DocImage.astro';
+import Endpoint from '../../../components/Endpoint.astro';
+import ParameterTable from '../../../components/ParameterTable.astro';
+import Help from '../../../components/Help.astro';
-Create reusable templates to make image generation easy.
-{: .fs-6 .fw-300 }
+
-[Get an API Key](https://htmlcsstoimage.com){: .btn .btn-blue .fs-5 .mb-4 .mb-md-0 }
+Create reusable templates to make image generation easy.
-
+Open Dashboard
-## What are Templates?
+## What are templates?
A template defines reusable image markup with **variables** that are replaced when an image is created.
@@ -27,7 +25,7 @@ You can create templates by sending HTML and CSS to the API, or by building a te
If you are building templates visually, start with the [Template Editor Quick Start](/template-editor/quick-start/). For API-only templates, continue below.
### Handlebars variables
-Templates support [Handlebars](https://handlebarsjs.com/) variables. Add {% raw %}`{{title_text}}`{% endraw %} to your HTML, then pass a value for `title_text` when creating the image.
+Templates support [Handlebars](https://handlebarsjs.com/) variables. Add `{{title_text}}` to your HTML, then pass a value for `title_text` when creating the image.
### Common use cases
- Define a reusable template, then pass variables to it to generate unique images.
@@ -37,23 +35,19 @@ Templates support [Handlebars](https://handlebarsjs.com/) variables. Add {% raw
### Example
This image was generated with a template.
-```javascript
+```json
{
"text": "With templates, you can use variables to replace parts of your image.",
"avatar_url": "https://avataaars.io/?avatarStyle=Transparent&topType=ShortHairDreads01&accessoriesType=Round&hairColor=BrownDark&facialHairType=BeardLight&facialHairColor=BrownDark&clotheType=BlazerShirt&eyeType=Happy&eyebrowType=DefaultNatural&mouthType=Eating&skinColor=Brown",
"name": "Freddy",
- "username": "@freddy",
+ "username": "@freddy"
}
```
-
-
- {% cloudinary /assets/images/template.png alt="Example of an image template use for converting html to an image" %}
-
-
-HTML:
-{: .text-delta }
+
+
+The HTML below uses Bootstrap utility classes; include the corresponding stylesheet in your template to reproduce the styling.
+
```html
-{% raw %}
{{text}}
@@ -66,47 +60,41 @@ HTML:
{{username}}
-{% endraw %}
```
-## Creating a Template
+## Creating a template
Requires [`templates:create_update`](/getting-started/using-the-api/permissions/).
To generate a template, make an HTTP request to the API.
-
- post https://hcti.io/v1/template
-
+
### Parameters
-The create template endpoint accepts the following parameters. Accepted as either `json` or `formdata`.
+Send the following parameters as JSON or form data.
| Name | Type | Description |
|:-------------|:------------------|:------|
-| **html***| `String` | This is the HTML you want to render. You can send an HTML snippet \(`
Your content
`\) or an entire webpage. |
-| **css** | `String` | The CSS for your image. |
-|**name**| `String` | A short name to identify your template `max length 64`|
-|**description**| `String` | Description to elaborate on the use of your template `max length 1024` |
+| `html` (required) | `String` | HTML to render: a snippet (`
Your content
`) or an entire webpage. |
+| `css` | `String` | The CSS for your image. |
+| `name` | `String` | A short name to identify your template `max length 64`|
+| `description` | `String` | Description to elaborate on the use of your template `max length 1024` |
-{% include hint.md title="Required params" text="For creating a template, `html` is required while `css` is optional.
- `name` and `description` are optional, but may be useful to help you differentiate your templates in the future." %}
+
### Additional parameters
Optional parameters for greater control over your image.
-{% include template_additional_parameters.md %}
-
-
+
### Example responses
```
STATUS: 201 CREATED
```
-```javascript
+```json
{
"template_id": "t-b0354248-e7f6-4cca-81c6-2b4a70a16388",
"template_version": 1594409399761
@@ -117,7 +105,7 @@ STATUS: 201 CREATED
STATUS: 400 BAD REQUEST
```
-```javascript
+```json
{
"error": "Bad Request",
"statusCode": 400,
@@ -129,92 +117,80 @@ STATUS: 400 BAD REQUEST
STATUS: 429 TOO MANY REQUESTS
```
-```javascript
+```json
{
"error": "Plan limit exceeded",
"statusCode": 429,
"message": "The tryit plan is limited to 5 templates"
}
```
-{% include hint.md title="Plan Limits" text="Free plans can create 5 templates. Paid plans can create 1,000. You can edit your existing templates an unlimited number of times." %}
-
+
-## Editing a Template
+## Editing a template
Requires [`templates:create_update`](/getting-started/using-the-api/permissions/).
-To edit a template you've already made, make an HTTP request to the API with the template_id listed in the CREATE response.
+To edit an existing template, send a request with the `template_id` returned when you created it.
-
- post https://hcti.io/v1/template/:template_id
-
+
### Parameters
-The edit template endpoint accepts the following parameters. Accepted as either `json` or `formdata`.
+Send the following parameters as JSON or form data.
| Name | Type | Description |
|:-------------|:------------------|:------|
-| **html***| `String` | This is the HTML you want to render. You can send an HTML snippet \(`
Your content
`\) or an entire webpage. |
-| **css** | `String` | The CSS for your image. |
-|**name**| `String` | A short name to identify your template `max length 64`|
-|**description**| `String` | Description to elaborate on the use of your template `max length 1024` |
+| `html` (required) | `String` | HTML to render: a snippet (`
Your content
`) or an entire webpage. |
+| `css` | `String` | The CSS for your image. |
+| `name` | `String` | A short name to identify your template `max length 64`|
+| `description` | `String` | Description to elaborate on the use of your template `max length 1024` |
-{% include hint.md title="Required params" text="For creating a template, `html` is required while `css` is optional.
- `name` and `description` are optional, but may be useful to help you differentiate your templates in the future." %}
+
### Additional parameters
Optional parameters for greater control over your image.
-{% include template_additional_parameters.md %}
-
-
+
## Creating an image with a template
Requires [`images:create`](/getting-started/using-the-api/permissions/).
-To generate a templated image, make an HTTP request to the API using the template_id listed in the [CREATE response](#example-responses).
+To generate a templated image, send a request using the `template_id` from the [create response](#example-responses).
-
- post https://hcti.io/v1/image/:template_id
-
+
You can also generate a templated image with a signed `GET` URL that renders on demand. See [Creating a templated image URL](/getting-started/create-and-render/#creating-a-templated-image-url).
To use one template for automatic social cards across an existing site, select it in an [OG Image Config](/getting-started/og-images/#template-values). HCTI can map page titles, descriptions, Open Graph metadata, and explicit `html:tv:` values to its variables.
-{% include hint.md title="Template Versions" text="When you create an image using a `template_id`, it will automatically use the most recent version of that template. If you want to create an image from a specific template_version you can append `/:template_version` to your POST: `hcti.io/v1/image/:template_id/:template_version`" %}
+
### Parameters
-The create templated image endpoint accepts the following parameters, accepted as either `json` or `formdata`.
-- If you use `formdata`, your `template_values` need to be JSON encoded.
+Send the following parameters as JSON or form data. When using form data, JSON-encode `template_values` as a string.
| Name | Type | Description |
|:-------------|:------------------|:------|
-| **template_values*** | `JSON` | Values for the variables in your template. For editor templates, see the [Variables guide](/template-editor/variables/). |
+| `template_values` (required) | `JSON` | Values for the variables in your template. For editor templates, see the [Variables guide](/template-editor/variables/). |
-
## Listing your templates
Requires [`templates:read`](/getting-started/using-the-api/permissions/).
-To list all of your templates, send a get to `v1/template`. Authentication is required.
+List your templates with an authenticated GET request.
-
- get https://hcti.io/v1/template
-
+
### Example responses
```
STATUS: 200 OK
```
-```javascript
+```json
{
"data": [
{
@@ -249,20 +225,18 @@ STATUS: 200 OK
| Field | Type | Description |
|:------|:-----|:------------|
-| **render_count** | `Integer` | Number of times this template has been used to generate images. |
-| **storage_destination_id** | `String` or `null` | Storage destination inherited by images created from this template. |
-| **color_scheme** | `String` | Light or dark mode setting, if configured. |
-| **timezone** | `String` | Timezone setting, if configured. |
+| `render_count` | `Integer` | Number of times this template has been used to generate images. |
+| `storage_destination_id` | `String` or `null` | Storage destination inherited by images created from this template. |
+| `color_scheme` | `String` | Light or dark mode setting, if configured. |
+| `timezone` | `String` | Timezone setting, if configured. |
## Listing your template versions
Requires [`templates:read`](/getting-started/using-the-api/permissions/).
-To list all versions of a template, send a get to `v1/template/:template_id`. Authentication is required.
+List a template's versions with an authenticated GET request.
-
- get https://hcti.io/v1/template/:template_id
-
+
### Example responses
@@ -270,7 +244,7 @@ To list all versions of a template, send a get to `v1/template/:template_id`. Au
STATUS: 200 OK
```
-```javascript
+```json
{
"data": [
{
@@ -292,7 +266,7 @@ STATUS: 200 OK
"updated_at": "2020-07-19T17:16:43.987+00:00",
"version": 1595179003987,
"viewport_height": null,
- "viewport_width": null,
+ "viewport_width": null
}
],
"pagination": {
@@ -303,4 +277,4 @@ STATUS: 200 OK
Deleting a template through the API requires `templates:delete`. See the [interactive API reference](https://htmlcsstoimage.com/api-docs) for endpoint schemas. Rendering a known template ID uses `images:create`; discovering templates first requires `templates:read`.
-{% include code_footer.md version=2 %}
+
diff --git a/getting-started/url-to-image.md b/src/content/docs/getting-started/url-to-image.mdx
similarity index 56%
rename from getting-started/url-to-image.md
rename to src/content/docs/getting-started/url-to-image.mdx
index a4c0fad..26813ab 100644
--- a/getting-started/url-to-image.md
+++ b/src/content/docs/getting-started/url-to-image.mdx
@@ -1,58 +1,52 @@
---
-layout: page
title: URL to Image
-permalink: /getting-started/url-to-image/
+slug: getting-started/url-to-image
description: >-
Take a screenshot of any URL using the API. Screenshot generated in a real
instance of Google Chrome.
-parent: Getting started
-nav_order: 2
---
-# URL to Image
-{: .no_toc }
-{: .fs-9 }
+import { Aside, LinkButton } from '@astrojs/starlight/components';
+import DocImage from '../../../components/DocImage.astro';
+import ParameterTable from '../../../components/ParameterTable.astro';
-The high resolution screenshot API you've been searching for.
-{: .fs-6 .fw-300 }
+
-[Get an API Key](https://htmlcsstoimage.com){: .btn .fs-5 .mb-4 .mb-md-0 }
+The high resolution screenshot API you've been searching for.
-
+Get an API key
## Creating an image from a URL
With the API, you can automate taking a screenshot of any website.
-{% include hint.md title="Need a social card for every page?" text="Use an [OG Image Config](/getting-started/og-images/) to map every public page path on a website or CMS to a screenshot or template—without making a separate API request for each page." %}
+
Pass the `url` param to the `hcti.io/v1/image` endpoint, and we'll generate a screenshot for you. Here's an example using cURL.
```bash
-curl -X POST https://hcti.io/v1/image -u 'UserID:APIKey'
+curl -X POST https://hcti.io/v1/image -u 'UserID:APIKey' \
--data-urlencode url="https://google.com"
```
Don't write code? Don't worry, you can also do this with our [Zapier integration](https://docs.htmlcsstoimage.com/integrations/zapier/).
-{% cloudinary /assets/images/google.jpg sizes="600px" alt="Screenshot of google.com" %}
+
## Additional parameters
To customize your image further, you can take advantage of the following optional parameters.
-{% include additional_parameters.md context="url" %}
-
-
+
## Screenshot examples
A full screenshot of `stripe.com`. With device scale set to 2, for a super high resolution image.
-{% cloudinary /assets/images/stripe.png sizes="600px" alt="Screenshot of stripe.com auto generated with the API" %}
+
-
-
-## Screenshot part of a page with Selector
+## Screenshot part of a page with selector
You can set a `selector` to target a specific part of the page.
@@ -60,16 +54,12 @@ For example, on Stripe's homepage, there is an element with the CSS selector `se
When passing that to the API, we crop to that element only.
-{% cloudinary /assets/images/url-selector-example.png sizes="600px" alt="Use a CSS selector to crop an image" %}
-
-
+
-## CSS Selectors
+## CSS selectors
To learn about CSS Selectors, we recommend [this article](https://www.w3schools.com/cssref/css_selectors.asp). There are also Chrome extensions that detect them for you, we like using: [Selector Gadget](https://chrome.google.com/webstore/detail/selectorgadget/mhjhnkcfbdhnjickkkdbjoemdmbfginb?hl=en).
-
-
## Blocking cookie consent banners
Many websites display cookie consent popups that can interfere with your screenshots. Use the `block_consent_banners` parameter to automatically hide these:
@@ -82,23 +72,18 @@ curl -X POST https://hcti.io/v1/image -u 'UserID:APIKey' \
This automatically detects and blocks common consent frameworks like OneTrust, Cookiebot, and others. For more advanced cases, see our [complete guide to blocking cookie banners](/guides/advanced/blocking-cookie-banners/).
-
-
## Pages requiring login or sign-in
-Our API does not support pages that require login. The URL must be publicly accessible for us to generate an image of it.
-Supporting login is a common request, but we have chosen not to support it due to the risk of storing and transmitting credentials.
+The API does not automate an interactive login flow. For a page you are authorized to access, you can send a short-lived session cookie or authorization token using the [`headers` parameter](/parameters/headers/). Custom headers are restricted by origin; review those rules before including credentials.
Some sites have the option of creating an "embed" of a page. This is the best option if it's available to you. You can then use the embed's HTML to generate a screenshot without login.
-
## Do you have an IP allowlist?
We don't provide a static IP list. Our rendering servers scale dynamically on AWS, so the source IPs can change.
-If you are screenshotting protected URLs, we recommend adding a custom query parameter to the URL (for example `?hcti=1`) and creating a firewall rule (such as in Cloudflare) that allows requests when that query parameter is present.
+For a stable egress IP, configure an [HTTP proxy](/guides/advanced/proxies/) and pass its [`proxy_id`](/parameters/proxy_id/). For sites you control, see [handling Cloudflare challenges](/guides/debugging/cloudflare-challenges/) and [custom request headers](/parameters/headers/). A public query flag such as `?hcti=1` does not authenticate a request and should not grant access to private content.
-
## Need help getting started?
-We'd be happy to walk you through getting started. Send us an email: **support@htmlcsstoimage.com**. We're experts at generating images and will help you get going using the API.
+We'd be happy to walk you through getting started. Send us an email: [support@htmlcsstoimage.com](mailto:support@htmlcsstoimage.com). We're experts at generating images and will help you get going using the API.
diff --git a/getting-started/using-the-api/api-keys.md b/src/content/docs/getting-started/using-the-api/api-keys.mdx
similarity index 83%
rename from getting-started/using-the-api/api-keys.md
rename to src/content/docs/getting-started/using-the-api/api-keys.mdx
index 6b56bc8..175379b 100644
--- a/getting-started/using-the-api/api-keys.md
+++ b/src/content/docs/getting-started/using-the-api/api-keys.mdx
@@ -1,16 +1,15 @@
---
-layout: page
title: Authentication and API keys
-permalink: /getting-started/using-the-api/api-keys/
-nav_title: Auth & API Keys
-parent: Using the API
-grand_parent: Getting started
-nav_order: 1
+slug: getting-started/using-the-api/api-keys
+sidebar:
+ label: Auth & API Keys
description: >-
Create scoped API keys, authenticate requests, choose permission presets, and replace or disable HTML/CSS to Image credentials.
---
-# Authentication and API keys
-{: .no_toc }
+import DocImage from '../../../../components/DocImage.astro';
+import Help from '../../../../components/Help.astro';
+
+
Every new HCTI organization automatically includes an API key, so you can start using the API without creating one yourself. You'll find its API ID and secret API key in the [API keys dashboard](https://htmlcsstoimage.com/dashboard/api-keys).
@@ -34,10 +33,7 @@ By default, the form groups permissions into **Read** and **Write** for each are
**Write includes deletion** where the area supports it. For example, selecting Write for Proxies grants both create/update and delete permissions. Usage has only a Read permission.
-
-
- The default view groups permissions by area. Click the screenshot to view it full size.
-
+
### Permission presets
@@ -55,19 +51,13 @@ Turn on **Advanced mode** to see the granular permissions within each area. Read
Simple and advanced mode are two views of the same permission selection. Advanced mode lets you choose exactly which actions a key can perform.
-
-
- Advanced mode exposes individual actions instead of grouping all writes together.
-
+
### See what each permission allows
Expand an area's details using the arrow next to its name. The details explain its read and write permissions. Expand **Endpoints and required permissions** to see which API endpoints use each permission, including the exact names to use in API requests.
-
-
- Each area includes descriptions and an endpoint-to-permission reference.
-
+
See the [permission reference](/getting-started/using-the-api/permissions/) for the complete list. Plan and authentication requirements still apply to each endpoint.
@@ -139,4 +129,4 @@ These eight permissions match the **Basics** preset and preserve the original im
To use management operations, edit the key's permissions in the dashboard or create a separate key with the required grants.
-{% include code_footer.md version=1 %}
+
diff --git a/getting-started/using-the-api/index.md b/src/content/docs/getting-started/using-the-api/index.mdx
similarity index 72%
rename from getting-started/using-the-api/index.md
rename to src/content/docs/getting-started/using-the-api/index.mdx
index dd2dce7..4e70e3a 100644
--- a/getting-started/using-the-api/index.md
+++ b/src/content/docs/getting-started/using-the-api/index.mdx
@@ -1,26 +1,24 @@
---
-layout: page
title: Using the API
-permalink: /getting-started/using-the-api/
-parent: Getting started
-has_children: true
-nav_order: 1
+slug: getting-started/using-the-api
description: >-
Complete API reference for HTML/CSS to Image. Authentication, endpoints, parameters, batch creation, and response formats.
---
-# Using the HTML/CSS to Image API
-{: .no_toc }
-{: .fs-9 }
+import { Aside, LinkButton } from '@astrojs/starlight/components';
+import ParameterTable from '../../../../components/ParameterTable.astro';
+import Endpoint from '../../../../components/Endpoint.astro';
+import DocImage from '../../../../components/DocImage.astro';
+import Help from '../../../../components/Help.astro';
+
+
Generate images from HTML and CSS.
-{: .fs-6 .fw-300 }
-[Live demo](https://htmlcsstoimage.com/#demo){: .btn .btn-primary .fs-5 .mb-4 .mb-md-0 .mr-2 }
-[Get an API Key](https://htmlcsstoimage.com){: .btn .fs-5 .mb-4 .mb-md-0 }
+Live demo
+Get an API Key
-{% include hint.md title="Using Cursor, Claude, Codex or another AI tool?" text="Generate images without writing code using our [MCP Server integration](/integrations/mcp/)." %}
+
-
Browse the [interactive API reference](https://htmlcsstoimage.com/api-docs) for endpoint schemas, try the [Postman collection](https://www.postman.com/htmlcsstoimage/html-css-to-image/collection/zgfamed/html-css-to-image-api), or use the [Management API](/management-api/) to configure organization resources. For credentials and access, see [API keys](/getting-started/using-the-api/api-keys/), [permissions](/getting-started/using-the-api/permissions/), and [rate limits](/getting-started/using-the-api/rate-limits/).
@@ -36,9 +34,7 @@ Requires [`images:create`](/getting-started/using-the-api/permissions/).
To generate an image, make an HTTP request to the API.
-
- post https://hcti.io/v1/image
-
+
### Parameters
@@ -46,21 +42,19 @@ The create image endpoint accepts the following parameters. Accepted as either `
| Name | Type | Description |
|:-------------|:------------------|:------|
-| **html**† | `String` | This is the HTML you want to render. You can send an HTML snippet \(`
Your content
`\) or an entire webpage. |
-| **css** | `String` | The CSS for your image. When using with `url` it will be injected into the page. |
-| **url**† | `String` | The fully qualified URL to a public webpage. Such as `https://htmlcsstoimage.com`. When passed this will override the html param and will generate a screenshot of the url. |
+| `html`† | `String` | This is the HTML you want to render. You can send an HTML snippet \(`
Your content
`\) or an entire webpage. |
+| `css` | `String` | The CSS for your image. When using with `url` it will be injected into the page. |
+| `url`† | `String` | The fully qualified URL to a public webpage. Such as `https://htmlcsstoimage.com`. When passed this will override the html param and will generate a screenshot of the url. |
-{% include hint.md title="Required params" text="† Either `url` OR `html` is required, but not both. `css` is optional." %}
+
-
### Additional parameters
Optional parameters for greater control over your image.
-{% include additional_parameters.md %}
+
-
### Example responses
```
@@ -100,15 +94,12 @@ STATUS: 429 TOO MANY REQUESTS
The `429` example above is an image-credit limit. Management operations can return a separate `429` for per-minute throttling; see [rate limits and retry guidance](/getting-started/using-the-api/rate-limits/). Missing or invalid credentials return `401`; insufficient permissions return `403` with the required permission in the message.
-
## Getting an image
After creating an image, you can use the returned URL to either download your image, or use it directly in your website.
-
- get https://hcti.io/v1/image/:image_id
-
+
This URL is permanent for as long as your account is active. It's automatically cached and optimized by Cloudflare's global content delivery network. You can use it directly on your webpages and not worry about hurting your page speed score.
@@ -126,9 +117,9 @@ The API supports `jpg`, `png`, `webp`, and `pdf`. If no file extension is passed
| webp | `https://hcti.io/v1/image/a3ab2ab2-906e-4b5c-a88d-41a1c3f3779e.webp` |
| pdf | `https://hcti.io/v1/image/a3ab2ab2-906e-4b5c-a88d-41a1c3f3779e.pdf` |
-{% include hint.md title="Tip" text="You can include the desired format as a parameter when creating your image. See [here](/parameters/format/) for more details." %}
+
-{% include hint.md title="PNG by default" text="The API returns `png` by default. If no extension is on the URL, a png will be generated." %}
+
### Query parameters
@@ -136,10 +127,10 @@ Query parameters can be added to the URL to adjust your image.
| Name | Type | Description |
|:-------------|:------------------|:------|
-| **height** | `Integer` | The height of the image. Maximum `5000`. |
-| **width** | `Integer` | The width of the image. Maximum `5000`. |
-| **dpi** | `Integer` | Sets the DPI metadata tag on the image. Minimum `30`, maximum `600`. |
-| **dl** | `Integer` | Set `dl=1` and the image will be served as a downloadable attachment. |
+| `height` | `Integer` | The height of the image. Maximum `5000`. |
+| `width` | `Integer` | The width of the image. Maximum `5000`. |
+| `dpi` | `Integer` | Sets the DPI metadata tag on the image. Minimum `30`, maximum `600`. |
+| `dl` | `Integer` | Set `dl=1` and the image will be served as a downloadable attachment. |
### Cropping parameters
@@ -147,15 +138,15 @@ Advanced cropping options for precise control over the rendered image region.
| Name | Type | Description |
|:-------------|:------------------|:------|
-| **aspect_ratio** | `String` | Crop to a specific aspect ratio. Format: `width_height` (e.g., `16_9`, `1_1`). |
-| **x_origin** | `String` | Horizontal alignment for a crop whose width is supplied by `crop_width` or derived from `aspect_ratio`. Accepts `start`, `center`, or `end`. Cannot be combined with `x_1` or `x_2`. |
-| **y_origin** | `String` | Vertical alignment for a crop whose height is supplied by `crop_height` or derived from `aspect_ratio`. Accepts `start`, `center`, or `end`. Cannot be combined with `y_1` or `y_2`. |
-| **x_1** | `String` | Starting x coordinate for crop region. Use with `x_2` or `crop_width`. |
-| **x_2** | `String` | Ending x coordinate for crop region. Must be used with `x_1`. |
-| **y_1** | `String` | Starting y coordinate for crop region. Use with `y_2` or `crop_height`. |
-| **y_2** | `String` | Ending y coordinate for crop region. Must be used with `y_1`. |
-| **crop_width** | `String` | Width of the crop region. Use with `x_1` or alone with `aspect_ratio`. |
-| **crop_height** | `String` | Height of the crop region. Use with `y_1` or alone with `aspect_ratio`. |
+| `aspect_ratio` | `String` | Crop to a specific aspect ratio. Format: `width_height` (e.g., `16_9`, `1_1`). |
+| `x_origin` | `String` | Horizontal alignment for a crop whose width is supplied by `crop_width` or derived from `aspect_ratio`. Accepts `start`, `center`, or `end`. Cannot be combined with `x_1` or `x_2`. |
+| `y_origin` | `String` | Vertical alignment for a crop whose height is supplied by `crop_height` or derived from `aspect_ratio`. Accepts `start`, `center`, or `end`. Cannot be combined with `y_1` or `y_2`. |
+| `x_1` | `String` | Starting x coordinate for crop region. Use with `x_2` or `crop_width`. |
+| `x_2` | `String` | Ending x coordinate for crop region. Must be used with `x_1`. |
+| `y_1` | `String` | Starting y coordinate for crop region. Use with `y_2` or `crop_height`. |
+| `y_2` | `String` | Ending y coordinate for crop region. Must be used with `y_1`. |
+| `crop_width` | `String` | Width of the crop region. Use with `x_1` or alone with `aspect_ratio`. |
+| `crop_height` | `String` | Height of the crop region. Use with `y_1` or alone with `aspect_ratio`. |
#### Cropping examples
@@ -163,11 +154,11 @@ Here's a base image (600x400 pixels) and how different cropping parameters affec
**Original image:**
-
+
**Crop to 1:1 square from center** (`aspect_ratio=1_1&crop_height=100%`):
-
+
```
?aspect_ratio=1_1&crop_height=100%
@@ -175,7 +166,7 @@ Here's a base image (600x400 pixels) and how different cropping parameters affec
**Crop left third** (`x_1=0&crop_width=33%`):
-
+
```
?x_1=0&crop_width=33%
@@ -183,7 +174,7 @@ Here's a base image (600x400 pixels) and how different cropping parameters affec
**Crop center section** (`x_1=33%&x_2=66%`):
-
+
```
?x_1=33%&x_2=66%
@@ -191,7 +182,7 @@ Here's a base image (600x400 pixels) and how different cropping parameters affec
**Crop right third** (`x_1=67%&crop_width=33%`):
-
+
```
?x_1=67%&crop_width=33%
@@ -199,21 +190,18 @@ Here's a base image (600x400 pixels) and how different cropping parameters affec
**Crop to 16:9 aspect ratio** (`aspect_ratio=16_9&crop_width=100%`):
-
+
```
?aspect_ratio=16_9&crop_width=100%
```
-
## Deleting an image
Requires [`images:delete`](/getting-started/using-the-api/permissions/).
-
- delete https://hcti.io/v1/image/:image_id
-
+
To delete an image using the API, you can send a **DELETE** request to your image URL. This will remove your image from our servers and clear the caching for the image in our CDN.
@@ -230,16 +218,14 @@ Requires [`images:create`](/getting-started/using-the-api/permissions/).
Create up to 25 images in a single API request. This is more efficient than making multiple individual requests.
-
- post https://hcti.io/v1/image/batch
-
+
### Parameters
| Name | Type | Description |
|:-------------|:------------------|:------|
-| **default_options** | `Object` | Default parameters applied to all images in the batch. Accepts all standard image creation parameters. |
-| **variations** | `Array` | Array of image objects. Each can override `default_options`. Maximum 25 items. |
+| `default_options` | `Object` | Default parameters applied to all images in the batch. Accepts all standard image creation parameters. |
+| `variations` | `Array` | Array of image objects. Each can override `default_options`. Maximum 25 items. |
### Example request
@@ -279,9 +265,7 @@ Requires [`images:delete`](/getting-started/using-the-api/permissions/).
Delete multiple images at once by sending their IDs.
-
- delete https://hcti.io/v1/image/batch
-
+
```javascript
{
@@ -293,7 +277,6 @@ Delete multiple images at once by sending their IDs.
STATUS: 202 ACCEPTED
```
-
## Listing images
@@ -301,16 +284,14 @@ Requires [`images:read`](/getting-started/using-the-api/permissions/).
Retrieve a list of all images created by your account with pagination support.
-
- get https://hcti.io/v1/images
-
+
### Query parameters
| Name | Type | Description |
|:-------------|:------------------|:------|
-| **count** | `Integer` | Number of images to return. Default `50`, maximum `50`. |
-| **page_token** | `String` | Token for pagination. Use `next_page_token` from the previous response. |
+| `count` | `Integer` | Number of images to return. Default `50`, maximum `50`. |
+| `page_token` | `String` | Token for pagination. Use `next_page_token` from the previous response. |
### Example response
@@ -337,15 +318,12 @@ STATUS: 200 OK
}
```
-
## Checking account usage
Requires [`usage:read`](/getting-started/using-the-api/permissions/). See [Usage and limits](/management-api/usage/) for monitoring headers, billing-period semantics, and MCP usage tools.
-
- get https://hcti.io/v1/usage
-
+
To check your account usage, you can make a request to the `usage` endpoint. It will return the total images created for your account rolled up into different time periods.
@@ -538,4 +516,4 @@ STATUS: 200 OK
}
```
-{% include code_footer.md version=1 %}
+
diff --git a/getting-started/using-the-api/permissions.md b/src/content/docs/getting-started/using-the-api/permissions.mdx
similarity index 97%
rename from getting-started/using-the-api/permissions.md
rename to src/content/docs/getting-started/using-the-api/permissions.mdx
index 1218119..baa37b5 100644
--- a/getting-started/using-the-api/permissions.md
+++ b/src/content/docs/getting-started/using-the-api/permissions.mdx
@@ -1,16 +1,14 @@
---
-layout: page
title: API permissions
-nav_title: Permissions
-permalink: /getting-started/using-the-api/permissions/
-parent: Using the API
-grand_parent: Getting started
-nav_order: 2
+slug: getting-started/using-the-api/permissions
+sidebar:
+ label: Permissions
description: >-
Reference for HCTI API key permissions, organization roles, and MCP OAuth grants across image and management operations.
---
-# API permissions
-{: .no_toc }
+import Help from '../../../../components/Help.astro';
+
+
Permissions control what an API key or MCP connection can do within its organization. Choose only the operations your application or assistant needs.
@@ -91,4 +89,4 @@ The original MCP defaults include image creation/read/store, template read/creat
A REST request with valid credentials but insufficient permissions returns `403` with a message identifying the required permission. MCP returns a tool error and does not execute the denied operation. Add the appropriate key grant or reauthorize the MCP connection before retrying. If the message identifies a plan restriction, changing permissions alone will not resolve it.
-{% include code_footer.md version=1 %}
+
diff --git a/getting-started/using-the-api/rate-limits.md b/src/content/docs/getting-started/using-the-api/rate-limits.mdx
similarity index 91%
rename from getting-started/using-the-api/rate-limits.md
rename to src/content/docs/getting-started/using-the-api/rate-limits.mdx
index c7ef0a5..6183d49 100644
--- a/getting-started/using-the-api/rate-limits.md
+++ b/src/content/docs/getting-started/using-the-api/rate-limits.mdx
@@ -1,19 +1,15 @@
---
-layout: page
title: Rate limits and usage limits
-permalink: /getting-started/using-the-api/rate-limits/
-parent: Using the API
-grand_parent: Getting started
-nav_order: 3
+slug: getting-started/using-the-api/rate-limits
description: >-
Image generation has no per-second or per-minute rate limit. Learn Management API and MCP request limits, retry headers, and image credit limits.
---
-# Rate limits and usage limits
-{: .no_toc }
+import { Aside } from '@astrojs/starlight/components';
+import Help from '../../../../components/Help.astro';
-
-
THERE IS NO RATE LIMIT FOR CREATING OR RENDERING IMAGES
-
+
+
+
Management operations have per-minute request limits. Image generation uses your plan's image credits. These are separate limits with different recovery steps.
@@ -73,4 +69,4 @@ Image creation consumes image credits. Exceeding the allowance can also return `
Use the [usage guide](/management-api/usage/) to monitor `x-renders-*` response headers, REST usage history, and MCP current usage. Batch size, template counts, and access to features such as proxies or storage destinations are separate plan restrictions.
-{% include code_footer.md version=1 %}
+
diff --git a/guides/account/account-settings.md b/src/content/docs/guides/account/account-settings.mdx
similarity index 93%
rename from guides/account/account-settings.md
rename to src/content/docs/guides/account/account-settings.mdx
index 768598f..48a2bee 100644
--- a/guides/account/account-settings.md
+++ b/src/content/docs/guides/account/account-settings.mdx
@@ -1,22 +1,15 @@
---
-layout: page
title: Account settings
-permalink: /guides/account/account-settings/
-parent: Account & organizations
-grand_parent: Guides
-nav_order: 1
+slug: guides/account/account-settings
description: >-
Your personal account: email, organizations, switching orgs, and accepting invites.
---
-# Account settings
-{: .no_toc }
-{: .fs-9 }
+import Help from '../../../../components/Help.astro';
-Manage your login, organizations, and pending invites in one place.
-{: .fs-6 .fw-300 }
+
-
+Manage your login, organizations, and pending invites in one place.
Account settings are available at [https://htmlcsstoimage.com/account/settings](https://htmlcsstoimage.com/account/settings). This page is about **you** — your email, which organizations you belong to, and how you switch between them — not about managing a single organization's members or billing (that's [organization settings](/guides/account/organization-settings/)).
@@ -60,4 +53,4 @@ API keys and MCP authorizations stay associated with their organization when you
- [Organization settings](/guides/account/organization-settings/) — Roles, inviting members, and managing an organization.
- [Account usage](/management-api/usage/) — Monitoring API usage.
-{% include code_footer.md version=1 %}
+
diff --git a/guides/account/index.md b/src/content/docs/guides/account/index.mdx
similarity index 80%
rename from guides/account/index.md
rename to src/content/docs/guides/account/index.mdx
index ef442c1..aa60c6e 100644
--- a/guides/account/index.md
+++ b/src/content/docs/guides/account/index.mdx
@@ -1,21 +1,12 @@
---
-layout: default
title: Account & organizations
-permalink: /guides/account/
-parent: Guides
-has_children: true
-nav_order: 1
+slug: guides/account
description: >-
Manage your account, organizations, members, invites, and roles in the dashboard.
---
-# Account & organizations
-{: .no_toc }
-{: .fs-9 }
+
Manage your account and team access in the dashboard.
-{: .fs-4 .fw-300 }
-
-
| Guide | Description |
|:------|:------------|
diff --git a/guides/account/organization-settings.md b/src/content/docs/guides/account/organization-settings.mdx
similarity index 94%
rename from guides/account/organization-settings.md
rename to src/content/docs/guides/account/organization-settings.mdx
index 284a933..2e2dd4b 100644
--- a/guides/account/organization-settings.md
+++ b/src/content/docs/guides/account/organization-settings.mdx
@@ -1,22 +1,15 @@
---
-layout: page
title: Organization settings
-permalink: /guides/account/organization-settings/
-parent: Account & organizations
-grand_parent: Guides
-nav_order: 2
+slug: guides/account/organization-settings
description: >-
Manage your organization, members, invites, and roles. Learn about Owner, Admin, and User permissions.
---
-# Organization settings
-{: .no_toc }
-{: .fs-9 }
+import Help from '../../../../components/Help.astro';
-Manage members, invites, usage, and billing for your organization.
-{: .fs-6 .fw-300 }
+
-
+Manage members, invites, usage, and billing for your organization.
Organization settings are available at [https://htmlcsstoimage.com/dashboard/organization-settings](https://htmlcsstoimage.com/dashboard/organization-settings). What you can do there depends on your role in the organization.
@@ -71,4 +64,4 @@ Usage and current period information for the organization are also shown on the
- [Account settings](/guides/account/account-settings/) — Your personal account, switching organizations, and pending invites.
- [Account usage](/management-api/usage/) — Monitoring API usage for your organization.
-{% include code_footer.md version=1 %}
+
diff --git a/guides/advanced/base64.md b/src/content/docs/guides/advanced/base64.mdx
similarity index 90%
rename from guides/advanced/base64.md
rename to src/content/docs/guides/advanced/base64.mdx
index 9f50af3..a86f99e 100644
--- a/guides/advanced/base64.md
+++ b/src/content/docs/guides/advanced/base64.mdx
@@ -1,21 +1,17 @@
---
-layout: page
title: Base64 to image
-permalink: /guides/advanced/base64/
-parent: Advanced
-grand_parent: Guides
-nav_order: 4
+slug: guides/advanced/base64
description: >-
Convert base64 to PNG, JPG, or PDF with HTML/CSS to Image using the API, Zapier, or AI assistants via MCP.
---
-# Convert base64 to an image
-{: .no_toc }
-{: .fs-9 }
+import { LinkButton } from '@astrojs/starlight/components';
+import DocImage from '../../../../components/DocImage.astro';
+import Help from '../../../../components/Help.astro';
+
+
Learn how to generate PNGs, JPGs, or PDFs from base64. Works with Zapier and Make.
-{: .fs-6 .fw-300 }
-
## How it works
@@ -34,24 +30,19 @@ The rendered result is available as PNG or JPG. To generate a PDF, append `.pdf`
In this example we convert a base64 of our logo into an image.
-
-
- {% cloudinary /assets/images/base64-logo.png sizes="100px" alt="Convert base64 to an image" %}
-
-
+
```html
```
-
## Use this with Zapier or Make
We have integrations for both Zapier and Make. You can use the example code above to convert base64 to images without writing any code yourself.
-[Zapier](/integrations/zapier/){: .btn .btn-green .fs-5 .mb-4 .mb-md-0 .mr-2 }
-[Make](/integrations/make/){: .btn .fs-5 .mb-4 .mb-md-0 .mr-2 }
+Zapier
+Make
-{% include code_footer.md version=3 %}
+
diff --git a/guides/advanced/blocking-cookie-banners.md b/src/content/docs/guides/advanced/blocking-cookie-banners.mdx
similarity index 69%
rename from guides/advanced/blocking-cookie-banners.md
rename to src/content/docs/guides/advanced/blocking-cookie-banners.mdx
index 1a0bf77..6071fb3 100644
--- a/guides/advanced/blocking-cookie-banners.md
+++ b/src/content/docs/guides/advanced/blocking-cookie-banners.mdx
@@ -1,21 +1,15 @@
---
-layout: page
title: Blocking cookie banners
-permalink: /guides/advanced/blocking-cookie-banners/
-parent: Advanced
-grand_parent: Guides
-nav_order: 1
+slug: guides/advanced/blocking-cookie-banners
description: >-
Learn how to hide cookie banners when taking screenshots
---
-# Blocking cookie banners
-{: .no_toc }
-{: .fs-9 }
+import { Aside } from '@astrojs/starlight/components';
+import Help from '../../../../components/Help.astro';
-Learn how to block cookie banners with taking screenshots
-{: .fs-6 .fw-300 }
+
-
+Hide cookie banners when taking screenshots.
## How it works
@@ -23,7 +17,7 @@ The easiest way to hide cookie banners in your screenshots is by using the `bloc
When set to `true`, the API will automatically detect and block common cookie consent banners and popups on websites. This is the recommended approach for most use cases.
-## Automatic Cookie Banner Blocking
+## Automatic cookie banner blocking
Simply add `block_consent_banners: true` to your API request:
@@ -42,9 +36,9 @@ This automatically handles the most common cookie consent frameworks including:
- Osano
- And many others
-{% include hint.md title="Recommended Method" text="Using block_consent_banners is the easiest and most reliable way to hide cookie popups. It's maintained and updated regularly to handle new consent frameworks." %}
+
-## Manual CSS Injection (Advanced)
+## Manual CSS injection (advanced)
For custom cookie banners not covered by the automatic blocking, you can still use CSS injection.
@@ -55,7 +49,7 @@ Use the developer tools inspector to find the ID or CSS class of the cookie bann
Then use this information to override the visibility of the cookie banner.
-Here is an example that hides some common cookie banner.s
+Here is an example that hides two common cookie banners.
```css
#onetrust-consent-sdk {
@@ -70,6 +64,5 @@ Here is an example that hides some common cookie banner.s
To learn about CSS Selectors, we recommend [this article](https://www.w3schools.com/cssref/css_selectors.asp).
-{% include code_footer.md version=1 %}
-
+
diff --git a/guides/advanced/duplicate-detection.md b/src/content/docs/guides/advanced/duplicate-detection.mdx
similarity index 78%
rename from guides/advanced/duplicate-detection.md
rename to src/content/docs/guides/advanced/duplicate-detection.mdx
index 4e6eef5..63d1da4 100644
--- a/guides/advanced/duplicate-detection.md
+++ b/src/content/docs/guides/advanced/duplicate-detection.mdx
@@ -1,21 +1,15 @@
---
-layout: page
title: Duplicate detection
-permalink: /guides/advanced/duplicate-detection/
-parent: Advanced
-grand_parent: Guides
-nav_order: 2
+slug: guides/advanced/duplicate-detection
description: >-
Reuse identical recent images without consuming additional image credits.
---
-# Duplicate image detection
-{: .no_toc }
-{: .fs-9 }
+import { Aside } from '@astrojs/starlight/components';
+import Help from '../../../../components/Help.astro';
-Control how far back the API looks for an identical image before rendering a new one.
-{: .fs-6 .fw-300 }
+
-
+Control how far back the API looks for an identical image before rendering a new one.
## How deduplication works
@@ -25,7 +19,7 @@ If a matching image is found in your organization, the response returns its exis
The rendered content and image parameters must match exactly. A different character or parameter value creates a different image. The `dedupe_duration_s` value itself is excluded from this comparison, so you can change the lookback window without changing what counts as an identical image.
-{% include hint.md title="Best effort, not an idempotency guarantee" text="Deduplication is best effort and eventually consistent. A new image can take a few seconds to become available for matching, so identical requests sent during that short delay—especially concurrent requests—may create separate images and consume separate image credits. This does not shorten the configured lookback window. Do not rely on this feature when your application requires strict idempotency." %}
+
## Setting the lookback window
@@ -66,4 +60,4 @@ It does not apply to:
See the [`dedupe_duration_s` parameter reference](/parameters/dedupe_duration_s/) for request examples.
-{% include code_footer.md version=1 %}
+
diff --git a/guides/advanced/index.md b/src/content/docs/guides/advanced/index.mdx
similarity index 84%
rename from guides/advanced/index.md
rename to src/content/docs/guides/advanced/index.mdx
index 30514fc..7690420 100644
--- a/guides/advanced/index.md
+++ b/src/content/docs/guides/advanced/index.mdx
@@ -1,21 +1,12 @@
---
-layout: default
-title: Advanced
-permalink: /guides/advanced/
-parent: Guides
-has_children: true
-nav_order: 4
+title: Advanced features
+slug: guides/advanced
description: >-
Advanced API features including cookie banner blocking, duplicate detection, account usage, and Base64 encoding.
---
-# Advanced Features
-{: .no_toc }
-{: .fs-9 }
+
Get more from the API.
-{: .fs-4 .fw-300 }
-
-
| Guide | Description |
|:------|:------------|
diff --git a/guides/advanced/jumbo-images.md b/src/content/docs/guides/advanced/jumbo-images.mdx
similarity index 77%
rename from guides/advanced/jumbo-images.md
rename to src/content/docs/guides/advanced/jumbo-images.mdx
index 856cb15..a9e817c 100644
--- a/guides/advanced/jumbo-images.md
+++ b/src/content/docs/guides/advanced/jumbo-images.mdx
@@ -1,27 +1,19 @@
---
-layout: page
title: Jumbo Images
-permalink: /guides/advanced/jumbo-images/
-parent: Advanced
-grand_parent: Guides
-nav_order: 6
+slug: guides/advanced/jumbo-images
description: >-
Render large images without reducing quality by splitting into tiles and stitching them together.
---
-# Jumbo Images
-{: .no_toc }
-{: .fs-9 }
+import { Aside } from '@astrojs/starlight/components';
+import Help from '../../../../components/Help.astro';
-Render large screenshots without reducing quality by splitting and stitching tiles.
-{: .fs-6 .fw-300 }
-
-
+
+
-1. TOC
-{:toc}
+Render large screenshots without reducing quality by splitting and stitching tiles.
-
+
## What are jumbo images?
@@ -29,6 +21,8 @@ Rendering really tall or wide html (~ 8,000px) causes quality to degrade. Most i
Jumbo images allow you to scale beyond this without sacrificing quality. When you set `jumbo_max_width` and `jumbo_max_height`, the API lets you generate images up to **80,000 pixels** on a side.
+
+
### Why jumbo matters
The big win is **quality**. Jumbo lets you produce huge images **without losing sharpness**:
@@ -37,7 +31,15 @@ The big win is **quality**. Jumbo lets you produce huge images **without losing
- Each tile is rendered natively in Chrome at full resolution, then stitched. There's no resampling or post-render shrinking.
- Crisp text, vector edges, and 1px borders that would normally turn fuzzy on huge canvases stay clean.
-{% include hint.md title="Without jumbo, large images get downscaled" text="If your output would naturally exceed ~8000px and you **don't** set the jumbo params, the API automatically scales the image down to fit Chrome's limit and avoid duplication issues. This manifests as a **blurry, lower-resolution image** — text gets soft, fine lines smear, and high `device_scale` values stop helping. If your render is anywhere near 8000px on either side, set `jumbo_max_width` and `jumbo_max_height` to keep it sharp." %}
+
+
+
### When to use it
@@ -49,7 +51,7 @@ The big win is **quality**. Jumbo lets you produce huge images **without losing
Jumbo works with both `html`/`css` and `url` requests. You can apply jumbo params on [Templates](/getting-started/templates) as well.
-
+
## Parameters
@@ -62,7 +64,7 @@ Both parameters must be set together. Setting one without the other returns a `4
These are **maximums**, not exact dimensions. If your content is smaller than the max, you get a smaller image. If it's larger, the renderer scales down to fit (see [How it works](#how-it-works)).
-
+
## How it works
@@ -77,9 +79,11 @@ When you pass `jumbo_max_width` and `jumbo_max_height`, the renderer:
The result is a single large screenshot.
-{% include hint.md title="Jumbo is measured after device_scale is applied." text="If you expect your final image to be `5,000` x `2,000` and you want a crisp retina image, you should set your jumbo max to at least `10,000` x `4,000`." %}
+
-
+
## Limits
@@ -100,7 +104,7 @@ The 400-million-pixel area cap means you can't max out both dimensions at once.
- `20,000 x 20,000`
- `12,500 x 32,000`
-
+
## Billing
@@ -110,6 +114,8 @@ The number of tiles is calculated from the **maximum specified size**.
When used with `ms_delay`, the [standard `ms_delay` multiple](/parameters/ms_delay#credit-usage) is applied only to the base image, not to the image credits for additional tiles.
+
+
### Examples
| Jumbo size | `ms_delay` | Total Tiles | Image credits billed |
@@ -123,10 +129,12 @@ When used with `ms_delay`, the [standard `ms_delay` multiple](/parameters/ms_del
-
+
## Examples
+
+
### HTML render
```json
@@ -138,6 +146,8 @@ When used with `ms_delay`, the [standard `ms_delay` multiple](/parameters/ms_del
}
```
+
+
### URL render
```json
@@ -149,6 +159,8 @@ When used with `ms_delay`, the [standard `ms_delay` multiple](/parameters/ms_del
}
```
+
+
### Render a specific large element
Combine `selector` with jumbo to capture one giant element on a page.
@@ -163,6 +175,8 @@ Combine `selector` with jumbo to capture one giant element on a page.
}
```
+
+
### cURL
```bash
@@ -177,46 +191,70 @@ curl -X POST https://hcti.io/v1/image \
}'
```
-
+
## Troubleshooting
+
+
+
+
### `jumbo_max_width must be present when jumbo_max_height is specified.`
Both params are required together. Add the missing one.
+
+
### `jumbo_max_height or jumbo_max_width should be greater than 8000`
At least one dimension must be `> 8000`. If both are `<= 8000`, drop the jumbo params and use a normal render — it will be cheaper and faster.
+
+
### `total jumbo pixel area cannot be greater than 400,000,000.`
`jumbo_max_width * jumbo_max_height` exceeds the 400M area cap. Reduce one or both dimensions.
+
+
### `cannot set pdf_options and jumbo options together`
Jumbo is image-only (PNG / JPG / WebP). Remove `pdf_options` if you want a jumbo render.
+
+
### My image came back smaller than `jumbo_max_width` x `jumbo_max_height`
Those parameters are **maximums**. If your content is naturally smaller, the output will be smaller too. If your content is larger, the renderer downscales to fit while preserving aspect ratio — so the output matches one dimension exactly and is shorter on the other axis.
+
+
### My large image is blurry (and I'm not using jumbo parameters)
When a render naturally exceeds ~8000px and `jumbo_max_width` / `jumbo_max_height` are not set, the API scales the image down to fit Chrome's limit. The result is a single-resolution image that no longer matches your `device_scale`, which usually looks blurry — especially text and thin lines.
Fix: set `jumbo_max_width` and `jumbo_max_height` to the size you actually want. The renderer will tile and stitch instead of downscaling, and your `device_scale` will be preserved.
+
+
#### For example:
If your final image is 10,000 px tall, the effective device_scale will be 0.8 to fit it within the 8000px boundary. If you're expecting a readable, sharp image - you need jumbo.
+
+
### Why did this cost N image credits?
See [Billing](#billing). The image credit count is `1 + ceil(width / 8000) * ceil(height / 8000)` based on the **maximum output size**, plus any [`ms_delay` image credit cost](/parameters/ms_delay#credit-usage). The dashboard usage view shows the image credit count for each image.
+
+
### Why does jumbo cost more?
Jumbo images require significantly more resources than a standard image to render, stitch and store.
-{% include code_footer.md version=1 %}
+
+
+
+
+
diff --git a/guides/advanced/proxies.md b/src/content/docs/guides/advanced/proxies.mdx
similarity index 83%
rename from guides/advanced/proxies.md
rename to src/content/docs/guides/advanced/proxies.mdx
index 1693900..2677484 100644
--- a/guides/advanced/proxies.md
+++ b/src/content/docs/guides/advanced/proxies.mdx
@@ -1,33 +1,28 @@
---
-layout: page
-title: HTTP Proxies
-permalink: /guides/advanced/proxies/
-parent: Advanced
-grand_parent: Guides
-nav_order: 5
+title: HTTP proxies
+slug: guides/advanced/proxies
description: >-
Route image renders through your own HTTP proxy. Restrict access to internal content, control egress IPs, or get past bot detection.
---
-# HTTP Proxies
-{: .no_toc }
-{: .fs-9 }
+import { Aside } from '@astrojs/starlight/components';
+import Help from '../../../../components/Help.astro';
+import DocImage from '../../../../components/DocImage.astro';
+
+
Route outbound traffic from your renders through an HTTP proxy you control.
-{: .fs-6 .fw-300 }
-
## Why use a proxy?
When you configure a proxy, **all outbound HTTP requests made while rendering your image** are routed through it. This is useful when you want to:
-- **Restrict access to internal content** by allowlisting our proxy's IP address on your origin (instead of opening it to the public internet).
+- **Restrict access to internal content** by allowlisting your proxy's IP address on your origin.
- **Control the egress IP** so requests appear to come from a known location.
- **Get past bot detection** that rate-limits or blocks our shared infrastructure IPs.
-{% include hint.md title="Plan availability" text="Proxies are available on the **10,000 images/month plan or higher**. If you'd like to try this out on a different plan, email **support@htmlcsstoimage.com**." %}
+
-
## Recommended proxy providers
@@ -58,9 +53,8 @@ ZenRows exposes a single proxy endpoint and uses your API key as the password.
- **Port**: `1338`
- **Authentication**: enabled, with your ZenRows username and API key as the password.
-{% include hint.md title="Use bypass hosts to save credits" text="Most paid proxies bill per request or per GB. Add hosts that don't need proxying — like Google Fonts, your own CDN, or analytics — to **Bypass Hosts** so they go out direct and don't burn through your proxy quota." %}
+
-
## Creating a proxy
@@ -68,7 +62,7 @@ Proxies are managed in the dashboard at [https://htmlcsstoimage.com/dashboard/pr
Click **Add New Proxy** and fill out the form:
-{% cloudinary /assets/images/proxies-dashboard.png alt="Create a new HTTP proxy in the HTML/CSS to Image dashboard" %}
+
| Field | Description |
|:------|:------------|
@@ -81,7 +75,6 @@ Click **Add New Proxy** and fill out the form:
After saving, the dashboard will show the proxy's `id`. You'll pass this `id` on each request to use the proxy.
-
## Using a proxy when generating an image
@@ -119,7 +112,6 @@ curl -X POST https://hcti.io/v1/image \
}
```
-
## How it works
@@ -128,9 +120,8 @@ curl -X POST https://hcti.io/v1/image \
- **Bypass Hosts skip the proxy.** Listed hostnames go out direct, which is helpful for high-volume third-party assets that don't need to be routed through your infrastructure.
- The proxy must be reachable over the public internet from our render servers.
-{% include hint.md title="HTTPS only" text="The proxy URL must use `https://`. We do not support plain `http://` proxies." %}
+
-
## Troubleshooting
@@ -143,4 +134,4 @@ curl -X POST https://hcti.io/v1/image \
Use the [Proxies API](/management-api/proxies/) or [MCP tools](/integrations/mcp/tools/#proxies) to create, list, inspect, update, and delete proxy configurations. Management uses `proxies:read`, `proxies:create_update`, or `proxies:delete`; rendering with an existing `proxy_id` uses image permissions. API updates replace the complete configuration. To keep an existing password, include the unchanged username and `authentication.retain_password: true`; omitting authentication removes it.
-{% include code_footer.md version=1 %}
+
diff --git a/guides/advanced/storage-destinations/backblaze-b2.md b/src/content/docs/guides/advanced/storage-destinations/backblaze-b2.mdx
similarity index 85%
rename from guides/advanced/storage-destinations/backblaze-b2.md
rename to src/content/docs/guides/advanced/storage-destinations/backblaze-b2.mdx
index bbeb28c..f803441 100644
--- a/guides/advanced/storage-destinations/backblaze-b2.md
+++ b/src/content/docs/guides/advanced/storage-destinations/backblaze-b2.mdx
@@ -1,28 +1,21 @@
---
-layout: page
title: Upload to Backblaze B2
-nav_title: Backblaze B2
-permalink: /guides/advanced/storage-destinations/backblaze-b2/
-parent: Storage Destinations
-grand_parent: Advanced
-great_grand_parent: Guides
-nav_order: 3
+slug: guides/advanced/storage-destinations/backblaze-b2
description: >-
Upload images generated from HTML/CSS, URL screenshots, and PDFs directly to Backblaze B2 using its S3-compatible API and HTML/CSS to Image.
---
-# Upload images to Backblaze B2
-{: .no_toc }
-{: .fs-9 }
+import { Aside } from '@astrojs/starlight/components';
+import Help from '../../../../../components/Help.astro';
+
+
Send every rendered image or PDF to a Backblaze B2 bucket you control.
-{: .fs-6 .fw-300 }
-
HTML/CSS to Image connects to Backblaze B2 through its S3-compatible API. A scoped B2 application key supplies the access key ID and secret used by HCTI.
-{% include hint.md title="Plan availability" text="Storage destinations are available on the **10,000 images/month plan or higher**. If you'd like to try this on a different plan, email **support@htmlcsstoimage.com**." %}
+
## Before you begin
@@ -100,10 +93,10 @@ The base object is stored as `/.png`, or `.pdf` when the r
Backblaze documents [creating scoped application keys](https://www.backblaze.com/docs/en/cloud-storage-get-started-with-a-backblaze-integration) and [calling the B2 S3-compatible API](https://www.backblaze.com/docs/en/cloud-storage-call-the-s3-compatible-api).
-{% include hint.md title="Next: use your storage destination" text="[Continue to the Storage Destinations guide](/guides/advanced/storage-destinations/) for templates, batches, object storage behavior, and the complete `PUT /v1/store` response." %}
+
## Automate destination setup
You can also automate configuration through the [Storage destinations API](/management-api/storage-destinations/) or [MCP tools](/integrations/mcp/tools/#storage-destinations).
-{% include code_footer.md version=1 %}
+
diff --git a/guides/advanced/storage-destinations/digitalocean-spaces.md b/src/content/docs/guides/advanced/storage-destinations/digitalocean-spaces.mdx
similarity index 83%
rename from guides/advanced/storage-destinations/digitalocean-spaces.md
rename to src/content/docs/guides/advanced/storage-destinations/digitalocean-spaces.mdx
index 588d537..ac68687 100644
--- a/guides/advanced/storage-destinations/digitalocean-spaces.md
+++ b/src/content/docs/guides/advanced/storage-destinations/digitalocean-spaces.mdx
@@ -1,28 +1,21 @@
---
-layout: page
title: Upload to DigitalOcean Spaces
-nav_title: DigitalOcean Spaces
-permalink: /guides/advanced/storage-destinations/digitalocean-spaces/
-parent: Storage Destinations
-grand_parent: Advanced
-great_grand_parent: Guides
-nav_order: 4
+slug: guides/advanced/storage-destinations/digitalocean-spaces
description: >-
Upload images generated from HTML/CSS, URL screenshots, and PDFs directly to DigitalOcean Spaces with a scoped Spaces key and HTML/CSS to Image.
---
-# Upload images to DigitalOcean Spaces
-{: .no_toc }
-{: .fs-9 }
+import { Aside } from '@astrojs/starlight/components';
+import Help from '../../../../../components/Help.astro';
+
+
Send every rendered image or PDF to a DigitalOcean Space you control.
-{: .fs-6 .fw-300 }
-
HTML/CSS to Image connects to DigitalOcean Spaces through its S3-compatible API. HCTI derives the service endpoint from the Space's region and authenticates with a Spaces access key and secret.
-{% include hint.md title="Plan availability" text="Storage destinations are available on the **10,000 images/month plan or higher**. If you'd like to try this on a different plan, email **support@htmlcsstoimage.com**." %}
+
## Before you begin
@@ -92,10 +85,10 @@ The base object is stored as `/.png`, or `.pdf` when the r
DigitalOcean documents the [Spaces S3-compatible API, endpoints, and permission levels](https://docs.digitalocean.com/reference/api/spaces/).
-{% include hint.md title="Next: use your storage destination" text="[Continue to the Storage Destinations guide](/guides/advanced/storage-destinations/) for templates, batches, object storage behavior, and the complete `PUT /v1/store` response." %}
+
## Automate destination setup
You can also automate configuration through the [Storage destinations API](/management-api/storage-destinations/) or [MCP tools](/integrations/mcp/tools/#storage-destinations).
-{% include code_footer.md version=1 %}
+
diff --git a/guides/advanced/storage-destinations/google-cloud-storage.md b/src/content/docs/guides/advanced/storage-destinations/google-cloud-storage.mdx
similarity index 85%
rename from guides/advanced/storage-destinations/google-cloud-storage.md
rename to src/content/docs/guides/advanced/storage-destinations/google-cloud-storage.mdx
index 7751858..8a720fc 100644
--- a/guides/advanced/storage-destinations/google-cloud-storage.md
+++ b/src/content/docs/guides/advanced/storage-destinations/google-cloud-storage.mdx
@@ -1,28 +1,21 @@
---
-layout: page
title: Upload to Google Cloud Storage
-nav_title: Google Cloud Storage
-permalink: /guides/advanced/storage-destinations/google-cloud-storage/
-parent: Storage Destinations
-grand_parent: Advanced
-great_grand_parent: Guides
-nav_order: 6
+slug: guides/advanced/storage-destinations/google-cloud-storage
description: >-
Upload images generated from HTML/CSS, URL screenshots, and PDFs directly to Google Cloud Storage with HMAC credentials and HTML/CSS to Image.
---
-# Upload images to Google Cloud Storage
-{: .no_toc }
-{: .fs-9 }
+import { Aside } from '@astrojs/starlight/components';
+import Help from '../../../../../components/Help.astro';
+
+
Send every rendered image or PDF to a Google Cloud Storage bucket you control.
-{: .fs-6 .fw-300 }
-
HTML/CSS to Image connects to Google Cloud Storage through its S3-compatible XML API. A Google Cloud service account supplies bucket permissions, and an HMAC access ID and secret authenticate HCTI requests.
-{% include hint.md title="Plan availability" text="Storage destinations are available on the **10,000 images/month plan or higher**. If you'd like to try this on a different plan, email **support@htmlcsstoimage.com**." %}
+
## Before you begin
@@ -103,10 +96,10 @@ The base object is stored as `/.png`, or `.pdf` when the r
Google Cloud documents [creating service account HMAC keys](https://cloud.google.com/storage/docs/authentication/managing-hmackeys) and [Cloud Storage IAM roles](https://cloud.google.com/storage/docs/access-control/iam-roles).
-{% include hint.md title="Next: use your storage destination" text="[Continue to the Storage Destinations guide](/guides/advanced/storage-destinations/) for templates, batches, object storage behavior, and the complete `PUT /v1/store` response." %}
+
## Automate destination setup
You can also automate configuration through the [Storage destinations API](/management-api/storage-destinations/) or [MCP tools](/integrations/mcp/tools/#storage-destinations).
-{% include code_footer.md version=1 %}
+
diff --git a/guides/advanced/storage-destinations/index.md b/src/content/docs/guides/advanced/storage-destinations/index.mdx
similarity index 95%
rename from guides/advanced/storage-destinations/index.md
rename to src/content/docs/guides/advanced/storage-destinations/index.mdx
index 179c0bd..f84517a 100644
--- a/guides/advanced/storage-destinations/index.md
+++ b/src/content/docs/guides/advanced/storage-destinations/index.mdx
@@ -1,24 +1,17 @@
---
-layout: page
-title: Storage Destinations
-permalink: /guides/advanced/storage-destinations/
-parent: Advanced
-grand_parent: Guides
-has_children: true
-has_toc: false
-nav_order: 7
+title: Storage destinations
+slug: guides/advanced/storage-destinations
description: >-
Use Storage Destinations to automate rendering HTML/CSS images, URL screenshots or dynamic templated images to your Amazon S3 or S3-compatible bucket.
---
-# Storage Destinations
-{: .no_toc }
-{: .fs-9 }
+import { Aside } from '@astrojs/starlight/components';
+import Help from '../../../../../components/Help.astro';
+
+
Store rendered images in an Amazon S3 or S3-compatible bucket you control.
-{: .fs-6 .fw-300 }
-
## Why use a storage destination?
@@ -31,9 +24,8 @@ A storage destination tells HTML/CSS to Image where to write a copy of each rend
By default, HTML/CSS to Image keeps its normal copy and also writes a copy to your destination. If you choose **Disable HCTI Storage**, your bucket becomes the only location where the rendered file is stored.
-{% include hint.md title="Plan availability" text="Storage destinations are available on the **10,000 images/month plan or higher**. If you'd like to try this on a different plan, email **support@htmlcsstoimage.com**." %}
+
-
## Supported providers
@@ -51,7 +43,6 @@ Choose your provider for credentials, permissions, dashboard fields, connection
The bucket must already exist. HTML/CSS to Image does not create buckets for you.
-
## Creating a storage destination
@@ -81,7 +72,6 @@ The connection test verifies that HCTI can write a small text object. Deleting t
Credentials are encrypted before being stored. Saved secret access keys are not displayed again.
-
## Using a destination
@@ -145,7 +135,6 @@ Creating an image records which destination to use, but rendering remains lazy.
The base object key is `/.png`, or `.pdf` for an image with `pdf_options` defined. Resized, cropped, or reformatted variants use separate object keys so they do not replace the base image (but will begin with the same `/`).
-
## Using a destination with templates
@@ -161,7 +150,6 @@ You can set `storage_destination_id` when creating or updating a template. Each
The destination and its **Disable HCTI Storage** behavior are captured when an image is created. Updating the template later, or changing that setting on the destination, does not change images that have already been created, even if they have not been rendered or stored.
-
## Disable HCTI Storage
@@ -173,7 +161,7 @@ When you choose to **Disable HCTI Storage**, images assigned to that destination
HTML/CSS to Image still retains the image settings and metadata needed to process the render, but it does not store a final rendered image. Your application is responsible for reading or serving the resulting object from your bucket.
-{% include hint.md title="This setting is captured per image" text="Changing **Disable HCTI Storage** affects only images created afterward. Existing images keep the storage behavior selected when they were created." %}
+
## `PUT /v1/store`
@@ -276,7 +264,6 @@ Inspect the object results when handling a `424` response. A `partial` outcome m
After a complete store request, read or serve the object from the bucket and key returned in the response. Objects use the access settings configured on your bucket; HCTI does not make them public or add a public-read ACL. Configure your bucket, application, or CDN to provide whatever access your use case requires.
-
## Disabling or deleting a destination
@@ -285,7 +272,6 @@ After a complete store request, read or serve the object from the bucket and key
- Deleting a destination removes its stored credentials, but does not delete files already written to your bucket. Deleting the image from HCTI also does not delete those files.
- Existing images keep their destination reference.
-
## Troubleshooting
@@ -299,4 +285,4 @@ After a complete store request, read or serve the object from the bucket and key
Use the [Storage destinations API](/management-api/storage-destinations/) or [MCP tools](/integrations/mcp/tools/#storage-destinations) for configuration management and AWS external-ID discovery. Configuration permissions are separate from `images:store`, which is required for authenticated `PUT /v1/store/{id}` requests. The API reference explains replacement updates, credential preservation, and automatic connection testing.
-{% include code_footer.md version=1 %}
+
diff --git a/guides/advanced/storage-destinations/r2.md b/src/content/docs/guides/advanced/storage-destinations/r2.mdx
similarity index 86%
rename from guides/advanced/storage-destinations/r2.md
rename to src/content/docs/guides/advanced/storage-destinations/r2.mdx
index b1dd9ef..2cae357 100644
--- a/guides/advanced/storage-destinations/r2.md
+++ b/src/content/docs/guides/advanced/storage-destinations/r2.mdx
@@ -1,28 +1,21 @@
---
-layout: page
title: Upload to Cloudflare R2
-nav_title: Cloudflare R2
-permalink: /guides/advanced/storage-destinations/r2/
-parent: Storage Destinations
-grand_parent: Advanced
-great_grand_parent: Guides
-nav_order: 2
+slug: guides/advanced/storage-destinations/r2
description: >-
Upload images generated from HTML/CSS, URL screenshots, and PDFs directly to Cloudflare R2 with S3 API credentials and HTML/CSS to Image.
---
-# Upload images to Cloudflare R2
-{: .no_toc }
-{: .fs-9 }
+import { Aside } from '@astrojs/starlight/components';
+import Help from '../../../../../components/Help.astro';
+
+
Send every rendered image or PDF to a Cloudflare R2 bucket you control.
-{: .fs-6 .fw-300 }
-
HTML/CSS to Image connects to Cloudflare R2 through its S3-compatible API. You provide an R2 access key, secret, Cloudflare account ID, and the bucket's data jurisdiction.
-{% include hint.md title="Plan availability" text="Storage destinations are available on the **10,000 images/month plan or higher**. If you'd like to try this on a different plan, email **support@htmlcsstoimage.com**." %}
+
## Before you begin
@@ -106,10 +99,10 @@ The base object is stored as `/.png`, or `.pdf` when the r
Cloudflare documents [creating R2 S3 API credentials](https://developers.cloudflare.com/r2/get-started/s3/) and [jurisdiction-specific R2 endpoints](https://developers.cloudflare.com/r2/reference/data-location/).
-{% include hint.md title="Next: use your storage destination" text="[Continue to the Storage Destinations guide](/guides/advanced/storage-destinations/) for templates, batches, object storage behavior, and the complete `PUT /v1/store` response." %}
+
## Automate destination setup
You can also automate configuration through the [Storage destinations API](/management-api/storage-destinations/) or [MCP tools](/integrations/mcp/tools/#storage-destinations).
-{% include code_footer.md version=1 %}
+
diff --git a/guides/advanced/storage-destinations/s3-compatible.md b/src/content/docs/guides/advanced/storage-destinations/s3-compatible.mdx
similarity index 88%
rename from guides/advanced/storage-destinations/s3-compatible.md
rename to src/content/docs/guides/advanced/storage-destinations/s3-compatible.mdx
index b9bfd53..525808c 100644
--- a/guides/advanced/storage-destinations/s3-compatible.md
+++ b/src/content/docs/guides/advanced/storage-destinations/s3-compatible.mdx
@@ -1,28 +1,21 @@
---
-layout: page
title: Upload to an S3-compatible service
-nav_title: S3-compatible
-permalink: /guides/advanced/storage-destinations/s3-compatible/
-parent: Storage Destinations
-grand_parent: Advanced
-great_grand_parent: Guides
-nav_order: 7
+slug: guides/advanced/storage-destinations/s3-compatible
description: >-
Upload images generated from HTML/CSS, URL screenshots, and PDFs directly to an S3-compatible object storage service with HTML/CSS to Image.
---
-# Upload images to an S3-compatible service
-{: .no_toc }
-{: .fs-9 }
+import { Aside } from '@astrojs/starlight/components';
+import Help from '../../../../../components/Help.astro';
+
+
Connect HTML/CSS to Image to an S3-compatible provider that is not listed separately in the dashboard.
-{: .fs-6 .fw-300 }
-
Use the **Other S3-compatible** provider when your object storage service supports the S3 API but does not have a dedicated HCTI option. You supply the service endpoint, signing region, addressing style, and access keys.
-{% include hint.md title="Plan availability" text="Storage destinations are available on the **10,000 images/month plan or higher**. If you'd like to try this on a different plan, email **support@htmlcsstoimage.com**." %}
+
## Before you begin
@@ -118,10 +111,10 @@ The base object is stored as `/.png`, or `.pdf` when the r
If the provider has a dedicated option in HCTI, use that option instead; it preconfigures the endpoint and addressing behavior. See the [Amazon S3](/guides/advanced/storage-destinations/s3/), [Cloudflare R2](/guides/advanced/storage-destinations/r2/), [Backblaze B2](/guides/advanced/storage-destinations/backblaze-b2/), [DigitalOcean Spaces](/guides/advanced/storage-destinations/digitalocean-spaces/), [Wasabi](/guides/advanced/storage-destinations/wasabi/), and [Google Cloud Storage](/guides/advanced/storage-destinations/google-cloud-storage/) guides.
-{% include hint.md title="Next: use your storage destination" text="[Continue to the Storage Destinations guide](/guides/advanced/storage-destinations/) for templates, batches, object storage behavior, and the complete `PUT /v1/store` response." %}
+
## Automate destination setup
You can also automate configuration through the [Storage destinations API](/management-api/storage-destinations/) or [MCP tools](/integrations/mcp/tools/#storage-destinations).
-{% include code_footer.md version=1 %}
+
diff --git a/guides/advanced/storage-destinations/s3.md b/src/content/docs/guides/advanced/storage-destinations/s3.mdx
similarity index 89%
rename from guides/advanced/storage-destinations/s3.md
rename to src/content/docs/guides/advanced/storage-destinations/s3.mdx
index f0d8fbc..3e0aa38 100644
--- a/guides/advanced/storage-destinations/s3.md
+++ b/src/content/docs/guides/advanced/storage-destinations/s3.mdx
@@ -1,28 +1,21 @@
---
-layout: page
title: Upload to Amazon S3
-nav_title: Amazon S3
-permalink: /guides/advanced/storage-destinations/s3/
-parent: Storage Destinations
-grand_parent: Advanced
-great_grand_parent: Guides
-nav_order: 1
+slug: guides/advanced/storage-destinations/s3
description: >-
Upload images generated from HTML/CSS, URL screenshots, and PDFs directly to Amazon S3 using an IAM role and HTML/CSS to Image.
---
-# Upload images to Amazon S3
-{: .no_toc }
-{: .fs-9 }
+import { Aside } from '@astrojs/starlight/components';
+import Help from '../../../../../components/Help.astro';
+
+
Send every rendered image or PDF to an Amazon S3 bucket you control.
-{: .fs-6 .fw-300 }
-
HTML/CSS to Image connects to Amazon S3 by assuming an IAM role in your AWS account. HCTI receives temporary credentials when it needs to write an object, so you do not need to create or share a long-lived AWS access key.
-{% include hint.md title="Plan availability" text="Storage destinations are available on the **10,000 images/month plan or higher**. If you'd like to try this on a different plan, email **support@htmlcsstoimage.com**." %}
+
## Before you begin
@@ -103,7 +96,7 @@ The base object is stored as `/.png`, or `.pdf` when the r
AWS documents [creating a role with a custom trust policy](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-custom.html) and [using external IDs for third-party access](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_common-scenarios_third-party.html).
-{% include hint.md title="Next: use your storage destination" text="[Continue to the Storage Destinations guide](/guides/advanced/storage-destinations/) for templates, batches, object storage behavior, and the complete `PUT /v1/store` response." %}
+
## Automate destination setup
@@ -111,4 +104,4 @@ You can also automate configuration through the [Storage destinations API](/mana
Use `writer_role_arn` as `Principal.AWS` and `external_id` in a `StringEquals` condition on `sts:ExternalId` when creating your role's trust policy. This supplies both values programmatically, without copying the principal from the dashboard. Attach the bucket permissions described above, then pass **your role's ARN** as the destination's `connection_info.role_arn`. The returned `writer_role_arn` identifies HCTI's existing role, which assumes your role; it is not the destination's role ARN.
-{% include code_footer.md version=1 %}
+
diff --git a/guides/advanced/storage-destinations/wasabi.md b/src/content/docs/guides/advanced/storage-destinations/wasabi.mdx
similarity index 84%
rename from guides/advanced/storage-destinations/wasabi.md
rename to src/content/docs/guides/advanced/storage-destinations/wasabi.mdx
index 198f1ef..b5c45bc 100644
--- a/guides/advanced/storage-destinations/wasabi.md
+++ b/src/content/docs/guides/advanced/storage-destinations/wasabi.mdx
@@ -1,28 +1,21 @@
---
-layout: page
title: Upload to Wasabi
-nav_title: Wasabi
-permalink: /guides/advanced/storage-destinations/wasabi/
-parent: Storage Destinations
-grand_parent: Advanced
-great_grand_parent: Guides
-nav_order: 5
+slug: guides/advanced/storage-destinations/wasabi
description: >-
Upload images generated from HTML/CSS, URL screenshots, and PDFs directly to Wasabi using S3-compatible access keys and HTML/CSS to Image.
---
-# Upload images to Wasabi
-{: .no_toc }
-{: .fs-9 }
+import { Aside } from '@astrojs/starlight/components';
+import Help from '../../../../../components/Help.astro';
+
+
Send every rendered image or PDF to a Wasabi bucket you control.
-{: .fs-6 .fw-300 }
-
HTML/CSS to Image connects to Wasabi through its S3-compatible API. HCTI derives the service endpoint from the bucket's Wasabi region and authenticates with an access key and secret.
-{% include hint.md title="Plan availability" text="Storage destinations are available on the **10,000 images/month plan or higher**. If you'd like to try this on a different plan, email **support@htmlcsstoimage.com**." %}
+
## Before you begin
@@ -93,10 +86,10 @@ The base object is stored as `/.png`, or `.pdf` when the r
Wasabi documents [creating access keys](https://docs.wasabi.com/docs/creating-a-new-access-key) and its [regional S3 service endpoints](https://docs.wasabi.com/docs/service-url-endpoints).
-{% include hint.md title="Next: use your storage destination" text="[Continue to the Storage Destinations guide](/guides/advanced/storage-destinations/) for templates, batches, object storage behavior, and the complete `PUT /v1/store` response." %}
+
## Automate destination setup
You can also automate configuration through the [Storage destinations API](/management-api/storage-destinations/) or [MCP tools](/integrations/mcp/tools/#storage-destinations).
-{% include code_footer.md version=1 %}
+
diff --git a/guides/debugging/blurry-images.md b/src/content/docs/guides/debugging/blurry-images.mdx
similarity index 88%
rename from guides/debugging/blurry-images.md
rename to src/content/docs/guides/debugging/blurry-images.mdx
index 6b8de4f..521d168 100644
--- a/guides/debugging/blurry-images.md
+++ b/src/content/docs/guides/debugging/blurry-images.mdx
@@ -1,22 +1,15 @@
---
-layout: page
title: Blurry images
-permalink: /guides/debugging/blurry-images/
-parent: Debugging
-grand_parent: Guides
-nav_order: 2
+slug: guides/debugging/blurry-images
description: >-
Complete guide to understanding and fixing blurry images with pixel density, resolution, and device scaling
---
-# Debugging Blurry Images
-{: .no_toc }
-{: .fs-9 }
+import { Aside } from '@astrojs/starlight/components';
-The complete guide to crystal-clear, high-resolution images
-{: .fs-6 .fw-300 }
+
-
+Understand pixel density and choose the right resolution for your images.
## Why Images Look Blurry
@@ -24,7 +17,7 @@ Blurry images are almost always caused by **insufficient pixel density** for the
The HTML/CSS to Image API automatically generates **high-resolution images by default** to ensure your images look crisp on all devices. Here's everything you need to know about creating pixel-perfect images.
-{% include hint.md title="Default High Resolution" text="The HCTI API defaults to 2x resolution (device_scale: 2) for HTML images to prioritize image quality. This ensures your images look sharp on both standard and high-DPI displays." %}
+
## Understanding Pixel Density
@@ -113,7 +106,7 @@ For HTML images, verify your API request:
```json
{
"html": "
Your content
",
- "device_scale": 2 // Ensure this is set to 2
+ "device_scale": 2
}
```
@@ -121,7 +114,7 @@ For URL images, always specify device scale:
```json
{
"url": "https://your-site.com",
- "device_scale": 2 // Add this for sharp screenshots
+ "device_scale": 2
}
```
@@ -225,10 +218,10 @@ Use SVG for logos and icons that scale perfectly at any resolution:
### File Size Impact
-Higher resolution means larger files:
-- `device_scale: 1` → Base file size
-- `device_scale: 2` → ~4x larger files
-- `device_scale: 3` → ~9x larger files
+Higher resolution increases pixel count; compressed file size also depends on content and format:
+- `device_scale: 1` → Base pixel count
+- `device_scale: 2` → 4x as many pixels
+- `device_scale: 3` → 9x as many pixels
### Optimization Strategies
@@ -239,7 +232,7 @@ Higher resolution means larger files:
- WebP for modern browsers (when supported)
3. **Consider bandwidth**: Higher resolution may impact load times
-{% include hint.md title="Performance Tip" text="Use device_scale: 2 as the sweet spot for web usage. It provides excellent quality on all displays while keeping file sizes reasonable." %}
+
## Troubleshooting Checklist
@@ -290,6 +283,5 @@ Include:
We love solving tricky rendering problems and helping you achieve pixel-perfect results.
-{% include code_footer.md version=2 %}
-
+
diff --git a/guides/debugging/cloudflare-challenges.md b/src/content/docs/guides/debugging/cloudflare-challenges.mdx
similarity index 86%
rename from guides/debugging/cloudflare-challenges.md
rename to src/content/docs/guides/debugging/cloudflare-challenges.mdx
index 7d23771..0818992 100644
--- a/guides/debugging/cloudflare-challenges.md
+++ b/src/content/docs/guides/debugging/cloudflare-challenges.mdx
@@ -1,25 +1,18 @@
---
-layout: page
-title: Avoiding Cloudflare Challenges in Screenshots
-nav_title: Cloudflare Challenges
-permalink: /guides/debugging/cloudflare-challenges/
-parent: Debugging
-grand_parent: Guides
-nav_order: 5
+title: Cloudflare challenges in screenshots
+slug: guides/debugging/cloudflare-challenges
description: >-
Allow authorized HTML/CSS to Image renders through a Cloudflare challenge on a site you control.
---
-# Bypassing Cloudflare challenges for sites you control
-{: .no_toc }
-{: .fs-9 }
+import { Aside } from '@astrojs/starlight/components';
+import Help from '../../../../components/Help.astro';
-Diagnose screenshots that capture a Cloudflare challenge instead of your page, then create a safe exception for authorized renders.
-{: .fs-6 .fw-300 }
+
-
+Diagnose screenshots that capture a Cloudflare challenge instead of your page, then create a safe exception for authorized renders.
-{% include hint.md title="Only for sites you control" text="Use this approach only for a domain and Cloudflare account you own or administer. It is not a way to bypass another site's access controls." %}
+
## The problem
@@ -127,6 +120,6 @@ Custom headers are never sent to an origin outside the requested URL's origin an
- Skip only the feature that blocks the authorized render.
- Rotate the value periodically and immediately after suspected exposure.
-{% include hint.md title="Not a Cloudflare challenge?" text="If the site is not protected by Cloudflare, or the problem is related to the render's source IP, network access, or region, an [HTTP proxy](/guides/advanced/proxies/) may be a better fit. Proxies can provide a stable allowlisted IP, access to private content, or different outbound routing." %}
+
-{% include code_footer.md version=2 %}
+
diff --git a/guides/debugging/cropping-issues.md b/src/content/docs/guides/debugging/cropping-issues.mdx
similarity index 73%
rename from guides/debugging/cropping-issues.md
rename to src/content/docs/guides/debugging/cropping-issues.mdx
index a207007..c77db41 100644
--- a/guides/debugging/cropping-issues.md
+++ b/src/content/docs/guides/debugging/cropping-issues.mdx
@@ -1,46 +1,41 @@
---
-layout: page
title: Cropping issues
-permalink: /guides/debugging/cropping-issues/
-parent: Debugging
-grand_parent: Guides
-nav_order: 1
+slug: guides/debugging/cropping-issues
description: >-
Fix images that aren't cropping correctly. Learn why full HTML pages don't auto-crop and how to use HTML snippets instead.
---
-# Debugging Cropping Issues
-{: .no_toc }
-{: .fs-9 }
+import { Aside } from '@astrojs/starlight/components';
+import DocImage from '../../../../components/DocImage.astro';
+import Help from '../../../../components/Help.astro';
-Why your image isn't cropping to the size you expect
-{: .fs-6 .fw-300 }
-
-
+
-## Table of contents
-{: .no_toc .text-delta }
+Why your image isn't cropping to the size you expect
-1. TOC
-{:toc}
+
+
---
## How Auto-Cropping Works
+
The HTML/CSS to Image API has a powerful **auto-cropping** feature that automatically sizes your image to match your HTML content. When you send an HTML snippet, the API crops the resulting image to the dimensions of the **outermost HTML element**.
However, this only works for **HTML snippets**—not full HTML pages.
-{% include hint.md title="Key Concept" text="Auto-cropping works by detecting the outermost element in your HTML and cropping to its dimensions. If you send a full HTML page with <html> and <body> tags, the API renders the entire viewport instead." %}
+
---
## The Most Common Mistake
+
The most frequent cropping issue we see: **sending a full HTML page when you meant to send an HTML snippet**.
### What Happens with a Full HTML Page
+
When you include ``, ``, ``, or `` tags, the API treats your content as a **full webpage** and renders it inside the default viewport (1920x1080 pixels).
@@ -65,13 +60,14 @@ When you include ``, ``, ``, or `` tags, the AP
**Result:** Instead of a 400x200 image, you get a 1920x1080 image with your content in the corner:
-
+
Notice how the image includes the entire viewport with a gray background, not just your 400x200 card.
---
## The Solution: Use HTML Snippets
+
Remove the ``, ``, ``, and `` tags. Send only the HTML elements you want in your image.
@@ -86,13 +82,14 @@ Remove the ``, ``, ``, and `` tags. Send only the HT
- {% include template-editor/Property_Padding.md details="Space between the panel edge and its children" panel=true %}
- {% include template-editor/property.md name="Direction" details="Row or column layout" %}
- {% include template-editor/property.md name="Wrap" details="Whether children stay on one line or wrap" %}
- {% include template-editor/property.md name="Gap" details="Space between child blocks" %}
- {% include template-editor/property.md name="Justify content" details="Distribution along the main direction" %}
- {% include template-editor/property.md name="Align items" details="Default alignment across the panel" more=align_items_details %}
-
- {% include template-editor/property.md name="Grow" details="How much the child expands when extra space is available" %}
- {% include template-editor/property.md name="Shrink" details="How much the child can contract when space is tight" %}
- {% include template-editor/property.md name="Align" details="Alignment override for this child" %}
- {% include template-editor/property.md name="Basis" details="Auto sizing or a specific starting size" more=flex_basis_details %}
- {% include template-editor/property.md name="Margin" details="Extra space around this child" %}
-
-
-See the [Advanced Panels guide](/template-editor/advanced/panels/) for help choosing and combining panel types.
diff --git a/template-editor/blocks/free-panel.md b/template-editor/blocks/free-panel.md
deleted file mode 100644
index 25762aa..0000000
--- a/template-editor/blocks/free-panel.md
+++ /dev/null
@@ -1,34 +0,0 @@
----
-layout: template-editor-block
-title: Free Panel
-permalink: /template-editor/blocks/free-panel/
-parent: Blocks
-grand_parent: Template Editor
-nav_order: 6
-description: >-
- Use the Free Panel block for grouped free-positioned content.
----
-# Free Panel
-{: .no_toc }
-{: .fs-9 }
-
-Use a Free Panel when a group of blocks should move together while its children keep normal canvas-style positioning inside the panel.
-{: .fs-4 .fw-300 }
-
-
-
-Add a Free Panel from the left sidebar. Put blocks inside it by dragging them in or using the reparent control.
-
-{% include template-editor/common-block-options.md %}
-
-{% include template-editor/block-specific-options-intro.md %}
-
-
-
PropertyDetails
- {% include template-editor/Property_Padding.md details="Space between the panel edge and its children" panel=true %}
- {% include template-editor/property.md name="Overflow" details="Content can be clipped or allowed to show outside the panel" %}
- {% include template-editor/property.md name="Child positioning" details="Children use X/Y position inside the panel area" %}
- {% include template-editor/property.md name="Reparenting" details="Move existing blocks into or out of the panel" %}
-
-
-See the [Advanced Panels guide](/template-editor/advanced/panels/) for help choosing and combining panel types.
diff --git a/template-editor/blocks/grid-panel.md b/template-editor/blocks/grid-panel.md
deleted file mode 100644
index 90e0449..0000000
--- a/template-editor/blocks/grid-panel.md
+++ /dev/null
@@ -1,61 +0,0 @@
----
-layout: template-editor-block
-title: Grid Panel
-permalink: /template-editor/blocks/grid-panel/
-parent: Blocks
-grand_parent: Template Editor
-nav_order: 8
-description: >-
- Use the Grid Panel block for two-dimensional layouts.
----
-# Grid Panel
-{: .no_toc }
-{: .fs-9 }
-
-Use Grid Panels for two-dimensional layouts where rows and columns guide the flow.
-{: .fs-4 .fw-300 }
-
-
-
-Add a Grid Panel from the left sidebar. Children flow through the grid in order. Dragging or moving children changes their place in that flow.
-
-{% capture grid_size_details %}
-{% include hint.md title="Auto size to grid cell" text="Blocks inside a Grid Panel can use Auto size to grid cell. When enabled, the child sizes to the grid area it receives. Turn it off when the child should keep its own width or height." heading=false %}
-{% endcapture %}
-
-{% include template-editor/common-block-options.md extra_size=grid_size_details %}
-
-{% include template-editor/block-specific-options-intro.md %}
-
-{% capture grid_flow_details %}
-Unlike a Table Panel, Grid Panel children are not assigned to specific cells. They flow in order through the available grid space.
-{% endcapture %}
-
-
-
PropertyDetails
- {% include template-editor/Property_Padding.md details="Space between the panel edge and its children" panel=true %}
- {% include template-editor/property.md name="Columns" details="Number and sizing of columns" more=grid_flow_details %}
- {% include template-editor/property.md name="Rows" details="Number and sizing of rows" %}
- {% include template-editor/property.md name="Column gap" details="Space between columns" %}
- {% include template-editor/property.md name="Row gap" details="Space between rows" %}
- {% include template-editor/property.md name="Justify items" details="Default horizontal alignment of children" %}
- {% include template-editor/property.md name="Align items" details="Default vertical alignment of children" %}
-
- {% include template-editor/property.md name="Columns" details="How many grid columns the child covers" %}
- {% include template-editor/property.md name="Rows" details="How many grid rows the child covers" %}
- {% include template-editor/property.md name="Horizontal align" details="Horizontal alignment override for this child" %}
- {% include template-editor/property.md name="Vertical align" details="Vertical alignment override for this child" %}
- {% include template-editor/property.md name="Margin" details="Extra space around this child" %}
- {% include template-editor/property.md name="Auto size to grid cell" details="Whether the child sizes to its grid area" %}
-
-
-{% include template-editor/grid-tracks.md %}
-
-See the [Advanced Panels guide](/template-editor/advanced/panels/) for help choosing and combining panel types.
diff --git a/template-editor/blocks/html.md b/template-editor/blocks/html.md
deleted file mode 100644
index cc05f25..0000000
--- a/template-editor/blocks/html.md
+++ /dev/null
@@ -1,39 +0,0 @@
----
-layout: template-editor-block
-title: HTML
-permalink: /template-editor/blocks/html/
-parent: Blocks
-grand_parent: Template Editor
-nav_order: 5
-description: >-
- Use the HTML block for custom markup inside a template editor design.
-templated_content: >-
- HTML blocks can use Handlebars-style placeholders inside their markup.
----
-# HTML Block
-{: .no_toc }
-{: .fs-9 }
-
-Use HTML blocks when a design needs custom markup inside the editor canvas.
-{: .fs-4 .fw-300 }
-
-
-
-Add an HTML block from the left sidebar. Use the right sidebar for block properties, or double-click the block on the canvas to edit its markup inline.
-
-{% include template-editor/common-block-options.md %}
-
-{% include template-editor/block-specific-options-intro.md %}
-
-
-
PropertyDetails
- {% include template-editor/property.md name="Markup" details="The HTML rendered inside the block" %}
- {% include template-editor/Property_Padding.md details="Space between the block edge and the HTML content" %}
- {% include template-editor/property.md name="Text color" details="Default text color inside the block" templated=true %}
-
-
-## Preview Notes
-
-HTML blocks are previewed in an iframe while you work in the editor. That keeps custom markup isolated from the editor UI, but it can make some preview behavior look slightly different from the exported HTML.
-
-If something looks off in the editor preview, use the HTML/export button in the top toolbar to inspect the generated HTML.
diff --git a/template-editor/blocks/image.md b/template-editor/blocks/image.md
deleted file mode 100644
index 5a4d171..0000000
--- a/template-editor/blocks/image.md
+++ /dev/null
@@ -1,78 +0,0 @@
----
-layout: template-editor-block
-title: Image
-permalink: /template-editor/blocks/image/
-parent: Blocks
-grand_parent: Template Editor
-nav_order: 3
-description: >-
- Use the Image block for media, image URLs, and stock photos.
-templated_content: >-
- URL sources can be assigned to a template variable, enabling each image generated to use a different URL.
----
-# Image Block
-{: .no_toc }
-{: .fs-9 }
-
-Use Image blocks for photos, logos, avatars, product images, media library assets, and remote image URLs.
-{: .fs-4 .fw-300 }
-
-
-
-Add an Image block from the left sidebar. You can start from your media library, a URL, or a stock photo, then change the source later from the block controls.
-
-
-
-
-
-{% capture image_border_details %}
-{% include hint.md title="Image borders" text="When an image uses `Contain` or `Scale Down`, the visible image may not fill the whole block. Borders and shadows can follow the rendered image more closely than the full block area." heading=false %}
-{% endcapture %}
-
-{% include template-editor/common-block-options.md extra_border=image_border_details %}
-
-{% include template-editor/block-specific-options-intro.md %}
-
-{% capture media_source_details %}
-Choose from your organization's media library, paste an external URL, or select a stock photo from [Pexels](https://www.pexels.com/).
-{% endcapture %}
-
-{% capture fit_details %}
-Fit controls how the image is sized inside the block, including whether it fills, stretches, crops, or stays at its natural size.
-{% endcapture %}
-
-
-
PropertyDetails
- {% include template-editor/property.md name="Media source" details="Media library, external URL, or stock photo" more=media_source_details templated=true %}
- {% include template-editor/property.md name="Fit" details="How the image fits inside the block" more=fit_details %}
- {% include template-editor/Property_Opacity.md %}
- {% include template-editor/property.md name="Object position" details="Which part of the image stays visible when using cover-style cropping" templated=true %}
-
-
-## Image Sources
-
-The source control decides where the block gets its image.
-
-### Media Library
-
-Use the Media Library for assets you expect to reuse, such as logos, product photos, brand images, background images, and icons.
-
-You can upload assets from the media browser when your plan supports uploads. Uploaded assets are shared across your organization's templates, so the same image can be reused without uploading it again for each template.
-
-### URL
-
-Use a URL source when the image already exists at an absolute URL, or when the image should come from render-time data through a template variable.
-
-### Stock Photos
-
-Use Stock Photos to choose an image provided by [Pexels](https://www.pexels.com/).
-
-## Fit Modes
-
-| Mode | Details |
-|:-----|:--------|
-| Cover | Fills the block and may crop the image |
-| Contain | Keeps the whole image visible inside the block |
-| Fill | Stretches the image to the block size |
-| Scale Down | Keeps the image natural size unless it needs to shrink |
-| None | Keeps the image at its natural size |
diff --git a/template-editor/blocks/rich-text.md b/template-editor/blocks/rich-text.md
deleted file mode 100644
index 731c432..0000000
--- a/template-editor/blocks/rich-text.md
+++ /dev/null
@@ -1,62 +0,0 @@
----
-layout: template-editor-block
-title: Rich Text
-permalink: /template-editor/blocks/rich-text/
-parent: Blocks
-grand_parent: Template Editor
-nav_order: 1
-description: >-
- Use the Rich Text block for styled and templated text in the template editor.
-templated_content: >-
- Rich Text supports Handlebars inside the text content. Use it for variables, loops, conditionals, and repeated content inside one text block.
----
-# Rich Text Block
-{: .no_toc }
-{: .fs-9 }
-
-Use Rich Text when one text block needs varied typography, inline styling, or Handlebars composition.
-{: .fs-4 .fw-300 }
-
-
-
-Add a Rich Text block from the left sidebar by choosing **Rich Text Box**. Double-click the block on the canvas to edit the text directly.
-
-
-
-
-
-Select a word, line, letter, or any range of text and use the inline toolbar to change font size, color, underline, and other text styling for only that selection.
-
-Use the right sidebar when you want one value across the whole block. Changing a text styling property there applies that value across the block for that property, replacing per-selection differences.
-
-Some inline toolbar controls can show `mixed` when the current selection already contains more than one value. Choosing a new value applies it to the current selection.
-
-{% capture text_shadow_details %}
-{% include hint.md title="Text block shadows" text="Rich Text blocks support drop shadows and glows. Inner shadows and spread are not available for text blocks." heading=false %}
-{% endcapture %}
-
-{% include template-editor/common-block-options.md extra_shadow=text_shadow_details %}
-
-{% include template-editor/block-specific-options-intro.md %}
-
-{% capture selected_text_details %}
-Select text on the canvas to apply this property to only that selection. Use the right sidebar to set one value across the whole block for this property.
-{% endcapture %}
-
-
-
PropertyDetails
- {% include template-editor/Property_Opacity.md %}
- {% include template-editor/property.md name="Font" details="Font family, weight, and italic style" %}
- {% include template-editor/property.md name="Font size" details="Text size for the selected text or block" more=selected_text_details templated=true %}
- {% include template-editor/property.md name="Font color" details="Text color" more=selected_text_details templated=true %}
- {% include template-editor/property.md name="Line height" details="Space between lines" templated=true %}
- {% include template-editor/property.md name="Letter spacing" details="Space between letters" templated=true %}
- {% include template-editor/property.md name="Horizontal align" details="How lines align inside the block" %}
- {% include template-editor/property.md name="Vertical align" details="How text sits inside the block" %}
- {% include template-editor/property.md name="Wrap" details="How text wraps across lines" %}
- {% include template-editor/property.md name="Overflow" details="What happens when text is larger than the block" %}
- {% include template-editor/property.md name="Underline" details="Underline style and color" more=selected_text_details templated=true %}
- {% include template-editor/property.md name="Text outline" details="Stroke around text" templated=true %}
- {% include template-editor/property.md name="Transform" details="Text casing" %}
- {% include template-editor/Property_Padding.md details="Space between the block edge and the text" %}
-
diff --git a/template-editor/blocks/shape.md b/template-editor/blocks/shape.md
deleted file mode 100644
index f4752a8..0000000
--- a/template-editor/blocks/shape.md
+++ /dev/null
@@ -1,52 +0,0 @@
----
-layout: template-editor-block
-title: Shape
-permalink: /template-editor/blocks/shape/
-parent: Blocks
-grand_parent: Template Editor
-nav_order: 4
-description: >-
- Use the Shape block for simple graphic elements.
----
-# Shape Block
-{: .no_toc }
-{: .fs-9 }
-
-Use Shape blocks for simple marks, backgrounds, accents, and decorative structure.
-{: .fs-4 .fw-300 }
-
-
-
-Add a Shape block from the left sidebar. Choose the shape when you add the block.
-
-{% capture shape_background_details %}
-{% include hint.md title="Shape background" text="Rectangles and circles support solid, gradient, and pattern backgrounds. Stars and triangles support solid backgrounds plus linear and radial gradients. Pattern backgrounds and conic gradients are not available for stars or triangles." heading=false %}
-{% endcapture %}
-
-{% capture shape_shadow_details %}
-{% include hint.md title="Shape shadow" text="Stars and triangles support drop shadows and glows. Inner shadows and spread are not available for those shapes." heading=false %}
-{% endcapture %}
-
-{% capture shape_border_details %}
-{% include hint.md title="Shape border" text="Rectangles and circles support advanced borders. Stars and triangles use simple border controls." heading=false %}
-{% endcapture %}
-
-{% include template-editor/common-block-options.md extra_background=shape_background_details extra_border=shape_border_details extra_shadow=shape_shadow_details %}
-
-## Shape Types
-
-Available shapes:
-
-- Rectangle
-- Circle
-- Triangle
-- Star
-
-The shape type is set when the block is added and cannot be changed later. If you need a different shape, add a new Shape block.
-
-## Block Specific Properties
-
-
-
PropertyDetails
- {% include template-editor/Property_Opacity.md %}
-
diff --git a/template-editor/blocks/simple-text.md b/template-editor/blocks/simple-text.md
deleted file mode 100644
index 8117620..0000000
--- a/template-editor/blocks/simple-text.md
+++ /dev/null
@@ -1,78 +0,0 @@
----
-layout: template-editor-block
-title: Simple Text
-permalink: /template-editor/blocks/simple-text/
-parent: Blocks
-grand_parent: Template Editor
-nav_order: 2
-description: >-
- Use the Simple Text block for direct templating and auto font sizing.
-templated_content: >-
- Simple Text can source its Content property directly from a template variable. Do not use Handlebars syntax in the text itself.
----
-# Simple Text Block
-{: .no_toc }
-{: .fs-9 }
-
-Use Simple Text when one block should render one text value with a shared style.
-{: .fs-4 .fw-300 }
-
-
-
-Add a Simple Text block from the left sidebar by choosing **Simple Text Box**. Its content and text properties are available in the right sidebar.
-
-{% capture text_shadow_details %}
-{% include hint.md title="Text block shadows" text="Simple Text blocks support drop shadows and glows. Inner shadows and spread are not available for text blocks." heading=false %}
-{% endcapture %}
-
-{% include template-editor/common-block-options.md extra_shadow=text_shadow_details %}
-
-{% include template-editor/block-specific-options-intro.md %}
-
-{% capture auto_font_size_details %}
-
-
-
Auto Font Size
-
When enabled, the text can scale to fit the block instead of staying at one fixed size.
-
-
-
-
Minimum Font Size
-
Available when auto font size is enabled. This is the smallest size the text can use while fitting the block.
-
-
-
-
Maximum Font Size
-
Available when auto font size is enabled. This is the largest size the text can use while fitting the block.
-
-
-
-{% include template-editor/templatable-properties.md items="Auto font size|Minimum font size|Maximum font size" %}
-{% endcapture %}
-
-
-
PropertyDetails
- {% include template-editor/property.md name="Content" details="The text value rendered by the block" templated=true %}
- {% include template-editor/property.md name="Auto font size" details="Scales text within a defined font-size range" more=auto_font_size_details templated=true %}
- {% include template-editor/Property_Padding.md details="Space between the block edge and the text" %}
- {% include template-editor/Property_Opacity.md %}
- {% include template-editor/property.md name="Font" details="Font family, weight, and italic style" %}
- {% include template-editor/property.md name="Font color" details="The text color" templated=true %}
- {% include template-editor/property.md name="Line height" details="Space between lines" templated=true %}
- {% include template-editor/property.md name="Letter spacing" details="Space between letters" templated=true %}
- {% include template-editor/property.md name="Horizontal align" details="Left, center, or right alignment" %}
- {% include template-editor/property.md name="Vertical align" details="Top, middle, or bottom alignment inside the block" %}
- {% include template-editor/property.md name="Underline" details="Underline style and color" templated=true %}
- {% include template-editor/property.md name="Text outline" details="Stroke around text" templated=true %}
- {% include template-editor/property.md name="Transform" details="Text casing" %}
-
-
-## Auto Font Size
-
-Auto font size lets the text scale to fit the block while staying inside the range you set. When it is enabled, Minimum font size and Maximum font size controls become available.
-
-
-
-
-
-Turn auto font size off when the block should keep one specific font size.
diff --git a/template-editor/blocks/table-panel.md b/template-editor/blocks/table-panel.md
deleted file mode 100644
index 60ba3d1..0000000
--- a/template-editor/blocks/table-panel.md
+++ /dev/null
@@ -1,63 +0,0 @@
----
-layout: template-editor-block
-title: Table Panel
-permalink: /template-editor/blocks/table-panel/
-parent: Blocks
-grand_parent: Template Editor
-nav_order: 9
-description: >-
- Use the Table Panel block for explicit cell layouts.
----
-# Table Panel
-{: .no_toc }
-{: .fs-9 }
-
-Use Table Panels when each child block belongs in a specific cell.
-{: .fs-4 .fw-300 }
-
-
-
-Add a Table Panel from the left sidebar. Children are assigned to table positions, which makes the layout explicit instead of flow-based.
-
-{% capture table_size_details %}
-{% include hint.md title="Auto size to grid cell" text="Blocks inside a Table Panel can use Auto size to grid cell. When enabled, the child sizes to the cell area. Turn it off when the child should keep its own width or height." heading=false %}
-{% endcapture %}
-
-{% include template-editor/common-block-options.md extra_size=table_size_details %}
-
-{% include template-editor/block-specific-options-intro.md %}
-
-{% capture table_position_details %}
-In a Table Panel, individual children have assigned table positions and can cover more than one row or column. In a Grid Panel, children flow in order instead of being assigned to exact cells.
-{% endcapture %}
-
-
-
PropertyDetails
- {% include template-editor/Property_Padding.md details="Space between the panel edge and its children" panel=true %}
- {% include template-editor/property.md name="Columns" details="Number and sizing of columns" more=table_position_details %}
- {% include template-editor/property.md name="Rows" details="Number and sizing of rows" %}
- {% include template-editor/property.md name="Column gap" details="Space between columns" %}
- {% include template-editor/property.md name="Row gap" details="Space between rows" %}
- {% include template-editor/property.md name="Cell alignment" details="Default alignment for blocks inside cells" %}
-
- {% include template-editor/property.md name="Position" details="Assigned column and row for the child" %}
- {% include template-editor/property.md name="Columns" details="How many table columns the child covers" %}
- {% include template-editor/property.md name="Rows" details="How many table rows the child covers" %}
- {% include template-editor/property.md name="Horizontal align" details="Horizontal alignment override for this child" %}
- {% include template-editor/property.md name="Vertical align" details="Vertical alignment override for this child" %}
- {% include template-editor/property.md name="Margin" details="Extra space around this child inside the table cell" %}
- {% include template-editor/property.md name="Auto size to grid cell" details="Whether the child sizes to the cell area" %}
-
-
-Blocks inside table cells can use margin to create spacing within the cell without changing the whole table.
-
-{% include template-editor/grid-tracks.md %}
-
-See the [Advanced Panels guide](/template-editor/advanced/panels/) for help choosing and combining panel types.
diff --git a/tests/external-links.test.mjs b/tests/external-links.test.mjs
new file mode 100644
index 0000000..d5b8fa5
--- /dev/null
+++ b/tests/external-links.test.mjs
@@ -0,0 +1,23 @@
+import test from 'node:test';
+import assert from 'node:assert/strict';
+import { load } from 'cheerio';
+import { externalLinksHtml, isExternalLink } from '../scripts/external-links.mjs';
+
+test('external HTTP links open in a new tab, retaining existing rel values', () => {
+ const $ = load(externalLinksHtml('HomeFonts
<a href="https://example.com">
', 'https://docs.test'));
+ for (const link of $('a').toArray()) {
+ assert.equal($(link).attr('target'), '_blank');
+ assert.match($(link).attr('rel'), /noopener/);
+ assert.doesNotMatch($(link).attr('rel'), /noreferrer/);
+ }
+ assert.equal($('main a').attr('rel'), 'nofollow noopener');
+ assert.equal($('code').text(), '');
+});
+
+test('internal, fragment, email and other non-HTTP links are left alone', () => {
+ for (const href of ['/parameters/', '#how-it-works', '../selector/', 'https://docs.test/parameters/', 'https://docs.htmlcsstoimage.com/parameters/', 'mailto:support@htmlcsstoimage.com', 'tel:+12345']) {
+ assert.equal(isExternalLink(href, 'https://docs.test'), false, href);
+ const $ = load(externalLinksHtml(`Link`, 'https://docs.test'));
+ assert.equal($('a').attr('target'), undefined, href);
+ }
+});
diff --git a/tests/images.test.mjs b/tests/images.test.mjs
new file mode 100644
index 0000000..71feedc
--- /dev/null
+++ b/tests/images.test.mjs
@@ -0,0 +1,51 @@
+import test from 'node:test';
+import assert from 'node:assert/strict';
+import fs from 'node:fs/promises';
+import os from 'node:os';
+import path from 'node:path';
+import sharp from 'sharp';
+import { imageVariants } from '../scripts/image-variants.mjs';
+
+test('static variants have correct dimensions, formats, alpha, and reuse unchanged output', async () => {
+ const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'docs-image-test-'));
+ try {
+ const input = await sharp({ create: { width: 500, height: 250, channels: 4, background: '#007a4e80' } }).png().toBuffer();
+ const result = await imageVariants(input, dir, '/assets/images/nested/example%20image.png');
+ assert.equal(result.width, 500);
+ assert.equal(result.height, 250);
+ for (const [format, sources] of Object.entries(result.sources)) {
+ assert.deepEqual(sources.map(source => source.width), [384, 500]);
+ for (const source of sources) {
+ assert.match(source.src, /^\/assets\/images\/g\/nested\/example%20image\/[a-f0-9]{20}\.w\d+\.(avif|webp)$/);
+ const meta = await sharp(path.join(dir, decodeURIComponent(source.src.slice('/assets/images/g/'.length)))).metadata();
+ assert.equal(meta.width, source.width);
+ assert.equal(meta.height, source.width / 2);
+ assert.equal(meta.format, format === 'avif' ? 'heif' : format);
+ assert.equal(meta.hasAlpha, true);
+ }
+ }
+ const file = path.join(dir, decodeURIComponent(result.sources.webp[0].src.slice('/assets/images/g/'.length)));
+ const before = (await fs.stat(file)).mtimeMs;
+ assert.deepEqual(await imageVariants(input, dir, '/assets/images/nested/example%20image.png'), result);
+ assert.equal((await fs.stat(file)).mtimeMs, before);
+ await fs.unlink(file);
+ assert.deepEqual(await imageVariants(input, dir, '/assets/images/nested/example%20image.png'), result);
+ assert.equal((await sharp(file).metadata()).width, 384);
+ const changed = await sharp(input).negate().png().toBuffer();
+ assert.notEqual((await imageVariants(changed, dir, '/assets/images/nested/example%20image.png')).sources.webp[0].src, result.sources.webp[0].src);
+ } finally {
+ await fs.rm(dir, { recursive: true, force: true });
+ }
+});
+
+test('small images produce one variant at their natural width', async () => {
+ const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'docs-image-small-'));
+ try {
+ const input = await sharp({ create: { width: 32, height: 16, channels: 3, background: '#ffffff' } }).png().toBuffer();
+ const result = await imageVariants(input, dir);
+ assert.deepEqual(result.sources.webp.map(source => source.width), [32]);
+ assert.deepEqual(result.sources.avif.map(source => source.width), [32]);
+ } finally {
+ await fs.rm(dir, { recursive: true, force: true });
+ }
+});
diff --git a/tests/markdown.test.mjs b/tests/markdown.test.mjs
new file mode 100644
index 0000000..3643550
--- /dev/null
+++ b/tests/markdown.test.mjs
@@ -0,0 +1,66 @@
+import test from 'node:test';
+import assert from 'node:assert/strict';
+import { marked } from 'marked';
+import { load } from 'cheerio';
+import { renderMarkdown } from '../scripts/markdown-exports.mjs';
+
+test('changelog publication dates remain separate from article text', () => {
+ const markdown = renderMarkdown('
New Go client.
', 'Go client');
+ assert.equal(markdown, '# Go client\n\nSeptember 15, 2026\n\nNew Go client.\n');
+});
+
+test('changelog Markdown includes neighboring entries even though search ignores navigation', () => {
+ const markdown = renderMarkdown('
Release notes.
', 'Update');
+ assert.match(markdown, /Previous: \[Older update\]\(\/changelog\/older\/\) — Sep 14, 2026/);
+ assert.match(markdown, /Next: \[Newer update\]\(\/changelog\/newer\/\) — Sep 16, 2026/);
+});
+
+test('link cards separate the linked title from the description', () => {
+ const markdown = renderMarkdown('