<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sherotree</title>
    <description>The latest articles on DEV Community by Sherotree (@sherotree).</description>
    <link>https://dev.to/sherotree</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F873577%2Fa596bd86-49ed-445b-bc37-0f7cd0c5e93a.jpg</url>
      <title>DEV Community: Sherotree</title>
      <link>https://dev.to/sherotree</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sherotree"/>
    <language>en</language>
    <item>
      <title>Plotkeep — a chart press that never phones home</title>
      <dc:creator>Sherotree</dc:creator>
      <pubDate>Tue, 13 Oct 2026 23:44:23 +0000</pubDate>
      <link>https://dev.to/sherotree/plotkeep-a-chart-press-that-never-phones-home-3n92</link>
      <guid>https://dev.to/sherotree/plotkeep-a-chart-press-that-never-phones-home-3n92</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week2-2026-10-12"&gt;Hacktoberfest Open-Source AI Challenge: Week 2&lt;/a&gt; (Side Hustle).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://plotkeep-pi.vercel.app/studio" rel="noopener noreferrer"&gt;https://plotkeep-pi.vercel.app/studio&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/sherotree/plotkeep" rel="noopener noreferrer"&gt;https://github.com/sherotree/plotkeep&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Prize category: &lt;strong&gt;Best Use of Gemma&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Plotkeep&lt;/strong&gt; is a static web app that turns a pasted table into a chart without sending the table anywhere.&lt;/p&gt;

&lt;p&gt;You paste CSV, TSV, or cells copied from a spreadsheet — or a sentence if you only have the shape of the question. The app:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Parses the sheet in the tab (quoted fields, &lt;code&gt;$4,200&lt;/code&gt;, European decimals, tab-separated paste).&lt;/li&gt;
&lt;li&gt;Asks an open-weight &lt;strong&gt;Gemma&lt;/strong&gt; model, running &lt;strong&gt;on the device&lt;/strong&gt;, for a chart specification: type, encodings, title, one-or-two-sentence insight.&lt;/li&gt;
&lt;li&gt;Validates that JSON with Zod against the &lt;strong&gt;real column names&lt;/strong&gt;. If the model wraps the object in markdown, trails a comma, or invents a column called &lt;code&gt;value&lt;/code&gt;, the error goes back to the model (up to three tries). After that, a deterministic heuristic draws anyway.&lt;/li&gt;
&lt;li&gt;Renders with Apache ECharts. Export PNG or SVG. Follow-up prompts revise the spec (&lt;code&gt;make it a pie&lt;/code&gt;, &lt;code&gt;title to "June collected"&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The product name in the brief was ChartLocal. I called it Plotkeep because the job is not “charts, but local” — it is keeping the plate on the desk.&lt;/p&gt;

&lt;p&gt;There is no account, no Plotkeep API, and no telemetry on the CSV. The only network the studio uses, if you opt in, is the first download of Gemma weights (WebLLM / Hugging Face) or a call to &lt;code&gt;127.0.0.1:11434&lt;/code&gt; if you already run Ollama.&lt;/p&gt;
&lt;h2&gt;
  
  
  Who would pay for this
&lt;/h2&gt;

&lt;p&gt;A freelance operator who already bought a laptop with a GPU, who pastes &lt;strong&gt;client names and invoice totals&lt;/strong&gt; into a chart generator, and who has noticed that “paste your CSV” often means “POST your CSV.”&lt;/p&gt;

&lt;p&gt;That is a real irritation even if I am not going to invent a user interview. Aging reports, hours-by-client, a cafe’s Saturday SKUs — the sheet is small, the names are not public, and a $12/month hosted plotter is the wrong object. The silicon is already on the desk. Charging monthly for tokens the GPU can spend locally is a weird shape.&lt;/p&gt;

&lt;p&gt;So the license idea is &lt;strong&gt;$29 once&lt;/strong&gt;, per operator, extra personal machines allowed. This repo does not take payment. The Pro surfaces (saved plates in &lt;code&gt;localStorage&lt;/code&gt;, 2×/3× PNG) unlock with the demo code &lt;code&gt;PLOTKEEP-PRO&lt;/code&gt; so the paid edge is visible, not vapor. Free forever includes the heuristic engine, local Gemma, Ollama, and 1× export.&lt;/p&gt;

&lt;p&gt;I am not claiming conversion rates. I am claiming the billing unit matches the cost structure: you pay for a press, not for inference.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Live: &lt;a href="https://plotkeep-pi.vercel.app/studio" rel="noopener noreferrer"&gt;plotkeep-pi.vercel.app/studio&lt;/a&gt;. Repo: &lt;a href="https://github.com/sherotree/plotkeep" rel="noopener noreferrer"&gt;sherotree/plotkeep&lt;/a&gt;. To run locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;/studio&lt;/code&gt;. The invoice sample is preloaded. &lt;strong&gt;Draw chart&lt;/strong&gt; works with no model — that is deliberate. On this build it suggested a three-series line (&lt;code&gt;invoiced / collected / overdue by month&lt;/code&gt;) and wrote “May leads invoiced at 6,100; Feb is the low at 3,100.” Load Gemma only if the browser has WebGPU and you can wait out a ~1.35 GB first fetch (cached after that). Screenshots of that flow live in &lt;a href="https://github.com/sherotree/plotkeep/tree/main/docs/screenshots" rel="noopener noreferrer"&gt;&lt;code&gt;docs/screenshots&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The screenshots below are real runs of Gemma 2 2B IT (&lt;code&gt;gemma-2-2b-it-q4f16_1-MLC-1k&lt;/code&gt;) loaded in-tab via WebLLM, in Chrome on a Mac mini.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F03dxfn9ojf00da3n9q4d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F03dxfn9ojf00da3n9q4d.png" alt="Gemma 2 2B loaded in the tab" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F92nlrfhbq6lah08rgfc1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F92nlrfhbq6lah08rgfc1.png" alt="Plain-English prompt drawn by Gemma" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmiby2cw0kk2akpktvnfj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmiby2cw0kk2akpktvnfj.png" alt="Freelancer CSV charted by Gemma" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2y3om29gqc7u368s3j2q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2y3om29gqc7u368s3j2q.png" alt="Gemma follow-up: line chart" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What went wrong, and how I fixed it
&lt;/h3&gt;

&lt;p&gt;In my first round of testing, Gemma 2 2B misread the freelancer CSV. Asked "which client grew fastest?", it said "Client B grew the most, increasing from 1800 to 2600." That was wrong: client_c went from 900 to 4100, and client_b ended at 3100, not 2600. It also plotted only client_b.&lt;/p&gt;

&lt;p&gt;So Plotkeep no longer trusts the model with arithmetic. For a parsed table it now computes the stats on device, in plain code: first and last value, min and max with their labels, absolute and percent change for each series, which series grew fastest, and the best and worst period. Those facts go into the Gemma prompt as ground truth. Gemma chooses the chart type and encoding and writes the wording. Every numeric series is plotted unless you ask for specific ones. After the model replies, a check compares the insight with the computed facts. If it credits the wrong series, names the wrong peak, or quotes a number that isn't in the table or the stats, the insight is replaced with a sentence built from the facts. The status line shows "facts checked on device" either way. In the reshoot above, Gemma's grounded answer passed the check: "Client C experienced the most significant growth, with a 355.6% increase over the period."&lt;/p&gt;

&lt;p&gt;If you are on a machine without WebGPU, stay on the heuristic engine or point Ollama at &lt;code&gt;gemma2:2b&lt;/code&gt;. The UI says which of those is true; it does not pretend a cloud fallback exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I built it
&lt;/h2&gt;

&lt;p&gt;Stack: Vite, React, TypeScript, Zod, Apache ECharts, &lt;code&gt;@mlc-ai/web-llm&lt;/code&gt;. Gemma 2 2B IT is the default WebLLM id (&lt;code&gt;gemma-2-2b-it-q4f16_1-MLC-1k&lt;/code&gt;); Gemma 3 1B is on the menu for smaller VRAM. Gemma 2 has no system role, so instructions are folded into the first user turn.&lt;/p&gt;

&lt;p&gt;The useful engineering is not the prompt. It is treating the model as an untrusted spec compiler:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Schema first.&lt;/strong&gt; The chart is a JSON object with &lt;code&gt;type&lt;/code&gt;, &lt;code&gt;x&lt;/code&gt;, &lt;code&gt;y&lt;/code&gt;, optional &lt;code&gt;series&lt;/code&gt;, &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;insight&lt;/code&gt;. The table is &lt;em&gt;not&lt;/em&gt; echoed back through the model. That keeps numbers honest and tokens short.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bind, don’t trust.&lt;/strong&gt; Column names are resolved case-insensitively and with a little fuzzy match. Unknown fields fail closed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retries are boring and they work.&lt;/strong&gt; A 2B instruction model often returns “Sure, here is the JSON” plus a fence. &lt;code&gt;extractJson&lt;/code&gt; peels that. Trailing commas get stripped. The next user turn is the Zod error, not a rewritten essay.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A fallback that is not a demo fake.&lt;/strong&gt; Date-like column + numbers → line. Short mix → pie. Two numeric columns and no labels → scatter. Insight is computed from max/min/share so the caption still names a real row when Gemma is absent or rambling. Follow-ups without a model understand type changes and quoted titles; they do not understand “emphasize March” — that is what the weights are for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline after first fetch.&lt;/strong&gt; Fonts are Fontsource in the bundle (COEP would block Google Fonts anyway). WebLLM needs &lt;code&gt;COOP&lt;/code&gt;/&lt;code&gt;COEP&lt;/code&gt; for &lt;code&gt;SharedArrayBuffer&lt;/code&gt;; Vite, &lt;code&gt;vercel.json&lt;/code&gt;, and &lt;code&gt;public/_headers&lt;/code&gt; set them. Weights cache in the browser. The heuristic path never needed a network.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I used ECharts instead of Vega-Lite because PNG/SVG export from a plate is a one-liner and the default dark plate looked like letterpress ink rather than a dashboard widget. That is a taste choice, not a moral one.&lt;/p&gt;

&lt;p&gt;What is still thin: I did not fine-tune Gemma. The 1k-context build will choke if you paste a 200-column dump — the parser will accept it, the prompt truncates to 20 rows, and the spec may pick the wrong y. Pro is a localStorage flag, not Stripe. Ollama from a static host still needs &lt;code&gt;OLLAMA_ORIGINS&lt;/code&gt;. Those are limits, not a backlog dressed up as vision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why open innovation matters here
&lt;/h2&gt;

&lt;p&gt;A hosted chart-from-CSV feature is easy, and it is also a copy of the sheet on someone else’s GPU. For this job that is the whole product risk.&lt;/p&gt;

&lt;p&gt;Open weights change the economics in four concrete ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Offline.&lt;/strong&gt; After the first Gemma download, a cafe Wi-Fi dropout does not stop an aging chart.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy.&lt;/strong&gt; The rows never become a training example at a plotting vendor, because they never arrive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero inference cost.&lt;/strong&gt; The meter is electricity you already bought. That is why the paid thing is a license, not a token pack.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Swappable model.&lt;/strong&gt; The spec schema does not care whether WebLLM loaded Gemma 2 2B, Gemma 3 1B, or Ollama’s &lt;code&gt;gemma2:2b&lt;/code&gt;. If a better open-weight JSON-follower shows up in &lt;code&gt;prebuiltAppConfig&lt;/code&gt;, it is a dropdown.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Gemma is the right default for this challenge because it is small enough to contemplate in a tab (2B, or 1B if the laptop is modest) and instruction-tuned enough to emit a short JSON object when you show it the schema and an example. It is not reliable enough to skip validation. Plotkeep’s bet is that &lt;strong&gt;validation plus a local model&lt;/strong&gt; is the product, not a chat window with a screenshot of a chart.&lt;/p&gt;

&lt;p&gt;If you bill by the hour and you are tired of uploading the hour sheet to draw a bar, the press is at &lt;code&gt;/studio&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>gemma</category>
      <category>webllm</category>
    </item>
    <item>
      <title>WCAG Color Contrast Explained: AA vs AAA, Large Text, and the Gray That Fails</title>
      <dc:creator>Sherotree</dc:creator>
      <pubDate>Tue, 13 Oct 2026 16:37:14 +0000</pubDate>
      <link>https://dev.to/sherotree/wcag-color-contrast-explained-aa-vs-aaa-large-text-and-the-gray-that-fails-12n3</link>
      <guid>https://dev.to/sherotree/wcag-color-contrast-explained-aa-vs-aaa-large-text-and-the-gray-that-fails-12n3</guid>
      <description>&lt;p&gt;I used to treat contrast as a box to tick at the end of a project. Then an audit flagged half our secondary text and most of our buttons, and I learned how close "looks fine to me" is to "fails WCAG."&lt;/p&gt;

&lt;p&gt;This is what I wish I'd known earlier. The thresholds, the formula, and a small JS &lt;code&gt;contrastRatio()&lt;/code&gt; function are in a gist: &lt;a href="https://gist.github.com/sherotree/7b64ef8617f0a44015308ef6d448fad9" rel="noopener noreferrer"&gt;WCAG Color Contrast Ratio Cheat Sheet&lt;/a&gt;. Every number in this post comes from that script.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h2&gt;
  
  
  What the ratio actually means
&lt;/h2&gt;

&lt;p&gt;A contrast ratio compares how bright two colors are, but not the way you'd guess. Each color gets turned into a "relative luminance" between 0 (black) and 1 (white). The formula weights green much more than blue, because our eyes are far more sensitive to green. Then you take &lt;code&gt;(lighter + 0.05) / (darker + 0.05)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The result runs from 1:1 (same color) to 21:1 (black on white). So 4.5:1 doesn't mean "4.5 times different" in any intuitive sense. It's just the scale WCAG uses.&lt;/p&gt;

&lt;p&gt;Because of that weighting, two colors that look equally "strong" can score very differently. Tailwind's &lt;code&gt;red-600&lt;/code&gt; (&lt;code&gt;#dc2626&lt;/code&gt;) behind white text gets 4.82:1, while &lt;code&gt;green-600&lt;/code&gt; (&lt;code&gt;#16a34a&lt;/code&gt;) gets only 3.29:1. Green is perceived as much lighter.&lt;/p&gt;

&lt;h2&gt;
  
  
  AA vs AAA
&lt;/h2&gt;

&lt;p&gt;WCAG 2.x has two levels you'll actually run into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AA&lt;/strong&gt; is what most laws and contracts point to (ADA cases, EN 301 549 in the EU). Normal text needs 4.5:1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AAA&lt;/strong&gt; is stricter. Normal text needs 7:1.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I aim for AA everywhere and AAA for long-form body text, like docs, articles, and anything people actually read for a while. Hitting AAA across a whole product is hard and usually not required.&lt;/p&gt;

&lt;p&gt;One detail that catches people: the ratio is not rounded. A color at 4.49:1 fails AA. I've seen "4.5" in a design tool that was really 4.47.&lt;/p&gt;

&lt;h2&gt;
  
  
  Large text gets a discount
&lt;/h2&gt;

&lt;p&gt;Bigger, bolder text is easier to read, so the bar drops: 3:1 for AA and 4.5:1 for AAA. "Large" has a specific meaning: at least 24px regular, or about 18.66px (14pt) bold. A 20px medium-weight heading does not count, even though it feels big.&lt;/p&gt;

&lt;h2&gt;
  
  
  UI components count too
&lt;/h2&gt;

&lt;p&gt;Since WCAG 2.1 there's a 3:1 rule for non-text things you need to see in order to use the interface: input borders, icons that carry meaning, focus indicators, chart lines, checkbox outlines. This is the one that bit me hardest.&lt;/p&gt;

&lt;p&gt;The classic failure is a light gray input border. Tailwind's &lt;code&gt;gray-300&lt;/code&gt; (&lt;code&gt;#d1d5db&lt;/code&gt;) on white is 1.47:1. It looks clean, but for a lot of people the field basically isn't there. You don't need a dark border, though. A border that's clearly visible, or a filled background with enough contrast, does the job.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gray pitfalls
&lt;/h2&gt;

&lt;p&gt;Gray text on white is where most contrast bugs live. These are the ones I check now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;#767676&lt;/code&gt; on white is &lt;strong&gt;4.54:1&lt;/strong&gt;. That's the lightest pure gray that passes AA for normal text. Burn it into your memory.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;#777777&lt;/code&gt;, one step lighter, is &lt;strong&gt;4.47:1&lt;/strong&gt; and fails. Same goes for any "close enough" gray.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;#999999&lt;/code&gt;, a very common "muted" color, is &lt;strong&gt;2.84:1&lt;/strong&gt;. It fails even the large-text bar.&lt;/li&gt;
&lt;li&gt;Tailwind's &lt;code&gt;gray-500&lt;/code&gt; (&lt;code&gt;#6b7280&lt;/code&gt;) is &lt;strong&gt;4.83:1&lt;/strong&gt; on white, so it's fine. &lt;code&gt;gray-400&lt;/code&gt; (&lt;code&gt;#9ca3af&lt;/code&gt;) is &lt;strong&gt;2.53:1&lt;/strong&gt;, so it isn't.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Backgrounds matter as much as text. &lt;code&gt;gray-500&lt;/code&gt; passes on white but drops to &lt;strong&gt;4.39:1&lt;/strong&gt; on &lt;code&gt;gray-100&lt;/code&gt; (&lt;code&gt;#f3f4f6&lt;/code&gt;) and fails. &lt;code&gt;#767676&lt;/code&gt; on &lt;code&gt;#f5f5f5&lt;/code&gt; is &lt;strong&gt;4.16:1&lt;/strong&gt;. So a gray that passes on your page background can fail inside a light gray card or table row. Check text against the surface it actually sits on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Brand colors on white
&lt;/h2&gt;

&lt;p&gt;The second big source of failures is the brand color, usually a bright blue or green. Designers want white text on it and it doesn't quite get there:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bootstrap 4's &lt;code&gt;#007bff&lt;/code&gt; with white text: &lt;strong&gt;3.97:1&lt;/strong&gt;. Fine for big buttons, fails for normal text.&lt;/li&gt;
&lt;li&gt;Bootstrap 5 switched to &lt;code&gt;#0d6efd&lt;/code&gt;: &lt;strong&gt;4.50:1&lt;/strong&gt;. It passes AA, but just barely.&lt;/li&gt;
&lt;li&gt;Tailwind &lt;code&gt;blue-500&lt;/code&gt; (&lt;code&gt;#3b82f6&lt;/code&gt;): &lt;strong&gt;3.67:1&lt;/strong&gt;. &lt;code&gt;blue-600&lt;/code&gt; (&lt;code&gt;#2563eb&lt;/code&gt;): &lt;strong&gt;5.16:1&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Tailwind &lt;code&gt;green-500&lt;/code&gt; (&lt;code&gt;#22c55e&lt;/code&gt;): &lt;strong&gt;2.27:1&lt;/strong&gt;. You need &lt;code&gt;green-700&lt;/code&gt; (&lt;code&gt;#15803d&lt;/code&gt;, &lt;strong&gt;5.01:1&lt;/strong&gt;) for white text.&lt;/li&gt;
&lt;li&gt;Amber/yellow (&lt;code&gt;#f59e0b&lt;/code&gt;) with white text is &lt;strong&gt;2.14:1&lt;/strong&gt;. With black text it's &lt;strong&gt;9.77:1&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My fix is usually to keep the bright brand color for big surfaces and illustrations, and add a darker shade of the same hue for text and buttons. On yellows and oranges, I switch to dark text. It almost always looks better anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I check now
&lt;/h2&gt;

&lt;p&gt;A simple trick: run a small script in CI that computes ratios for your design tokens (the &lt;a href="https://gist.github.com/sherotree/7b64ef8617f0a44015308ef6d448fad9" rel="noopener noreferrer"&gt;gist&lt;/a&gt; function is enough for this) and fail the build if any text/background pair you actually use drops below the threshold. That catches regressions when someone tweaks a color.&lt;/p&gt;

&lt;p&gt;For quick one-off checks while designing, I paste two colors into &lt;a href="https://www.uwarp.design/colour-contrast-checker" rel="noopener noreferrer"&gt;this colour contrast checker&lt;/a&gt;. It runs in the browser and shows AA/AAA pass or fail right away.&lt;/p&gt;

&lt;p&gt;Also check the states people forget: hover, focus rings, placeholders, and text over images or semi-transparent overlays. Those are where audits find the leftovers.&lt;/p&gt;

&lt;p&gt;Contrast isn't the whole accessibility story, but it's measurable and cheap to fix. Fixing it early beats finding out in an audit.&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>css</category>
      <category>webdev</category>
      <category>design</category>
    </item>
    <item>
      <title>CSS Easing Explained: Picking the Right cubic-bezier() for Enter, Exit and Move Animations</title>
      <dc:creator>Sherotree</dc:creator>
      <pubDate>Tue, 13 Oct 2026 16:37:09 +0000</pubDate>
      <link>https://dev.to/sherotree/css-easing-explained-picking-the-right-cubic-bezier-for-enter-exit-and-move-animations-31om</link>
      <guid>https://dev.to/sherotree/css-easing-explained-picking-the-right-cubic-bezier-for-enter-exit-and-move-animations-31om</guid>
      <description>&lt;p&gt;For a long time I wrote &lt;code&gt;transition: all 0.3s ease&lt;/code&gt; on everything and moved on. It worked fine. It also made every interface I built feel a little bit off, and I couldn't explain why until I started paying attention to easing.&lt;/p&gt;

&lt;p&gt;This post is the mental model I use now. If you only want the values, I keep the full list of curves as CSS variables in a gist: &lt;a href="https://gist.github.com/sherotree/3446772f9f60554b135ea5fa8dbbb08f" rel="noopener noreferrer"&gt;CSS cubic-bezier() Easing Cheat Sheet&lt;/a&gt;.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h2&gt;
  
  
  Why easing matters at all
&lt;/h2&gt;

&lt;p&gt;Nothing in the real world moves at a constant speed. A drawer you pull out speeds up and then slows to a stop. A ball you drop speeds up until it hits the floor. When a UI element moves linearly, your brain notices that it's "wrong" even if you can't say why.&lt;/p&gt;

&lt;p&gt;Easing is how you describe that change in speed. In CSS, every timing function is really a &lt;code&gt;cubic-bezier(x1, y1, x2, y2)&lt;/code&gt;. The x axis is time and the y axis is progress, both from 0 to 1. Even the keywords are just presets: &lt;code&gt;ease&lt;/code&gt; is &lt;code&gt;cubic-bezier(0.25, 0.1, 0.25, 1)&lt;/code&gt; and &lt;code&gt;ease-in-out&lt;/code&gt; is &lt;code&gt;cubic-bezier(0.42, 0, 0.58, 1)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The most useful way to read a curve: &lt;strong&gt;a steep part means fast, a flat part means slow.&lt;/strong&gt; That's really all there is to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter, exit, move
&lt;/h2&gt;

&lt;p&gt;I use three rules and they cover about 90% of what I build.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Things entering the screen: ease-out.&lt;/strong&gt; A modal, a toast, a dropdown. It should show up fast and settle gently. Ease-out puts the speed at the start, so the element looks like it reacts to the user right away and then slows down where the eye lands on it. My default is &lt;code&gt;cubic-bezier(0.33, 1, 0.68, 1)&lt;/code&gt; (easeOutCubic). When I want something snappier, I use easeOutQuint, &lt;code&gt;cubic-bezier(0.22, 1, 0.36, 1)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Things leaving: ease-in.&lt;/strong&gt; Same logic in reverse. The element starts slowly and speeds up as it goes away, which reads as "it's gone." Exit animations should also be a bit shorter than enters. Nobody wants to watch something leave.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Things moving from A to B on screen: ease-in-out.&lt;/strong&gt; A tab indicator sliding, a card changing position, a carousel. Both ends are visible, so the element should speed up and slow down: &lt;code&gt;cubic-bezier(0.65, 0, 0.35, 1)&lt;/code&gt; (easeInOutCubic).&lt;/p&gt;

&lt;p&gt;One thing I avoid is plain ease-in for enter animations. It feels laggy, because the slowest part happens right when the user is waiting for feedback.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.toast&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;transform&lt;/span&gt; &lt;span class="m"&gt;250ms&lt;/span&gt; &lt;span class="n"&gt;cubic-bezier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.33&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.68&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
              &lt;span class="n"&gt;opacity&lt;/span&gt; &lt;span class="m"&gt;250ms&lt;/span&gt; &lt;span class="n"&gt;cubic-bezier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.33&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.68&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.toast&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;data-leaving&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;transition-duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;180ms&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;transition-timing-function&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cubic-bezier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.67&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c"&gt;/* easeInCubic */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Durations
&lt;/h2&gt;

&lt;p&gt;Easing and duration go together. A good curve at the wrong duration still feels bad. These are the ranges I start from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hover, press, small color changes: 100–200ms&lt;/li&gt;
&lt;li&gt;Dropdowns, tooltips, toasts: 150–250ms&lt;/li&gt;
&lt;li&gt;Modals, drawers, page sections: 250–400ms&lt;/li&gt;
&lt;li&gt;Big full-screen transitions: up to about 500ms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bigger elements and longer distances can take a bit longer. On mobile, I go a little shorter than on desktop. If something feels slow, I almost always fix it by cutting the duration, not by changing the curve.&lt;/p&gt;

&lt;p&gt;Also, stop using &lt;code&gt;transition: all&lt;/code&gt;. List the properties (&lt;code&gt;transform&lt;/code&gt;, &lt;code&gt;opacity&lt;/code&gt;). It's cheaper to render, and you won't accidentally animate layout properties like &lt;code&gt;width&lt;/code&gt; or &lt;code&gt;top&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overshoot with Back curves
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;cubic-bezier()&lt;/code&gt; lets the y values go outside 0–1 (the x values can't). If y goes above 1, the element shoots past its final value and settles back. That's the "Back" family:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* easeOutBack: overshoots ~10% then settles */&lt;/span&gt;
&lt;span class="nc"&gt;.badge-pop&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;transform&lt;/span&gt; &lt;span class="m"&gt;300ms&lt;/span&gt; &lt;span class="n"&gt;cubic-bezier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.34&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1.56&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I like this on small playful things: a notification badge popping in, a like button, a checkbox tick. I'd never use it on a modal or anything large. On a big surface, a wobble feels sloppy instead of fun. A real bounce or elastic effect, with several wobbles, can't be done with one cubic-bezier. For that you need &lt;code&gt;@keyframes&lt;/code&gt; or the newer &lt;code&gt;linear()&lt;/code&gt; function.&lt;/p&gt;

&lt;h2&gt;
  
  
  Respect reduced motion
&lt;/h2&gt;

&lt;p&gt;Some people get dizzy or nauseous from motion on screen, and they turn on "reduce motion" in their OS. Honoring that setting takes a few lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefers-reduced-motion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="o"&gt;*,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nd"&gt;::before&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nd"&gt;::after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;animation-duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.01ms&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;animation-iteration-count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;transition-duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.01ms&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I use 0.01ms instead of &lt;code&gt;none&lt;/code&gt; so that &lt;code&gt;transitionend&lt;/code&gt; and &lt;code&gt;animationend&lt;/code&gt; events still fire and JS that waits on them doesn't break. A softer approach is to keep opacity fades and drop only the transforms (sliding, scaling, parallax). Those cause most of the trouble.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep curves in one place
&lt;/h2&gt;

&lt;p&gt;My last tip is to put your curves in CSS custom properties and use only those:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--ease-out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cubic-bezier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.33&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.68&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--ease-in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cubic-bezier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.67&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--ease-in-out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cubic-bezier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.65&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.35&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three or four named curves applied consistently do more for how an app "feels" than a dozen hand-tuned ones. The &lt;a href="https://gist.github.com/sherotree/3446772f9f60554b135ea5fa8dbbb08f" rel="noopener noreferrer"&gt;gist&lt;/a&gt; has the full Sine through Back set if you want more options.&lt;/p&gt;

&lt;p&gt;When I need a custom curve, I drag the handles in a visual editor until it looks right, then copy the four numbers. Lately I've been using &lt;a href="https://www.uwarp.design/cubic-bezier" rel="noopener noreferrer"&gt;this cubic-bezier editor&lt;/a&gt;. It runs in the browser and doesn't need an account. Seeing the curve beats guessing numbers every time.&lt;/p&gt;

&lt;p&gt;That's it: ease-out to enter, ease-in to leave, ease-in-out to move, keep it short, and respect reduced motion.&lt;/p&gt;

</description>
      <category>css</category>
      <category>webdev</category>
      <category>frontend</category>
      <category>animation</category>
    </item>
    <item>
      <title>I built a free chart maker that runs entirely in the browser (CSV sankey, sunburst, hand-drawn)</title>
      <dc:creator>Sherotree</dc:creator>
      <pubDate>Sun, 11 Oct 2026 09:50:23 +0000</pubDate>
      <link>https://dev.to/sherotree/i-built-a-free-chart-maker-that-runs-entirely-in-the-browser-csv-sankey-sunburst-hand-drawn-5i3</link>
      <guid>https://dev.to/sherotree/i-built-a-free-chart-maker-that-runs-entirely-in-the-browser-csv-sankey-sunburst-hand-drawn-5i3</guid>
      <description>&lt;p&gt;I'm a solo indie developer, and BeCharts is my third product after UCharts and Uwarp.&lt;/p&gt;

&lt;p&gt;The problem: every time I needed a quick chart for a post, a report or a slide, I had two options. Fight with spreadsheet styling, or write plotting code. I wanted something in between.&lt;/p&gt;

&lt;h2&gt;
  
  
  What BeCharts does
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Import CSV/Excel or edit data in a table; the chart updates live&lt;/li&gt;
&lt;li&gt;100+ templates, 20+ chart types (bar, line, pie, scatter, radar, sankey, sunburst, treemap, heatmap, funnel)&lt;/li&gt;
&lt;li&gt;Live data sources: World Bank, EIA, stock prices, weather&lt;/li&gt;
&lt;li&gt;AI-assisted dataset generation from a one-line prompt&lt;/li&gt;
&lt;li&gt;Hand-drawn style and dark mode&lt;/li&gt;
&lt;li&gt;Export PNG / SVG / PDF, plus JSON / HTML&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No install and no account. The core features are free.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest indie numbers
&lt;/h2&gt;

&lt;p&gt;My products earned about $1 for a long time, AdSense took ~7 applications, and they now make about $243/month combined.&lt;/p&gt;

&lt;p&gt;Try it: &lt;a href="https://www.becharts.com/" rel="noopener noreferrer"&gt;https://www.becharts.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What would you want from a browser chart tool that this doesn't do yet?&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>datavisualization</category>
    </item>
    <item>
      <title>Uwarp: 500+ Free Browser UI Tools (No Signup)</title>
      <dc:creator>Sherotree</dc:creator>
      <pubDate>Sat, 26 Sep 2026 13:08:32 +0000</pubDate>
      <link>https://dev.to/sherotree/uwarp-500-free-browser-ui-tools-no-signup-1b6h</link>
      <guid>https://dev.to/sherotree/uwarp-500-free-browser-ui-tools-no-signup-1b6h</guid>
      <description>&lt;p&gt;Default design tools often mean installers, accounts, or uploading files to someone else’s server.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.uwarp.design" rel="noopener noreferrer"&gt;Uwarp&lt;/a&gt; is a free collection of &lt;strong&gt;500+ browser UI tools&lt;/strong&gt;—image and audio conversion, SVG, color, icons, mockups, and a few training games—meant to run locally in your tab. No signup for the tools themselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you can do
&lt;/h2&gt;

&lt;p&gt;A few examples from the homepage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Colour Contrast Checker&lt;/strong&gt; — live WCAG ratio for foreground / background pairs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tints and Shades&lt;/strong&gt; — build scales from any hex, export HEX / RGB / CSS / JSON&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Free SVG Icons&lt;/strong&gt; — search Material Icons, Font Awesome, Heroicons, and more&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Image to Pixel Art&lt;/strong&gt; — convert photos with grid size and palettes, download PNG&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MP3 Cover Adder&lt;/strong&gt; — embed album art and edit ID3 tags in the browser&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GIF to Live Photo&lt;/strong&gt; — turn GIFs into Live Photo–ready &lt;code&gt;.mov&lt;/code&gt; without uploading&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are also lighter “recall” games (color, shape, sound) if you want a break that still trains visual / audio memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Built for makers, too
&lt;/h2&gt;

&lt;p&gt;If you ship a product, &lt;a href="https://www.uwarp.design/submit" rel="noopener noreferrer"&gt;submit a curated listing&lt;/a&gt; for manual review. Approved tools can show up as link-out pages under &lt;code&gt;/curated&lt;/code&gt;. Free curated submission is open; paid featured placement is not for sale yet.&lt;/p&gt;

&lt;p&gt;There’s also a separate hardware check playground at &lt;a href="https://hardware-test.uwarp.design/" rel="noopener noreferrer"&gt;hardware-test.uwarp.design&lt;/a&gt; if you need quick browser device detection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Site: &lt;a href="https://www.uwarp.design" rel="noopener noreferrer"&gt;https://www.uwarp.design&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Submit a product: &lt;a href="https://www.uwarp.design/submit" rel="noopener noreferrer"&gt;https://www.uwarp.design/submit&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a tool saves you five minutes, share it with a teammate—or star related open-source bits when you find them. Feedback welcome.&lt;/p&gt;

</description>
      <category>design</category>
      <category>css</category>
      <category>webdev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>SVG Cursors You Can Actually Ship: Introducing `@sherotree/cursors`</title>
      <dc:creator>Sherotree</dc:creator>
      <pubDate>Sat, 26 Sep 2026 12:04:04 +0000</pubDate>
      <link>https://dev.to/sherotree/svg-cursors-you-can-actually-ship-introducing-sherotreecursors-7oc</link>
      <guid>https://dev.to/sherotree/svg-cursors-you-can-actually-ship-introducing-sherotreecursors-7oc</guid>
      <description>&lt;p&gt;Default OS cursors are fine. For a product UI, they often feel generic.&lt;/p&gt;

&lt;p&gt;I extracted the SVG hotspot cursors we use on &lt;a href="https://www.uwarp.design" rel="noopener noreferrer"&gt;Uwarp&lt;/a&gt; into a tiny MIT package: &lt;strong&gt;&lt;a href="https://www.npmjs.com/package/@sherotree/cursors" rel="noopener noreferrer"&gt;&lt;code&gt;@sherotree/cursors&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F79gyj4czqllhoctidxwn.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F79gyj4czqllhoctidxwn.gif" alt="@sherotree/cursors demo" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s included
&lt;/h2&gt;

&lt;p&gt;Four cursors with correct hotspot offsets baked into CSS:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;default&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;pointer&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;grab&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;grabbing&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Plus slightly smaller variants under a &lt;code&gt;1280px&lt;/code&gt; viewport width.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @sherotree/cursors
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Global usage
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@sherotree/cursors/style.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Next.js App Router, import once from &lt;code&gt;app/layout.tsx&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That sets &lt;code&gt;body&lt;/code&gt; cursors, remaps common interactive selectors (&lt;code&gt;a:hover&lt;/code&gt;, &lt;code&gt;button:hover&lt;/code&gt;, &lt;code&gt;[role=button]&lt;/code&gt;, …), and overrides Tailwind’s &lt;code&gt;.cursor-default&lt;/code&gt; / &lt;code&gt;.cursor-pointer&lt;/code&gt; / &lt;code&gt;.cursor-grab&lt;/code&gt; / &lt;code&gt;.cursor-grabbing&lt;/code&gt; so those utilities keep the custom look.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scoped usage
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@sherotree/cursors/scoped.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"sherotree-cursors"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- custom cursors only inside here --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;npm: &lt;a href="https://www.npmjs.com/package/@sherotree/cursors" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/@sherotree/cursors&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/sherotree/cursors" rel="noopener noreferrer"&gt;https://github.com/sherotree/cursors&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you try it, a star or issue on the repo helps a lot. Happy shipping.&lt;/p&gt;

</description>
      <category>css</category>
      <category>frontend</category>
      <category>webdev</category>
      <category>svg</category>
    </item>
    <item>
      <title>Jev Isn't a Chatbot — It's a System One Model for Typed Decisions</title>
      <dc:creator>Sherotree</dc:creator>
      <pubDate>Mon, 21 Sep 2026 15:56:54 +0000</pubDate>
      <link>https://dev.to/sherotree/jev-isnt-a-chatbot-its-a-system-one-model-for-typed-decisions-5fmi</link>
      <guid>https://dev.to/sherotree/jev-isnt-a-chatbot-its-a-system-one-model-for-typed-decisions-5fmi</guid>
      <description>&lt;p&gt;If you've built agents in 2025–2026, you already know the awkward middle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hand-written &lt;code&gt;if&lt;/code&gt; rules are too brittle for fuzzy judgments ("is this urgent?", "which team?", "is this tool call safe?").&lt;/li&gt;
&lt;li&gt;A frontier LLM can answer those questions — but it's slow, expensive, and returns &lt;em&gt;prose&lt;/em&gt; you still have to parse.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Jev&lt;/strong&gt; (from TypeSafe AI) is aimed squarely at that gap. It doesn't write essays. It reads your program's state and returns &lt;strong&gt;typed, calibrated decisions&lt;/strong&gt; your code can branch on immediately.&lt;/p&gt;

&lt;p&gt;This post is a practical intro for indie / SaaS builders who want the mental model, a first API call, and a few real patterns — not a press release.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Jev actually is
&lt;/h2&gt;

&lt;p&gt;TypeSafe calls Jev a &lt;strong&gt;System One&lt;/strong&gt; model.&lt;/p&gt;

&lt;p&gt;Think Kahneman: System Two is slow, verbal reasoning (what chat LLMs imitate). System One is the fast gut check. Jev is built for the &lt;em&gt;software&lt;/em&gt; version of that gut check — routing, scoring, filtering, gating — at high volume.&lt;/p&gt;

&lt;p&gt;You send:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;state&lt;/code&gt;&lt;/strong&gt; — the context (ticket text, JSON event, agent messages, …)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;questions&lt;/code&gt;&lt;/strong&gt; — one or more typed questions about that state&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You get back structured answers with probabilities. No free-text. No "sorry, as an AI…" preamble.&lt;/p&gt;

&lt;p&gt;Training focus is &lt;strong&gt;RLCD&lt;/strong&gt; (&lt;em&gt;Reinforcement Learning for Calibrated Decisions&lt;/em&gt;): when Jev says ~80% confidence, the goal is that it's right about ~80% of the time. That calibration is what makes thresholds usable in production instead of vibes-based prompt engineering.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three question types (that's the whole surface)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;What you ask&lt;/th&gt;
&lt;th&gt;What you get back&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;choice&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Pick 1 of up to 255 labelled options&lt;/td&gt;
&lt;td&gt;Winning key + per-option probabilities + confidence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;score&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Place input on an ordered 2–10 level scale&lt;/td&gt;
&lt;td&gt;Fractional score + distribution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;noul&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes/no as a probability&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;noul&lt;/code&gt; in &lt;code&gt;[0, 1]&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You can ask &lt;strong&gt;several questions in one request&lt;/strong&gt;. They evaluate in parallel and share the state cost — so batching beats a loop of tiny calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tiny example: urgency gate
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.typesafe.ai/v1/systemone &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$TYPESAFE_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "model": "jev-latest",
    "state": "Hi, I'&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;'ve been trying to connect Stripe for 3 days and it keeps failing. I'&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;'m losing sales. Please help ASAP.",
    "questions": {
      "is_urgent": {
        "type": "noul",
        "instructions": "The message conveys urgency or time-sensitivity"
      }
    }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A typical answer looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"is_urgent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"noul"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"noul"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.999&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then your code stays boring (in a good way):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;answers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;is_urgent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;noul&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;escalate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ticket&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No regex. No JSON-schema-from-an-LLM-that-sometimes-breaks. The type is fixed by the request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters for agents (not just classifiers)
&lt;/h2&gt;

&lt;p&gt;Agents spend most of their loop on &lt;em&gt;tiny&lt;/em&gt; judgments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is this still needed?&lt;/li&gt;
&lt;li&gt;Which model should handle this turn?&lt;/li&gt;
&lt;li&gt;Is this tool call too risky?&lt;/li&gt;
&lt;li&gt;Which queue should own this event?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those used to be either brittle rules or another LLM round-trip. Jev is the middle lane: &lt;strong&gt;fast enough for the hot path, structured enough for code&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Public numbers TypeSafe / ecosystem write-ups commonly cite:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Latency roughly &lt;strong&gt;70–500ms&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Far cheaper than frontier LLMs on comparable classification-style tasks (pricing differs between direct TypeSafe access and hosted gateways — check the console you use)&lt;/li&gt;
&lt;li&gt;Output tokens free on the decision API shape (you pay for input / state)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use an LLM for generation and open-ended reasoning. Use Jev for the decisions &lt;em&gt;around&lt;/em&gt; that generation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Patterns that actually ship
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Ticket / email triage
&lt;/h3&gt;

&lt;p&gt;One &lt;code&gt;state&lt;/code&gt;, multiple questions: route (choice), urgency (noul), lead quality (score). Auto-handle high-confidence cases; escalate the uncertain band.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Model routing
&lt;/h3&gt;

&lt;p&gt;Ask Jev which tier should run this turn ("fast lookup" vs "architecture / high-stakes"). LangChain's experimental &lt;code&gt;ModelRouterMiddleware&lt;/code&gt; is built around this idea.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Tool-call guardrails ("auto mode")
&lt;/h3&gt;

&lt;p&gt;Before &lt;code&gt;bash&lt;/code&gt; / delete / payment tools execute, ask a noul: "is this risky / irreversible?" Block or confirm when probability is high. Same pattern coding harnesses already use — now available as a cheap, open classifier layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Indie product glue
&lt;/h3&gt;

&lt;p&gt;If you run SEO tools, support inboxes, or agent workflows (hello, build-in-public SaaS), Jev is a good fit anywhere you currently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;prompt an LLM just to get a label, or&lt;/li&gt;
&lt;li&gt;maintain a growing pile of keyword heuristics&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  LangChain sketch
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain_typesafe&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Noul&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TypeSafeClassifier&lt;/span&gt;

&lt;span class="n"&gt;classifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TypeSafeClassifier&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;classifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The deploy failed twice and customers are seeing 500s. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Can someone look now?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;questions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;urgent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Noul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Does this need attention right now?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;urgency&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nouls&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;urgent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;noul&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;State can be text, structured data, or LangChain messages — handy inside middleware / nodes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ecosystem tips (beyond the core API)
&lt;/h2&gt;

&lt;p&gt;The community moved fast after launch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;awesome-jev / jev-mcp&lt;/strong&gt; style tooling around MCP clients&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;jevlang&lt;/strong&gt; — Python "smart if" syntax for decision workflows&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;jev-router&lt;/strong&gt; — route Claude Code / Codex turns to cheaper vs stronger models&lt;/li&gt;
&lt;li&gt;browser / automation pilots that treat Jev as the &lt;em&gt;chooser&lt;/em&gt;, not the writer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're exploring for a weekend project, start with one noul gate in an existing script. Don't rebuild your whole agent day one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical gotchas
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pin model versions in production&lt;/strong&gt; (&lt;code&gt;jev-1.x.x&lt;/code&gt;) if thresholds matter — &lt;code&gt;jev-latest&lt;/code&gt; can shift.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Batch questions&lt;/strong&gt; on shared state instead of N sequential calls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use confidence / probability bands&lt;/strong&gt;: auto-approve high certainty, human or LLM escalate the middle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Jev is not a chat UX.&lt;/strong&gt; Don't put it in front of end users as a conversational product. Put it &lt;em&gt;inside&lt;/em&gt; the product.&lt;/li&gt;
&lt;li&gt;Access paths vary (TypeSafe waitlist / console, gateways like OpenRouter / Vercel AI Gateway, or hosted metered keys). Pick one and keep the key server-side only.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Who's behind it (short)
&lt;/h2&gt;

&lt;p&gt;TypeSafe AI was founded by Diogo Almeida (ex-OpenAI, co-inventor work around RLHF), with Erik Gafni and Sasha Sheng. The company came out of stealth in September 2026. The thesis is blunt: if AI is going to change work, &lt;strong&gt;people can't be the only consumers of intelligence&lt;/strong&gt; — most of it should live quietly in software.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;p&gt;Jev won't write your README. It will decide, in under half a second, whether this ticket is urgent, which team owns it, and whether your agent should be allowed to run that delete.&lt;/p&gt;

&lt;p&gt;For indie builders drowning in "tiny LLM calls that only return a label," that might be the most useful new primitive of the season.&lt;/p&gt;

&lt;h3&gt;
  
  
  Links
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;What is Jev: &lt;a href="https://www.jevtypesafeai.com/what-is-jev" rel="noopener noreferrer"&gt;https://www.jevtypesafeai.com/what-is-jev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;How to use / API: &lt;a href="https://www.jevtypesafeai.com/how-to-use" rel="noopener noreferrer"&gt;https://www.jevtypesafeai.com/how-to-use&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;LangChain write-up: &lt;a href="https://www.langchain.com/blog/building-a-harness-with-jev" rel="noopener noreferrer"&gt;https://www.langchain.com/blog/building-a-harness-with-jev&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;If you ship something with Jev (routing, triage, guardrails), drop a comment — always hungry for real indie use cases.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>architecture</category>
      <category>llm</category>
    </item>
    <item>
      <title>Build UChart with KendoReact</title>
      <dc:creator>Sherotree</dc:creator>
      <pubDate>Sun, 23 Mar 2025 03:09:12 +0000</pubDate>
      <link>https://dev.to/sherotree/build-uchart-with-kendoreact-4ldd</link>
      <guid>https://dev.to/sherotree/build-uchart-with-kendoreact-4ldd</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/kendoreact"&gt;KendoReact Free Components Challenge&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;UChart&lt;/strong&gt;&lt;br&gt;
Provides out-of-the-box chart templates and supports custom chart styles to help you quickly generate beautiful charts&lt;/p&gt;

&lt;h3&gt;
  
  
  Demo
&lt;/h3&gt;

&lt;p&gt;Link: &lt;a href="https://www.uwarp.design/" rel="noopener noreferrer"&gt;https://www.uwarp.design/&lt;/a&gt;&lt;br&gt;
Repo:&lt;a href="https://github.com/sherotree" rel="noopener noreferrer"&gt;https://github.com/sherotree&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhlhedd7wyqivgbkdltsb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhlhedd7wyqivgbkdltsb.png" alt=" " width="800" height="887"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv5yc3r9fio7lo0xzs3ke.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv5yc3r9fio7lo0xzs3ke.png" alt=" " width="800" height="874"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg73o7mks0y948rrmlifw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg73o7mks0y948rrmlifw.png" alt=" " width="800" height="883"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuhbi9d3rl4cgnbo26i1a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuhbi9d3rl4cgnbo26i1a.png" alt=" " width="800" height="894"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcc2e79txfpwcbmjdydli.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcc2e79txfpwcbmjdydli.png" alt=" " width="800" height="890"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ed2jgsxfyzvhjrf11qa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ed2jgsxfyzvhjrf11qa.png" alt=" " width="800" height="879"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  KendoReact Experience
&lt;/h2&gt;

&lt;p&gt;I use the Checkbox,Splitter,TabStrip,TabStripTab,Button,NumericTextBox,Notification,NotificationGroup,Fade,Dialog,Label,Slider,Switch on the Uchart, help me quickly build the UI part. KendoReact is very easy to get started with, It took me almost no time to use it in my projects.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>kendoreactchallenge</category>
      <category>react</category>
      <category>webdev</category>
    </item>
    <item>
      <title>🏆 Pick Your Champion - Netlify Dynamic Site Challenge</title>
      <dc:creator>Sherotree</dc:creator>
      <pubDate>Sun, 12 May 2024 06:25:06 +0000</pubDate>
      <link>https://dev.to/sherotree/pick-your-champion-netlify-dynamic-site-challenge-502k</link>
      <guid>https://dev.to/sherotree/pick-your-champion-netlify-dynamic-site-challenge-502k</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/netlify"&gt;Netlify Dynamic Site Challenge&lt;/a&gt;: Visual Feast &amp;amp; Build with Blobs &amp;amp; Clever Caching.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://uwarp.netlify.app/pick-your-champion" rel="noopener noreferrer"&gt;Pick Your Champion&lt;/a&gt;&lt;/strong&gt; is an interactive Quiz, player can pick the ideal type here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;🚀 &lt;a href="https://uwarp.netlify.app/pick-your-champion" rel="noopener noreferrer"&gt;Pick Your Champion&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📸 Screenshots&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl9cwk8ddphe80udylr5r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl9cwk8ddphe80udylr5r.png" alt=" " width="799" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmgvml9gbr4u1fppggjme.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmgvml9gbr4u1fppggjme.png" alt=" " width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy4l00ror8gtmrucxpoil.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy4l00ror8gtmrucxpoil.png" alt=" " width="799" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fobtfwfxhcjne67cb7dhh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fobtfwfxhcjne67cb7dhh.png" alt=" " width="800" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Platform Primitives
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://docs.netlify.com/image-cdn/overview/" rel="noopener noreferrer"&gt;Netlify Image CDN&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Integrated Netlify Image CDN to dynamically manipulate images at the time of delivery.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Utilized parameterized transformation by adjusting parameters like width, height, quality, format, fit, and position in the image URL.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://docs.netlify.com/blobs/overview/" rel="noopener noreferrer"&gt;Netlify Blobs&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
Use it to store voting data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getStore&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@netlify/blobs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;searchParams&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gameId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gameId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;roleId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;roleId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`pick-your-champion`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;gameId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}))&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;preCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;roleId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;roleId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;preCount&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;gameId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;lastModified&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;a href="https://docs.netlify.com/platform/caching/" rel="noopener noreferrer"&gt;Netlify's fine-grained cache control capabilities&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Implement custom Cache-Control headers, utilize the Netlify-CDN-Cache-Control header for edge caching, and demonstrate cache invalidation using cache tags.&lt;/p&gt;

&lt;p&gt;First fetching timing cost&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flzgdnekps1o0lusxyfbr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flzgdnekps1o0lusxyfbr.png" alt=" " width="579" height="355"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fetching from the cache&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5t9gn3su23mr4mlipg0e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5t9gn3su23mr4mlipg0e.png" alt=" " width="575" height="357"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Response Headers&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fejlji0e2e9355e8qqat3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fejlji0e2e9355e8qqat3.png" alt=" " width="535" height="277"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Netlify's cache control capabilities is so good, make the API 10 times faster from the cache!!!&lt;/p&gt;

</description>
      <category>netlifychallenge</category>
      <category>devchallenge</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>🎨 Logo Hunter - Netlify Dynamic Site Challenge</title>
      <dc:creator>Sherotree</dc:creator>
      <pubDate>Sun, 12 May 2024 03:55:18 +0000</pubDate>
      <link>https://dev.to/sherotree/logo-hunter-netlify-dynamic-site-challenge-367g</link>
      <guid>https://dev.to/sherotree/logo-hunter-netlify-dynamic-site-challenge-367g</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/netlify"&gt;Netlify Dynamic Site Challenge&lt;/a&gt;: Visual Feast.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://uwarp.netlify.app/logo-hunter" rel="noopener noreferrer"&gt;Logo Hunter&lt;/a&gt;&lt;/strong&gt; boasts a user-friendly interface that allows visitors to effortlessly find app logos from the App Store. At the same time, it was also released to the Figma plug-in community, designers can click the logo to place it in a Figma file.&lt;br&gt;
The project utilizes Netlify's image CDN to fetch and optimize images dynamically. By applying resizing and format optimization, the project creates visually appealing galleries based on user input. This implementation guarantees efficient image delivery and a smooth user experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;🚀 &lt;a href="https://www.uwarp.design/logo-hunter" rel="noopener noreferrer"&gt;Logo Hunter&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🎨 &lt;a href="https://www.figma.com/community/plugin/1365349580836615037/logo-hunter-for-apple-store" rel="noopener noreferrer"&gt;Logo Hunter in Figma&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📌 Features&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Find any app logo from the App Store by searching with keywords and filtering by country.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Transform the logo by adjusting parameters such as width, height, quality, format, fit, and position in the image URL.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You have the convenience of either downloading or copying the logo&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Insert the logo to the Figma file&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;📸 Screenshots&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhc7qyzcw4t6yike9iibq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhc7qyzcw4t6yike9iibq.png" alt="homescreen" width="800" height="535"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frexxdn8w7vhk84qjmgpu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frexxdn8w7vhk84qjmgpu.png" alt="screenshot" width="800" height="630"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc4tuc6cag6uwgq6yy6xe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc4tuc6cag6uwgq6yy6xe.png" alt="screenshot" width="361" height="627"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Platform Primitives
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Netlify Image CDN&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Integrated Netlify Image CDN to dynamically manipulate images at the time of delivery.&lt;/li&gt;
&lt;li&gt;Utilized parameterized transformation by adjusting parameters like width, height, quality, format, fit, and position in the image URL.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://uwarp.netlify.app/.netlify/images?url=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="nx"&gt;artworkUrl512&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
 &lt;span class="s2"&gt;`&amp;amp;fit=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;w=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;h=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;fm=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;q=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;position=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>netlifychallenge</category>
      <category>devchallenge</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
