---
title: "Transformers.js 4 in a Vue app: embeddings and classification in the browser · vuejs.school"
description: "Run a sentiment classifier and a small semantic search in a Vue app with Transformers.js 4, in a web worker, on WebGPU when the browser has it."
canonical_url: "https://vuejs.school/community/blog/transformers-js-4-in-a-vue-app"
meta:
  "og:description": "Run a sentiment classifier and a small semantic search in a Vue app with Transformers.js 4, in a web worker, on WebGPU when the browser has it."
  "og:image": "https://vuejs.school/_og/d/a_~VHJhbnNmb3JtZXJzLmpzIDQgaW4gYSBWdWUgYXBwOiBlbWJlZGRpbmdzIGFuZCBjbGFzc2lmaWNhdGlvbiBpbiB0aGUgYnJvd3NlciDCtyB2dWVqcy5zY2hvb2w%2Cc_OgCard.takumi%2Ctitle_~VHJhbnNmb3JtZXJzLmpzIDQgaW4gYSBWdWUgYXBwOiBlbWJlZGRpbmdzIGFuZCBjbGFzc2lmaWNhdGlvbiBpbiB0aGUgYnJvd3Nlcg%2Cdescription_~UnVuIGEgc2VudGltZW50IGNsYXNzaWZpZXIgYW5kIGEgc21hbGwgc2VtYW50aWMgc2VhcmNoIGluIGEgVnVlIGFwcCB3aXRoIFRyYW5zZm9ybWVycy5qcyA0LCBpbiBhIHdlYiB3b3JrZXIsIG9uIFdlYkdQVSB3aGVuIHRoZSBicm93c2VyIGhhcyBpdC4%2Ceyebrow_~VHV0b3JpYWwgwrcgNyBtaW4gcmVhZA%2Ctint_sand%2Cp_Ii9jb21tdW5pdHkvYmxvZy90cmFuc2Zvcm1lcnMtanMtNC1pbi1hLXZ1ZS1hcHAi%2Cs_ziTAbLjqdhfBjIL0.png"
  "og:image:alt": "Transformers.js 4 in a Vue app: embeddings and classification in the browser · vuejs.school"
  "og:image:height": "600"
  "og:image:type": image/png
  "og:image:width": "1200"
  "og:title": "Transformers.js 4 in a Vue app: embeddings and classification in the browser · vuejs.school"
  "twitter:image": "https://vuejs.school/_og/d/a_~VHJhbnNmb3JtZXJzLmpzIDQgaW4gYSBWdWUgYXBwOiBlbWJlZGRpbmdzIGFuZCBjbGFzc2lmaWNhdGlvbiBpbiB0aGUgYnJvd3NlciDCtyB2dWVqcy5zY2hvb2w%2Cc_OgCard.takumi%2Ctitle_~VHJhbnNmb3JtZXJzLmpzIDQgaW4gYSBWdWUgYXBwOiBlbWJlZGRpbmdzIGFuZCBjbGFzc2lmaWNhdGlvbiBpbiB0aGUgYnJvd3Nlcg%2Cdescription_~UnVuIGEgc2VudGltZW50IGNsYXNzaWZpZXIgYW5kIGEgc21hbGwgc2VtYW50aWMgc2VhcmNoIGluIGEgVnVlIGFwcCB3aXRoIFRyYW5zZm9ybWVycy5qcyA0LCBpbiBhIHdlYiB3b3JrZXIsIG9uIFdlYkdQVSB3aGVuIHRoZSBicm93c2VyIGhhcyBpdC4%2Ceyebrow_~VHV0b3JpYWwgwrcgNyBtaW4gcmVhZA%2Ctint_sand%2Cp_Ii9jb21tdW5pdHkvYmxvZy90cmFuc2Zvcm1lcnMtanMtNC1pbi1hLXZ1ZS1hcHAi%2Cs_ziTAbLjqdhfBjIL0.png"
  "twitter:image:alt": "Transformers.js 4 in a Vue app: embeddings and classification in the browser · vuejs.school"
---

> Index of every page on this site: [llms.txt](https://vuejs.school/llms.txt)

## What version 4 changes

The [release notes](https://github.com/huggingface/transformers.js/releases/tag/4.0.0) lead with a new WebGPU runtime, rewritten in C++ together with the ONNX Runtime team and tested across about 200 model architectures. The same code now runs WebGPU-accelerated in browsers and in Node, Bun and Deno. The notes report a speed-up of about four times for BERT-based embedding models from a new attention operator, and support for models above 8 billion parameters.

The other changes you will notice:

- A `ModelRegistry` API that lists a pipeline's files, their sizes and whether they are cached, and a new `progress_total` event that reports loading progress across all files at once.
- New `env` settings: `useWasmCache` caches the runtime's WebAssembly files, `fetch` swaps in your own fetch function, and `logLevel` controls console output. ONNX Runtime's WebGPU warnings are now hidden by default.
- Tokenisation moved into its own package, `@huggingface/tokenizers`, which Transformers.js depends on.
- The build moved from webpack to esbuild, and `transformers.web.js`, the default browser bundle, is 53 percent smaller.

Some things stay as they were in 3.x:

- The same 25 pipeline tasks exist.
- In the browser the default device is still WebAssembly.
- The default precision is still `q8` on WebAssembly and `fp32` everywhere else.

One default did move: `pipeline('feature-extraction')` without a model name now loads `onnx-community/all-MiniLM-L6-v2-ONNX` instead of `Xenova/all-MiniLM-L6-v2`. This tutorial names its models, so the change does not affect it.

The release notes list no breaking changes, so upgrading is `npm install @huggingface/transformers@4`.

## Sizes, limits and where the data goes

Two models from the Hugging Face Hub do the work. Each comes in several precisions, called `dtype`: `fp32` stores each weight (a number the model learned) in 32 bits, `fp16` in 16 bits, and `q8` squeezes it into 8. The precision sets the download size:

- `Xenova/distilbert-base-uncased-finetuned-sst-2-english` classifies text as positive or negative: 68 MB as `q8`, 134 MB as `fp16`, 268 MB as `fp32`.
- `Xenova/all-MiniLM-L6-v2` turns text into embeddings, lists of 384 numbers that place similar meanings close together: 23 MB as `q8`, 45 MB as `fp16`, 90 MB as `fp32`.

On top of that comes the ONNX Runtime WebAssembly file, about 23 MB (under 6 MB compressed), which Transformers.js fetches from the jsDelivr CDN. The model files come from `huggingface.co`. Everything is stored in the browser's Cache Storage under `transformers-cache`, so the second visit downloads nothing. The text the user types never leaves the device.

The limits are real:

- Both models are English.
- Input longer than 512 tokens (the word pieces a model reads) is cut off.
- The classifier only knows two labels. With the `q8` model, a mixed review such as "The zip broke after a week, but customer service sent a new one straight away." comes back `NEGATIVE` with a score of 0.998. The score is the model's confidence in its label, not a measure of how mixed the text is.
- Neither is a chat model. They classify and compare; they do not write.

## Use WebGPU only when the GPU supports fp16

`fp16` halves the size of `fp32` and is the natural choice on a GPU. It is also the easiest way to break the app:

*src/ai/worker.ts**Breaks***

```
const extractor = await pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2', {
  device: 'webgpu',
  dtype: 'fp16',
})
```

On a GPU without the `shader-f16` feature (support for 16-bit numbers) this throws `The device (webgpu) does not support fp16.`, and in a browser without WebGPU it cannot run at all: the error there starts `Unsupported device: "webgpu"`. Ask the GPU first, and fall back to WebAssembly with `q8`:

*src/ai/worker.ts**Works***

```
async function runtime() {
  const adapter = await navigator.gpu?.requestAdapter()
  return adapter?.features.has('shader-f16')
    ? ({ device: 'webgpu', dtype: 'fp16' } as const)
    : ({ device: 'wasm', dtype: 'q8' } as const)
}
```

A GPU without `fp16` support falls back to WebAssembly with `q8` rather than staying on WebGPU with `fp32`, on purpose: the `fp32` files are four times the size (268 MB against 68 MB for the classifier).

`navigator.gpu` is typed in the DOM library of TypeScript 6.0, released on 23 March. On TypeScript 5.9, install the `@webgpu/types` package and add `"@webgpu/types"` to `compilerOptions.types` in `tsconfig.app.json`; TypeScript does not pick it up on its own.

## Run the models in a web worker

Running a model blocks the thread it runs on for as long as it takes. On the main thread that freezes the page, so the models live in a web worker. Install the library first:

```
npm install @huggingface/transformers@4
```

Then create the worker file. It includes `runtime()` from the previous section:

*src/ai/worker.ts*

```
import { pipeline, type ProgressInfo } from '@huggingface/transformers'

export type JobInput = { task: 'classify'; input: string } | { task: 'embed'; input: string[] }
export type Job = JobInput & { id: number }

async function runtime() {
  const adapter = await navigator.gpu?.requestAdapter()
  return adapter?.features.has('shader-f16')
    ? ({ device: 'webgpu', dtype: 'fp16' } as const)
    : ({ device: 'wasm', dtype: 'q8' } as const)
}

function onProgress(info: ProgressInfo) {
  if (info.status === 'progress_total') postMessage({ type: 'progress', percent: Math.round(info.progress) })
}

const loadClassifier = async () =>
  pipeline('sentiment-analysis', 'Xenova/distilbert-base-uncased-finetuned-sst-2-english', { ...(await runtime()), progress_callback: onProgress })
const loadExtractor = async () =>
  pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2', { ...(await runtime()), progress_callback: onProgress })

let classifier: ReturnType<typeof loadClassifier> | undefined
let extractor: ReturnType<typeof loadExtractor> | undefined

self.onmessage = async ({ data: job }: MessageEvent<Job>) => {
  try {
    let output: unknown
    if (job.task === 'classify') {
      classifier ??= loadClassifier()
      output = await (await classifier)(job.input)
    } else {
      extractor ??= loadExtractor()
      output = (await (await extractor)(job.input, { pooling: 'mean', normalize: true })).tolist()
    }
    postMessage({ type: 'result', id: job.id, output })
  } catch (error) {
    if (job.task === 'classify') classifier = undefined
    else extractor = undefined
    postMessage({ type: 'error', id: job.id, message: String(error) })
  }
}
```

What the less obvious lines do:

- Each model loads on the first job that needs it, and the worker keeps the loading promise, so a second job waits for the same download instead of starting another.
- If a job fails, the worker drops that model's promise so the next click can try again.
- `progress_total` arrives with a `progress` value from 0 to 100 that covers every file of the model.
- For embeddings, `pooling: 'mean'` averages the per-token vectors into one vector per text, `normalize: true` scales each to length 1, and `tolist()` turns the result into plain arrays that can cross to the main thread.

## Wrap the worker in a composable

The component side needs three things: send a job, get its answer back as a promise, and see the progress. A composable wraps that, and it only creates the worker on the first job:

*src/composables/useLocalModel.ts*

```
import { onScopeDispose, ref } from 'vue'
import type { Job, JobInput } from '@/ai/worker'

type Reply =
  | { type: 'progress'; percent: number }
  | { type: 'result'; id: number; output: unknown }
  | { type: 'error'; id: number; message: string }

export function useLocalModel() {
  const progress = ref(0)
  const pending = new Map<number, { resolve: (value: unknown) => void; reject: (error: Error) => void }>()
  let worker: Worker | undefined
  let nextId = 0

  function start() {
    const w = new Worker(new URL('../ai/worker.ts', import.meta.url), { type: 'module' })
    w.onmessage = ({ data }: MessageEvent<Reply>) => {
      if (data.type === 'progress') {
        progress.value = data.percent
        return
      }
      const job = pending.get(data.id)
      pending.delete(data.id)
      if (data.type === 'error') job?.reject(new Error(data.message))
      else job?.resolve(data.output)
    }
    return w
  }

  function send<T>(job: JobInput) {
    worker ??= start()
    const id = nextId++
    worker.postMessage({ ...job, id } satisfies Job)
    return new Promise<T>((resolve, reject) => {
      pending.set(id, { resolve: value => resolve(value as T), reject })
    })
  }

  onScopeDispose(() => worker?.terminate())

  return {
    progress,
    classify: (text: string) => send<{ label: string; score: number }[]>({ task: 'classify', input: text }),
    embed: (texts: string[]) => send<number[][]>({ task: 'embed', input: texts }),
  }
}
```

`new Worker(new URL('../ai/worker.ts', import.meta.url), { type: 'module' })` is the pattern Vite recognises: it bundles the worker file and its imports separately. Vite 8 builds a worker that imports Transformers.js 4 with its default settings, no extra config.

Each job gets an `id`, and `pending` holds that job's `resolve` and `reject` until the worker replies with the same `id`, so each answer reaches the job that asked for it.

Because nothing is created until `send()` runs, opening the page costs nothing. Each component that calls `useLocalModel()` gets its own worker, and `onScopeDispose` terminates it when the component goes away, which also frees the model's memory.

## Classify the sentiment of a review

The first component sends the review to `classify()` and shows the top label with its score:

*src/components/ReviewSentiment.vue*

```
<script setup lang="ts">
import { ref } from 'vue'
import { useLocalModel } from '@/composables/useLocalModel'

type Sentiment = { label: string; score: number }

const review = ref('')
const result = ref<Sentiment | null>(null)
const busy = ref(false)
const ready = ref(false)
const failed = ref(false)
const { progress, classify } = useLocalModel()

async function analyse() {
  busy.value = true
  failed.value = false
  try {
    const [top] = await classify(review.value)
    result.value = top ?? null
    ready.value = true
  } catch {
    failed.value = true
  } finally {
    busy.value = false
  }
}
</script>

<template>
  <label for="review">Your review</label>
  <textarea id="review" v-model="review" rows="4" />
  <button type="button" :disabled="busy || !review.trim()" @click="analyse">
    {{ ready ? 'Analyse' : 'Download the model and analyse' }}
  </button>
  <progress v-if="busy && !ready" :value="progress" max="100">{{ progress }}%</progress>
  <p v-if="failed" role="alert">The model could not run in this browser.</p>
  <p v-else-if="result">{{ result.label === 'POSITIVE' ? 'Positive' : 'Negative' }}, {{ Math.round(result.score * 100) }}% confident</p>
</template>
```

The classifier returns an array with the top label first, such as `[{ label: 'POSITIVE', score: 0.9989 }]` for "Not bad at all.", so it does handle simple negation. The button says what the first click will do. Put the size next to it in your own copy: 68 MB on WebAssembly, 134 MB on a GPU with `fp16`.

## Rank FAQ answers by meaning

Semantic search compares meaning rather than words. Every FAQ answer becomes an embedding once, the question becomes one too, and the answers are ranked by how close their vectors are:

*src/components/FaqSearch.vue*

```
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useLocalModel } from '@/composables/useLocalModel'

const faqs = [
  'You can return any item within 30 days for a full refund.',
  'Standard shipping takes three to five working days.',
  'Reset your password from the sign-in page with "Forgot password".',
  'We ship to the UK, the EU and the United States.',
]

const question = ref('')
const scores = ref<number[]>([])
const busy = ref(false)
const { progress, embed } = useLocalModel()
let faqVectors: number[][] | undefined

const dot = (a: number[], b: number[]) => a.reduce((sum, x, i) => sum + x * (b[i] ?? 0), 0)

async function search() {
  busy.value = true
  try {
    faqVectors ??= await embed(faqs)
    const [q] = await embed([question.value])
    if (q) scores.value = faqVectors.map(v => dot(q, v))
  } finally {
    busy.value = false
  }
}

const ranked = computed(() =>
  faqs.map((text, i) => ({ text, score: scores.value[i] ?? 0 })).sort((a, b) => b.score - a.score))
</script>

<template>
  <form @submit.prevent="search">
    <label for="faq-q">Ask a question</label>
    <input id="faq-q" v-model="question" type="search">
    <button type="submit" :disabled="busy || !question.trim()">Search</button>
  </form>
  <progress v-if="busy && !faqVectors" :value="progress" max="100">{{ progress }}%</progress>
  <ol v-if="scores.length">
    <li v-for="item in ranked" :key="item.text">{{ item.text }} ({{ item.score.toFixed(2) }})</li>
  </ol>
</template>
```

Cosine similarity measures the angle between two vectors. Because the worker normalised every vector to length 1, cosine similarity is simply the dot product, which is all `dot` computes.

With the `q8` model, "Do you deliver to Germany?" scores 0.54 against the answer about shipping countries and 0.30 or less against the rest, though the two share no words. "I forgot my login" scores 0.60 against the password answer. Scores on a GPU with `fp16` differ slightly in the second decimal.

Four answers fit in memory without thinking. A few thousand still do: each vector is 384 numbers, so a thousand answers take about 3 MB as plain JavaScript arrays. Embed them once, keep the vectors, and only embed the question on each search.

**Check it stuck**3 questions from the Models in the browser: WebLLM and Transformers.js quiz.

[**Practice now**](https://vuejs.school/learn/ai/models-in-the-browser-webllm-and-transformers-js?tab=quiz)

**Was this helpful?**

## **Keep reading**

[Tutorial · 7 min read**RAG in a Nuxt app: embeddings, retrieval and a chatbot that knows your docs**](https://vuejs.school/community/blog/rag-in-a-nuxt-app-a-chatbot-that-knows-your-docs) [Tutorial · 6 min read**Tools and structured output with the AI SDK 7**](https://vuejs.school/community/blog/tools-and-structured-output-with-the-ai-sdk-7)

```json
{"@context":"https://schema.org","@graph":[{"@id":"https://vuejs.school/#website","@type":"WebSite","description":"A free AI-instructed school for Vue.js. Short tutorials, runnable code and quizzes that adapt to you.","name":"vuejs.school","url":"https://vuejs.school/","publisher":{"@id":"https://vuejs.school/#identity"}},{"@id":"https://vuejs.school/community/blog/transformers-js-4-in-a-vue-app#webpage","@type":"WebPage","dateModified":"2026-10-27T19:31:40.253Z","datePublished":"2026-03-31","description":"Run a sentiment classifier and a small semantic search in a Vue app with Transformers.js 4, in a web worker, on WebGPU when the browser has it.","name":"Transformers.js 4 in a Vue app: embeddings and classification in the browser","url":"https://vuejs.school/community/blog/transformers-js-4-in-a-vue-app","breadcrumb":{"@id":"https://vuejs.school/community/blog/transformers-js-4-in-a-vue-app#breadcrumb"},"isPartOf":{"@id":"https://vuejs.school/#website"},"potentialAction":[{"@type":"ReadAction","target":["https://vuejs.school/community/blog/transformers-js-4-in-a-vue-app"]}],"primaryImageOfPage":{"@id":"https://vuejs.school/#logo"}},{"@id":"https://vuejs.school/#identity","@type":"Organization","description":"A free AI-instructed school for Vue.js. Short tutorials, runnable code and quizzes that adapt to you.","name":"vuejs.school","url":"https://vuejs.school","logo":{"@id":"https://vuejs.school/#logo"}},{"@id":"https://vuejs.school/community/blog/transformers-js-4-in-a-vue-app#article","dateModified":"2026-10-27T19:31:40.253Z","datePublished":"2026-03-31","description":"Run a sentiment classifier and a small semantic search in a Vue app with Transformers.js 4, in a web worker, on WebGPU when the browser has it.","headline":"Transformers.js 4 in a Vue app: embeddings and classification in the browser","inLanguage":"en","isAccessibleForFree":true,"thumbnailUrl":"https://vuejs.school/_og/d/a_~VHJhbnNmb3JtZXJzLmpzIDQgaW4gYSBWdWUgYXBwOiBlbWJlZGRpbmdzIGFuZCBjbGFzc2lmaWNhdGlvbiBpbiB0aGUgYnJvd3NlciDCtyB2dWVqcy5zY2hvb2w%2Cc_OgCard.takumi%2Ctitle_~VHJhbnNmb3JtZXJzLmpzIDQgaW4gYSBWdWUgYXBwOiBlbWJlZGRpbmdzIGFuZCBjbGFzc2lmaWNhdGlvbiBpbiB0aGUgYnJvd3Nlcg%2Cdescription_~UnVuIGEgc2VudGltZW50IGNsYXNzaWZpZXIgYW5kIGEgc21hbGwgc2VtYW50aWMgc2VhcmNoIGluIGEgVnVlIGFwcCB3aXRoIFRyYW5zZm9ybWVycy5qcyA0LCBpbiBhIHdlYiB3b3JrZXIsIG9uIFdlYkdQVSB3aGVuIHRoZSBicm93c2VyIGhhcyBpdC4%2Ceyebrow_~VHV0b3JpYWwgwrcgNyBtaW4gcmVhZA%2Ctint_sand%2Cp_Ii9jb21tdW5pdHkvYmxvZy90cmFuc2Zvcm1lcnMtanMtNC1pbi1hLXZ1ZS1hcHAi%2Cs_ziTAbLjqdhfBjIL0.png","@type":["Article","BlogPosting"],"articleSection":["Tutorial"],"author":{"@id":"https://vuejs.school/#identity"},"image":{"@id":"https://vuejs.school/#/schema/image/1"},"isPartOf":{"@id":"https://vuejs.school/community/blog/transformers-js-4-in-a-vue-app#webpage"},"mainEntityOfPage":{"@id":"https://vuejs.school/community/blog/transformers-js-4-in-a-vue-app#webpage"},"publisher":{"@id":"https://vuejs.school/#identity"}},{"@id":"https://vuejs.school/community/blog/transformers-js-4-in-a-vue-app#breadcrumb","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","name":"Blog","item":"https://vuejs.school/community/blog","position":1},{"@type":"ListItem","name":"Transformers.js 4 in a Vue app: embeddings and classification in the browser","position":2}]},{"@id":"https://vuejs.school/#logo","@type":"ImageObject","caption":"vuejs.school","contentUrl":"https://vuejs.school/apple-touch-icon.png","inLanguage":"en","url":"https://vuejs.school/apple-touch-icon.png"},{"@id":"https://vuejs.school/#/schema/image/1","@type":"ImageObject","contentUrl":"https://vuejs.school/_og/d/a_~VHJhbnNmb3JtZXJzLmpzIDQgaW4gYSBWdWUgYXBwOiBlbWJlZGRpbmdzIGFuZCBjbGFzc2lmaWNhdGlvbiBpbiB0aGUgYnJvd3NlciDCtyB2dWVqcy5zY2hvb2w%2Cc_OgCard.takumi%2Ctitle_~VHJhbnNmb3JtZXJzLmpzIDQgaW4gYSBWdWUgYXBwOiBlbWJlZGRpbmdzIGFuZCBjbGFzc2lmaWNhdGlvbiBpbiB0aGUgYnJvd3Nlcg%2Cdescription_~UnVuIGEgc2VudGltZW50IGNsYXNzaWZpZXIgYW5kIGEgc21hbGwgc2VtYW50aWMgc2VhcmNoIGluIGEgVnVlIGFwcCB3aXRoIFRyYW5zZm9ybWVycy5qcyA0LCBpbiBhIHdlYiB3b3JrZXIsIG9uIFdlYkdQVSB3aGVuIHRoZSBicm93c2VyIGhhcyBpdC4%2Ceyebrow_~VHV0b3JpYWwgwrcgNyBtaW4gcmVhZA%2Ctint_sand%2Cp_Ii9jb21tdW5pdHkvYmxvZy90cmFuc2Zvcm1lcnMtanMtNC1pbi1hLXZ1ZS1hcHAi%2Cs_ziTAbLjqdhfBjIL0.png","inLanguage":"en","url":"https://vuejs.school/_og/d/a_~VHJhbnNmb3JtZXJzLmpzIDQgaW4gYSBWdWUgYXBwOiBlbWJlZGRpbmdzIGFuZCBjbGFzc2lmaWNhdGlvbiBpbiB0aGUgYnJvd3NlciDCtyB2dWVqcy5zY2hvb2w%2Cc_OgCard.takumi%2Ctitle_~VHJhbnNmb3JtZXJzLmpzIDQgaW4gYSBWdWUgYXBwOiBlbWJlZGRpbmdzIGFuZCBjbGFzc2lmaWNhdGlvbiBpbiB0aGUgYnJvd3Nlcg%2Cdescription_~UnVuIGEgc2VudGltZW50IGNsYXNzaWZpZXIgYW5kIGEgc21hbGwgc2VtYW50aWMgc2VhcmNoIGluIGEgVnVlIGFwcCB3aXRoIFRyYW5zZm9ybWVycy5qcyA0LCBpbiBhIHdlYiB3b3JrZXIsIG9uIFdlYkdQVSB3aGVuIHRoZSBicm93c2VyIGhhcyBpdC4%2Ceyebrow_~VHV0b3JpYWwgwrcgNyBtaW4gcmVhZA%2Ctint_sand%2Cp_Ii9jb21tdW5pdHkvYmxvZy90cmFuc2Zvcm1lcnMtanMtNC1pbi1hLXZ1ZS1hcHAi%2Cs_ziTAbLjqdhfBjIL0.png"}]}
```