[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"playground-compressionstream-native-gzip":3,"verticals":17},{"slug":4,"title":5,"summary":6,"html":7,"css":8,"js":9,"config":10,"doc":13},"compressionstream-native-gzip","CompressionStream — interactive playground",null,"\u003Cdiv class=\"pg-header\">\n  \u003Cdiv class=\"pg-title\">CompressionStream\u003C\u002Fdiv>\n  \u003Cdiv class=\"pg-sub\">Compress text natively — no pako, no imports\u003C\u002Fdiv>\n\u003C\u002Fdiv>\n\n\u003Csection class=\"pg-section\">\n  \u003Cdiv class=\"pg-section-label\">Input text\u003C\u002Fdiv>\n\n  \u003Cdiv class=\"preset-row\">\n    \u003Cspan style=\"font-size:0.74rem;color:var(--muted);align-self:center\">Presets:\u003C\u002Fspan>\n    \u003Cbutton class=\"preset-btn\" data-preset=\"hello\">Short string\u003C\u002Fbutton>\n    \u003Cbutton class=\"preset-btn\" data-preset=\"json\">JSON object\u003C\u002Fbutton>\n    \u003Cbutton class=\"preset-btn\" data-preset=\"repeat\">Repetitive text\u003C\u002Fbutton>\n    \u003Cbutton class=\"preset-btn\" data-preset=\"lorem\">Lorem ipsum\u003C\u002Fbutton>\n  \u003C\u002Fdiv>\n\n  \u003Ctextarea class=\"input-box\" id=\"inputText\" placeholder=\"Type or paste text to compress…\">{\"users\":[{\"id\":1,\"name\":\"Alice\"},{\"id\":2,\"name\":\"Bob\"},{\"id\":3,\"name\":\"Carol\"}]}\u003C\u002Ftextarea>\n\n  \u003Cdiv style=\"display:flex;align-items:center;gap:10px;margin-top:10px;flex-wrap:wrap\">\n    \u003Clabel style=\"font-size:0.78rem;color:var(--muted)\">Format:\u003C\u002Flabel>\n    \u003Cselect class=\"format-select\" id=\"formatSelect\">\n      \u003Coption value=\"gzip\" selected>gzip\u003C\u002Foption>\n      \u003Coption value=\"deflate\">deflate\u003C\u002Foption>\n      \u003Coption value=\"deflate-raw\">deflate-raw\u003C\u002Foption>\n    \u003C\u002Fselect>\n    \u003Cbutton class=\"btn-primary\" id=\"compressBtn\" style=\"margin-left:auto\">Compress\u003C\u002Fbutton>\n  \u003C\u002Fdiv>\n\u003C\u002Fsection>\n\n\u003Csection class=\"pg-section\">\n  \u003Cdiv class=\"pg-section-label\">Compression result\u003C\u002Fdiv>\n\n  \u003Cdiv class=\"compress-grid\">\n    \u003Cdiv class=\"panel\">\n      \u003Cdiv class=\"panel-header\">\n        \u003Cspan>Original\u003C\u002Fspan>\n        \u003Cspan id=\"origBytes\" class=\"stat-value accent\">— bytes\u003C\u002Fspan>\n      \u003C\u002Fdiv>\n      \u003Cdiv class=\"panel-body\">\n        \u003Cdiv class=\"stat-row\">\n          \u003Cdiv class=\"stat\">\n            \u003Cspan class=\"stat-label\">Characters\u003C\u002Fspan>\n            \u003Cspan class=\"stat-value\" id=\"origChars\">—\u003C\u002Fspan>\n          \u003C\u002Fdiv>\n          \u003Cdiv class=\"stat\">\n            \u003Cspan class=\"stat-label\">UTF-8 bytes\u003C\u002Fspan>\n            \u003Cspan class=\"stat-value accent\" id=\"origBytesVal\">—\u003C\u002Fspan>\n          \u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n\n    \u003Cdiv class=\"panel\">\n      \u003Cdiv class=\"panel-header\">\n        \u003Cspan>Compressed\u003C\u002Fspan>\n        \u003Cspan id=\"compBytes\" class=\"stat-value good\">— bytes\u003C\u002Fspan>\n      \u003C\u002Fdiv>\n      \u003Cdiv class=\"panel-body\">\n        \u003Cdiv class=\"stat-row\">\n          \u003Cdiv class=\"stat\">\n            \u003Cspan class=\"stat-label\">Compressed bytes\u003C\u002Fspan>\n            \u003Cspan class=\"stat-value good\" id=\"compBytesVal\">—\u003C\u002Fspan>\n          \u003C\u002Fdiv>\n          \u003Cdiv class=\"stat\">\n            \u003Cspan class=\"stat-label\">Savings\u003C\u002Fspan>\n            \u003Cspan class=\"stat-value good\" id=\"savingsPct\">—\u003C\u002Fspan>\n          \u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\n  \u003Cdiv style=\"margin-top:10px;\">\n    \u003Cdiv style=\"font-size:0.78rem;color:var(--muted);margin-bottom:4px\">Size ratio\u003C\u002Fdiv>\n    \u003Cdiv class=\"byte-bar-wrap\">\n      \u003Cdiv class=\"byte-bar\" id=\"byteBar\" style=\"width:0%\">\u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n    \u003Cdiv style=\"display:flex;justify-content:space-between;font-size:0.72rem;color:var(--muted);margin-top:3px\">\n      \u003Cspan>0% (nothing)\u003C\u002Fspan>\u003Cspan id=\"barLabel\">—\u003C\u002Fspan>\u003Cspan>100% (original)\u003C\u002Fspan>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\n  \u003Cdiv style=\"margin-top:12px\">\n    \u003Cdiv style=\"font-size:0.78rem;color:var(--muted);margin-bottom:4px\">First 64 bytes (hex preview)\u003C\u002Fdiv>\n    \u003Cdiv class=\"hex-preview\" id=\"hexPreview\">—\u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Fsection>\n\n\u003Csection class=\"pg-section\">\n  \u003Cdiv class=\"pg-section-label\">Round-trip decompression\u003C\u002Fdiv>\n  \u003Cp style=\"font-size:0.85rem;color:var(--muted);margin:0 0 8px\">Decompress the output and verify it matches the original:\u003C\u002Fp>\n  \u003Cbutton class=\"btn-secondary\" id=\"decompressBtn\" disabled>Decompress\u003C\u002Fbutton>\n  \u003Cdiv class=\"result-text\" id=\"decompressResult\" style=\"margin-top:8px\">—\u003C\u002Fdiv>\n  \u003Cdiv id=\"matchBadge\" style=\"display:none\">\u003C\u002Fdiv>\n\u003C\u002Fsection>\n\n\u003Csection class=\"pg-section\">\n  \u003Cdiv class=\"pg-section-label\">The code — copy and use\u003C\u002Fdiv>\n  \u003Cpre class=\"pg-code\">\u003Cspan class=\"tok-comment\">\u002F\u002F Two-function wrapper — write once, use everywhere\u003C\u002Fspan>\n\u003Cspan class=\"tok-kw\">async function\u003C\u002Fspan> \u003Cspan class=\"tok-fn\">compress\u003C\u002Fspan>(input, format = \u003Cspan class=\"tok-str\">'gzip'\u003C\u002Fspan>) {\n  \u003Cspan class=\"tok-kw\">const\u003C\u002Fspan> stream = \u003Cspan class=\"tok-kw\">new\u003C\u002Fspan> \u003Cspan class=\"tok-cls\">Blob\u003C\u002Fspan>([input])\n    .stream()\n    .pipeThrough(\u003Cspan class=\"tok-kw\">new\u003C\u002Fspan> \u003Cspan class=\"tok-cls\">CompressionStream\u003C\u002Fspan>(format));\n  \u003Cspan class=\"tok-kw\">return new\u003C\u002Fspan> \u003Cspan class=\"tok-cls\">Uint8Array\u003C\u002Fspan>(\u003Cspan class=\"tok-kw\">await new\u003C\u002Fspan> \u003Cspan class=\"tok-cls\">Response\u003C\u002Fspan>(stream).arrayBuffer());\n}\n\n\u003Cspan class=\"tok-kw\">async function\u003C\u002Fspan> \u003Cspan class=\"tok-fn\">decompress\u003C\u002Fspan>(input, format = \u003Cspan class=\"tok-str\">'gzip'\u003C\u002Fspan>) {\n  \u003Cspan class=\"tok-kw\">const\u003C\u002Fspan> stream = \u003Cspan class=\"tok-kw\">new\u003C\u002Fspan> \u003Cspan class=\"tok-cls\">Blob\u003C\u002Fspan>([input])\n    .stream()\n    .pipeThrough(\u003Cspan class=\"tok-kw\">new\u003C\u002Fspan> \u003Cspan class=\"tok-cls\">DecompressionStream\u003C\u002Fspan>(format));\n  \u003Cspan class=\"tok-kw\">return new\u003C\u002Fspan> \u003Cspan class=\"tok-cls\">Response\u003C\u002Fspan>(stream).text();\n}\n\n\u003Cspan class=\"tok-comment\">\u002F\u002F Usage\u003C\u002Fspan>\n\u003Cspan class=\"tok-kw\">const\u003C\u002Fspan> compressed = \u003Cspan class=\"tok-kw\">await\u003C\u002Fspan> compress(jsonString);\n\u003Cspan class=\"tok-kw\">const\u003C\u002Fspan> restored   = \u003Cspan class=\"tok-kw\">await\u003C\u002Fspan> decompress(compressed);\u003C\u002Fpre>\n\u003C\u002Fsection>","\u002F*\n * Shared playground design system — daily-post\n * ============================================\n * Link it from any post's playground\u002Findex.html (no per-post boilerplate):\n *\n *   \u003Clink rel=\"stylesheet\" href=\"..\u002F..\u002F..\u002Fplayground\u002Fassets\u002Fbase.css\" \u002F>\n *\n * The relative path resolves the same locally (posts\u002F\u003Cbase>\u002Fplayground\u002F) and on\n * the deployed host (public\u002Fposts\u002F\u003Cbase>\u002Fplayground\u002F), because build-site.js\n * mirrors this file to public\u002Fplayground\u002Fassets\u002Fbase.css. For a CodeSandbox\n * --embed, playground.js inlines this file so the single-file sandbox still styles.\n *\n * Then write ONLY the demo-specific CSS in your inline \u003Cstyle>. Everything below\n * — tokens, layout, panels, buttons, toggles, sliders, code blocks, status\u002Flog,\n * banners, presets — comes for free and looks consistent across every post.\n *\n * Theme-aware: dark by default, light via prefers-color-scheme AND a\n * [data-theme] override on \u003Chtml> (so a toggle can force either). Accessible:\n * real focus rings, adequate contrast, reduced-motion honored.\n *\u002F\n\n\u002F* ---------- design tokens ---------- *\u002F\n:root {\n  --bg: #0d1117;\n  --bg-grid: #ffffff0a;\n  --panel: #161b22;\n  --panel-2: #0b0e14;\n  --card: #0b0e14;\n  --ink: #e8ebf2;\n  --muted: #9aa4b2;\n  --faint: #6b7688;\n  --line: #232a35;\n  --line-soft: #ffffff12;\n  --accent: #7c9cff;\n  --accent-ink: #0b0e14;\n  --good: #3fb950;\n  --warn: #f0883e;\n  --bad: #f85149;\n  --info: #58a6ff;\n  --radius: 14px;\n  --radius-sm: 9px;\n  --shadow: 0 8px 30px #0006;\n  --shadow-sm: 0 4px 14px #0005;\n  --mono: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n  --sans: ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", sans-serif;\n  color-scheme: dark;\n}\n\n\u002F* Light theme — system preference, overridable by [data-theme] on \u003Chtml>. *\u002F\n@media (prefers-color-scheme: light) {\n  :root:not([data-theme=\"dark\"]) {\n    --bg: #f4f6fb;\n    --bg-grid: #00000008;\n    --panel: #ffffff;\n    --panel-2: #f0f3f9;\n    --card: #f7f9fc;\n    --ink: #131722;\n    --muted: #5b6472;\n    --faint: #8a93a3;\n    --line: #e2e7f0;\n    --line-soft: #00000010;\n    --accent: #2f6df0;\n    --accent-ink: #ffffff;\n    --good: #1a7f37;\n    --warn: #bc4c00;\n    --bad: #cf222e;\n    --info: #0969da;\n    --shadow: 0 8px 30px #0000001f;\n    --shadow-sm: 0 4px 14px #00000014;\n    color-scheme: light;\n  }\n}\n:root[data-theme=\"light\"] {\n  --bg: #f4f6fb;\n  --bg-grid: #00000008;\n  --panel: #ffffff;\n  --panel-2: #f0f3f9;\n  --card: #f7f9fc;\n  --ink: #131722;\n  --muted: #5b6472;\n  --faint: #8a93a3;\n  --line: #e2e7f0;\n  --line-soft: #00000010;\n  --accent: #2f6df0;\n  --accent-ink: #ffffff;\n  --good: #1a7f37;\n  --warn: #bc4c00;\n  --bad: #cf222e;\n  --info: #0969da;\n  --shadow: 0 8px 30px #0000001f;\n  --shadow-sm: 0 4px 14px #00000014;\n  color-scheme: light;\n}\n\n\u002F* ---------- reset & base ---------- *\u002F\n*, *::before, *::after { box-sizing: border-box; }\n\nbody {\n  margin: 0;\n  min-height: 100vh;\n  font-family: var(--sans);\n  color: var(--ink);\n  background:\n    radial-gradient(circle at 1px 1px, var(--bg-grid) 1px, transparent 0) 0 0 \u002F 24px 24px,\n    var(--bg);\n  display: flex;\n  justify-content: center;\n  align-items: flex-start;\n  padding: clamp(14px, 3vw, 26px);\n  line-height: 1.55;\n  -webkit-font-smoothing: antialiased;\n  text-rendering: optimizeLegibility;\n}\n\n\u002F* The single content column. *\u002F\n.pg {\n  width: 100%;\n  max-width: 860px;\n}\n\n\u002F* ---------- header ---------- *\u002F\n.pg-title {\n  font-size: clamp(1.15rem, 1rem + 1vw, 1.4rem);\n  font-weight: 800;\n  letter-spacing: -0.02em;\n  margin: 0 0 4px;\n  display: flex;\n  align-items: center;\n  gap: 9px;\n}\n.pg-sub {\n  color: var(--muted);\n  font-size: 0.92rem;\n  line-height: 1.5;\n  margin: 0 0 16px;\n  max-width: 66ch;\n}\n.pg-sub strong, .pg-sub b { color: var(--ink); font-weight: 700; }\n.pg-sub code { font-family: var(--mono); background: var(--line-soft); color: var(--accent);\n  padding: 1px 6px; border-radius: 6px; font-size: 0.85em; }\n\n\u002F* ---------- panels ---------- *\u002F\n.panel {\n  background: var(--panel);\n  border: 1px solid var(--line);\n  border-radius: var(--radius);\n  padding: 16px 18px;\n  margin-bottom: 14px;\n  box-shadow: var(--shadow-sm);\n}\n.panel-label {\n  font-size: 0.7rem;\n  text-transform: uppercase;\n  letter-spacing: 0.08em;\n  color: var(--muted);\n  font-weight: 800;\n  margin: 0 0 13px;\n  display: flex;\n  align-items: center;\n  gap: 8px;\n}\n.panel-label .n {\n  display: inline-grid; place-items: center;\n  width: 18px; height: 18px; border-radius: 50%;\n  background: var(--accent); color: var(--accent-ink);\n  font-size: 0.66rem; font-weight: 800;\n}\n\n\u002F* ---------- layout helpers ---------- *\u002F\n.row { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; }\n.row.between { justify-content: space-between; }\n.col { display: flex; flex-direction: column; gap: 10px; }\n.grow { flex: 1 1 0; min-width: 0; }\n.wrap-controls { display: flex; flex-wrap: wrap; gap: 16px 24px; align-items: flex-end; }\n.field { display: flex; flex-direction: column; gap: 7px; min-width: 0; }\n.field > .label, .ctl-label {\n  font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.04em;\n  color: var(--muted); font-weight: 800;\n}\n\n\u002F* ---------- buttons ---------- *\u002F\n.btn {\n  font: inherit; font-weight: 650; font-size: 0.88rem;\n  cursor: pointer; user-select: none;\n  border: 1px solid var(--line);\n  background: var(--card); color: var(--ink);\n  padding: 9px 15px; border-radius: var(--radius-sm);\n  display: inline-flex; align-items: center; gap: 8px;\n  transition: background .15s ease, border-color .15s ease, transform .05s ease, color .15s ease;\n}\n.btn:hover { background: var(--line-soft); border-color: var(--muted); }\n.btn:active { transform: translateY(1px); }\n.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }\n.btn[disabled] { opacity: 0.45; cursor: not-allowed; }\n.btn.primary { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }\n.btn.primary:hover { filter: brightness(1.08); background: var(--accent); }\n.btn.good { background: var(--good); border-color: var(--good); color: #04140a; }\n.btn.bad  { background: var(--bad);  border-color: var(--bad);  color: #fff; }\n.btn.ghost { background: transparent; }\n.btn.sm { padding: 6px 11px; font-size: 0.82rem; }\n\n.btn-row { display: flex; flex-wrap: wrap; gap: 9px; }\n\n\u002F* Segmented control — a row of buttons where one is active (aria-pressed). *\u002F\n.seg { display: inline-flex; background: var(--card); border: 1px solid var(--line);\n  border-radius: var(--radius-sm); padding: 3px; gap: 3px; }\n.seg button {\n  font: inherit; font-weight: 650; font-size: 0.84rem; cursor: pointer;\n  border: none; background: transparent; color: var(--muted);\n  padding: 7px 13px; border-radius: 7px;\n  transition: background .15s ease, color .15s ease;\n}\n.seg button:hover { color: var(--ink); }\n.seg button[aria-pressed=\"true\"] { background: var(--accent); color: var(--accent-ink); }\n.seg button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }\n\n\u002F* Preset chips — worked-example shortcuts. *\u002F\n.presets { display: flex; flex-wrap: wrap; gap: 8px; }\n.presets .btn { background: var(--panel-2); }\n\n\u002F* ---------- toggle switch ---------- *\u002F\n.toggle { display: inline-flex; align-items: center; gap: 10px; cursor: pointer;\n  font-size: 0.88rem; font-weight: 600; user-select: none; }\n.toggle input { position: absolute; opacity: 0; width: 0; height: 0; }\n.switch { position: relative; width: 44px; height: 25px; border-radius: 999px; flex: none;\n  background: var(--line); border: 1px solid var(--line); transition: background .18s ease; }\n.switch::after { content: \"\"; position: absolute; top: 2px; left: 2px; width: 19px; height: 19px;\n  border-radius: 50%; background: #cfd6e4; transition: transform .18s ease; box-shadow: 0 1px 3px #0006; }\n.toggle input:checked + .switch { background: var(--good); border-color: var(--good); }\n.toggle input:checked + .switch::after { transform: translateX(19px); background: #fff; }\n.toggle input:focus-visible + .switch { outline: 2px solid var(--accent); outline-offset: 2px; }\n\n\u002F* ---------- range slider ---------- *\u002F\ninput[type=\"range\"] {\n  -webkit-appearance: none; appearance: none; width: 100%; height: 6px;\n  background: var(--line); border-radius: 999px; outline: none; cursor: pointer; margin: 8px 0;\n}\ninput[type=\"range\"]::-webkit-slider-thumb {\n  -webkit-appearance: none; appearance: none; width: 19px; height: 19px; border-radius: 50%;\n  background: var(--accent); border: 2px solid var(--panel); box-shadow: var(--shadow-sm); cursor: pointer;\n}\ninput[type=\"range\"]::-moz-range-thumb {\n  width: 19px; height: 19px; border-radius: 50%; background: var(--accent);\n  border: 2px solid var(--panel); box-shadow: var(--shadow-sm); cursor: pointer;\n}\ninput[type=\"range\"]:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }\n\n\u002F* ---------- text inputs \u002F selects ---------- *\u002F\ninput[type=\"text\"], input[type=\"number\"], input[type=\"search\"], select, textarea {\n  font: inherit; color: var(--ink); background: var(--card);\n  border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 8px 11px; width: 100%;\n}\ntextarea { font-family: var(--mono); font-size: 0.85rem; line-height: 1.6; resize: vertical; }\ninput:focus-visible, select:focus-visible, textarea:focus-visible {\n  outline: 2px solid var(--accent); outline-offset: 1px; border-color: var(--accent);\n}\n\n\u002F* ---------- status \u002F output \u002F log ---------- *\u002F\n.status {\n  display: flex; align-items: center; gap: 9px; flex-wrap: wrap;\n  font-size: 0.9rem; font-weight: 600;\n  padding: 11px 14px; border-radius: var(--radius-sm);\n  background: var(--panel-2); border: 1px solid var(--line); color: var(--ink);\n}\n.status.good { border-color: color-mix(in srgb, var(--good) 55%, var(--line)); }\n.status.warn { border-color: color-mix(in srgb, var(--warn) 55%, var(--line)); }\n.status.bad  { border-color: color-mix(in srgb, var(--bad) 55%, var(--line)); }\n.dot { width: 9px; height: 9px; border-radius: 50%; background: var(--muted); flex: none; }\n.dot.good { background: var(--good); } .dot.warn { background: var(--warn); }\n.dot.bad { background: var(--bad); } .dot.info { background: var(--info); }\n\n.output { background: var(--panel-2); border: 1px solid var(--line); border-radius: var(--radius-sm);\n  padding: 13px 15px; }\n.log { font-family: var(--mono); font-size: 0.8rem; line-height: 1.7; background: var(--card);\n  border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 11px 14px;\n  max-height: 220px; overflow-y: auto; white-space: pre-wrap; }\n\n\u002F* ---------- code block + syntax tokens ---------- *\u002F\n.code {\n  font-family: var(--mono); font-size: 0.82rem; line-height: 1.7;\n  background: var(--panel-2); border: 1px solid var(--line); border-radius: var(--radius-sm);\n  padding: 13px 15px; overflow-x: auto; white-space: pre; tab-size: 2;\n}\n.code .tok-sel  { color: var(--accent); }\n.code .tok-prop { color: #86e29b; }\n.code .tok-kw   { color: #ff9bcb; }\n.code .tok-str  { color: #f0c674; }\n.code .tok-num  { color: #f0883e; }\n.code .tok-cmt  { color: var(--faint); font-style: italic; }\n.code .hl { background: color-mix(in srgb, var(--good) 22%, transparent); border-radius: 4px; padding: 0 3px; }\n\n\u002F* ---------- hint \u002F caption ---------- *\u002F\n.hint { font-size: 0.83rem; color: var(--muted); line-height: 1.55; margin: 10px 0 0; }\n.hint code { font-family: var(--mono); color: var(--ink); background: var(--line-soft);\n  padding: 1px 5px; border-radius: 5px; font-size: 0.86em; }\n.hint strong { color: var(--ink); }\n\n\u002F* ---------- banner (feature-support \u002F notices) ---------- *\u002F\n.banner {\n  display: none; margin-bottom: 14px; padding: 11px 14px; border-radius: 11px;\n  font-size: 0.85rem; line-height: 1.5;\n  color: var(--ink); background: color-mix(in srgb, var(--warn) 12%, var(--panel));\n  border: 1px solid color-mix(in srgb, var(--warn) 45%, var(--line));\n}\n.banner.show { display: block; }\n.banner.info { background: color-mix(in srgb, var(--info) 12%, var(--panel));\n  border-color: color-mix(in srgb, var(--info) 45%, var(--line)); }\n.banner code { font-family: var(--mono); background: var(--line-soft); padding: 1px 5px; border-radius: 5px; }\n\n\u002F* ---------- misc atoms ---------- *\u002F\n.pill { display: inline-flex; align-items: center; gap: 6px; font-size: 0.75rem; font-weight: 700;\n  padding: 3px 9px; border-radius: 999px; background: var(--line-soft); color: var(--muted); }\n.pill.good { color: var(--good); background: color-mix(in srgb, var(--good) 16%, transparent); }\n.pill.bad  { color: var(--bad);  background: color-mix(in srgb, var(--bad) 16%, transparent); }\n.mono { font-family: var(--mono); }\n.muted { color: var(--muted); }\n.big-num { font-family: var(--mono); font-size: 1.6rem; font-weight: 800; letter-spacing: -0.02em; }\n\n\u002F* Optional theme toggle button (top-right). Wire it in JS if you want it. *\u002F\n.theme-btn { position: fixed; top: 12px; right: 12px; z-index: 20; }\n\n\u002F* ---------- responsive ---------- *\u002F\n@media (max-width: 560px) {\n  .panel { padding: 14px; }\n  .wrap-controls { gap: 14px; }\n  .btn { padding: 9px 13px; }\n}\n\n\u002F* ---------- reduced motion ---------- *\u002F\n@media (prefers-reduced-motion: reduce) {\n  *, *::before, *::after { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }\n}\n\n\n.compress-grid {\n    display: grid;\n    grid-template-columns: 1fr 1fr;\n    gap: 12px;\n  }\n  @media (max-width: 560px) {\n    .compress-grid { grid-template-columns: 1fr; }\n  }\n\n  .panel {\n    background: var(--panel-2);\n    border: 1px solid var(--line);\n    border-radius: var(--radius-sm);\n    overflow: hidden;\n  }\n  .panel-header {\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n    padding: 8px 12px;\n    background: var(--panel-3, color-mix(in srgb, var(--ink) 6%, var(--bg)));\n    border-bottom: 1px solid var(--line);\n    font-size: 0.78rem;\n    font-weight: 700;\n    color: var(--muted);\n    letter-spacing: 0.04em;\n    text-transform: uppercase;\n  }\n  .panel-body { padding: 12px; }\n\n  textarea.input-box {\n    width: 100%;\n    height: 120px;\n    resize: vertical;\n    font-family: var(--mono);\n    font-size: 0.82rem;\n    background: var(--bg);\n    color: var(--ink);\n    border: 1px solid var(--line);\n    border-radius: var(--radius-sm);\n    padding: 8px;\n    box-sizing: border-box;\n  }\n\n  .byte-bar-wrap {\n    background: var(--panel-2);\n    border: 1px solid var(--line);\n    border-radius: var(--radius-sm);\n    height: 18px;\n    margin-top: 10px;\n    overflow: hidden;\n    position: relative;\n  }\n  .byte-bar {\n    height: 100%;\n    background: var(--accent);\n    transition: width 0.35s ease;\n  }\n\n  .stat-row {\n    display: flex;\n    gap: 16px;\n    flex-wrap: wrap;\n    margin-top: 10px;\n    font-size: 0.82rem;\n  }\n  .stat {\n    display: flex;\n    flex-direction: column;\n    gap: 2px;\n  }\n  .stat-label { color: var(--muted); font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em; }\n  .stat-value { font-family: var(--mono); font-weight: 700; color: var(--ink); }\n  .stat-value.good { color: var(--good); }\n  .stat-value.accent { color: var(--accent); }\n\n  .hex-preview {\n    font-family: var(--mono);\n    font-size: 0.74rem;\n    color: var(--muted);\n    word-break: break-all;\n    background: var(--bg);\n    border: 1px solid var(--line);\n    border-radius: var(--radius-sm);\n    padding: 8px;\n    min-height: 48px;\n    max-height: 80px;\n    overflow-y: auto;\n    margin-top: 8px;\n  }\n\n  .result-text {\n    font-family: var(--mono);\n    font-size: 0.82rem;\n    color: var(--ink);\n    background: var(--bg);\n    border: 1px solid var(--line);\n    border-radius: var(--radius-sm);\n    padding: 8px;\n    min-height: 60px;\n    max-height: 120px;\n    overflow-y: auto;\n    margin-top: 8px;\n    white-space: pre-wrap;\n    word-break: break-all;\n  }\n\n  .result-text.match { border-color: var(--good); }\n  .result-text.mismatch { border-color: var(--bad, #f87171); }\n\n  .preset-row {\n    display: flex;\n    flex-wrap: wrap;\n    gap: 6px;\n    margin-top: 8px;\n  }\n  .preset-btn {\n    font-size: 0.74rem;\n    padding: 3px 10px;\n    border-radius: 99px;\n    border: 1px solid var(--line);\n    background: var(--panel-2);\n    color: var(--ink);\n    cursor: pointer;\n  }\n  .preset-btn:hover { background: var(--accent); color: #fff; border-color: var(--accent); }\n\n  .format-select {\n    font-size: 0.78rem;\n    padding: 3px 8px;\n    border: 1px solid var(--line);\n    border-radius: var(--radius-sm);\n    background: var(--panel-2);\n    color: var(--ink);\n    cursor: pointer;\n  }\n\n  .check-badge {\n    display: inline-flex;\n    align-items: center;\n    gap: 5px;\n    font-size: 0.78rem;\n    padding: 3px 10px;\n    border-radius: 99px;\n    border: 1px solid transparent;\n    margin-top: 8px;\n  }\n  .check-badge.ok  { background: color-mix(in srgb, var(--good) 15%, transparent); border-color: var(--good); color: var(--good); }\n  .check-badge.err { background: color-mix(in srgb, #f87171 15%, transparent); border-color: #f87171; color: #f87171; }","\u002F\u002F The same two-function wrapper from the article.\n  async function compress(input, format) {\n    const stream = new Blob([input]).stream().pipeThrough(new CompressionStream(format));\n    return new Uint8Array(await new Response(stream).arrayBuffer());\n  }\n\n  async function decompress(input, format) {\n    const stream = new Blob([input]).stream().pipeThrough(new DecompressionStream(format));\n    return new Response(stream).text();\n  }\n\n  const PRESETS = {\n    hello: 'Hello, world!',\n    json: JSON.stringify({ users: Array.from({ length: 20 }, (_, i) => ({ id: i + 1, name: `User ${i + 1}`, active: true })) }, null, 2),\n    repeat: 'abcabc'.repeat(80),\n    lorem: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',\n  };\n\n  const inputText    = document.getElementById('inputText');\n  const formatSelect = document.getElementById('formatSelect');\n  const compressBtn  = document.getElementById('compressBtn');\n  const decompressBtn = document.getElementById('decompressBtn');\n  const origChars    = document.getElementById('origChars');\n  const origBytesVal = document.getElementById('origBytesVal');\n  const origBytes    = document.getElementById('origBytes');\n  const compBytesVal = document.getElementById('compBytesVal');\n  const compBytes    = document.getElementById('compBytes');\n  const savingsPct   = document.getElementById('savingsPct');\n  const byteBar      = document.getElementById('byteBar');\n  const barLabel     = document.getElementById('barLabel');\n  const hexPreview   = document.getElementById('hexPreview');\n  const decompressResult = document.getElementById('decompressResult');\n  const matchBadge   = document.getElementById('matchBadge');\n\n  let lastCompressed = null;\n  let lastFormat = 'gzip';\n\n  document.querySelectorAll('[data-preset]').forEach(btn => {\n    btn.addEventListener('click', () => {\n      inputText.value = PRESETS[btn.dataset.preset];\n      reset();\n    });\n  });\n\n  function reset() {\n    lastCompressed = null;\n    decompressBtn.disabled = true;\n    decompressResult.textContent = '—';\n    decompressResult.className = 'result-text';\n    matchBadge.style.display = 'none';\n    origChars.textContent = '—';\n    origBytesVal.textContent = '—';\n    origBytes.textContent = '— bytes';\n    compBytesVal.textContent = '—';\n    compBytes.textContent = '— bytes';\n    savingsPct.textContent = '—';\n    byteBar.style.width = '0%';\n    barLabel.textContent = '—';\n    hexPreview.textContent = '—';\n  }\n\n  inputText.addEventListener('input', reset);\n  formatSelect.addEventListener('change', reset);\n\n  compressBtn.addEventListener('click', async () => {\n    const text = inputText.value;\n    if (!text) return;\n    const format = formatSelect.value;\n    compressBtn.disabled = true;\n    compressBtn.textContent = 'Compressing…';\n\n    try {\n      const enc = new TextEncoder();\n      const originalBytes = enc.encode(text);\n      const compressed = await compress(text, format);\n\n      lastCompressed = compressed;\n      lastFormat = format;\n\n      const orig = originalBytes.length;\n      const comp = compressed.length;\n      const pct = orig > 0 ? Math.round((1 - comp \u002F orig) * 100) : 0;\n      const ratio = orig > 0 ? Math.round((comp \u002F orig) * 100) : 100;\n\n      origChars.textContent = text.length.toLocaleString();\n      origBytesVal.textContent = orig.toLocaleString();\n      origBytes.textContent = `${orig.toLocaleString()} bytes`;\n      compBytesVal.textContent = comp.toLocaleString();\n      compBytes.textContent = `${comp.toLocaleString()} bytes`;\n      savingsPct.textContent = pct > 0 ? `${pct}% smaller` : (pct === 0 ? 'same size' : `${-pct}% larger`);\n\n      byteBar.style.width = `${Math.min(ratio, 100)}%`;\n      barLabel.textContent = `${ratio}% of original`;\n\n      \u002F\u002F Hex preview — first 64 bytes\n      const hexBytes = Array.from(compressed.slice(0, 64))\n        .map(b => b.toString(16).padStart(2, '0'))\n        .join(' ');\n      hexPreview.textContent = hexBytes + (compressed.length > 64 ? ' …' : '');\n\n      decompressBtn.disabled = false;\n    } catch (err) {\n      hexPreview.textContent = `Error: ${err.message}`;\n    }\n\n    compressBtn.disabled = false;\n    compressBtn.textContent = 'Compress';\n  });\n\n  decompressBtn.addEventListener('click', async () => {\n    if (!lastCompressed) return;\n    decompressBtn.disabled = true;\n    decompressBtn.textContent = 'Decompressing…';\n\n    try {\n      const restored = await decompress(lastCompressed, lastFormat);\n      const original = inputText.value;\n      const match = restored === original;\n\n      decompressResult.textContent = restored.slice(0, 500) + (restored.length > 500 ? '…' : '');\n      decompressResult.className = `result-text ${match ? 'match' : 'mismatch'}`;\n\n      matchBadge.style.display = 'inline-flex';\n      matchBadge.className = `check-badge ${match ? 'ok' : 'err'}`;\n      matchBadge.textContent = match ? '✓ Round-trip successful — output matches input exactly' : '✗ Mismatch';\n    } catch (err) {\n      decompressResult.textContent = `Error: ${err.message}`;\n    }\n\n    decompressBtn.disabled = false;\n    decompressBtn.textContent = 'Decompress';\n  });",{"height":11,"autorun":12},520,true,{"slug":4,"title":14,"excerpt":15,"coverUrl":16},"You're importing pako to gzip data. `CompressionStream` does it natively.","pako is one of npm's most downloaded packages — pulled in by thousands of projects to compress data before storing or sending it. CompressionStream and DecompressionStream do the same job natively, in any modern browser and Node.js 18+, with no dependencies and native C-speed execution.","\u002Fmedia\u002Fcovers\u002Fcompressionstream-native-gzip.png",[18,33],{"id":19,"slug":20,"name":21,"tagline":22,"description":23,"accentFrom":24,"accentTo":25,"icon":26,"defaultLocale":27,"locales":28,"features":30,"position":32},"019fe637-3d33-714b-b57f-23e163ffca0c","dev","Web Development","Build. Learn. Ship.","Practical courses, engineering-grade articles and open-source tools for people who ship.","violet-500","cyan-400","◇","en",[27,29],"fa",{"courses":12,"paths":12,"articles":12,"exams":31,"flashcards":31,"packages":12,"community":12,"certificates":12,"teams":12,"commerce":12},false,0,{"id":34,"slug":35,"name":36,"tagline":37,"description":38,"accentFrom":39,"accentTo":24,"icon":40,"defaultLocale":27,"locales":41,"features":43,"position":44},"019fe637-3dc2-754c-8657-0f175bfee7c6","lang","Languages","Learn a language the way you learn a codebase.","Structured paths, listening drills and spaced repetition that actually sticks.","amber-400","⌘",[27,29,42],"es",{"courses":12,"paths":12,"articles":31,"exams":31,"flashcards":31,"packages":31,"community":12,"certificates":31,"teams":31,"commerce":12},2]