Compare commits

..
Author SHA1 Message Date
claude[bot] dfbc85781f docs: review document for #362
Issue: #362
User-Visible: no
2026-08-29 06:39:25 +00:00
Sergey Matyuninandclaude[bot] f57a0c08ce fix: make devices inert in the Background editor
Issue: #362
User-Visible: yes
2026-08-29 06:25:50 +00:00
Codex 9431a5cea2 docs: #39 spec revision 4 per SPEC-REVIEW-39-r2
Issue: #39
User-Visible: no
2026-08-29 09:22:55 +03:00
claude[bot] ff47c5728c docs: review document for #39
Issue: #39
User-Visible: no
2026-08-29 06:22:11 +00:00
Codex f787de997b docs: #39 spec revision 3 per SPEC-REVIEW-39-r1
Issue: #39
User-Visible: no
2026-08-29 09:15:21 +03:00
claude[bot] b3ed32d2de docs: review document for #362
Issue: #362
User-Visible: no
2026-08-29 06:14:17 +00:00
claude[bot] 3262b097e6 docs: review document for #39
Issue: #39
User-Visible: no
2026-08-29 06:14:09 +00:00
Codex 156be64559 docs: actualize #39 large-backdrops spec (revision 2, benchmark-calibrated)
Issue: #39
User-Visible: no
2026-08-29 09:07:17 +03:00
Matysh e728eab876 build: prepare v1.69.0-beta.3 candidate
Проверка (CI) / Классификация изменённых файлов (push) Successful in 16s
Проверка (CI) / HACS: валидация репозитория (push) Failing after 14s
Проверка (CI) / Hassfest: манифест интеграции (push) Failing after 15s
Проверка (CI) / Переиспользование: это дерево уже проверено (push) Successful in 35s
Проверка (CI) / Предполётные проверки: документация, провенанс, процесс (push) Failing after 40s
Проверка (CI) / Фронтенд: типы, юниты, мутанты, синхрон бандла (push) Failing after 59s
Проверка (CI) / Смоки в браузере (шард 1 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 2 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 3 из 3) (push) Skipped
Проверка (CI) / Смоки: все шарды зелёные (push) Skipped
Проверка (CI) / Golden-кадры против принятых эталонов (push) Skipped
Проверка (CI) / Перф-смок: бюджет времени кадра (push) Skipped
Проверка (CI) / Бэкенд: pytest в Home Assistant (push) Failing after 3m47s
Package door-state-aware Glow, smoothed vacuum trails, cold-View reliability fixes and the furniture placement preview reviewed since beta.2.

Issue: #20
Issue: #209
Issue: #357
Issue: #358
Issue: #359
User-Visible: yes
2026-08-29 01:14:03 +03:00
claude[bot] 38042e0fad docs: review document for #359
Issue: #359
User-Visible: no
2026-08-28 21:58:39 +00:00
Matysh fd762fa744 fix: restore reviewed furniture preview tree
The code-review worker published its report from a stale local snapshot and unintentionally reverted the already reviewed implementation artifacts. Restore every affected path exactly to the reviewed 84bdc7ef tree while retaining CODE-REVIEW-359-r1.md.

Issue: #359
User-Visible: no
2026-08-29 00:51:43 +03:00
claude[bot] bb2919f8c0 docs: review document for #359
Issue: #359
User-Visible: no
2026-08-28 21:49:12 +00:00
Matysh 84bdc7efbe test: accept furniture placement preview golden
Reviewed the Linux candidate from the full CI run. Only the new furniture placement preview frame is accepted; 141 existing baselines remain byte-identical.

Issue: #359
User-Visible: no
Release: v1.69.0-beta.2
Baseline-Reviewed: https://github.com/Matysh/houseplan-card/actions/runs/33213146088
2026-08-29 00:44:36 +03:00
Matysh 8b7219e955 docs: accept furniture preview screenshot index
Capture: https://github.com/Matysh/houseplan-card/actions/runs/33213453705
Issue: #359
User-Visible: no
2026-08-29 00:40:53 +03:00
Matysh 8b66d67d3d feat: preview furniture placement
Issue: #359
User-Visible: yes
2026-08-29 00:34:06 +03:00
claude[bot] 3c281bb12c docs: review document for #359
Issue: #359
User-Visible: no
2026-08-28 21:14:24 +00:00
Matysh 842dc373b1 docs: prove furniture preview edge cases
Issue: #359
User-Visible: no
2026-08-29 00:09:18 +03:00
claude[bot] d6d2ae4ae4 docs: review document for #359
Issue: #359
User-Visible: no
2026-08-28 21:08:22 +00:00
Matysh 392ef22c0d docs: specify furniture placement preview
Issue: #359
User-Visible: no
2026-08-29 00:00:55 +03:00
claude[bot] ccbbe94cab docs: review document for #209
Issue: #209
User-Visible: no
2026-08-28 20:54:22 +00:00
Matysh 17da1e3b3c test: accept pending door-state golden alongside #209
Accept the reviewed Linux frame left pending by #20 alongside the already accepted #209 vacuum frame. The closed and partially open doors now form the visual contract for proportional Glow transmission.

Issue: #209
User-Visible: no
Release: v1.69.0-beta.2
Baseline-Reviewed: https://github.com/Matysh/houseplan-card/actions/runs/33207816479
2026-08-28 23:38:42 +03:00
Matysh c743e1ecd8 test: accept vacuum smoothing golden (#209)
Reviewed the Linux candidate for the deterministic current and previous vacuum routes. Only the new #209 frame is accepted; the unrelated door-hover drift was explicitly retained at its existing reviewed baseline.

Issue: #209
User-Visible: no
Release: v1.69.0-beta.2
Baseline-Reviewed: https://github.com/Matysh/houseplan-card/actions/runs/33207816479
2026-08-28 23:34:56 +03:00
Matysh 6c0b3f7c31 docs: refresh screenshots for final #209 tree
Issue: #209
User-Visible: no
2026-08-28 23:28:09 +03:00
Matysh 3c2d9e8863 build: refresh bundle after golden contract (#209)
Issue: #209
User-Visible: no
2026-08-28 23:20:28 +03:00
Matysh afd1ba6175 test: bind vacuum golden to smoothing default (#209)
Issue: #209
User-Visible: no
2026-08-28 23:18:02 +03:00
Matysh 9d2ef12c96 feat: smooth vacuum trails (#209)
Issue: #209
User-Visible: yes
2026-08-28 23:16:58 +03:00
claude[bot] 2890b9a1ab docs: review document for #209
Issue: #209
User-Visible: no
2026-08-28 20:01:24 +00:00
Matysh 43516cc15f docs: specify bounded vacuum trail smoothing (#209)
Issue: #209
User-Visible: no
2026-08-28 22:55:58 +03:00
claude[bot] 52fbff6cbe docs: review document for #20
Issue: #20
User-Visible: no
2026-08-28 19:50:06 +00:00
Matysh f62926fbeb feat: make Glow follow door opening state (#20)
Issue: #20
User-Visible: yes
2026-08-28 22:33:07 +03:00
claude[bot]andMatysh 72e6939f67 docs: review document for #20
Issue: #20
User-Visible: no
2026-08-28 22:31:02 +03:00
Matysh 3fa98a0915 docs: actualize #20 door-state glow spec
Issue: #20
User-Visible: no
2026-08-28 22:31:02 +03:00
claude[bot] 50d3dbe7cc docs: review document for #358
Issue: #358
User-Visible: no
2026-08-28 18:59:48 +00:00
Codex bb3c1fd12f fix: a cold View survives a telemetry-bearing vacuum (#358)
Systematic audit after #357 ("can there be more bugs with this root
cause?"): _vacMapId was the one remaining hard stub reachable from the
eager View path. It runs inside willUpdate for every vacuum whose
integration reports live telemetry (Tasshack, XCME, Valetudo), so on a
cold tab the #337 stub threw there and the exception took the whole Lit
update cycle with it — the card froze on its very first frame. The demo
mower has no position attributes, telemetry resolved to null, and every
existing smoke (warm and cold) sailed past the branch.

The card now owns the implementation (both dependencies — _vacEntity and
vacMapIdWithFallback — were already eager); the editor runtime delegates
back to the host. The HP-1541-01 invariant (selected_map: 0 is a real map
id, nullish not truthy) moves verbatim and is pinned by the new smoke.

Hardened alongside (audit Lows): _decorShapeDown gets the same
cold-tab guard its twin _decorShapeDbl received in #337 — decor shapes
render in View and CSS pointer-events alone must not be what prevents a
throw; the _vacCalConfirm dialog renders behind the same _editorRuntime
gate as every other editor dialog instead of relying on the implicit
"only the runtime ever sets it".

smoke_cold_view_vacuum: cold tab, vacuum with vacuum_position and
selected_map: 0 — the card commits three successive telemetry frames
(willUpdate alive, not merely the first paint), map id resolves to '0',
no editor chunk requested, a decor pointerdown is a quiet no-op. A
registry mutant restores the delegation and is killed by that smoke.

Issue: #358
User-Visible: yes
2026-08-28 21:49:50 +03:00
claude[bot] 5a662d829d docs: review document for #358
Issue: #358
User-Visible: no
2026-08-28 18:39:51 +00:00
claude[bot] 61911b8656 docs: review document for #357
Issue: #357
User-Visible: no
2026-08-28 18:14:04 +00:00
Codex 49174e81f7 fix: a cold View resolves taps without the lazy editor runtime (#357)
Field report from the dacha: the wall switch "Гостиная основной свет",
whose controls name three virtual light sources, periodically ignored taps
— no toggle, no glow — until its settings dialog was opened once with no
changes. "Periodically" was every fresh tab: the #337 lazy split left
_toggleIntent (and the confirm-line helpers) on the card as stubs
delegating into the editor runtime, so a plain View tap on a cold tab
threw `Houseplan editor runtime is not loaded` synchronously inside the
click handler. Opening any editor surface loaded the runtime and "healed"
the tab for its lifetime.

The View card now owns toggle resolution: _toggleIntent calls
resolveToggleIntent directly (device-toggle.ts was already in the initial
graph; the card owns _planHass/_fullRegistryHass/_virtualLights), and
_toggleStateText/_toggleConfirmationStateText/_toggleConfirmationLines
moved with it. The editor runtime delegates back to the host — one source
of truth, editor consumers (dialog preview, hint lines) unchanged.

Every product smoke preloads the runtime, so none of them could see this
class of regression. The new smoke_cold_view_toggle mirrors the field
config on a genuinely cold tab: a real switch drives three passive
virtual lamps with one tap, a controlled lamp drives its switch back,
tap_confirm renders its state lines and confirms, and the editor chunk is
never requested. A registry mutant restores the old delegation and is
killed by that smoke.

Issue: #357
User-Visible: yes
2026-08-28 21:02:19 +03:00
claude[bot] c2cf4a6996 docs: review document for #357
Issue: #357
User-Visible: no
2026-08-28 17:50:59 +00:00
Matysh 399df907ee build: prepare v1.69.0-beta.2 candidate
Проверка (CI) / Классификация изменённых файлов (push) Successful in 15s
Проверка (CI) / Предполётные проверки: документация, провенанс, процесс (push) Failing after 45s
Проверка (CI) / Hassfest: манифест интеграции (push) Failing after 25s
Проверка (CI) / HACS: валидация репозитория (push) Failing after 27s
Проверка (CI) / Переиспользование: это дерево уже проверено (push) Successful in 44s
Проверка (CI) / Бэкенд: pytest в Home Assistant (push) Failing after 4m29s
Проверка (CI) / Фронтенд: типы, юниты, мутанты, синхрон бандла (push) Failing after 5m39s
Проверка (CI) / Смоки в браузере (шард 1 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 2 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 3 из 3) (push) Skipped
Проверка (CI) / Смоки: все шарды зелёные (push) Skipped
Проверка (CI) / Golden-кадры против принятых эталонов (push) Skipped
Проверка (CI) / Перф-смок: бюджет времени кадра (push) Skipped
Package the expanded furniture library, room-aware climate placement, controller-state parity, resilient editor delivery, revision-safe writes and the reviewed supporting fixes since beta.1.

Issue: #159
Issue: #317
Issue: #318
Issue: #335
Issue: #339
Issue: #340
Issue: #341
Issue: #353
Issue: #354
Issue: #356
User-Visible: yes
2026-08-28 20:37:13 +03:00
claude[bot] 07d59b1cad docs: review document for #159
Issue: #159
User-Visible: no
2026-08-28 16:42:00 +00:00
Matysh 8ab20444d3 test: accept furniture golden baselines
Release: v1.69.0-beta.1
Baseline-Reviewed: https://github.com/Matysh/houseplan-card/actions/runs/33189726315
Issue: #159
User-Visible: no
2026-08-28 19:31:09 +03:00
Matysh b688714fd7 docs: pin furniture source archive
Issue: #159
User-Visible: no
2026-08-28 19:23:16 +03:00
claude[bot] 3633a3db2b docs: review document for #159
Issue: #159
User-Visible: no
2026-08-28 16:20:21 +00:00
Matysh 3c3f85e10d docs: accept furniture screenshots
Capture: https://github.com/Matysh/houseplan-card/actions/runs/33187544417
Issue: #159
User-Visible: no
2026-08-28 18:58:17 +03:00
Matysh 94ecf260b5 fix: type furniture translation keys
Issue: #159
User-Visible: no
2026-08-28 18:52:31 +03:00
Matysh 7909ab6eed feat: expand furniture library
Issue: #159
User-Visible: yes
2026-08-28 18:52:03 +03:00
claude[bot]andMatysh cc05df0b33 docs: review document for #159
Issue: #159
User-Visible: no
2026-08-28 18:50:48 +03:00
Matysh 0a8d23bb21 docs: record MIT grant for furniture artwork
Issue: #159
User-Visible: no
2026-08-28 18:50:48 +03:00
Matysh e78e8ed0e1 docs: address first furniture spec review
Issue: #159
User-Visible: no
2026-08-28 18:50:48 +03:00
claude[bot]andMatysh 296edbf827 docs: review document for #159
Issue: #159
User-Visible: no
2026-08-28 18:50:48 +03:00
Matysh 8e2d995ef6 docs: specify furniture pack rollout
Issue: #159
User-Visible: no
2026-08-28 18:50:48 +03:00
Codex 7c285d25b7 test: golden acceptance keeps environment witnesses (#355)
Declaring the whole matrix in --expect-change could accept a completely
foreign capture (different font stack, different machine): no undeclared
passed scenes would remain, and undeclared passed scenes are exactly what
proves the capture environment equals the accepted baseline's. The
realistic failure is fatigue, not malice — a mass framing change where the
author lists "everything that went red", accidentally sweeping in scenes
that diverged because of the environment.

Acceptance now requires a witness floor: after subtracting
--expect-change/--expect-new, at least min(10, 10% of baseline scenes)
undeclared scenes must match their accepted baselines BYTE-FOR-BYTE (a
sub-threshold 'passed' proves nothing about the environment — #351). A
truly total repaint passes only with an explicit
--no-witnesses --reason="…", and the reason is written into the baseline
manifest — a trace in the artifact and its git history, not just in the
shell history. A first-ever capture with no baselines requires no
witnesses: every frame there is declared in --expect-new anyway.

Issue: #355
User-Visible: no
2026-08-28 18:18:24 +03:00
claude[bot] 4808426369 docs: review document for #354
Issue: #354
User-Visible: no
2026-08-28 15:15:05 +00:00
Codex c1356fae18 test: prove the failure delivery itself, not only the unsubscription (#354 r1-M1)
The r1 reviewer cut the listener loop in the production registry and all
three #354 units stayed green — the subscription unit was the same class of
decoy the issue itself fights. The fan-out now lives in an exported
notifyLanguageLoadFailures(code); the unit drives it directly and asserts
real delivery, partial unsubscription and silence after the last listener
leaves; the contract unit additionally pins the runtime wiring
(`loadFailed` → notifyLanguageLoadFailures) in source. A new registry
mutant `locale-failure-delivery-cut` replays the reviewer's exact cut and
is killed by the unit. The r1 Low is taken too: both USER-GUIDEs now
mention the toast in the German-failure paragraph.

Issue: #354
User-Visible: no
2026-08-28 18:08:27 +03:00
claude[bot] 4e10f69cfb docs: review document for #354
Issue: #354
User-Visible: no
2026-08-28 15:04:07 +00:00
Codex 1397afefd5 fix: the locale runtime the card ships is the code the tests prove (#354)
The production LANGUAGE_RUNTIME was a handwritten twin of the tested
LanguageRuntime class (germanDictionary/Pending/Failed): equivalent on the
day it was written, invisible to every i18n-runtime test afterwards. The
registry now exports one page-scoped `new LanguageRuntime(LANGUAGE_REGISTRY,
…)` instance — the whole existing suite starts proving the object production
actually runs, and a contract unit (instanceof + source free of the old
field names) keeps the duplicate from returning.

The class gains an optional `loadFailed(code)` hook — fired once when a
dictionary load settles into English fallback — and the registry fans it out
through `subscribeLanguageLoadFailures`. Only the View card subscribes (it
alone owns toast infrastructure): a failed language pack now shows the new
`toast.locale_load_failed` message (en/ru/de) instead of a console-only
warning; space card and both GUI editors keep the console warning as before.

Proofs: contract unit, hook unit, subscription unit; smoke_german_locale
extended — the both-attempts-failed scenario now asserts the visible toast;
two new registry mutants (handwritten-twin returns, toast dropped).

Issue: #354
User-Visible: yes
2026-08-28 17:49:59 +03:00
claude[bot] 468eacdb3b docs: review document for #354
Issue: #354
User-Visible: no
2026-08-28 14:45:28 +00:00
claude[bot] 149b2af7f4 docs: review document for #354
Issue: #354
User-Visible: no
2026-08-28 14:40:08 +00:00
claude[bot] 77302eafb1 docs: review document for #353
Issue: #353
User-Visible: no
2026-08-28 14:32:08 +00:00
Codexandclaude[bot] 78c6020747 fix: lazy delivery survives flaky networks and stale caches (#353)
Network failure of the editor runtime is no longer terminal: the loader
re-arms to idle and the next explicit press starts a fresh cycle, while a
fingerprint mismatch on either attempt stays terminal. The toast now says
what actually helps — retry advice for the network, refresh advice for a
foreign build — via one shared lazyLoadFailureMessage helper (new i18n key
editor.retry_advice in en/ru/de).

The field smoke caught a second, deeper bug on the way: Chromium records a
FAILED module in the page module map permanently, so retrying the same URL
(even the cache-busted one) never touched the network again. Every retry
now carries a per-cycle nonce and becomes a genuinely new module request.

A proxy-cached stale entry no longer kills the card silently: the entry
facade is rewritten at build time from a static re-export into a top-level
`try{await import(...)}catch{...}` — importers keep the happy-path
guarantee (await import(entry) still resolves only after
customElements.define), and the catch defines a fallback element with a
localized "reload the page" panel. Content-hashed chunks are served with
`public, max-age=31536000, immutable`, and verifyBundleTree now fails on
orphan chunks that the manifest does not name.

Proofs: loader units for re-arm/terminality/toast wording + an AST check
that both loaders forward the terminality flag; smoke_entry_stale (en/ru)
against a tree without the main chunk; smoke_lazy_editor_chunk extended —
second press after network failure now really opens the editor; pytest for
the immutable header; orphan-tree unit; five new registry mutants.
TESTING.md budget line updated to the #352 ceiling alongside.

Issue: #353
User-Visible: yes
2026-08-28 14:32:08 +00:00
claude[bot] 8cacfda934 docs: review document for #356
Issue: #356
User-Visible: no
2026-08-28 14:30:04 +00:00
Matysh 24c9a169ac fix: require revision for saved layout replacements
Issue: #356
User-Visible: yes
2026-08-28 17:23:01 +03:00
claude[bot] 3a25b0d908 docs: review document for #317
Issue: #317
User-Visible: no
2026-08-28 14:05:47 +00:00
Matysh 6c9752f5e6 fix: follow marker placement for room climate
Issue: #317
User-Visible: yes
2026-08-28 16:48:10 +03:00
claude[bot] b3df25ab85 docs: review document for #317
Issue: #317
User-Visible: no
2026-08-28 13:33:25 +00:00
Matysh f19ed10f2f docs: specify room-aware climate placement
Issue: #317
User-Visible: no
2026-08-28 16:26:57 +03:00
claude[bot] a6d6d11dc1 docs: review document for #318
Issue: #318
User-Visible: no
2026-08-28 13:18:35 +00:00
Matyshandclaude[bot] 72913fee84 fix: show entityless active controllers
Issue: #318
User-Visible: yes
2026-08-28 13:07:58 +00:00
claude[bot] 4b81dc2ad9 docs: review document for #318
Issue: #318
User-Visible: no
2026-08-28 13:07:58 +00:00
Matyshandclaude[bot] 5514fdfe15 docs: specify empty-roster controller state
Issue: #318
User-Visible: no
2026-08-28 13:07:58 +00:00
Claude 583637313f test: гейт «новый код не добавляет any»
В src/** сейчас 1034 вхождения явного any в 49 файлах — больше, чем называл
аудит (330), потому что монолит с тех пор разделился и его обвязка уехала в
houseplan-editor-runtime.ts. Разовая замена такого объёма — месяц риска ради
нуля пользовательской ценности, поэтому долг снимается при плановом извлечении
подсистем (#34). Задача гейта одна: не давать долгу расти.

Судятся только добавленные строки диапазона. Изменённая строка со старым any
выглядит в диффе добавленной, и это намеренно: тронул — либо типизируй, либо
обоснуй на той же строке `// any-ok: <причина>`. Голый маркер, пустая причина и
шаблоны вроде todo, hack, потом не проходят.

Ложных срабатываний нет по построению, а не по старанию: текст разбирается
парсером TypeScript, и нарушением считается узел AnyKeyword. Регулярка по строке
ловила бы слово any в прозе внутри шаблона html и в комментариях; здесь
комментарии, строковые литералы, многострочные шаблоны и идентификаторы
company, anyOf, manyRooms узлами такого вида не являются вовсе.

Проверено исполнением на настоящем дереве, а не только юнитами: пробные коммиты
в src/wall-thickness.ts показали, что добавленный any падает с файлом и строкой,
типизированная строка в файле с 122 старыми any проходит, any-ok с конкретной
причиной проходит, а голый и «todo» — нет, и что any в прозе, строке и
идентификаторах не даёт ни одного срабатывания.

В job frontend checkout получил полную историю без блобов: diff-aware проверке
нужен диапазон, а содержимое старых ревизий — нет.

Заодно закрыта ловушка в test/validate-workflow.test.mjs: имя job искалось через
indexOf('  frontend:'), а эта строка встречается внутри `      frontend: ${{ ...
}}` в outputs job changes, поэтому срез уходил не туда. Теперь имя ищется с
начала строки.

Четыре мутанта проверены руками, два добавлены в реестр: гейт, судящий все
строки, и гейт, принимающий голый маркер.

Issue: #342
User-Visible: no
2026-08-28 16:02:17 +03:00
claude[bot] a7f0facf9a docs: review document for #341
Issue: #341
User-Visible: no
2026-08-28 11:52:12 +00:00
Matysh 2b6db4c8c3 docs: refresh screenshot fingerprint
Issue: #341
User-Visible: no
2026-08-28 14:46:52 +03:00
Matysh 5f6bddeaf3 fix: deduplicate Labs location listeners
Issue: #341
User-Visible: no
2026-08-28 14:40:06 +03:00
claude[bot] c4a83ce875 docs: review document for #340
Issue: #340
User-Visible: no
2026-08-28 11:34:13 +00:00
Matyshandclaude[bot] 3e437ca3ec fix: reject config writes without a revision
Issue: #340
User-Visible: yes
2026-08-28 11:26:19 +00:00
claude[bot] 9239f9d3f5 docs: review document for #340
Issue: #340
User-Visible: no
2026-08-28 11:26:19 +00:00
Matyshandclaude[bot] c9981e6844 docs: specify config revision enforcement
Issue: #340
User-Visible: no
2026-08-28 11:26:19 +00:00
claude[bot] 356b187ede docs: review document for #353
Issue: #353
User-Visible: no
2026-08-28 11:20:42 +00:00
claude[bot] 5044c28153 docs: review document for #353
Issue: #353
User-Visible: no
2026-08-28 11:13:38 +00:00
claude[bot] 07b6a1f200 docs: review document for #335
Issue: #335
User-Visible: no
2026-08-28 11:01:40 +00:00
Matyshandclaude[bot] ca16d1fe59 Fix vacuum trail lifecycle persistence
Issue: #335
User-Visible: yes
2026-08-28 11:01:40 +00:00
Codex 93177cb74c ci: the bundle budget keeps real headroom and reports the trend (#352)
v1.69.0-beta.1 shipped at 255 993 B gzip against a 256 000 B ceiling —
seven bytes of headroom turned the gate into a lottery where the unlucky
last commit goes red, not the one that grew the bundle (5740324b was
exactly that fix; and today's dev already measures 256 012 B, so the old
ceiling would be red right now on an untouched tree).

The ceiling moves to 282 000 B — a deliberate ~10% allowance over the
calibration fact, recorded next to the constant: the budget guards the
CLASS of regression (tens of kilobytes from an accidental dependency or an
eager dictionary), not every byte. Every run now prints the fact, the
budget and the headroom, and CI adds the same row to the step summary so
the trend is visible long before the wall.

The lazy-ru idea from the issue (biggest single cut, ~25 KB gzip) is left
out deliberately: it changes what Russian users see on first paint and
deserves its own decision, not a ride-along.

Issue: #352
User-Visible: no
2026-08-28 13:55:31 +03:00
claude[bot] dd0936258c docs: review document for #339
Issue: #339
User-Visible: no
2026-08-28 10:46:27 +00:00
Codexandclaude[bot] ac077d9b6d fix: a mixed-thickness legacy apex converges honestly (#339)
isDegenerateApexCorner measured the inner-face convergence as
max(h1,h2)/tan(theta/2) — for a 10-degree apex between a 15 cm and a 30 cm
wall that overstates the distance (171.5 cm against the true 128.3/128.9 on
160 cm edges), the corner failed the "inside both edges" test and rendered
as the #329 trident again. Worse, the verdict depended on which neighbouring
edge carried the thicker wall.

The check now intersects the two actual face lines: the meeting point lands
at (hOther + hOwn*cos(theta))/sin(theta) along each edge, degenerate only
when inside both. With equal halves this reduces algebraically to the old
h/tan(theta/2), so equal-thickness verdicts are unchanged by construction —
pinned by the untouched section-4 units and the full golden matrix (136
scenes verified). New units cover both traversal orders of the mixed apex,
the one-point outset tip, the 30-degree ordinary pair and the zero-thickness
guard.

The write path is untouched: P1 forbids new sub-15-degree corners since
issue 329, this is purely how a legacy document renders.

Issue: #339
User-Visible: yes
2026-08-28 10:46:27 +00:00
claude[bot] ab5e5ed96f docs: review document for #335
Issue: #335
User-Visible: no
2026-08-28 10:41:54 +00:00
claude[bot] ccf685e0f8 docs: review document for #335
Issue: #335
User-Visible: no
2026-08-28 10:33:42 +00:00
Matysh 79aa0a90c4 docs: accept v1.69.0-beta.1 runtime screenshot
Issue: #337
User-Visible: no
2026-08-28 12:50:03 +03:00
Matysh 1179a02122 ci: restore smoke test modules and isolate resize camera
Issue: #336
User-Visible: no
2026-08-28 12:47:37 +03:00
Matysh 801e49092f fix: keep lazy editor release version synchronized
Issue: #337
User-Visible: yes
2026-08-28 12:47:30 +03:00
Matysh 568eae3fa4 test: accept German localization baselines
Release: v1.69.0-beta.1
Baseline-Reviewed: https://github.com/Matysh/houseplan-card/actions/runs/33159601496
Issue: #348
User-Visible: no
2026-08-28 12:41:59 +03:00
Matysh eef1ec844c docs: accept v1.69.0-beta.1 screenshots
Accept the complete canonical Linux capture from run 33159459520 after visual
review of View, touch and Device editor surfaces.

Issue: #345
User-Visible: no
2026-08-28 12:29:42 +03:00
Matysh 58ebf8b4cd build: prepare v1.69.0-beta.1 candidate
Проверка (CI) / Классификация изменённых файлов (push) Successful in 18s
Проверка (CI) / Предполётные проверки: документация, провенанс, процесс (push) Failing after 40s
Проверка (CI) / Переиспользование: это дерево уже проверено (push) Successful in 51s
Проверка (CI) / HACS: валидация репозитория (push) Failing after 15s
Проверка (CI) / Hassfest: манифест интеграции (push) Failing after 22s
Проверка (CI) / Бэкенд: pytest в Home Assistant (push) Failing after 11m19s
Проверка (CI) / Фронтенд: типы, юниты, мутанты, синхрон бандла (push) Failing after 11m46s
Проверка (CI) / Смоки в браузере (шард 1 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 2 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 3 из 3) (push) Skipped
Проверка (CI) / Смоки: все шарды зелёные (push) Skipped
Проверка (CI) / Golden-кадры против принятых эталонов (push) Skipped
Проверка (CI) / Перф-смок: бюджет времени кадра (push) Skipped
Package the complete German interface, the unified Device lifecycle catalog,
wall-junction correctness and performance work, and the supporting delivery
infrastructure reviewed since v1.68.1.

Issue: #29
Issue: #62
Issue: #264
Issue: #267
Issue: #329
Issue: #330
Issue: #331
Issue: #333
Issue: #337
Issue: #348
User-Visible: yes
2026-08-28 12:24:15 +03:00
claude[bot] 6a0a349b2e docs: review document for #348
Issue: #348
User-Visible: no
2026-08-28 09:08:12 +00:00
Matysh 4cdd6d7aac test: prove German dialog layout at narrow widths
Extend the existing localized dialog footer measurements to German at desktop and 320 px. The smoke now checks opening, physical-wall and space dialogs for containment, responsive wrapping and horizontal overflow, closing #348 review r1-M1.

Issue: #348
User-Visible: no
2026-08-28 11:58:47 +03:00
claude[bot]andMatysh 37cb415100 docs: review document for #348
Issue: #348
User-Visible: no
2026-08-28 11:58:47 +03:00
Matysh 5740324bed fix: keep locale gate within view budget
Track locale-owned inert and busy state together, preserving the same render contract while keeping the deterministic initial View graph below its hard gzip budget. Refresh generated assets and the documentation fingerprint after the source cleanup.

Issue: #348
User-Visible: no
2026-08-28 11:58:46 +03:00
Matysh 12cd8a1af3 feat: add complete German localization
Add Deutsch across all card surfaces and backend flows, backed by the language registry introduced in #62. German loads as a fingerprint-checked page-shared locale chunk so EN/RU remain synchronous and the initial View budget stays intact. Root render gates prevent mixed-language flashes, retry once, and fail open to English. Extend parity, runtime, bundle, browser and visual coverage, plus contributor and user documentation.

Issue: #348
User-Visible: yes
2026-08-28 11:58:46 +03:00
claude[bot]andMatysh 7a0702adea docs: review document for #348
Issue: #348
User-Visible: no
2026-08-28 11:58:46 +03:00
Matysh dcbaea2c08 docs: specify German localization
Issue: #348
User-Visible: no
2026-08-28 11:58:46 +03:00
Claude 0c9cf9503e test: приёмка переписывает только объявленные эталоны
`passed` означает «в пределах порога», а не «байт в байт»: comparePng считает
diffRatio, и статус ставится по нему. А приёмка копировала кандидата поверх
КАЖДОГО эталона матрицы, поэтому подпороговый дрейф уезжал в контракт молча — и
накапливался: каждая приёмка подтягивала эталон к последней среде, порог не
пересекался никогда, а эталон уходил. Так 1e341c60 заменил 22 картинки, объявив
четыре.

Проект уже сталкивался с этим: ad3f9981 восстанавливал девять уехавших эталонов
руками. Такую работу обязан делать инструмент.

Теперь копируются только сцены из --expect-change и --expect-new; остальные
сохраняют и файл, и свой хеш из прежнего индекса. Индекс по-прежнему
перезаписывается на полный набор — сирота или пропавшая запись делают манифест
недействительным целиком.

Решение вынесено в чистую функцию goldenAcceptancePlan: оно одно, и ошибка в нём
дорога. Отсутствие прежнего хеша у необъявленной сцены — ошибка, а не повод
взять кандидата: без эталона бывает только новая сцена, а она обязана быть
названа в --expect-new.

Логика вернулась в demo/golden/accept.mjs, где ей и место: после #344 эти файлы
исключены из корпуса отпечатка, так что правка больше не требует пересборки и
пересъёмки. scripts/golden-accept.mjs остался проходным вызовом ради
документированной команды.

Проверено сквозным прогоном на синтетическом кандидате: у двух сцен байты
другие, объявлена одна — на диске изменились ровно два файла, эталон и индекс, а
хеш второй сцены остался прежним. Два мутанта убиты руками: «брать кандидата
вместо прежнего хеша» и «заменять всё».

Issue: #351
User-Visible: no
2026-08-28 11:52:36 +03:00
Matysh 1e341c608c test: accept the device catalog baselines (#346)
Причина установлена бисекцией: cab8d128 (#29, feat: add device lifecycle
catalog, User-Visible: yes). На cab8d128^ сцена device-dialog-mobile-ru
совпадала, на cab8d128 разошлась. Изменение объявленное: каталог «Devices»
заменил кнопки «Add» и «Hidden and disabled» в тулбаре редактора устройств.

Стили каталога проверены на протечку: все 32 добавленных селектора и блок
@media (max-width: 680px) заскоплены на .device-inbox*, незаскопленных нет.
Кадры просмотрены — контент не обрезан, сместился.

Съёмка локальная в WSL: параллельность раннеру доказана по правилу #334 —
113 сцен из 117 совпали с эталонами, разошлись ровно объявленные четыре.

Issue: #346
Release: v1.68.2
Baseline-Reviewed: run 33146828502, job 98769832040 (Golden-кадры против принятых эталонов)
User-Visible: no
2026-08-28 11:46:06 +03:00
Claude 3c6b473c94 test: новая golden-сцена объявляется отдельно, флагом --expect-new
Правило из #334 требовало объявлять только сцены со статусом different, а
missing-baseline пропускало без вопросов. При закрытии #346 из-за этого три
эталона каталога устройств стали контрактом без единого взгляда.

Половина прежнего обоснования верна и остаётся: расхождение растеризации новая
сцена выявить не может, параллельность среды доказывают только сцены с
эталонами. Но правило отвечало лишь на вопрос «та ли это среда» и молчало про
второй — «правильный ли это кадр». Пустой, обрезанный или снятый в неверном
состоянии кадр новая сцена закрепляет так же надёжно, как испорченный старый, и
README об этом предупреждает прямо.

Поэтому флагов два и они утверждают разное: --expect-change — «я знаю, почему
старый кадр изменился», --expect-new — «я посмотрел на новый кадр». Имя в чужом
флаге тоже останавливает приёмку: путаница означает, что ревьюер думал об одной
сцене, а утверждал про другую.

Новые эталоны печатаются отдельной строкой «СТАНУТ КОНТРАКТОМ ВПЕРВЫЕ», а не
растворяются в общем списке — раньше они там и растворились.

Прежний тест «новая сцена объявления не требует» заменён: он кодировал снятое
правило. Два мутанта проверены руками — возврат молчаливого пропуска и
разрешённая путаница флагов, — каждый убит.

Issue: #350
User-Visible: no
2026-08-28 11:34:29 +03:00
Claude e3a4ac7655 test: дерево бандла проверяется настоящее, а не синтетическое
Логика проверки существовала и была написана правильно: verifyBundleTree и
compareBundleTrees в scripts/bundle-tree.mjs. Но применялась только к фикстуре в
tmpdir(), поэтому манифест, ссылающийся на пять несуществующих файлов, прожил в
dev при 1444 зелёных тестах и зелёном check-docs. Установка через HACS получила
бы 404 на каждом ленивом импорте.

Второй тест спрашивает git, а не файловую систему, и это не перестраховка.
Дефект родился так: пересборка дала чанки с новыми хешами содержимого,
`git commit -a --amend` удалил старые (отслеживались) и не добавил новые (не
отслеживались). На машине автора проверка наличия файлов прошла бы — файлы там
были. Отличить «собрано» от «закоммичено» умеет только индекс.

Пропуск проверки при недоступном git — громкий: тихий пропуск это тот самый
класс, из-за которого задача и появилась.

Доказательство пользы исполнением: оба теста прогнаны на c665c7d3, коммите до
починки, и оба падают — первый с «manifest asset is missing:
houseplan-assets/editor-DMlizeQy.js», второй с перечислением десяти путей вне
индекса.

Мутанта не добавляю намеренно. Это утверждение о состоянии дерева, а не о
логике: на здоровом дереве ослабленная проверка проходит, то есть мутант
выживает, а выживающий мутант хуже отсутствующего. Логику verifyBundleTree
по-прежнему держат синтетические мутанты в bundle-assets.test.mjs.

Issue: #349
User-Visible: no
2026-08-28 10:44:27 +03:00
Sergey Matyunin c1b37f0dc5 build: restore the content-hashed asset chunks
Issue: #345
User-Visible: no
Baseline-Reviewed: https://github.com/Matysh/houseplan-card/actions/runs/33151614951
2026-08-28 10:39:20 +03:00
Sergey Matyunin c665c7d344 docs: accept re-captured screenshots
Issue: #345
User-Visible: no
2026-08-28 10:33:20 +03:00
ClaudeandSergey Matyunin 1ea460e467 ci: перепаковка кадров документации без потерь при съёмке
oxipng снимает с набора 19.4%: 2096 КБ становятся 1689 КБ, и все десять кадров
остаются пиксельно идентичными — декодированные RGBA совпадают по sha256. Это
выбор фильтров строки и уровня сжатия, а не квантование: визуального решения нет.

Внутри съёмки, а не отдельным проходом по закоммиченным файлам: манифест хранит
imageSha256 каждого кадра, поэтому жать их в репозитории руками нельзя —
check-docs покраснеет; а если жать после подсчёта хешей, следующая съёмка вернёт
неоптимизированные байты. Хеш считается после перепаковки.

Версия oxipng попадает в манифест рядом с версией браузера и по той же причине:
байты кадра зависят от того, чем жали. Отсюда же правка шага «Вердикт» — иначе он
объявил бы «тот же браузер, а картинки изменились — изменился продукт», хотя
изменился упаковщик.

Пин версии и контрольной суммы вместо apt-get: пакет из образа раннера может
пропасть, а падение шага съёмки стоит целого цикла приёмки (#175, #206).

Проверено исполнением на прежней базе: съёмка прогнана целиком с подставным
oxipng, 2096 -> 1689 КБ, хеши манифеста совпали с файлами, check-docs зелёный.

Issue: #345
User-Visible: no
2026-08-28 10:28:13 +03:00
Claude b428eacca6 build: корпус отпечатка — только файлы, способные изменить кадр
accept.mjs копирует уже снятые PNG и пишет манифест, policy.mjs — чистые
предикаты. Ни тот, ни другой в момент рендера не исполняется, но оба входили в
корпус, и правка любого объявляла устаревшими бандл и манифест скриншотов. В
#334 из-за этого правило приёмки пришлось вынести в scripts/ и вызывать
обёрткой вместо того, чтобы положить туда, где ему место.

Возражение «run.mjs импортирует policy.mjs, значит исключение протекает» снято в
комментарии: оттуда берутся проверка аргументов, действительность манифеста и
код возврата — байты кадра определяются аргументами браузера и подготовкой сцены.

Исключение — список, а не фильтр по имени. Тест закрепляет обе стороны, и
обратная важнее прямой: исключение, доехавшее до matrix, harness, run или
фикстур, сделает несвежий бандл неотличимым от свежего.

Коммит меняет значение отпечатков, поэтому в dev идёт вместе с пересборкой
бандла и пересъёмкой скриншотов. Golden при этом не затронут: sourceFingerprint,
записанный в baselines-index.json, не валидирует никто — manifestValid смотрит
matrixVersion, chromium и полноту набора сцен.

Issue: #344
User-Visible: no
2026-08-28 10:25:36 +03:00
Codex b573590a96 ci: a force-push runs everything — the classifier stops guessing a rewritten range (#347)
github.event.before dies with a force-push, and the merge-base fallback then
guessed a diff range: on issue/333 it reported two review-doc files while the
real diff touched custom_components/** — frontend and backend jobs silently
skipped and the run stayed success, the exact #171/#207 class of silent pass
that nearly hid a genuine backend regression from code review.

The classifier now distinguishes the two fallback cases instead of merging
them: a ZERO before is a genuinely new branch and keeps the merge-base
range; a NON-ZERO before that no longer exists is a rewritten history, and
the range is not provable — frontend/backend/integration all go true, with
a loud note in the step summary. A force-push is rare and almost always
follows a rebase, where the full run is what an honest signal costs.

The three branches of the decision are pinned by a workflow-contract unit
next to the existing performance-workflow contracts.

Issue: #347
User-Visible: no
2026-08-28 10:19:37 +03:00
claude[bot] 918fc9e2fe docs: review document for #333
Issue: #333
User-Visible: no
2026-08-28 06:06:23 +00:00
Codex 4ded9c0b7d test: the #248 roundtrip fixture is seeded, not first-written (#333 r1-H1)
The junction gate reads an empty previous as "a first write may not arrive
already broken", and the #248 storage-roundtrip fixture legitimately carries
a 6 cm wall — so the untouched test went red on this branch. The subject of
#248 is byte-exact storage of an optimize commit, not first-write semantics:
the fixture is now seeded as the stored document and optimize inherits its
violations per rule, exactly like a real repair flow. Every storage
assertion (intent, pending, final pair, canonical serialisation) is
unchanged.

Issue: #333
User-Visible: no
2026-08-28 08:55:39 +03:00
claude[bot]andCodex 783cc68b91 docs: review document for #333
Issue: #333
User-Visible: no
2026-08-28 08:55:39 +03:00
Codex 5a2dd333d2 fix: plan/optimize passes the junction gate; import stays free by design (#333)
The owner's decision (2026-08-28): optimize is one of the two commands a
client can use to write arbitrary geometry, so it validates its candidate
against the stored document exactly as config/set does — inheritance counted
per rule (repairing a legacy plan with violations still passes; #329 AC10
already proves an honest optimization adds none, so the gate is a no-op for
legitimate flows), while a crafted payload is refused with the stable
junction_limit_<rule> code the except list has been ready for since #329.
The call lives inside the existing executor function, and a successful
optimize refreshes rt.junction_baseline with the candidate's counts so the
next config/set inherits from the cache (#330 §4.2 symmetry).

Import and backup restore stay OUTSIDE the gate on purpose — #329 §3
promises a restore is never blocked. The module docstring stops promising
more than the code does, and spec #329 §5 records the perimeter and the
trade-off explicitly: a crafted import can persist violations, but they are
inherited, never legalised as new ones.

HA tests pin AC1 (crafted spike refused, stored config and rev
byte-unchanged), AC2 (echo-optimize of a stored plan that already carries a
violation passes) and AC3 (the follow-up config/set takes its baseline from
the cache — observed through a recording wrapper). The
junction-limit-optimize-unguarded mutant turns AC1 red through the
backend-test-guard convention.

Issue: #333
User-Visible: no
2026-08-28 08:55:39 +03:00
claude[bot] b37a59874c docs: review document for #337
Issue: #337
User-Visible: no
2026-08-28 05:40:28 +00:00
Matyshandclaude[bot] 9b79807b69 docs: accept lazy editor screenshot fingerprint
Reviewed artifact: https://github.com/Matysh/houseplan-card/actions/runs/33144737317

Issue: #337
User-Visible: no
2026-08-28 05:40:28 +00:00
claude[bot] c88af61be2 docs: review document for #337
Issue: #337
User-Visible: no
2026-08-28 05:40:28 +00:00
Matyshandclaude[bot] 81420a0d94 fix: preserve warm editor camera during lazy adoption
Issue: #337
User-Visible: no
2026-08-28 05:40:28 +00:00
claude[bot] 3cb9e2ba62 docs: review document for #337
Issue: #337
User-Visible: no
2026-08-28 05:40:28 +00:00
Matyshandclaude[bot] 165dabc5d7 test: restore lazy split mutation coverage
Issue: #337
User-Visible: no
2026-08-28 05:40:28 +00:00
Matyshandclaude[bot] bd82a6709e test: guard eager config canonicalization
Issue: #337
User-Visible: no
2026-08-28 05:40:28 +00:00
Matyshandclaude[bot] 9b519b0d4c fix: preserve editor behavior across lazy runtime
Issue: #337
User-Visible: no
2026-08-28 05:40:28 +00:00
claude[bot] 37a5d14569 docs: review document for #337
Issue: #337
User-Visible: no
2026-08-28 05:40:28 +00:00
Matyshandclaude[bot] 61d9952543 docs: refresh screenshots after dev rebase
Issue: #337
User-Visible: no
2026-08-28 05:40:28 +00:00
Matyshandclaude[bot] 7c13b16afd test: enter lazy editor in resize smoke
Issue: #337
User-Visible: no
2026-08-28 05:40:28 +00:00
Matyshandclaude[bot] 542e54034b docs: accept canonical screenshots for lazy runtime
Issue: #337
User-Visible: no
2026-08-28 05:40:28 +00:00
Matyshandclaude[bot] eedff24b29 fix: sync docs capture asset tree
Issue: #337
User-Visible: no
2026-08-28 05:40:28 +00:00
Matyshandclaude[bot] e3f5c49502 fix: keep lazy editor build portable
Issue: #337
User-Visible: no
2026-08-28 05:40:28 +00:00
Matyshandclaude[bot] 2c65fcec03 perf: lazy-load editor runtime
Issue: #337
User-Visible: yes
2026-08-28 05:40:28 +00:00
claude[bot] 5a703ef562 docs: review document for #337
Issue: #337
User-Visible: no
2026-08-28 05:40:28 +00:00
Matyshandclaude[bot] 4485417027 docs: specify lazy editor bundle
Issue: #337
User-Visible: no
2026-08-28 05:40:28 +00:00
claude[bot] 7340622524 docs: review document for #333
Issue: #333
User-Visible: no
2026-08-28 05:32:29 +00:00
claude[bot] ff6e53a48a docs: review document for #333
Issue: #333
User-Visible: no
2026-08-28 05:26:53 +00:00
367 changed files with 53683 additions and 27429 deletions
+2 -2
View File
@@ -46,6 +46,8 @@ jobs:
- name: Install pinned Chromium
if: steps.pw.outputs.cache-hit != 'true'
run: npx playwright install chromium
- name: Build the bundle the screenshots must come from
run: npm run build
# oxipng без потерь снимает с набора ~19% (замер в #345 на pyoxipng 9.1.1;
# у 10.2.0 пресеты уровней перебалансированы, точная доля может отличаться,
# но кадры остаются пиксельно идентичными в любой версии — это перепаковка).
@@ -69,8 +71,6 @@ jobs:
"oxipng-${OXIPNG_VERSION}-x86_64-unknown-linux-gnu/oxipng"
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
"$HOME/.local/bin/oxipng" --version
- name: Build the bundle the screenshots must come from
run: npm run build
- name: Capture
run: node demo/docs/capture.mjs
# Вердикт до всякой приёмки. Само число изменившихся файлов ничего не
+5 -9
View File
@@ -82,17 +82,13 @@ jobs:
set -euo pipefail
npm ci
npm run build
cmp dist/houseplan-card.js custom_components/houseplan/frontend/houseplan-card.js
node scripts/bundle-tree.mjs dist custom_components/houseplan/frontend
npm run bundle:budget
VERSION=${TAG#v}
grep -Fq "$VERSION" dist/houseplan-card.js
grep -RFq "$VERSION" dist
(cd custom_components/houseplan && zip -qr ../../houseplan.zip .)
unzip -l houseplan.zip | grep -q "manifest.json"
ZIP_VERSION=$(unzip -p houseplan.zip manifest.json | node -e \
"let s='';process.stdin.on('data',d=>s+=d).on('end',()=>process.stdout.write(JSON.parse(s).version))")
test "$ZIP_VERSION" = "$VERSION" || {
echo "::error::houseplan.zip manifest version $ZIP_VERSION != $VERSION"
exit 1
}
node scripts/verify-houseplan-zip.mjs houseplan.zip \
custom_components/houseplan/frontend "$VERSION"
test -s dist/houseplan-card.js
test -s houseplan.zip
- name: Create or verify the annotated tag
+1 -1
View File
@@ -34,7 +34,7 @@ jobs:
- name: Build houseplan.zip (contents of custom_components/houseplan at zip root)
run: cd custom_components/houseplan && zip -qr ../../houseplan.zip .
- name: Sanity check
run: unzip -l houseplan.zip | grep -q "manifest.json"
run: node scripts/verify-houseplan-zip.mjs houseplan.zip custom_components/houseplan/frontend
- name: Upload asset
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+2 -1
View File
@@ -67,7 +67,8 @@ jobs:
with:
name: continuity-screencast
path: artifacts/continuity-screencast
- run: cp dist/houseplan-card.js custom_components/houseplan/frontend/
- name: Verify the complete committed frontend tree
run: node scripts/bundle-tree.mjs dist custom_components/houseplan/frontend
- name: Attach card to release
uses: softprops/action-gh-release@v3
with:
+71 -6
View File
@@ -165,10 +165,28 @@ jobs:
zero=$(printf '%040d' 0)
base="$BEFORE_SHA"
if [ "$EVENT_NAME" = "pull_request" ]; then base="$BASE_SHA"; fi
# #347: непустой before, которого больше не существует, — это
# force-push (ребейз/аменд переписал историю ветки). Диапазон в
# этом случае НЕ угадывается: merge-base после переписанной истории
# уже выдавал «изменились только два doc-файла» при реальном диффе
# в custom_components/**, и тяжёлые job тихо скипались, а общий
# статус рана оставался success — ровно класс тихого пропуска
# #171/#207. Force-push редок, полный прогон дешевле ложного
# «зелёного».
if [ -n "$base" ] && [ "$base" != "$zero" ] \
&& ! git cat-file -e "$base" 2>/dev/null; then
echo "force-push: before=$base переписан — полный прогон без классификации"
{
echo "### Классификация изменённых файлов"
echo "\`before\` (=$base) переписан force-push'ем: диапазон недоказуем,"
echo "все тяжёлые job запускаются без фильтра путей (#347)."
} >> "$GITHUB_STEP_SUMMARY"
printf 'frontend=true\nbackend=true\nintegration=true\n' >> "$GITHUB_OUTPUT"
exit 0
fi
# Новая ветка: before нулевой, диапазон считается от merge-base с dev,
# иначе классифицировалась бы вся история.
if [ -z "$base" ] || [ "$base" = "$zero" ] \
|| ! git cat-file -e "$base" 2>/dev/null; then
if [ -z "$base" ] || [ "$base" = "$zero" ]; then
git fetch -q origin dev
base=$(git merge-base origin/dev "$HEAD_SHA" || echo "$HEAD_SHA~1")
fi
@@ -302,7 +320,10 @@ jobs:
if: needs.changes.outputs.frontend == 'true'
runs-on: ubuntu-latest
steps:
# Полная история без блобов (#342): гейт «новый код не добавляет any»
# diff-aware, ему нужен диапазон, а содержимое старых ревизий — нет.
- uses: actions/checkout@v7
with: { fetch-depth: 0, filter: 'blob:none' }
- uses: actions/setup-node@v7
with:
node-version: 22
@@ -310,13 +331,42 @@ jobs:
- run: npm ci
- name: Typecheck
run: npm run typecheck
# Долг типизации снимается при извлечении подсистем (#34), а не разовой
# заменой: в src/** его 1034 вхождения в 49 файлах. Гейт держит приращение
# на нуле — судит только добавленные строки (#342).
- name: "Новый код не добавляет any"
env:
EVENT_NAME: ${{ github.event_name }}
BEFORE_SHA: ${{ github.event.before }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.sha }}
REF: ${{ github.ref }}
run: |
set -euo pipefail
git fetch -q origin dev
zero=$(printf '%040d' 0)
base="$BEFORE_SHA"
if [ "$EVENT_NAME" = "pull_request" ]; then base="$BASE_SHA"; fi
# Пуш прямо в dev: диапазон — то, что этот пуш добавил. Новая ветка или
# перезаписанная история: точки отсчёта нет, берём merge-base с dev.
if [ "$REF" != "refs/heads/dev" ] || [ -z "$base" ] || [ "$base" = "$zero" ] \
|| ! git cat-file -e "$base" 2>/dev/null; then
base=$(git merge-base origin/dev "$HEAD_SHA" || echo "")
fi
if [ -z "$base" ]; then
echo "не нашёл базу диапазона — гейт пропущен, это увидит следующий пуш"
exit 0
fi
node scripts/no-new-any.mjs --base "$base" --head "$HEAD_SHA"
- name: Unit tests
run: npm test
- name: Build
run: npm run build
# Копия стенда больше не коммитится (#255): сверяются две обязательные.
- name: Card bundle snapshots in sync
run: cmp dist/houseplan-card.js custom_components/houseplan/frontend/houseplan-card.js
# Entry, manifest and every content-hashed chunk are one artifact (#337).
- name: Card bundle trees in sync
run: |
node scripts/bundle-tree.mjs dist custom_components/houseplan/frontend
npm run bundle:budget
# Бандл собирается один раз и уезжает артефактом (#336). Прежде каждая из
# пяти браузерных job собирала его заново: `bundle:sync` — это `tsc
# --noEmit` плюс rollup, около минуты на job.
@@ -328,7 +378,17 @@ jobs:
uses: actions/upload-artifact@v7
with:
name: card-bundle
path: dist/houseplan-card.js
path: dist/
retention-days: 1
if-no-files-found: error
# Несколько браузерных контрактов импортируют чистые функции из
# скомпилированного тестового дерева. Оно создаётся `npm test`, но не
# входит в production-бандл и потому едет отдельным артефактом (#336).
- name: Выложить тестовые модули для смоков
uses: actions/upload-artifact@v7
with:
name: card-test-build
path: test-build/
retention-days: 1
if-no-files-found: error
@@ -374,6 +434,11 @@ jobs:
with:
name: card-bundle
path: dist
- name: Забрать тестовые модули
uses: actions/download-artifact@v7
with:
name: card-test-build
path: test-build
- name: Разложить бандл по копиям
run: node scripts/bundle-sync.mjs
- name: Smoke suite (шард ${{ matrix.shard }} из 3)
+2
View File
@@ -12,3 +12,5 @@ artifacts/
# она только росла — 364 версии по 1.16 МБ за семь недель (#255). Обязательных
# копий две: `dist/` (артефакт сборки) и `custom_components/` (её ставит HACS).
demo/srv/assets/houseplan-card.js
demo/srv/assets/houseplan-assets.json
demo/srv/assets/houseplan-assets/
+6 -5
View File
@@ -2,7 +2,8 @@
House Plan is one HACS package with two parts plus a demo harness:
- **Lovelace card** (`src/`, TypeScript + Lit) — the primary product, bundled to `dist/houseplan-card.js`.
- **Lovelace card** (`src/`, TypeScript + Lit) — the primary product, bundled to
the entry, manifest and hashed chunks under `dist/`.
- **Storage integration** (`custom_components/houseplan/`, Python) — the Home Assistant backend.
- **Demo harness** (`demo/`) — a self-contained Playwright page (`demo/srv/demo.html`) that renders the card against a fake `hass`, used for screenshots and the `smoke_*.mjs` end-to-end suite.
@@ -93,7 +94,7 @@ Start with the spec?" is the correct answer, not a smaller patch.
| **A — product** | `src/**`, `custom_components/houseplan/**/*.py`, `manifest.json`, `hacs.json`, i18n, `custom_components/**/translations/**` | yes |
| **B — gates and tooling** | `test/**`, `tests_backend/**`, `demo/**`, `scripts/**`, `.github/workflows/**`, `rollup.config.mjs`, `tsconfig*.json` | yes; may reuse the issue it covers |
| **C — documentation** | `docs/**`, `README*`, `CHANGELOG*`, `AGENTS.md` | not if it is part of its issue's DoD |
| **D — generated** | `dist/**`, `custom_components/houseplan/frontend/**`, `demo/golden/baselines/**` | never changes on its own. The stand copy `demo/srv/assets/houseplan-card.js` is no longer committed (#255): build it with `npm run bundle:sync` |
| **D — generated** | `dist/**`, `custom_components/houseplan/frontend/**`, `demo/golden/baselines/**` | never changes on its own. The stand copy `demo/srv/assets/**` is no longer committed (#255): build the complete tree with `npm run bundle:sync` |
The table above is a summary; `PROCESS.md` §1 is the authority and now covers the
configuration files this one omits — `package.json`, `package-lock.json`,
@@ -325,12 +326,12 @@ npm run inventory # the only correct way to get test counts
Never copy test counts into documents by hand; they go stale in days.
After building, keep all three bundle snapshots in sync — CI compares them
byte-for-byte:
After building, keep the complete manifest-driven bundle trees in sync — CI
verifies every listed file byte-for-byte:
```
cp dist/houseplan-card.js custom_components/houseplan/frontend/houseplan-card.js
npm run bundle:sync # dist → custom_components + demo/srv/assets (#255)
npm run bundle:budget # initial View graph <= 256000 B gzip (#337)
```
During the implementation cycle the fast gates always run. Since 2026-08-14 the
+11 -2
View File
@@ -16,8 +16,8 @@ A shipped UI language has three matching parts:
1. `src/i18n/<code>.json` for the card;
2. `custom_components/houseplan/translations/<code>.json` for the Home Assistant
integration;
3. one static entry (dictionary import, code and native label) in
`src/i18n/registry.ts`.
3. one registry entry (code, native label and eager dictionary or lazy loader)
in `src/i18n/registry.ts`.
Use the canonical Home Assistant/BCP 47 language tag as `<code>` (for example,
`fr` or `pt-BR`) and use that exact spelling for both JSON filenames. Lookup is
@@ -29,6 +29,15 @@ also fail on mismatched keys, empty values and changed placeholders.
Placeholders such as `{name}` and `{n}` are a contract: do not translate, add
or remove them.
English and Russian are synchronous fallback/legacy locales. German is the
reference lazy third-locale implementation: its module carries the same build
fingerprint as the entry bundle, has one content-hashed retry URL and is listed
under `lazyLocaleFiles` in `houseplan-assets.json`. New sizeable locales should
follow that path unless a measured initial-bundle budget explicitly justifies
an eager import. Extend the runtime, manifest, file/key/placeholder parity and
regional-locale tests together; never bypass the registry by importing a locale
directly in a component.
The current `subst()` helper does not implement plural rules. Phrase strings so
their grammar does not depend on the numeric value (for example, use a neutral
label followed by `{n}` rather than an English singular/plural pair).
+11
View File
@@ -567,12 +567,23 @@ npm run build && cmp dist/houseplan-card.js custom_components/houseplan/frontend
# копия стенда собирается `npm run bundle:sync`, в репозитории её нет (#255)
node scripts/smoke-select.mjs --base origin/dev --head HEAD # какие смоки относятся к диффу
node demo/smoke_<целевые>.mjs
node scripts/no-new-any.mjs --base origin/dev --head HEAD # новый код не добавляет any
npm run golden:verify # если менялся визуал
node scripts/check-docs.mjs # если менялся src/**
node scripts/model-invariants.mjs --config <экспорт> # если правилась геометрия или ссылки
python -m pytest tests_backend -q # py3.13, если менялся бэкенд
```
**Новый код не добавляет `any`** (#342). В `src/**` уже 1034 вхождения явного
`any` в 49 файлах; перетипизировать это одним заходом — месяц риска ради нуля
пользовательской ценности, поэтому долг снимается при плановом извлечении
подсистем (#34), а не разовой заменой. Гейт `scripts/no-new-any.mjs` судит
**только добавленные строки**: существующий долг на нетронутой строке законен,
правка строки со старым `any` — новая ответственность. Исключение объявляется на
той же строке, `// any-ok: <конкретная причина>`; голый маркер и причины вида
«todo» не проходят. Текст разбирается парсером TypeScript, поэтому слово «any» в
комментарии, строке или идентификаторе ложных срабатываний не даёт.
**Объём гейтов на код-ревью соразмерен задаче** (issue #127). Всегда:
`typecheck`, `npm test`, `npm run build` со сверкой трёх копий бандла, а при
любом diff'е по `src/**` — ещё и `node scripts/check-docs.mjs`. По
+4 -2
View File
@@ -108,8 +108,10 @@ serve that path as a JavaScript module.
### Manual installation
Copy `custom_components/houseplan` to `config/custom_components`, restart Home
Assistant, and add the House Plan integration.
Copy the complete `custom_components/houseplan` release folder to
`config/custom_components`, restart Home Assistant, and add the House Plan
integration. Do not copy only `houseplan-card.js`: the card also uses an
internal manifest and content-hashed modules from the same release.
### Add the card
+4 -2
View File
@@ -112,8 +112,10 @@ resources:
### Вручную
Скопируйте `custom_components/houseplan` в `config/custom_components`,
перезапустите Home Assistant и добавьте интеграцию House Plan.
Скопируйте всю папку `custom_components/houseplan` из релиза в
`config/custom_components`, перезапустите Home Assistant и добавьте интеграцию
House Plan. Одного `houseplan-card.js` недостаточно: карточке также нужны
внутренний манифест и хешированные модули из того же релиза.
### Добавление карточки
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Sergey Matyushin (Matysh)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,23 @@
# House Plan furniture pack 0.3.0
Canonical source artwork for the built-in House Plan furniture library.
- `svg/menu/`: 33 front-view category illustrations used only by the lazy
editor bundle.
- `svg/plan/`: 44 top-view drawings used on the plan.
- `pack.json`: stable ids, category links, default dimensions and names. The
filename deliberately avoids the `*manifest.json` suffix reserved by HACS.
The original author, Sergey Matyushin (`Matysh`), granted House Plan permission
to use, modify and distribute all 77 SVG files under the repository MIT
License without separate UI attribution in
[issue #159](https://github.com/Matysh/houseplan-card/issues/159#issuecomment-5454085168).
The reviewed source archive is `houseplan-furniture-custom-0.3.0.zip`, attached
to [issue #159](https://github.com/Matysh/houseplan-card/issues/159#issuecomment-5449707137),
with SHA-256
`9E969016EE3B4B4E3DB776FEC53C8B387B91368B118EB5E39911483DEF1B0953`.
The editable source is linked from `pack.json`. Generated TypeScript must
not be edited by hand; run `npm run furniture:generate` after changing this
directory.
+818
View File
@@ -0,0 +1,818 @@
{
"schema_version": 1,
"view_schema": 2,
"pack_id": "houseplan",
"pack_version": "0.3.0",
"title_ru": "Набор мебели: меню и план",
"title_en": "Furniture pack: menu and plan",
"author": "Sergey Matyushin (Matysh)",
"license": "MIT",
"source_url": "https://www.figma.com/design/cpGN8MhJGydwVOUzZv8dia/House-plan?node-id=271-95",
"menu_icons": [
{
"id": "air_conditioner",
"name_ru": "Кондиционер",
"name_en": "Air conditioner",
"group": "appliance",
"file": "svg/menu/air_conditioner.svg"
},
{
"id": "armchair",
"name_ru": "Кресла",
"name_en": "Armchairs",
"group": "furniture",
"file": "svg/menu/armchair.svg"
},
{
"id": "bathtub",
"name_ru": "Ванны",
"name_en": "Bathtubs",
"group": "sanitary",
"file": "svg/menu/bathtub.svg"
},
{
"id": "bed",
"name_ru": "Кровати",
"name_en": "Beds",
"group": "furniture",
"file": "svg/menu/bed.svg"
},
{
"id": "bidet",
"name_ru": "Биде",
"name_en": "Bidets",
"group": "sanitary",
"file": "svg/menu/bidet.svg"
},
{
"id": "boiler",
"name_ru": "Бойлер",
"name_en": "Water heater",
"group": "appliance",
"file": "svg/menu/boiler.svg"
},
{
"id": "chair",
"name_ru": "Стулья",
"name_en": "Chairs",
"group": "furniture",
"file": "svg/menu/chair.svg"
},
{
"id": "coffee_table",
"name_ru": "Журнальные столы",
"name_en": "Coffee tables",
"group": "furniture",
"file": "svg/menu/coffee_table.svg"
},
{
"id": "computer",
"name_ru": "Компьютер",
"name_en": "Computer",
"group": "appliance",
"file": "svg/menu/computer.svg"
},
{
"id": "cooktop",
"name_ru": "Варочные панели",
"name_en": "Cooktops",
"group": "appliance",
"file": "svg/menu/cooktop.svg"
},
{
"id": "dining_table",
"name_ru": "Обеденные столы",
"name_en": "Dining tables",
"group": "furniture",
"file": "svg/menu/dining_table.svg"
},
{
"id": "dishwasher",
"name_ru": "Посудомоечная машина",
"name_en": "Dishwasher",
"group": "appliance",
"file": "svg/menu/dishwasher.svg"
},
{
"id": "dryer",
"name_ru": "Сушильная машина",
"name_en": "Dryer",
"group": "appliance",
"file": "svg/menu/dryer.svg"
},
{
"id": "exercise",
"name_ru": "Тренажёр",
"name_en": "Exercise equipment",
"group": "other",
"file": "svg/menu/exercise.svg"
},
{
"id": "fireplace",
"name_ru": "Камин",
"name_en": "Fireplace",
"group": "other",
"file": "svg/menu/fireplace.svg"
},
{
"id": "fridge",
"name_ru": "Холодильник",
"name_en": "Refrigerator",
"group": "appliance",
"file": "svg/menu/fridge.svg"
},
{
"id": "hood",
"name_ru": "Вытяжка",
"name_en": "Range hood",
"group": "appliance",
"file": "svg/menu/hood.svg"
},
{
"id": "kitchen_cabinet",
"name_ru": "Кухонные шкафы",
"name_en": "Kitchen cabinets",
"group": "furniture",
"file": "svg/menu/kitchen_cabinet.svg"
},
{
"id": "kitchen_sink",
"name_ru": "Кухонные мойки",
"name_en": "Kitchen sinks",
"group": "sanitary",
"file": "svg/menu/kitchen_sink.svg"
},
{
"id": "nightstand",
"name_ru": "Тумбы",
"name_en": "Cabinets",
"group": "furniture",
"file": "svg/menu/nightstand.svg"
},
{
"id": "oven",
"name_ru": "Духовка",
"name_en": "Oven",
"group": "appliance",
"file": "svg/menu/oven.svg"
},
{
"id": "plant",
"name_ru": "Растение",
"name_en": "Plant",
"group": "other",
"file": "svg/menu/plant.svg"
},
{
"id": "rug",
"name_ru": "Ковер",
"name_en": "Rug",
"group": "other",
"file": "svg/menu/rug.svg"
},
{
"id": "shelving",
"name_ru": "Стеллажи",
"name_en": "Shelving",
"group": "furniture",
"file": "svg/menu/shelving.svg"
},
{
"id": "shower",
"name_ru": "Душ",
"name_en": "Shower",
"group": "sanitary",
"file": "svg/menu/shower.svg"
},
{
"id": "sink",
"name_ru": "Раковина",
"name_en": "Sink",
"group": "sanitary",
"file": "svg/menu/sink.svg"
},
{
"id": "sofa",
"name_ru": "Диваны",
"name_en": "Sofas",
"group": "furniture",
"file": "svg/menu/sofa.svg"
},
{
"id": "stairs",
"name_ru": "Лестница",
"name_en": "Stairs",
"group": "other",
"file": "svg/menu/stairs.svg"
},
{
"id": "toilet",
"name_ru": "Унитазы",
"name_en": "Toilets",
"group": "sanitary",
"file": "svg/menu/toilet.svg"
},
{
"id": "tv",
"name_ru": "Телевизоры",
"name_en": "Televisions",
"group": "appliance",
"file": "svg/menu/tv.svg"
},
{
"id": "wardrobe",
"name_ru": "Шкафы",
"name_en": "Wardrobes",
"group": "furniture",
"file": "svg/menu/wardrobe.svg"
},
{
"id": "washer",
"name_ru": "Стиральная машина",
"name_en": "Washing machine",
"group": "appliance",
"file": "svg/menu/washer.svg"
},
{
"id": "work_table",
"name_ru": "Рабочие столы",
"name_en": "Desks",
"group": "furniture",
"file": "svg/menu/work_table.svg"
}
],
"symbols": [
{
"id": "coffee_table",
"operation": "replace",
"name_ru": "Журнальный стол, прямоугольный",
"name_en": "Rectangular coffee table",
"group": "furniture",
"width_cm": 120,
"depth_cm": 60,
"back": "top",
"file": "svg/plan/coffee_table.svg",
"menu_icon": "coffee_table",
"notes": "Типовой размер; свободно стоящий предмет."
},
{
"id": "coffee_table_round",
"operation": "add",
"name_ru": "Журнальный стол, круглый",
"name_en": "Round coffee table",
"group": "furniture",
"width_cm": 80,
"depth_cm": 80,
"back": "top",
"file": "svg/plan/coffee_table_round.svg",
"menu_icon": "coffee_table",
"notes": "Круглый свободно стоящий предмет."
},
{
"id": "coffee_table_oval",
"operation": "add",
"name_ru": "Журнальный стол, овальный",
"name_en": "Oval coffee table",
"group": "furniture",
"width_cm": 120,
"depth_cm": 60,
"back": "top",
"file": "svg/plan/coffee_table_oval.svg",
"menu_icon": "coffee_table",
"notes": "Свободно стоящий предмет."
},
{
"id": "coffee_table_rounded",
"operation": "add",
"name_ru": "Журнальный стол, скруглённый",
"name_en": "Rounded coffee table",
"group": "furniture",
"width_cm": 120,
"depth_cm": 60,
"back": "top",
"file": "svg/plan/coffee_table_rounded.svg",
"menu_icon": "coffee_table",
"notes": "Прямоугольная форма со скруглёнными углами."
},
{
"id": "table_dining",
"operation": "replace",
"name_ru": "Обеденный стол, прямоугольный",
"name_en": "Rectangular dining table",
"group": "furniture",
"width_cm": 160,
"depth_cm": 90,
"back": "top",
"file": "svg/plan/table_dining.svg",
"menu_icon": "dining_table",
"notes": "Типовой стол на 4–6 мест."
},
{
"id": "table_round",
"operation": "replace",
"name_ru": "Обеденный стол, круглый",
"name_en": "Round dining table",
"group": "furniture",
"width_cm": 110,
"depth_cm": 110,
"back": "top",
"file": "svg/plan/table_round.svg",
"menu_icon": "dining_table",
"notes": "Круглый стол на 4 места."
},
{
"id": "table_dining_oval",
"operation": "add",
"name_ru": "Обеденный стол, овальный",
"name_en": "Oval dining table",
"group": "furniture",
"width_cm": 180,
"depth_cm": 100,
"back": "top",
"file": "svg/plan/table_dining_oval.svg",
"menu_icon": "dining_table",
"notes": "Овальный стол на 6 мест."
},
{
"id": "table_dining_rounded",
"operation": "add",
"name_ru": "Обеденный стол, скруглённый",
"name_en": "Rounded dining table",
"group": "furniture",
"width_cm": 160,
"depth_cm": 90,
"back": "top",
"file": "svg/plan/table_dining_rounded.svg",
"menu_icon": "dining_table",
"notes": "Прямоугольная форма со скруглёнными углами."
},
{
"id": "desk",
"operation": "replace",
"name_ru": "Рабочий стол, прямоугольный",
"name_en": "Rectangular desk",
"group": "furniture",
"width_cm": 140,
"depth_cm": 70,
"back": "top",
"file": "svg/plan/desk.svg",
"menu_icon": "work_table",
"notes": "BACK — длинная сторона у стены."
},
{
"id": "desk_corner",
"operation": "add",
"name_ru": "Рабочий стол, угловой",
"name_en": "Corner desk",
"group": "furniture",
"width_cm": 160,
"depth_cm": 160,
"back": "top",
"file": "svg/plan/desk_corner.svg",
"menu_icon": "work_table",
"notes": "Угловой стол; основная задняя сторона направлена вверх."
},
{
"id": "chair",
"operation": "replace",
"name_ru": "Стул",
"name_en": "Chair",
"group": "furniture",
"width_cm": 50,
"depth_cm": 50,
"back": "top",
"file": "svg/plan/chair.svg",
"menu_icon": "chair",
"notes": "Спинка находится сверху."
},
{
"id": "chair_bar",
"operation": "add",
"name_ru": "Барный стул",
"name_en": "Bar stool",
"group": "furniture",
"width_cm": 45,
"depth_cm": 48,
"back": "top",
"file": "svg/plan/chair_bar.svg",
"menu_icon": "chair",
"notes": "Спинка находится сверху."
},
{
"id": "armchair",
"operation": "replace",
"name_ru": "Кресло мягкое",
"name_en": "Soft armchair",
"group": "furniture",
"width_cm": 90,
"depth_cm": 90,
"back": "top",
"file": "svg/plan/armchair.svg",
"menu_icon": "armchair",
"notes": "Спинка находится сверху."
},
{
"id": "armchair_office",
"operation": "add",
"name_ru": "Кресло офисное",
"name_en": "Office chair",
"group": "furniture",
"width_cm": 65,
"depth_cm": 65,
"back": "top",
"file": "svg/plan/armchair_office.svg",
"menu_icon": "armchair",
"notes": "Спинка находится сверху."
},
{
"id": "sofa",
"operation": "replace",
"name_ru": "Диван двухместный",
"name_en": "Two-seat sofa",
"group": "furniture",
"width_cm": 180,
"depth_cm": 90,
"back": "top",
"file": "svg/plan/sofa.svg",
"menu_icon": "sofa",
"notes": "Спинка находится сверху."
},
{
"id": "sofa_three_seat",
"operation": "add",
"name_ru": "Диван трёхместный",
"name_en": "Three-seat sofa",
"group": "furniture",
"width_cm": 240,
"depth_cm": 90,
"back": "top",
"file": "svg/plan/sofa_three_seat.svg",
"menu_icon": "sofa",
"notes": "Спинка находится сверху."
},
{
"id": "sofa_corner_right",
"operation": "add",
"name_ru": "Угловой диван, правый",
"name_en": "Right sectional sofa",
"group": "furniture",
"width_cm": 260,
"depth_cm": 170,
"back": "top",
"file": "svg/plan/sofa_corner_right.svg",
"menu_icon": "sofa",
"notes": "Шезлонг справа при взгляде сверху; спинка сверху."
},
{
"id": "bed_single",
"operation": "replace",
"name_ru": "Кровать односпальная",
"name_en": "Single bed",
"group": "furniture",
"width_cm": 90,
"depth_cm": 200,
"back": "top",
"file": "svg/plan/bed_single.svg",
"menu_icon": "bed",
"notes": "Узкая кровать с одной подушкой; изголовье сверху."
},
{
"id": "bed_double",
"operation": "replace",
"name_ru": "Кровать двуспальная",
"name_en": "Double bed",
"group": "furniture",
"width_cm": 160,
"depth_cm": 200,
"back": "top",
"file": "svg/plan/bed_double.svg",
"menu_icon": "bed",
"notes": "Широкая кровать с двумя подушками; изголовье сверху."
},
{
"id": "nightstand",
"operation": "replace",
"name_ru": "Тумба прикроватная",
"name_en": "Bedside table",
"group": "furniture",
"width_cm": 50,
"depth_cm": 40,
"back": "top",
"file": "svg/plan/nightstand.svg",
"menu_icon": "nightstand",
"notes": "Задняя сторона сверху."
},
{
"id": "cabinet_tv",
"operation": "add",
"name_ru": "Тумба под телевизор",
"name_en": "TV cabinet",
"group": "furniture",
"width_cm": 140,
"depth_cm": 45,
"back": "top",
"file": "svg/plan/cabinet_tv.svg",
"menu_icon": "nightstand",
"notes": "Задняя длинная сторона сверху."
},
{
"id": "cabinet_shoe",
"operation": "add",
"name_ru": "Тумба для обуви",
"name_en": "Shoe cabinet",
"group": "furniture",
"width_cm": 80,
"depth_cm": 35,
"back": "top",
"file": "svg/plan/cabinet_shoe.svg",
"menu_icon": "nightstand",
"notes": "Задняя сторона сверху."
},
{
"id": "cabinet_sink",
"operation": "add",
"name_ru": "Тумба под раковину",
"name_en": "Sink cabinet",
"group": "furniture",
"width_cm": 80,
"depth_cm": 50,
"back": "top",
"file": "svg/plan/cabinet_sink.svg",
"menu_icon": "nightstand",
"notes": "Задняя сторона сверху."
},
{
"id": "bookshelf",
"operation": "replace",
"name_ru": "Шкаф книжный",
"name_en": "Bookcase",
"group": "furniture",
"width_cm": 100,
"depth_cm": 35,
"back": "top",
"file": "svg/plan/bookshelf.svg",
"menu_icon": "wardrobe",
"notes": "Задняя длинная сторона сверху."
},
{
"id": "wall_unit",
"operation": "add",
"name_ru": "Шкаф-стенка",
"name_en": "Wall unit",
"group": "furniture",
"width_cm": 240,
"depth_cm": 45,
"back": "top",
"file": "svg/plan/wall_unit.svg",
"menu_icon": "wardrobe",
"notes": "Задняя длинная сторона сверху."
},
{
"id": "wardrobe",
"operation": "replace",
"name_ru": "Шкаф гардеробный",
"name_en": "Wardrobe",
"group": "furniture",
"width_cm": 180,
"depth_cm": 60,
"back": "top",
"file": "svg/plan/wardrobe.svg",
"menu_icon": "wardrobe",
"notes": "Задняя длинная сторона сверху."
},
{
"id": "kitchen_floor",
"operation": "add",
"name_ru": "Кухонный напольный модуль",
"name_en": "Kitchen floor module",
"group": "furniture",
"width_cm": 60,
"depth_cm": 60,
"back": "top",
"file": "svg/plan/kitchen_floor.svg",
"menu_icon": "kitchen_cabinet",
"notes": "Задняя сторона сверху."
},
{
"id": "kitchen_floor_corner",
"operation": "add",
"name_ru": "Кухонный напольный угловой модуль",
"name_en": "Kitchen floor corner module",
"group": "furniture",
"width_cm": 90,
"depth_cm": 90,
"back": "top",
"file": "svg/plan/kitchen_floor_corner.svg",
"menu_icon": "kitchen_cabinet",
"notes": "Угловой модуль; основной BACK сверху."
},
{
"id": "kitchen_wall",
"operation": "add",
"name_ru": "Кухонный навесной модуль",
"name_en": "Kitchen wall module",
"group": "furniture",
"width_cm": 60,
"depth_cm": 35,
"back": "top",
"file": "svg/plan/kitchen_wall.svg",
"menu_icon": "kitchen_cabinet",
"notes": "Задняя сторона сверху."
},
{
"id": "kitchen_wall_corner",
"operation": "add",
"name_ru": "Кухонный навесной угловой модуль",
"name_en": "Kitchen wall corner module",
"group": "furniture",
"width_cm": 60,
"depth_cm": 60,
"back": "top",
"file": "svg/plan/kitchen_wall_corner.svg",
"menu_icon": "kitchen_cabinet",
"notes": "Угловой модуль; основной BACK сверху."
},
{
"id": "shelf_floor",
"operation": "add",
"name_ru": "Стеллаж напольный",
"name_en": "Floor shelving unit",
"group": "furniture",
"width_cm": 100,
"depth_cm": 35,
"back": "top",
"file": "svg/plan/shelf_floor.svg",
"menu_icon": "shelving",
"notes": "Задняя длинная сторона сверху."
},
{
"id": "shelf_wall",
"operation": "add",
"name_ru": "Полка настенная",
"name_en": "Wall shelf",
"group": "furniture",
"width_cm": 100,
"depth_cm": 25,
"back": "top",
"file": "svg/plan/shelf_wall.svg",
"menu_icon": "shelving",
"notes": "Задняя длинная сторона сверху."
},
{
"id": "cooktop_two",
"operation": "add",
"name_ru": "Варочная панель, 2 конфорки",
"name_en": "Two-burner cooktop",
"group": "appliance",
"width_cm": 30,
"depth_cm": 50,
"back": "top",
"file": "svg/plan/cooktop_two.svg",
"menu_icon": "cooktop",
"notes": "Панель ориентирована управляющей стороной вниз."
},
{
"id": "stove",
"operation": "replace",
"name_ru": "Варочная панель, 4 конфорки",
"name_en": "Four-burner cooktop",
"group": "appliance",
"width_cm": 60,
"depth_cm": 60,
"back": "top",
"file": "svg/plan/stove.svg",
"menu_icon": "cooktop",
"notes": "Панель ориентирована управляющей стороной вниз."
},
{
"id": "tv",
"operation": "replace",
"name_ru": "Телевизор на подставке",
"name_en": "TV on stand",
"group": "appliance",
"width_cm": 120,
"depth_cm": 28,
"back": "top",
"file": "svg/plan/tv.svg",
"menu_icon": "tv",
"notes": "Задняя сторона экрана сверху."
},
{
"id": "tv_wall",
"operation": "add",
"name_ru": "Телевизор на кронштейне",
"name_en": "Wall-mounted TV",
"group": "appliance",
"width_cm": 120,
"depth_cm": 15,
"back": "top",
"file": "svg/plan/tv_wall.svg",
"menu_icon": "tv",
"notes": "Сторона крепления к стене сверху."
},
{
"id": "toilet",
"operation": "replace",
"name_ru": "Унитаз напольный",
"name_en": "Floor-standing toilet",
"group": "sanitary",
"width_cm": 40,
"depth_cm": 70,
"back": "top",
"file": "svg/plan/toilet.svg",
"menu_icon": "toilet",
"notes": "Сторона подключения к стене сверху."
},
{
"id": "toilet_built_in",
"operation": "add",
"name_ru": "Унитаз встроенный",
"name_en": "Built-in toilet",
"group": "sanitary",
"width_cm": 40,
"depth_cm": 55,
"back": "top",
"file": "svg/plan/toilet_built_in.svg",
"menu_icon": "toilet",
"notes": "Инсталляция находится сверху."
},
{
"id": "bathtub",
"operation": "replace",
"name_ru": "Ванна прямоугольная",
"name_en": "Rectangular bathtub",
"group": "sanitary",
"width_cm": 170,
"depth_cm": 75,
"back": "top",
"file": "svg/plan/bathtub.svg",
"menu_icon": "bathtub",
"notes": "Длинная задняя сторона сверху."
},
{
"id": "bathtub_corner",
"operation": "add",
"name_ru": "Ванна угловая",
"name_en": "Corner bathtub",
"group": "sanitary",
"width_cm": 140,
"depth_cm": 140,
"back": "top",
"file": "svg/plan/bathtub_corner.svg",
"menu_icon": "bathtub",
"notes": "Угловая ванна; основной BACK сверху."
},
{
"id": "bidet",
"operation": "replace",
"name_ru": "Биде напольное",
"name_en": "Floor-standing bidet",
"group": "sanitary",
"width_cm": 40,
"depth_cm": 60,
"back": "top",
"file": "svg/plan/bidet.svg",
"menu_icon": "bidet",
"notes": "Сторона подключения к стене сверху."
},
{
"id": "bidet_built_in",
"operation": "add",
"name_ru": "Биде встроенное",
"name_en": "Built-in bidet",
"group": "sanitary",
"width_cm": 40,
"depth_cm": 50,
"back": "top",
"file": "svg/plan/bidet_built_in.svg",
"menu_icon": "bidet",
"notes": "Инсталляция находится сверху."
},
{
"id": "kitchen_sink",
"operation": "replace",
"name_ru": "Кухонная мойка одинарная",
"name_en": "Single kitchen sink",
"group": "sanitary",
"width_cm": 60,
"depth_cm": 50,
"back": "top",
"file": "svg/plan/kitchen_sink.svg",
"menu_icon": "kitchen_sink",
"notes": "Задняя сторона столешницы сверху."
},
{
"id": "kitchen_sink_double",
"operation": "add",
"name_ru": "Кухонная мойка двойная",
"name_en": "Double kitchen sink",
"group": "sanitary",
"width_cm": 90,
"depth_cm": 50,
"back": "top",
"file": "svg/plan/kitchen_sink_double.svg",
"menu_icon": "kitchen_sink",
"notes": "Задняя сторона столешницы сверху."
}
]
}
@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 110 110">
<g>
<path d="M20.5 39h69a2.5 2.5 0 0 1 2.5 2.5V69a2.5 2.5 0 0 1-2.5 2.5h-69A2.5 2.5 0 0 1 18 69V41.5a2.5 2.5 0 0 1 2.5-2.5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M25 63a2.5 2.5 0 0 1 2.5-2.5h55A2.5 2.5 0 0 1 85 63v8.5H25z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M25 63a2.5 2.5 0 0 1 2.5-2.5h55A2.5 2.5 0 0 1 85 63v3.5H25z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 661 B

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 110 110">
<path d="M42.5 29h25a10 10 0 0 1 10 10v26.5a10 10 0 0 1-10 10h-25a10 10 0 0 1-10-10V39a10 10 0 0 1 10-10" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M77.5 48.5a6 6 0 0 1 2 11.657V71.5a4 4 0 0 1-4 4H34a4 4 0 0 1-4-4V60.157A6.001 6.001 0 0 1 32 48.5a6 6 0 0 1 5.419 3.424A4 4 0 0 1 38 54v7h33.5v-6.5q0-.107.005-.213A6 6 0 0 1 77.5 48.5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M42.75 57h24.5A4.75 4.75 0 0 1 72 61.75a4.75 4.75 0 0 1-4.75 4.75h-24.5A4.75 4.75 0 0 1 38 61.75 4.75 4.75 0 0 1 42.75 57M36.5 75.5h8l-1.674 5.441A1.5 1.5 0 0 1 41.392 82H38a1.5 1.5 0 0 1-1.5-1.5zm36 0h-8l1.674 5.441A1.5 1.5 0 0 0 67.608 82H71a1.5 1.5 0 0 0 1.5-1.5z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 960 B

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 110 110">
<g>
<path d="M28 75h8l-1.674 5.441a1.5 1.5 0 0 1-1.434 1.059H29.5A1.5 1.5 0 0 1 28 80zm53 0h-8l1.674 5.441a1.5 1.5 0 0 0 1.434 1.059H79.5A1.5 1.5 0 0 0 81 80zM19.5 46.5H90a2.5 2.5 0 0 1 2.5 2.5 2.5 2.5 0 0 1-2.5 2.5H19.5A2.5 2.5 0 0 1 17 49a2.5 2.5 0 0 1 2.5-2.5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M19.5 51.5H90L86.633 68a10 10 0 0 1-9.798 8h-44.17a10 10 0 0 1-9.798-8zM30 36v-2a5 5 0 1 0-10 0v12.5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 666 B

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 110 110">
<g>
<path d="M16 75.5h8l-1.674 5.441A1.5 1.5 0 0 1 20.892 82H17.5a1.5 1.5 0 0 1-1.5-1.5zm78.5 0h-8l1.674 5.441A1.5 1.5 0 0 0 89.608 82H93a1.5 1.5 0 0 0 1.5-1.5zM25 29h60.5a2.5 2.5 0 0 1 2.5 2.5V68a2.5 2.5 0 0 1-2.5 2.5H25a2.5 2.5 0 0 1-2.5-2.5V31.5A2.5 2.5 0 0 1 25 29" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16 59.5c0-5.523 4.477-10 10-10h58.5c5.523 0 10 4.477 10 10v6H16zm-1 6h80.5a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1H15a1 1 0 0 1-1-1v-8a1 1 0 0 1 1-1m15.5-20a5 5 0 0 1 5-5h11a5 5 0 0 1 5 5v4h-21zm28.5 0a5 5 0 0 1 5-5h11a5 5 0 0 1 5 5v4H59z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 801 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 110 110">
<path d="M36 57.5c0 5.523 4.477 10 10 10h18.5c5.523 0 10-4.477 10-10V53H36zm8.5 22a1 1 0 0 0 1 1H65a1 1 0 0 0 1-1v-12H44.5zm-8.5-32a1 1 0 0 1 1-1h36.5a1 1 0 0 1 1 1V53H36zM50.5 41a1 1 0 0 1 1-1H59a1 1 0 0 1 1 1v5.5h-9.5zM53 30a1 1 0 0 1 1-1h2.5a1 1 0 0 1 1 1v10H53zm-4 6a1 1 0 0 1-1-1v-2.5a1 1 0 0 1 1-1h4V36z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 485 B

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 110 110">
<g>
<path d="M46.5 81.5h5V86a1 1 0 0 1-1 1h-3a1 1 0 0 1-1-1zm12.5 0h5V86a1 1 0 0 1-1 1h-3a1 1 0 0 1-1-1zM44 23h22.5a8 8 0 0 1 8 8v42.5a8 8 0 0 1-8 8H44a8 8 0 0 1-8-8V31a8 8 0 0 1 8-8" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M36 31a8 8 0 0 1 8-8h22.5a8 8 0 0 1 8 8v42H36z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M48.5 42.5a7 7 0 1 0 14 0 7 7 0 1 0-14 0m6.5 0 3.5-3.5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 703 B

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 110 110">
<g>
<path d="M40.5 39.5C40.5 32.596 46.096 27 53 27h4c6.904 0 12.5 5.596 12.5 12.5v13a5 5 0 0 1-5 5h-19a5 5 0 0 1-5-5z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M41.25 54h27.5A3.25 3.25 0 0 1 72 57.25a3.25 3.25 0 0 1-3.25 3.25h-27.5A3.25 3.25 0 0 1 38 57.25 3.25 3.25 0 0 1 41.25 54m28.25 6.5H64l1.88 21.63a1.5 1.5 0 0 0 1.495 1.37H68a1.5 1.5 0 0 0 1.5-1.5zm-29 0H46l-1.88 21.63a1.5 1.5 0 0 1-1.495 1.37H42a1.5 1.5 0 0 1-1.5-1.5z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 690 B

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 110 110">
<g>
<path d="M25 51h5.238l-1.534 15.646A1.5 1.5 0 0 1 27.211 68H26.5a1.5 1.5 0 0 1-1.5-1.5zm60.238 0H80l1.534 15.646A1.5 1.5 0 0 0 83.027 68h.711a1.5 1.5 0 0 0 1.5-1.5z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M24.34 43.485A2.5 2.5 0 0 1 26.625 42h56.75a2.5 2.5 0 0 1 2.285 1.485l2.715 6.109A1 1 0 0 1 87.461 51H22.54a1 1 0 0 1-.914-1.406z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M87.5 51h-66v3a1 1 0 0 0 1 1h64a1 1 0 0 0 1-1z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 764 B

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 110 110">
<path d="M24.5 38h40a2.5 2.5 0 0 1 2.5 2.5v22a2.5 2.5 0 0 1-2.5 2.5h-40a2.5 2.5 0 0 1-2.5-2.5v-22a2.5 2.5 0 0 1 2.5-2.5m50 0h11a2.5 2.5 0 0 1 2.5 2.5v29a2.5 2.5 0 0 1-2.5 2.5h-11a2.5 2.5 0 0 1-2.5-2.5v-29a2.5 2.5 0 0 1 2.5-2.5M43 65h5v7h-5zm-6 7h16m23-12h8m-8-17h8" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M35 56V46h5m-5 7h5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M39.5 53a3.5 3.5 0 1 0 0-7m15.036 8.536a5 5 0 1 1 0-7.071" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 745 B

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 110 110">
<g>
<path d="M27.5 29h55a2.5 2.5 0 0 1 2.5 2.5v47a2.5 2.5 0 0 1-2.5 2.5h-55a2.5 2.5 0 0 1-2.5-2.5v-47a2.5 2.5 0 0 1 2.5-2.5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M33.5 66.5a8 8 0 1 0 16 0 8 8 0 1 0-16 0m26.5-23a8 8 0 1 0 16 0 8 8 0 1 0-16 0m3 23a5 5 0 1 0 10 0 5 5 0 1 0-10 0m-29.5-23a8 8 0 1 0 16 0 8 8 0 1 0-16 0" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 579 B

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 110 110">
<g>
<path d="M83.5 43H78l6.742 30.82A1.5 1.5 0 0 0 86.207 75h.54a1.5 1.5 0 0 0 1.482-1.732zm-56 0H33l-6.742 30.82A1.5 1.5 0 0 1 24.793 75h-.54a1.5 1.5 0 0 1-1.482-1.732zm-2.62-7.406A2.5 2.5 0 0 1 27.21 34h56.58a2.5 2.5 0 0 1 2.33 1.594l2.35 6.044A1 1 0 0 1 87.538 43H23.462a1 1 0 0 1-.932-1.362z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M88.5 43h-66v3a1 1 0 0 0 1 1h64a1 1 0 0 0 1-1z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 645 B

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 110 110">
<g>
<path d="M31.5 25h47a2.5 2.5 0 0 1 2.5 2.5v55a2.5 2.5 0 0 1-2.5 2.5h-47a2.5 2.5 0 0 1-2.5-2.5v-55a2.5 2.5 0 0 1 2.5-2.5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M31.5 37.5h47A2.5 2.5 0 0 1 81 40v36a2.5 2.5 0 0 1-2.5 2.5h-47A2.5 2.5 0 0 1 29 76V40a2.5 2.5 0 0 1 2.5-2.5M43 42h24m-1.5-11a2 2 0 1 0 4 0 2 2 0 1 0-4 0m7.5 0a2 2 0 1 0 4 0 2 2 0 1 0-4 0m-37.25 0h9.5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 626 B

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 110 110">
<g>
<path d="M31.5 25h47a2.5 2.5 0 0 1 2.5 2.5v55a2.5 2.5 0 0 1-2.5 2.5h-47a2.5 2.5 0 0 1-2.5-2.5v-55a2.5 2.5 0 0 1 2.5-2.5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M31.5 37.5h47A2.5 2.5 0 0 1 81 40v42.5a2.5 2.5 0 0 1-2.5 2.5h-47a2.5 2.5 0 0 1-2.5-2.5V40a2.5 2.5 0 0 1 2.5-2.5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M38 60.5a17 17 0 1 0 34 0 17 17 0 1 0-34 0" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M43.5 60.5a11.5 11.5 0 1 0 23 0 11.5 11.5 0 1 0-23 0m22-29.5a2 2 0 1 0 4 0 2 2 0 1 0-4 0m7.5 0a2 2 0 1 0 4 0 2 2 0 1 0-4 0m-37.25 0h9.5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M55.994 55.5s-1.329 1.81-1.479 3.143c-.198 1.761 1.599 2.554 1.48 4.321-.1 1.479-1.48 3.536-1.48 3.536m5.979-11s-1.329 1.81-1.479 3.143c-.198 1.761 1.599 2.554 1.48 4.321-.1 1.479-1.48 3.536-1.48 3.536m-8.519-11s-.886 1.81-.986 3.143c-.132 1.761 1.066 2.554.986 4.321-.067 1.479-.986 3.536-.986 3.536" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 110 110">
<g>
<path d="m60.752 62.03-5.379-2.312 11.066-25.75a1.5 1.5 0 0 1 1.97-.786l2.624 1.127a1.5 1.5 0 0 1 .786 1.97zm-18.648-4.512-4.353 1.189-4.474-16.382a1.5 1.5 0 0 1 1.052-1.842l1.459-.398a1.5 1.5 0 0 1 1.842 1.052z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M41.297 38.173a2.215 2.215 0 0 1 0 4.43H29.781a2.215 2.215 0 0 1 0-4.43zm33.219 38.091a2.657 2.657 0 1 1 0 5.315H28.453a2.657 2.657 0 1 1 0-5.315zm8.641-51.366a1.795 1.795 0 0 1 .68 2.41l-3.03 5.59a5 5 0 0 1-4.396 2.618H65.657a1.772 1.772 0 1 1 0-3.544h9.61a3 3 0 0 0 2.598-1.5l2.84-4.917a1.795 1.795 0 0 1 2.452-.657M29.339 65.787c0-6.699 6.457-11.503 12.873-9.579l24.291 7.288a10 10 0 0 1 7.127 9.578v3.19H29.339zm0 15.852h7.086l-1.394 3.486a1.5 1.5 0 0 1-1.393.943h-2.8a1.5 1.5 0 0 1-1.5-1.5zm44.291 0h-7.087l1.395 3.486a1.5 1.5 0 0 0 1.392.943h2.8a1.5 1.5 0 0 0 1.5-1.5z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M35.54 66.077a4.872 4.872 0 1 0 9.743 0 4.872 4.872 0 1 0-9.744 0" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 110 110">
<g>
<path d="M26.672 34.425h55.99v39.47h-55.99Z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M23 74.894a1 1 0 0 1 1-1h61.334a1 1 0 0 1 1 1v5.802a1 1 0 0 1-1 1H24a1 1 0 0 1-1-1zM23 29a1 1 0 0 1 1-1h61.334a1 1 0 0 1 1 1v4.425a1 1 0 0 1-1 1H24a1 1 0 0 1-1-1zm15.145 21.44a5 5 0 0 1 5-5h23.043a5 5 0 0 1 5 5v23.454H38.145z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M44.57 65.633a2.754 2.754 0 0 1 2.754-2.753H62.01v5.507H47.324a2.754 2.754 0 0 1-2.754-2.754m20.194 5.507a2.754 2.754 0 0 0-2.754-2.753H47.324v5.507H62.01a2.754 2.754 0 0 0 2.754-2.754" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M44.57 71.14a2.754 2.754 0 1 0 5.508 0 2.754 2.754 0 1 0-5.508 0m14.688-5.507a2.754 2.754 0 1 0 5.507 0 2.754 2.754 0 1 0-5.507 0" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 110 110">
<g>
<path d="M35.5 18H75a2.5 2.5 0 0 1 2.5 2.5v64A2.5 2.5 0 0 1 75 87H35.5a2.5 2.5 0 0 1-2.5-2.5v-64a2.5 2.5 0 0 1 2.5-2.5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M33 46.5h44.5v38A2.5 2.5 0 0 1 75 87H35.5a2.5 2.5 0 0 1-2.5-2.5zm2.5 44a1 1 0 0 0 1 1h37a1 1 0 0 0 1-1V87h-39zm3-49.5v-9.5m0 30.5v-9.5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 560 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 110 110">
<path d="M44.308 57h21.384L83 69H27zM44 37.5a2.5 2.5 0 0 1 2.5-2.5h17a2.5 2.5 0 0 1 2.5 2.5V57H44zM80.5 74h-51a2.5 2.5 0 0 1-2.5-2.5V70a1 1 0 0 1 1-1h54a1 1 0 0 1 1 1v1.5a2.5 2.5 0 0 1-2.5 2.5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 368 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 110 110">
<path d="M38 88a1 1 0 0 0 1 1h32a1 1 0 0 0 1-1v-3H38zm17-68h16.5a2.5 2.5 0 0 1 2.5 2.5v17a2.5 2.5 0 0 1-2.5 2.5H55zm-19 2.5a2.5 2.5 0 0 1 2.5-2.5H55v22H38.5a2.5 2.5 0 0 1-2.5-2.5zM59 33v5m-8-5v5m4 47h16.5a2.5 2.5 0 0 0 2.5-2.5v-17a2.5 2.5 0 0 0-2.5-2.5H55zm-19-2.5a2.5 2.5 0 0 0 2.5 2.5H55V63H38.5a2.5 2.5 0 0 0-2.5 2.5zM67 67h-5m-14 0h-5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 514 B

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 110 110">
<g>
<path d="M21.5 84h68a2.5 2.5 0 0 0 2.5-2.5v-35a2.5 2.5 0 0 0-2.5-2.5h-68a2.5 2.5 0 0 0-2.5 2.5v35a2.5 2.5 0 0 0 2.5 2.5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M26 78h26.5a1 1 0 0 0 1-1V51a1 1 0 0 0-1-1H26a1 1 0 0 0-1 1v26a1 1 0 0 0 1 1m23-24v5m9.5 19H85a1 1 0 0 0 1-1V51a1 1 0 0 0-1-1H58.5a1 1 0 0 0-1 1v26a1 1 0 0 0 1 1m-6-39.5a1 1 0 0 0-1-1H44a1 1 0 0 0-1 1V44h9.5zM58 33v-2a5 5 0 1 0-10 0v6.5M62 54v5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 671 B

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 110 110">
<g>
<path d="M76.5 73.5h-8l1.674 5.441A1.5 1.5 0 0 0 71.608 80H75a1.5 1.5 0 0 0 1.5-1.5zm-42.5 0h8l-1.674 5.441A1.5 1.5 0 0 1 38.892 80H35.5a1.5 1.5 0 0 1-1.5-1.5z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M34.5 30.5h41A2.5 2.5 0 0 1 78 33v40.5a2.5 2.5 0 0 1-2.5 2.5h-41a2.5 2.5 0 0 1-2.5-2.5V33a2.5 2.5 0 0 1 2.5-2.5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M32 39.5h46v18H32Zm18.5 9H60M50.5 66H60" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 734 B

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 110 110">
<g>
<path d="M31.5 25h47a2.5 2.5 0 0 1 2.5 2.5v55a2.5 2.5 0 0 1-2.5 2.5h-47a2.5 2.5 0 0 1-2.5-2.5v-55a2.5 2.5 0 0 1 2.5-2.5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M31.5 37.5h47A2.5 2.5 0 0 1 81 40v36a2.5 2.5 0 0 1-2.5 2.5h-47A2.5 2.5 0 0 1 29 76V40a2.5 2.5 0 0 1 2.5-2.5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M40.5 47h29a2.5 2.5 0 0 1 2.5 2.5v20a2.5 2.5 0 0 1-2.5 2.5h-29a2.5 2.5 0 0 1-2.5-2.5v-20a2.5 2.5 0 0 1 2.5-2.5m25-16a2 2 0 1 0 4 0 2 2 0 1 0-4 0m7.5 0a2 2 0 1 0 4 0 2 2 0 1 0-4 0m-37.25 0h9.5M43 42h24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 851 B

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 110 110">
<g>
<path d="M45.952 75.849a2.5 2.5 0 0 0 2.485 2.231H63.43a2.5 2.5 0 0 0 2.485-2.231l1.571-14.532H44.381zM60.236 44.78C58.423 36.174 66.805 29.605 77 31.87c0 8.155-8.609 16.763-16.763 12.912m-9.761 5.147c1.517-8.665-7.085-14.943-17.195-12.33.28 8.15 9.178 16.458 17.195 12.33" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M55.706 55.88v-2.944m0 0c1.133-5.89 7.09-10.956 12.912-15.404M55.706 52.936c-4.938-4.146-7.982-6.16-14.044-9.061m1.453 12.005h25.636a1 1 0 0 1 1 1v3.437a1 1 0 0 1-1 1H43.115a1 1 0 0 1-1-1v-3.436a1 1 0 0 1 1-1" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 788 B

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 110 110">
<g>
<path d="M90.27 33.874h-5.622m-59.028 0H20m70.27 5.152h-5.622m-59.028 0H20m70.27 5.154h-5.622m-59.028 0H20m70.27 5.152h-5.622m-59.028 0H20m70.27 5.153h-5.622m-59.028 0H20m70.27 5.153h-5.622m-59.028 0H20m70.27 5.153h-5.622m-59.028 0H20m70.27 5.153h-5.622m-59.028 0H20m70.27 5.153h-5.622m-59.028 0H20M28.12 32h54.025a2.5 2.5 0 0 1 2.5 2.5v40.439a2.5 2.5 0 0 1-2.5 2.5H28.12a2.5 2.5 0 0 1-2.5-2.5V34.5a2.5 2.5 0 0 1 2.5-2.5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M25.62 34.5a2.5 2.5 0 0 1 2.5-2.5h1.717v45.439H28.12a2.5 2.5 0 0 1-2.5-2.5zm59.026 0a2.5 2.5 0 0 0-2.5-2.5H80.43v45.439h1.716a2.5 2.5 0 0 0 2.5-2.5zM37.8 54.485l17.801-8.432 17.801 8.432-17.8 8.432z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 926 B

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 110 110">
<g>
<path d="M28 58.5h53V39H28Z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M64 58.5h5V47a1 1 0 0 0-1-1h-3a1 1 0 0 0-1 1zM52.5 39h-5V27.5a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1zm-12 0h-5V27.5a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1zm-5 29.5V71a1 1 0 0 1 1 1H49a1 1 0 0 1 1-1v-2.5a1 1 0 0 1-1-1H36.5a1 1 0 0 1-1 1m33.5-10h7v-14a1 1 0 0 0-1-1h-5a1 1 0 0 0-1 1zM47.5 39h-7V25a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1zm-12 34v4a1 1 0 0 1 1 1h14a1 1 0 0 1 1-1v-4a1 1 0 0 1-1-1h-14a1 1 0 0 1-1 1M28 78h53v7H28Z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M28 89.5V23a2.5 2.5 0 0 1 2.5-2.5h48A2.5 2.5 0 0 1 81 23v66.5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 911 B

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 110 110">
<g>
<path d="M31 84h49a1 1 0 0 1 1 1v3a1 1 0 0 1-1 1H31a1 1 0 0 1-1-1v-3a1 1 0 0 1 1-1m31.5-47.6v-4.686a11.7 11.7 0 0 0-3.441-8.283A11.77 11.77 0 0 0 50.75 20a11.77 11.77 0 0 0-8.309 3.431A11.7 11.7 0 0 0 39 31.714V84" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M72 39.25a10.25 10.25 0 1 0-20.5 0zm-17.25 5v2.5m7.25-2.5v2.5m7.25-2.5v2.5m-14.5 3.75V53M62 50.5V53m7.25-2.5V53m-14.5 3.75v2.5m7.25-2.5v2.5m7.25-2.5v2.5M54.75 63v2.5M62 63v2.5m7.25-2.5v2.5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 709 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 110 110">
<path d="M30 57c0 5.523 4.477 10 10 10h29.5c5.523 0 10-4.477 10-10v-6.5A2.5 2.5 0 0 0 77 48H32.5a2.5 2.5 0 0 0-2.5 2.5zm14 22a1 1 0 0 0 1 1h19.5a1 1 0 0 0 1-1V67H44zm6-36.5a1 1 0 0 1 1-1h7.5a1 1 0 0 1 1 1V48H50zM45 37v-2a5 5 0 1 1 10 0v6.5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 415 B

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 110 110">
<path d="M59.5 29h24a5 5 0 0 1 5 5v32a5 5 0 0 1-5 5h-24a5 5 0 0 1-5-5V34a5 5 0 0 1 5-5m-34 0h24a5 5 0 0 1 5 5v32a5 5 0 0 1-5 5h-24a5 5 0 0 1-5-5V34a5 5 0 0 1 5-5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M18.5 44a5 5 0 0 1 5 5v12H86V49a5 5 0 0 1 5-5h.5a5 5 0 0 1 5 5v21.5a5 5 0 0 1-5 5H18a5 5 0 0 1-5-5V49a5 5 0 0 1 5-5z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M25.25 57h24.5a4.75 4.75 0 0 1 4.75 4.75 4.75 4.75 0 0 1-4.75 4.75h-24.5a4.75 4.75 0 0 1-4.75-4.75A4.75 4.75 0 0 1 25.25 57m34 0h24.5a4.75 4.75 0 0 1 4.75 4.75 4.75 4.75 0 0 1-4.75 4.75h-24.5a4.75 4.75 0 0 1-4.75-4.75A4.75 4.75 0 0 1 59.25 57M18.5 75.5h8l-1.674 5.441A1.5 1.5 0 0 1 23.392 82H20a1.5 1.5 0 0 1-1.5-1.5zm72 0h-8l1.674 5.441A1.5 1.5 0 0 0 85.608 82H89a1.5 1.5 0 0 0 1.5-1.5z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 110 110">
<g>
<path d="M73.47 46.365v-17.62m-10.571 25.55V36.674M52.326 61.783V45.484M42.193 69.273v-15.86M28.979 77.761a1 1 0 0 1 1-1h53.624v6.489a1 1 0 0 1-1 1H29.98a1 1 0 0 1-1-1z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M26.777 57.497a1 1 0 0 1 1-1h2.846a1 1 0 0 1 1 1V83.25a1 1 0 0 1-1 1h-2.846a1 1 0 0 1-1-1z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="m73.113 28.633-41.492 32.93v-4.405l39.9-32.007a.7.7 0 0 1 .428-.151h3.724a1.762 1.762 0 1 1 0 3.524h-2.248a.5.5 0 0 0-.312.109M25.895 53.414a3.084 3.084 0 1 0 6.167 0 3.084 3.084 0 1 0-6.167 0m14.978 16.859a1 1 0 0 1 1-1h41.73v7.488h-42.73zm10.131-7.489a1 1 0 0 1 1-1h31.598v7.488H51.004z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M61.576 55.294a1 1 0 0 1 1-1h21.026v7.49H61.576z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M72.15 47.806a1 1 0 0 1 1-1h9.454a1 1 0 0 1 1 1v6.489H72.15z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 110 110">
<path d="M32 26.5a2.5 2.5 0 0 1 2.5-2.5H45a2.5 2.5 0 0 1 2.5 2.5V57H32zM47.5 52h29a2.5 2.5 0 0 1 2.5 2.5V57H47.5zm17.037 20.102C73.95 70.288 79 62.01 79 57H32c.5 7.5 8.072 7 7 11l-2.342 16.862a1 1 0 0 0 .99 1.138H66.72a1 1 0 0 0 .97-1.243z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 415 B

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 110 110">
<g>
<path d="M23.5 31H86a2.5 2.5 0 0 1 2.5 2.5v34A2.5 2.5 0 0 1 86 70H23.5a2.5 2.5 0 0 1-2.5-2.5v-34a2.5 2.5 0 0 1 2.5-2.5M50 70h9.5v8H50Zm-10 8h30M42.5 45.5h5m5 0h-5m0 0v9.75" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="m56.75 45.5 5 10 5-10" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 500 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 110 110">
<path d="M28 23a2.5 2.5 0 0 1 2.5-2.5h24V85h-24a2.5 2.5 0 0 1-2.5-2.5zm2 65.5a1 1 0 0 0 1 1h47a1 1 0 0 0 1-1V85H30zM81 23a2.5 2.5 0 0 0-2.5-2.5h-24V85h24a2.5 2.5 0 0 0 2.5-2.5zM50.5 48v9.5m8-9.5v9.5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 374 B

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 110 110">
<g>
<path d="M31.5 25h47a2.5 2.5 0 0 1 2.5 2.5v55a2.5 2.5 0 0 1-2.5 2.5h-47a2.5 2.5 0 0 1-2.5-2.5v-55a2.5 2.5 0 0 1 2.5-2.5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M31.5 37.5h47A2.5 2.5 0 0 1 81 40v42.5a2.5 2.5 0 0 1-2.5 2.5h-47a2.5 2.5 0 0 1-2.5-2.5V40a2.5 2.5 0 0 1 2.5-2.5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M38 60.5a17 17 0 1 0 34 0 17 17 0 1 0-34 0" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M43.5 60.5a11.5 11.5 0 1 0 23 0 11.5 11.5 0 1 0-23 0m22-29.5a2 2 0 1 0 4 0 2 2 0 1 0-4 0m7.5 0a2 2 0 1 0 4 0 2 2 0 1 0-4 0m-37.25 0h9.5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 949 B

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 110 110">
<g>
<path d="M88.5 47h-20v14h20zm-12 4h4m7.984-8h-66v4h66zm-66 4h4v26.5a1.5 1.5 0 0 1-1.5 1.5h-1a1.5 1.5 0 0 1-1.5-1.5z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M24.866 35.597A2.5 2.5 0 0 1 27.197 34h56.577a2.5 2.5 0 0 1 2.33 1.594l2.35 6.044a1 1 0 0 1-.93 1.362H23.46a1 1 0 0 1-.933-1.361zM88.5 61h-20v12.5A1.5 1.5 0 0 0 70 75h17a1.5 1.5 0 0 0 1.5-1.5zm-12 4h4" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 623 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 90 90">
<path d="M75.702 13.011q.012-.225.012-.453c0-4.843-3.917-8.768-8.748-8.768H23.034c-4.83 0-8.747 3.925-8.747 8.768q0 .228.011.453M77.132 72.09q.59.091 1.206.092c4.348 0 7.873-3.533 7.873-7.892V20.45c0-4.359-3.525-7.892-7.873-7.892-.924 0-1.812.16-2.636.453a7.89 7.89 0 0 0-5.237 7.438v.148m0 0a8.7 8.7 0 0 1-3.499.73H23.034a8.7 8.7 0 0 1-3.499-.73m50.93 0V64.29a7.89 7.89 0 0 0 6.667 7.8M14.298 13.01a7.89 7.89 0 0 1 5.237 7.438v.148m0 0V64.29a7.89 7.89 0 0 1-6.666 7.8q-.59.091-1.206.092c-4.348 0-7.873-3.533-7.873-7.892V20.45c0-4.359 3.525-7.892 7.873-7.892.924 0 1.811.16 2.635.453M77.132 72.09c-1.585 8.05-8.667 14.12-17.164 14.12H30.032c-8.496 0-15.578-6.07-17.163-14.12" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 848 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 65 65">
<path d="M56.968 18.845q0-.368-.05-.72M49.051 13.8H15.95a5.04 5.04 0 0 1-5.038-5.043 5.04 5.04 0 0 1 5.038-5.043h33.103a5.04 5.04 0 0 1 5.038 5.043 5.04 5.04 0 0 1-5.038 5.043m-33.103 0h-2.88a5.04 5.04 0 0 0-4.986 4.323M49.052 13.8h2.879a5.04 5.04 0 0 1 4.986 4.324m.051 24.826a4.32 4.32 0 0 0 4.318-4.323v-16.18a4.32 4.32 0 0 0-4.369-4.322m.051 24.826a4.32 4.32 0 0 1-4.318-4.323V22.447a4.32 4.32 0 0 1 4.267-4.322m-11.84 37.24h4.695c.736 0 1.446-.111 2.115-.317a7.21 7.21 0 0 0 5.081-6.889v-5.208M8.084 18.124h-.051a4.32 4.32 0 0 0-4.318 4.323v16.18a4.32 4.32 0 0 0 4.318 4.324 4.32 4.32 0 0 0 4.317-4.323V22.447a4.32 4.32 0 0 0-4.266-4.323m11.935 37.24h-4.79a7.2 7.2 0 0 1-2.04-.294 7.21 7.21 0 0 1-5.156-6.91v-5.21m37.045 12.414 5.177 5.183c.969.97 2.534.987 3.524.038a2.526 2.526 0 0 0 .038-3.605l-1.93-1.932m-6.81.316H20.02m-6.83-.294-1.909 1.91c-.998 1-.98 2.627.038 3.605.99.949 2.555.932 3.524-.038l5.177-5.183" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 170 75">
<g>
<path d="M3.885 6c0-1.243 1.068-2.25 2.386-2.25h157.458c1.318 0 2.386 1.007 2.386 2.25v63c0 1.243-1.068 2.25-2.386 2.25H6.27c-1.318 0-2.386-1.007-2.386-2.25z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.428 37.5c0-13.669 11.75-24.75 26.243-24.75h90.658c14.493 0 26.243 11.081 26.243 24.75s-11.75 24.75-26.243 24.75H39.67c-14.493 0-26.243-11.081-26.243-24.75" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M31.56 37.5c0 2.237-1.923 4.05-4.295 4.05s-4.294-1.813-4.294-4.05 1.923-4.05 4.294-4.05c2.372 0 4.295 1.813 4.295 4.05" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 857 B

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 140 140">
<g>
<path d="M3.862 6.224a2.36 2.36 0 0 1 2.362-2.362H133.97c1.197 0 2.168.97 2.168 2.168 0 71.857-58.251 130.108-130.108 130.108a2.17 2.17 0 0 1-2.168-2.168z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.31 32.207c0-10.437 8.46-18.897 18.897-18.897h87.628a2.13 2.13 0 0 1 2.13 2.13c0 58.833-47.692 106.526-106.524 106.526a2.13 2.13 0 0 1-2.13-2.131z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M32.207 27.483a4.724 4.724 0 1 1-9.449 0 4.724 4.724 0 0 1 9.449 0" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 794 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 200">
<path d="M3.879 14.073c0 5.618 4.516 10.172 10.088 10.172h132.065c5.571 0 10.088-4.554 10.088-10.172m-81.165 47.16V46.438c0-5.107-4.106-9.247-9.171-9.247H45.607c-5.065 0-9.17 4.14-9.17 9.247v14.795m89.877 0V46.438c0-5.107-4.107-9.247-9.172-9.247H96.966c-5.065 0-9.171 4.14-9.171 9.247v14.795m-83.916 0H156.12m0 22.192H3.88m9.17 112.673h133.9c5.065 0 9.171-4.14 9.171-9.247V13.15c0-5.107-4.106-9.247-9.171-9.247H13.05c-5.064 0-9.17 4.14-9.17 9.247v173.7c0 5.107 4.106 9.247 9.17 9.247" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 659 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 90 200">
<path d="M3.79 14.024c0 5.59 4.318 10.122 9.645 10.122h63.13c5.328 0 9.646-4.532 9.646-10.122m-22.797 46.93V46.23c0-5.082-3.926-9.201-8.769-9.201h-19.29c-4.842 0-8.768 4.12-8.768 9.201v14.723m-22.797 0h82.42m0 22.085H3.79m8.768 113.06h64.885c4.842 0 8.768-4.12 8.768-9.202V13.104c0-5.082-3.926-9.202-8.768-9.202H12.558c-4.843 0-8.768 4.12-8.768 9.202v173.792c0 5.082 3.925 9.202 8.768 9.202" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 565 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 60">
<path d="M22.466 3.692v10.085c0 1.21-.92 2.192-2.055 2.192h-.822c-1.136 0-2.056-.981-2.056-2.192V3.692m4.933 8.77h2.878c1.135 0 2.056-.982 2.056-2.193v-.877c0-1.21-.92-2.192-2.056-2.192h-2.878m-4.933 5.262h-2.878c-1.135 0-2.055-.982-2.055-2.193v-.877c0-1.21.92-2.192 2.055-2.192h2.878M5.611 3.692h28.777c1.136 0 2.056.981 2.056 2.192v33.45c0 9.374-7.125 16.973-15.914 16.973h-1.06c-8.79 0-15.915-7.599-15.915-16.972V5.885c0-1.212.92-2.193 2.056-2.193m4.11 15.785h20.556c1.136 0 2.056.981 2.056 2.192v17.807c0 6.874-5.225 12.447-11.67 12.447h-1.327c-6.445 0-11.67-5.573-11.67-12.447V21.67c0-1.21.92-2.192 2.056-2.192m13.567 7.892c0 1.937-1.472 3.508-3.288 3.508-1.817 0-3.29-1.57-3.29-3.508s1.473-3.507 3.29-3.507 3.288 1.57 3.288 3.507" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 909 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 50">
<path d="M17.318 12.182h-8.06c-.88 0-1.592.74-1.592 1.654v16.126c0 6.698 5.225 12.129 11.67 12.129h1.327c6.445 0 11.67-5.43 11.67-12.13V13.837c0-.914-.713-1.654-1.591-1.654H22.68m-.215-8.545v9.827c0 1.18-.92 2.136-2.055 2.136h-.822c-1.136 0-2.056-.956-2.056-2.136V3.637m10.689 8.545V3.637m-16.445 0v8.545M5.611 3.637h28.777c1.136 0 2.056.956 2.056 2.136v24.051c0 9.135-7.125 16.54-15.914 16.54h-1.06c-8.79 0-15.915-7.405-15.915-16.54V5.773c0-1.18.92-2.136 2.056-2.136M23.289 23.29c0 1.888-1.473 3.418-3.29 3.418s-3.288-1.53-3.288-3.418 1.472-3.418 3.289-3.418c1.816 0 3.288 1.53 3.288 3.418" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 764 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 35">
<path d="M57.12 20.488H45.473c-.514 0-1.008.124-1.372.345s-.568.52-.568.832m4.076-8.236h5.434M6.119 3.5h87.762c1.276 0 2.31.895 2.31 2v24c0 1.105-1.034 2-2.31 2H6.12c-1.275 0-2.31-.895-2.31-2v-24c0-1.105 1.035-2 2.31-2m51 6.4v14.118H45.474a2.04 2.04 0 0 1-1.372-.517 1.7 1.7 0 0 1-.568-1.248V11.665c0-.468.204-.917.568-1.248a2.04 2.04 0 0 1 1.372-.517z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 527 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 35">
<path d="m39.722 16.231 1.505-1.596m1.134 3.15 1.53-1.738M6.03 31.5h67.94c1.251 0 2.265-.895 2.265-2v-24c0-1.105-1.014-2-2.264-2H6.03c-1.251 0-2.265.895-2.265 2v24c0 1.105 1.014 2 2.265 2m41.966-14.305L46.92 17.1a3.4 3.4 0 0 1-1.813-.706l-4.583-3.62c-1.264-1-3.277-.326-3.475 1.161-.185 1.39-1.983 2.1-3.28 1.299l-1.532-.947c-.968-.513-2.2.103-2.2 1.1v3.463c0 1.559 1.43 2.823 3.196 2.823h14.51c1.404 0 2.542-1.005 2.542-2.245 0-1.153-.99-2.118-2.289-2.233" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 630 B

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 50">
<g>
<path d="M3.765 5.773c0-1.18 1.014-2.136 2.265-2.136h67.94c1.251 0 2.265.956 2.265 2.136v38.454c0 1.18-1.014 2.137-2.264 2.137H6.03c-1.251 0-2.265-.957-2.265-2.137z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M36.088 32.135c2.525 1.88 6.205 1.88 8.73 0s3.167-5.101 1.52-7.637l-5.879-8.043-5.893 8.043c-1.644 2.536-1.003 5.755 1.522 7.637" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 598 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 140 45">
<path d="M56.772 17.88h5.315m0 0h5.315m-5.315 0v9.214m9.832-9.214 5.315 9.45 5.314-9.45M6.224 41.4h127.552c1.305 0 2.362-.94 2.362-2.1V5.7c0-1.16-1.057-2.1-2.362-2.1H6.224c-1.305 0-2.362.94-2.362 2.1v33.6c0 1.16 1.057 2.1 2.362 2.1" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 406 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50">
<path d="M39.452 12.433H10.548a4.398 4.398 0 1 1 0-8.796h28.904a4.398 4.398 0 0 1 0 8.796m-28.904 0H8.035a4.4 4.4 0 0 0-4.398 4.399V40.08a6.283 6.283 0 0 0 6.283 6.284h30.16a6.283 6.283 0 0 0 6.284-6.284V16.832c0-2.43-1.97-4.399-4.399-4.399h-2.513" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 421 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 45 48">
<path d="M38.579 17.168c1.326 2.526 1.947 6.015 1.947 9.071 0 10.018-8.07 18.139-18.025 18.139S4.475 36.257 4.475 26.239c0-3.056.586-6.552 1.912-9.078m32.192.007a3.59 3.59 0 0 1-3.333-.976m3.333.976a3.6 3.6 0 0 0 1.765-.976m-33.827.996-.13-.027a3.6 3.6 0 0 1-1.731-.969m30.59 0a18 18 0 0 0-5.848-3.932 17.93 17.93 0 0 0-13.796 0 18 18 0 0 0-5.848 3.932 3.59 3.59 0 0 1-3.237.996m28.729-.996a3.59 3.59 0 0 0 5.098 0m-33.827.996a3.6 3.6 0 0 1-1.861-.996 3.644 3.644 0 0 1 0-5.13 25.2 25.2 0 0 1 8.187-5.505A25.1 25.1 0 0 1 22.5 3.623a25.1 25.1 0 0 1 9.657 1.934 25.2 25.2 0 0 1 8.187 5.505 3.644 3.644 0 0 1 0 5.13" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 786 B

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 60">
<g>
<path d="M3.84 5.884c0-1.21 1.048-2.192 2.34-2.192h107.64c1.293 0 2.34.981 2.34 2.192v48.23c0 1.212-1.047 2.193-2.34 2.193H6.18c-1.292 0-2.34-.981-2.34-2.192z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M103.056 15.093V9.83a.936.877 0 0 1 .936-.877h5.616a.936.877 0 0 1 .936.877v5.262a.936.877 0 0 1-.936.877h-5.616a.936.877 0 0 1-.936-.877m0 35.078v-5.262a.936.877 0 0 1 .936-.877h5.616a.936.877 0 0 1 .936.877v5.262a.936.877 0 0 1-.936.877h-5.616a.936.877 0 0 1-.936-.877m-93.6-35.077V9.83a.936.877 0 0 1 .936-.877h5.616a.936.877 0 0 1 .936.877v5.262a.936.877 0 0 1-.936.877h-5.616a.936.877 0 0 1-.936-.877m0 35.078v-5.262a.936.877 0 0 1 .936-.877h5.616a.936.877 0 0 1 .936.877v5.262a.936.877 0 0 1-.936.877h-5.616a.936.877 0 0 1-.936-.877" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1003 B

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 60">
<g>
<path d="M3.84 30c0-14.53 12.572-26.308 28.08-26.308h56.16c15.509 0 28.08 11.778 28.08 26.308s-12.571 26.307-28.08 26.307H31.92C16.412 56.307 3.84 44.53 3.84 30" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M80.592 15.093V9.83a.936.877 0 0 1 .936-.877h5.616a.936.877 0 0 1 .936.877v5.262a.936.877 0 0 1-.936.877h-5.616a.936.877 0 0 1-.936-.877m0 35.077v-5.262a.936.877 0 0 1 .936-.877h5.616a.936.877 0 0 1 .936.877v5.262a.936.877 0 0 1-.936.877h-5.616a.936.877 0 0 1-.936-.877M31.92 15.093V9.83a.936.877 0 0 1 .936-.877h5.616a.936.877 0 0 1 .936.877v5.262a.936.877 0 0 1-.936.876h-5.616a.936.877 0 0 1-.936-.876m0 35.077v-5.262a.936.877 0 0 1 .936-.877h5.616a.936.877 0 0 1 .936.877v5.262a.936.877 0 0 1-.936.876h-5.616a.936.877 0 0 1-.936-.876" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1004 B

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80">
<g>
<path d="M3.765 40a36.235 36.235 0 1 0 72.47 0 36.235 36.235 0 1 0-72.47 0" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="m56.26 26.96-3.843-3.843a.906.906 0 0 1 0-1.28l3.843-3.844a.906.906 0 0 1 1.281 0l3.844 3.843a.906.906 0 0 1 0 1.281l-3.844 3.844a.906.906 0 0 1-1.28 0m-.001 34.66-3.843-3.843a.906.906 0 0 1 0-1.281l3.843-3.844a.906.906 0 0 1 1.281 0l3.843 3.844a.906.906 0 0 1 0 1.28l-3.843 3.844a.906.906 0 0 1-1.281 0m-34.423-34.66-3.843-3.843a.906.906 0 0 1 0-1.281l3.843-3.844a.906.906 0 0 1 1.281 0l3.844 3.844a.906.906 0 0 1 0 1.28l-3.844 3.844a.906.906 0 0 1-1.28 0m-.001 34.66-3.843-3.843a.906.906 0 0 1 0-1.281l3.843-3.843a.906.906 0 0 1 1.281 0l3.844 3.843a.906.906 0 0 1 0 1.281l-3.844 3.843a.906.906 0 0 1-1.28 0" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 988 B

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 60">
<g>
<path d="M3.84 12.461c0-4.843 4.19-8.77 9.36-8.77h93.6c5.17 0 9.36 3.927 9.36 8.77v35.077c0 4.843-4.19 8.77-9.36 8.77H13.2c-5.17 0-9.36-3.927-9.36-8.77z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M103.056 15.093V9.83a.936.877 0 0 1 .936-.877h5.616a.936.877 0 0 1 .936.877v5.262a.936.877 0 0 1-.936.877h-5.616a.936.877 0 0 1-.936-.877m0 35.078v-5.262a.936.877 0 0 1 .936-.877h5.616a.936.877 0 0 1 .936.877v5.262a.936.877 0 0 1-.936.877h-5.616a.936.877 0 0 1-.936-.877m-93.6-35.077V9.83a.936.877 0 0 1 .936-.877h5.616a.936.877 0 0 1 .936.877v5.262a.936.877 0 0 1-.936.877h-5.616a.936.877 0 0 1-.936-.877m0 35.078v-5.262a.936.877 0 0 1 .936-.877h5.616a.936.877 0 0 1 .936.877v5.262a.936.877 0 0 1-.936.877h-5.616a.936.877 0 0 1-.936-.877" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 997 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 50">
<path d="M15 30.982v-1.71m3.086 5.128h1.542M15 39.527v-1.709M10.372 34.4h1.542M5.84 46.364h18.322c1.331 0 2.41-1.196 2.41-2.67V6.306c0-1.475-1.079-2.67-2.41-2.67H5.839c-1.331 0-2.41 1.195-2.41 2.67v37.386c0 1.475 1.079 2.67 2.41 2.67m14.56-29.91c0 3.304-2.417 5.982-5.4 5.982-2.981 0-5.4-2.678-5.4-5.981s2.419-5.982 5.4-5.982c2.983 0 5.4 2.678 5.4 5.982M18.858 34.4c0 2.36-1.727 4.273-3.857 4.273s-3.857-1.913-3.857-4.273S12.87 30.127 15 30.127s3.857 1.913 3.857 4.273" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 642 B

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 140 70">
<g>
<path d="M3.862 5.967c0-1.234 1.057-2.234 2.362-2.234h127.552c1.305 0 2.362 1 2.362 2.234v58.066c0 1.234-1.057 2.233-2.362 2.233H6.224c-1.305 0-2.362-1-2.362-2.233z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.531 15.346v-5.36a.945.893 0 0 1 .945-.893h5.67a.945.893 0 0 1 .944.893v5.36a.945.893 0 0 1-.945.894h-5.669a.945.893 0 0 1-.945-.894m113.379.001v-5.36a.945.893 0 0 1 .945-.894h5.67a.945.893 0 0 1 .944.894v5.36a.945.893 0 0 1-.945.893h-5.669a.945.893 0 0 1-.945-.893M9.53 60.013v-5.36a.945.893 0 0 1 .946-.893h5.669a.945.893 0 0 1 .945.893v5.36a.945.893 0 0 1-.945.894h-5.67a.945.893 0 0 1-.944-.894m113.38.001v-5.36a.945.893 0 0 1 .945-.894h5.669a.945.893 0 0 1 .945.894v5.36a.945.893 0 0 1-.945.893h-5.67a.945.893 0 0 1-.944-.893m-97.318-2.681h88.814" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 160">
<g>
<path d="M156.12 66.202a2.38 2.38 0 0 1-2.379 2.38H94.272c-13.138 0-23.788 10.65-23.788 23.787v61.372a2.38 2.38 0 0 1-2.379 2.38H6.257a2.38 2.38 0 0 1-2.378-2.38V6.257A2.38 2.38 0 0 1 6.257 3.88h147.484a2.38 2.38 0 0 1 2.38 2.378z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.588 16.249v-5.71a.95.95 0 0 1 .951-.95h5.71a.95.95 0 0 1 .95.95v5.71a.95.95 0 0 1-.95.951h-5.71a.95.95 0 0 1-.951-.951m133.212.001v-5.71a.95.95 0 0 1 .951-.951h5.71a.95.95 0 0 1 .95.951v5.71a.95.95 0 0 1-.95.951h-5.71a.95.95 0 0 1-.951-.951m0 45.672v-5.71a.95.95 0 0 1 .952-.951h5.709a.95.95 0 0 1 .951.951v5.71a.95.95 0 0 1-.951.951h-5.71a.95.95 0 0 1-.95-.951M9.587 149.46v-5.708a.95.95 0 0 1 .952-.952h5.709a.95.95 0 0 1 .951.952v5.709a.95.95 0 0 1-.951.951h-5.71a.95.95 0 0 1-.951-.951m47.576 0v-5.709a.95.95 0 0 1 .952-.951h5.709a.95.95 0 0 1 .951.951v5.71a.95.95 0 0 1-.951.95h-5.71a.95.95 0 0 1-.951-.95m51.382-90.396H95.569a34.6 34.6 0 0 0-34.6 34.6v12.976" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 60">
<path d="M3.692 5.884c0-1.21.981-2.192 2.192-2.192h48.23c1.212 0 2.193.981 2.193 2.192v48.23a2.19 2.19 0 0 1-2.192 2.193H5.885a2.19 2.19 0 0 1-2.193-2.192z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 329 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 90 90">
<path d="M45.944 41.767a2.29 2.29 0 0 0 2.29 2.29H83.92a2.29 2.29 0 0 1 2.29 2.29V83.92a2.29 2.29 0 0 1-2.29 2.29H6.08a2.29 2.29 0 0 1-2.29-2.29V6.08a2.29 2.29 0 0 1 2.29-2.29h37.576a2.29 2.29 0 0 1 2.29 2.29z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 383 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 50">
<path d="M38.188 8.7h10.598c1.211 0 2.193.956 2.193 2.136v28.2c0 1.18-.982 2.136-2.193 2.136H11.08c-1.21 0-2.192-.956-2.192-2.136v-28.2c0-1.18.981-2.136 2.192-2.136h10.599m10.885-5.128V15.11c0 1.18-.981 2.136-2.192 2.136h-.877c-1.21 0-2.192-.957-2.192-2.136V3.572m5.261 7.691h3.07c1.21 0 2.192-.956 2.192-2.136v-.855c0-1.18-.982-2.136-2.192-2.136h-3.07m-5.261 5.127h-3.07c-1.21 0-2.192-.956-2.192-2.136v-.855c0-1.18.982-2.136 2.193-2.136h3.069M5.817 46.299h48.231c1.21 0 2.192-.956 2.192-2.136V5.709c0-1.18-.981-2.137-2.192-2.137H5.818c-1.211 0-2.193.957-2.193 2.137v38.454c0 1.18.982 2.136 2.192 2.136M33.44 25.79c0 1.888-1.57 3.418-3.507 3.418s-3.508-1.53-3.508-3.418c0-1.887 1.57-3.418 3.508-3.418s3.507 1.53 3.507 3.418" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 897 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 90 50">
<path d="M53.173 12.182c-.468 0-.847.354-.847.79s.38.79.847.79zm21.979 1.58c.467 0 .846-.353.846-.79 0-.436-.379-.79-.846-.79zM6.01 3.572v.79h77.842v-1.58H6.01zM86.14 5.71h-.847v38.454h1.694V5.709zM83.85 46.3v-.79H6.01v1.58h77.842zM3.72 44.163h.847V5.709H2.873v38.454zm2.29 2.136v-.79c-.797 0-1.443-.603-1.443-1.346H2.873c0 1.616 1.404 2.926 3.137 2.926zm80.131-2.136h-.847c0 .743-.646 1.346-1.442 1.346v1.58c1.732 0 3.136-1.31 3.136-2.926zm-2.29-40.59v.79c.797 0 1.443.602 1.443 1.346h1.694c0-1.617-1.404-2.927-3.136-2.927zm-77.841 0v-.79c-1.733 0-3.137 1.31-3.137 2.926h1.694c0-.744.646-1.347 1.443-1.347zm5.494 37.599v.79h28.39v-1.58h-28.39zm30.68-2.136h.846v-28.2h-1.694v28.2zm-32.97-28.2h-.846v28.2h1.694v-28.2zM39.895 8.7v-.79h-5.669v1.58h5.669zm-22.721 0v-.79h-5.669v1.58h5.669zm-7.958 2.136h.847c0-.744.645-1.346 1.442-1.346V7.91c-1.732 0-3.136 1.31-3.136 2.926zm32.968 0h.847c0-1.616-1.404-2.927-3.136-2.927v1.58c.797 0 1.442.603 1.442 1.347zm-2.29 30.336v.79c1.733 0 3.137-1.31 3.137-2.926h-1.694c0 .743-.645 1.346-1.442 1.346zm-28.389 0v-.79c-.797 0-1.442-.603-1.442-1.346H8.368c0 1.616 1.404 2.926 3.136 2.926zm13.737-23.927v.79h.916v-1.58h-.916zm3.205-2.136h.847V3.572H27.6V15.11zM22.952 3.572h-.847V15.11h1.693V3.572zm3.205 13.673v.79c1.732 0 3.136-1.31 3.136-2.926H27.6c0 .743-.646 1.346-1.443 1.346zm-.916 0v-.79c-.797 0-1.443-.603-1.443-1.346h-1.693c0 1.616 1.404 2.926 3.136 2.926zm3.205-5.982v.79h3.206v-1.58h-3.206zm5.495-2.136h.847v-.855h-1.694v.855zm-2.29-2.991v-.79h-3.205v1.58h3.206zm2.29 2.136h.847c0-1.616-1.404-2.926-3.136-2.926v1.58c.796 0 1.442.603 1.442 1.346zm-2.29 2.991v.79c1.733 0 3.137-1.31 3.137-2.926h-1.694c0 .743-.646 1.346-1.442 1.346zm-11.905 0v.79h3.206v-1.58h-3.206zm3.206-5.127v-.79h-3.206v1.58h3.206zm-5.495 2.136h-.847v.855h1.694v-.855zm2.29-2.136v-.79c-1.733 0-3.137 1.31-3.137 2.926h1.694c0-.743.646-1.346 1.442-1.346zm0 5.127v-.79c-.797 0-1.443-.603-1.443-1.346H16.61c0 1.616 1.404 2.926 3.136 2.926zm9.615 14.527h-.847c0 1.452-1.26 2.628-2.816 2.628v1.58c2.49 0 4.51-1.884 4.51-4.208zM25.7 29.208v-.79c-1.555 0-2.816-1.176-2.816-2.628H21.19c0 2.324 2.02 4.209 4.51 4.209zm-3.663-3.418h.847c0-1.451 1.26-2.628 2.816-2.628v-1.58c-2.49 0-4.51 1.884-4.51 4.208zm3.663-3.418v.79c1.555 0 2.816 1.177 2.816 2.628h1.694c0-2.324-2.02-4.208-4.51-4.208zm24.268 18.8v.79h28.39v-1.58h-28.39zm30.68-2.136h.846v-28.2H79.8v28.2zM78.356 8.7v-.79h-28.39v1.58h28.39zm-30.68 2.136h-.846v28.2h1.694v-28.2zm2.29-2.136v-.79c-1.732 0-3.136 1.31-3.136 2.926h1.694c0-.744.646-1.346 1.442-1.346zm30.68 2.136h.846c0-1.616-1.404-2.927-3.136-2.927v1.58c.797 0 1.443.603 1.443 1.347zm-2.29 30.336v.79c1.732 0 3.136-1.31 3.136-2.926H79.8c0 .743-.646 1.346-1.443 1.346zm-28.39 0v-.79c-.796 0-1.442-.603-1.442-1.346H46.83c0 1.616 1.404 2.926 3.136 2.926zm3.206-28.2v.79h21.979v-1.58h-21.98z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 35">
<path d="m4.444 31.033 50.75-27.007m-50.752-.06 50.75 27.008M5.884 31.5h48.23c1.212 0 2.193-.895 2.193-2v-24c0-1.105-.981-2-2.192-2H5.885c-1.212 0-2.193.895-2.193 2v24c0 1.105.981 2 2.192 2" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 363 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 60">
<path d="M3.772 29.59 29.88 4.383M4.57 55.43 55.43 30M4.175 4.383l26.701 23.863m-27.184.877L55.43 55.43M28.41 3.692H5.884c-1.21 0-2.192.981-2.192 2.192v48.23c0 1.212.981 2.193 2.192 2.193h48.23a2.19 2.19 0 0 0 2.193-2.192V31.589c0-1.21-.981-2.192-2.192-2.192h-21.32a2.19 2.19 0 0 1-2.193-2.192V5.885a2.19 2.19 0 0 0-2.192-2.193" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 501 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 40">
<path d="M5.773 3.555h38.454a2.136 2.056 0 0 1 2.137 2.056v28.777a2.136 2.056 0 0 1-2.137 2.056H5.773a2.136 2.056 0 0 1-2.136-2.056V5.611a2.136 2.056 0 0 1 2.136-2.056" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 341 B

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 35">
<g>
<path d="M6.12 31.5h87.76a2.31 2 0 0 0 2.31-2v-24a2.31 2 0 0 0-2.31-2H6.12a2.31 2 0 0 0-2.31 2v24a2.31 2 0 0 0 2.31 2" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.089 25.933c-.494.112-.776.546-.63.97s.664.675 1.157.564l-.263-.767zM90.912 9.067c.493-.112.775-.546.63-.97s-.664-.675-1.158-.564l.264.767zM11.875 26.956c.493-.112.775-.546.63-.97s-.664-.676-1.158-.564l.264.767zm3.988-2.556c-.493.112-.775.546-.63.969s.664.676 1.158.564l-.264-.766zm5.045.511c.493-.112.775-.545.63-.969s-.664-.676-1.158-.564l.264.766zm3.988-2.555c-.493.111-.775.545-.63.968.146.424.664.677 1.158.565l-.264-.767zm5.044.51c.494-.111.776-.545.63-.968-.145-.424-.664-.676-1.157-.565l.263.767zm3.99-2.555c-.495.112-.777.546-.63.97.145.423.663.675 1.157.563l-.264-.766zm5.043.511c.494-.112.776-.545.63-.969s-.664-.676-1.158-.564l.264.767zm3.989-2.555c-.494.111-.776.545-.63.969s.664.676 1.157.564l-.263-.767zm5.044.51c.494-.11.776-.545.63-.968-.146-.424-.664-.676-1.158-.565l.264.767zm3.989-2.555c-.494.112-.776.546-.63.97.145.423.664.675 1.157.564l-.264-.767zm5.044.511c.493-.111.775-.545.63-.969s-.664-.676-1.158-.564l.264.767zm3.988-2.555c-.493.112-.775.545-.63.969s.664.676 1.158.564l-.264-.767zm5.045.51c.493-.11.775-.545.63-.968-.146-.424-.664-.676-1.158-.564l.264.766zm3.988-2.555c-.493.112-.775.546-.63.97s.664.675 1.158.564l-.264-.767zm5.044.511c.494-.111.776-.545.63-.968-.145-.424-.664-.677-1.157-.565l.264.767zm3.99-2.555c-.494.112-.777.545-.63.969.145.423.663.676 1.157.564l-.264-.766zm5.043.511c.494-.112.776-.546.63-.969s-.664-.676-1.157-.564l.263.766zm3.989-2.556c-.494.112-.776.546-.63.97s.664.676 1.158.564l-.264-.767zM9.353 26.7l.263.767 2.259-.511-.264-.767-.264-.767-2.258.511zm6.774-1.533.264.766 4.517-1.022-.264-.767-.264-.766-4.517 1.022zm9.033-2.045.264.767 4.516-1.022-.264-.767-.263-.767-4.517 1.023zm9.033-2.044.264.766 4.516-1.022-.264-.766-.264-.767-4.516 1.022zm9.033-2.045.263.767 4.517-1.022-.264-.767-.264-.767-4.516 1.023zm9.032-2.044.264.767 4.517-1.023-.264-.766-.264-.767-4.516 1.022zm9.033-2.045.264.767 4.517-1.022-.264-.767-.264-.766-4.517 1.022zm9.033-2.044.264.767 4.516-1.023-.263-.766-.264-.767-4.517 1.022zm9.033-2.044.264.766 4.516-1.022-.264-.767-.263-.766-4.517 1.022zM88.39 8.81l.264.767 2.258-.511-.264-.767-.264-.767-2.258.511z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.089 9.067c-.494-.112-.776-.546-.63-.97s.664-.675 1.157-.564l-.263.767zm81.823 16.866c.493.112.775.546.63.97s-.664.675-1.158.564l.264-.767zM11.875 8.044c.493.112.775.546.63.97s-.664.676-1.158.564l.264-.767zm3.988 2.556c-.493-.112-.775-.546-.63-.969s.664-.676 1.158-.564l-.264.766zm5.045-.511c.493.112.775.545.63.969s-.664.676-1.158.564l.264-.766zm3.988 2.555c-.493-.111-.775-.545-.63-.968.146-.424.664-.677 1.158-.565l-.264.767zm5.044-.51c.494.111.776.545.63.968-.145.424-.664.676-1.157.565l.263-.767zm3.99 2.555c-.495-.112-.777-.546-.63-.97.145-.423.663-.675 1.157-.563l-.264.766zm5.043-.511c.494.112.776.545.63.969s-.664.676-1.158.564l.264-.767zm3.989 2.555c-.494-.111-.776-.545-.63-.969s.664-.676 1.157-.564l-.263.767zm5.044-.51c.494.11.776.545.63.968-.146.424-.664.676-1.158.565l.264-.767zm3.989 2.555c-.494-.112-.776-.546-.63-.97.145-.423.664-.675 1.157-.564l-.264.767zm5.044-.511c.493.111.775.545.63.969s-.664.676-1.158.564l.264-.767zm3.988 2.555c-.493-.112-.775-.545-.63-.969s.664-.676 1.158-.564l-.264.767zm5.045-.51c.493.11.775.545.63.968-.146.424-.664.676-1.158.564l.264-.766zm3.988 2.555c-.493-.112-.775-.546-.63-.97s.664-.675 1.158-.564l-.264.767zm5.044-.511c.494.111.776.545.63.968-.145.424-.664.677-1.157.565l.264-.767zm3.99 2.555c-.494-.112-.777-.545-.63-.969.145-.423.663-.676 1.157-.564l-.264.766zm5.043-.511c.494.112.776.546.63.969s-.664.676-1.157.564l.263-.766zm3.989 2.556c-.494-.112-.776-.546-.63-.97s.664-.676 1.158-.564l-.264.767zM9.353 8.3l.263-.767 2.259.511-.264.767-.264.767-2.258-.511zm6.774 1.533.264-.766 4.517 1.022-.264.767-.264.766-4.517-1.022zm9.033 2.045.264-.767 4.516 1.022-.264.767-.263.767-4.517-1.023zm9.033 2.044.264-.766 4.516 1.022-.264.766-.264.767-4.516-1.022zm9.033 2.045.263-.767 4.517 1.022-.264.767-.264.767-4.516-1.023zm9.032 2.044.264-.767 4.517 1.023-.264.766-.264.767-4.516-1.022zm9.033 2.045.264-.767 4.517 1.022-.264.767-.264.766-4.517-1.022zm9.033 2.044.264-.767 4.516 1.023-.263.766-.264.767-4.517-1.022zm9.033 2.044.264-.766 4.516 1.022-.264.767-.263.766-4.517-1.022zm9.033 2.045.264-.767 2.258.511-.264.767-.264.767-2.258-.511z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 25">
<path d="M18.36 3.15v2.567c0 1.012-1.034 1.833-2.31 1.833h-.924c-1.275 0-2.309-.82-2.309-1.833V3.15m73.905 0v2.567c0 1.012-1.034 1.833-2.31 1.833h-.924c-1.275 0-2.31-.82-2.31-1.833V3.15m-75.29 0H93.65c1.276 0 2.31.82 2.31 1.833V19.65c0 1.012-1.034 1.833-2.31 1.833H5.888c-1.275 0-2.31-.82-2.31-1.833V4.983c0-1.012 1.035-1.833 2.31-1.833" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 511 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 90">
<path d="M90 19.365v57.111m0-57.11V7.683m0 11.681c0 2.151-1.836 3.894-4.1 3.894H30.544a10.7 10.7 0 0 1-4.136-.825M90 19.365c0 2.151 1.836 3.894 4.1 3.894h55.356c1.472 0 2.871-.294 4.137-.825M90 76.476c0 5.376-4.59 9.735-10.25 9.735H30.543c-5.266 0-9.605-3.772-10.185-8.625M90 76.476c0 5.376 4.59 9.735 10.25 9.735h49.206c5.266 0 9.605-3.772 10.185-8.625M90 7.684c0-2.15-1.837-3.894-4.101-3.894H30.544c-5.661 0-10.25 4.358-10.25 9.735q0 .416.036.825M90 7.684c0-2.15 1.836-3.894 4.1-3.894h55.356c5.661 0 10.25 4.358 10.25 9.735q0 .416-.036.825m0 0a10.7 10.7 0 0 1 4.137-.825h2.05c5.662 0 10.252 4.358 10.252 9.734v45.43c0 5.376-4.59 9.734-10.251 9.734h-2.05c-1.484 0-2.894-.3-4.167-.837m0 0c-3.585-1.517-6.084-4.93-6.084-8.898V23.26q0-.417.036-.825c.32-3.622 2.728-6.68 6.077-8.084m-139.34 0c3.35 1.404 5.758 4.462 6.078 8.084q.036.408.036.825v45.43c0 3.967-2.5 7.38-6.085 8.897a10.7 10.7 0 0 1-4.166.837h-2.05c-5.662 0-10.251-4.358-10.251-9.735V23.26c0-5.376 4.59-9.734 10.25-9.734h2.05c1.473 0 2.872.294 4.138.825" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 260 170">
<path d="M93.673 20.687V83.69m0-63.004v-12.6m0 12.6c0 2.32-1.913 4.2-4.273 4.2H31.704a10.8 10.8 0 0 1-4.311-.89m66.28-3.31c0 2.32 1.914 4.2 4.274 4.2h64.106c2.36 0 4.274-1.88 4.274-4.2M93.673 83.692c0 5.8-4.783 10.501-10.684 10.501H31.704c-5.489 0-10.011-4.068-10.616-9.304m72.585-1.197c0 5.8 4.784 10.501 10.684 10.501h51.286c5.9 0 10.684-4.701 10.684-10.5m0-63.005V83.69m0-63.004v-12.6m0 12.6c0 2.32 1.914 4.2 4.273 4.2h57.696c1.534 0 2.993-.317 4.312-.89m-66.28 59.695v71.923c0 5.799 4.782 10.5 10.684 10.5h51.284c5.49 0 10.012-4.068 10.616-9.304V84.888M93.673 8.086c0-2.32-1.913-4.2-4.273-4.2H31.704c-5.9 0-10.684 4.7-10.684 10.5q0 .45.038.89m72.615-7.19c0-2.32 1.914-4.2 4.274-4.2h64.106c2.36 0 4.274 1.88 4.274 4.2m0 0c0-2.32 1.914-4.2 4.273-4.2h57.696c5.9 0 10.685 4.7 10.685 10.5 0 .293.024.587 0 .874m-.07 69.628c1.327.58 2.797.904 4.344.904h2.136c5.901 0 10.684-4.702 10.684-10.501V24.887c0-5.8-4.783-10.5-10.684-10.5h-2.136c-5.596 0-10.187 4.227-10.647 9.61q-.038.44-.038.89V75.29c0 4.28 2.605 7.961 6.342 9.597M21.059 15.276a10.8 10.8 0 0 0-4.312-.89H14.61c-5.9 0-10.684 4.701-10.684 10.5v50.405c0 5.8 4.783 10.5 10.684 10.5h2.137c1.546 0 3.016-.322 4.342-.903 3.737-1.636 6.342-5.317 6.342-9.597V24.887q0-.45-.037-.89c-.334-3.907-2.844-7.206-6.335-8.72" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 90">
<path d="M86.553 18.593V76.96m0-58.366V7.491m0 11.102c0 2.044-1.762 3.701-3.935 3.701H29.496c-1.413 0-2.756-.28-3.97-.784m61.027-2.917c0 2.044 1.762 3.701 3.935 3.701h59.024c2.174 0 3.935-1.657 3.935-3.7M86.553 76.958c0 5.11-4.404 9.252-9.837 9.252h-47.22c-5.054 0-9.218-3.585-9.775-8.198m66.832-1.054c0 5.11 4.404 9.252 9.837 9.252h47.22c5.433 0 9.837-4.142 9.837-9.252m0-58.366V76.96m0-58.366V7.491m0 11.102c0 2.044 1.762 3.701 3.936 3.701h53.122c1.413 0 2.756-.28 3.97-.784m-61.028 55.449c0 5.11 4.405 9.252 9.838 9.252h47.22c5.054 0 9.218-3.585 9.775-8.198M86.553 7.491c0-2.044-1.762-3.701-3.935-3.701H29.496c-5.433 0-9.838 4.142-9.838 9.252q0 .396.035.784m66.86-6.335c0-2.044 1.762-3.701 3.935-3.701h59.024c2.174 0 3.935 1.657 3.935 3.7m0 0c0-2.043 1.762-3.7 3.936-3.7h53.122c5.433 0 9.838 4.142 9.838 9.252 0 .258.022.517 0 .77m-.063 64.201c1.22.512 2.574.796 3.998.796h1.967c5.433 0 9.837-4.142 9.837-9.252V22.294c0-5.11-4.404-9.252-9.837-9.252h-1.967c-5.153 0-9.38 3.725-9.803 8.468q-.036.387-.035.784v47.263c0 3.771 2.399 7.015 5.84 8.456M19.693 13.826a10.3 10.3 0 0 0-3.97-.784h-1.967c-5.433 0-9.838 4.142-9.838 9.252v47.263c0 5.11 4.405 9.252 9.838 9.252h1.967c1.424 0 2.777-.284 3.998-.796 3.441-1.441 5.84-4.685 5.84-8.456V22.294a9 9 0 0 0-.035-.784c-.307-3.443-2.618-6.349-5.833-7.684" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 60">
<path d="M41.4 35.261a.877.877 0 1 0-1.754 0zm4.384 6.138a.877.877 0 1 0 0-1.753zM41.4 44.03a.877.877 0 1 0-1.753 0zm-4.384-2.63a.877.877 0 1 0 0-1.754zM5.885 3.691v.877h48.23V2.815H5.885zm50.422 2.192h-.877v48.23h1.754V5.885zm-2.192 50.423v-.877H5.885v1.754h48.23zM3.692 54.115h.877V5.885H2.815v48.23zm2.192 2.192v-.877a1.315 1.315 0 0 1-1.315-1.315H2.815a3.07 3.07 0 0 0 3.07 3.069zm50.423-2.192h-.877c0 .726-.589 1.315-1.315 1.315v1.754a3.07 3.07 0 0 0 3.069-3.07zM54.115 3.692v.877c.726 0 1.315.589 1.315 1.315h1.754a3.07 3.07 0 0 0-3.07-3.069zm-48.23 0v-.877a3.07 3.07 0 0 0-3.07 3.07h1.754c0-.727.589-1.316 1.315-1.316zm19.73 36.83h-.877a5.26 5.26 0 0 1-5.262 5.262v1.754a7.015 7.015 0 0 0 7.016-7.015zm-6.139 6.139v-.877a5.26 5.26 0 0 1-5.261-5.261h-1.754a7.015 7.015 0 0 0 7.015 7.015zm-6.138-6.138h.877a5.26 5.26 0 0 1 5.261-5.262v-1.754a7.015 7.015 0 0 0-7.015 7.016zm6.138-6.139v.877a5.26 5.26 0 0 1 5.262 5.262h1.754a7.015 7.015 0 0 0-7.016-7.016zm27.185-14.908h-.877a5.26 5.26 0 0 1-5.261 5.262v1.754a7.015 7.015 0 0 0 7.015-7.016zm-6.138 6.139v-.877a5.26 5.26 0 0 1-5.262-5.262h-1.754a7.015 7.015 0 0 0 7.016 7.016zm-6.139-6.139h.877a5.26 5.26 0 0 1 5.262-5.261v-1.754a7.015 7.015 0 0 0-7.016 7.015zm6.139-6.138v.877a5.26 5.26 0 0 1 5.261 5.261h1.754a7.015 7.015 0 0 0-7.015-7.015zm4.384 27.185h-.877a3.51 3.51 0 0 1-3.507 3.507v1.754a5.26 5.26 0 0 0 5.261-5.261zm-4.384 4.384v-.877a3.51 3.51 0 0 1-3.508-3.507H35.26a5.26 5.26 0 0 0 5.262 5.261zm-4.385-4.384h.877a3.51 3.51 0 0 1 3.508-3.508V35.26a5.26 5.26 0 0 0-5.262 5.262zm4.385-4.385v.877a3.51 3.51 0 0 1 3.507 3.508h1.754a5.26 5.26 0 0 0-5.261-5.262zm0 .877h.876V35.26h-1.753v1.754zm3.508 3.508v.876h1.753v-1.753h-1.753zm-3.508 5.261h.876V44.03h-1.753v1.754zm-5.261-5.261v.876h1.753v-1.753h-1.753zm-9.647-21.047h-.877a5.26 5.26 0 0 1-5.262 5.262v1.754a7.015 7.015 0 0 0 7.016-7.016zm-6.139 6.139v-.877a5.26 5.26 0 0 1-5.261-5.262h-1.754a7.015 7.015 0 0 0 7.015 7.016zm-6.138-6.139h.877a5.26 5.26 0 0 1 5.261-5.261v-1.754a7.015 7.015 0 0 0-7.015 7.015zm6.138-6.138v.877a5.26 5.26 0 0 1 5.262 5.261h1.754a7.015 7.015 0 0 0-7.016-7.015z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 90">
<g>
<path d="M3.879 6.08c0-1.265 1.065-2.29 2.378-2.29h147.484c1.314 0 2.38 1.025 2.38 2.29v77.84c0 1.265-1.066 2.29-2.38 2.29H6.257c-1.313 0-2.378-1.025-2.378-2.29z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M138.994 19.358v-5.495a.952.916 0 0 1 .951-.915h5.71a.952.916 0 0 1 .95.915v5.495a.952.916 0 0 1-.95.916h-5.71a.952.916 0 0 1-.951-.916m-.001 56.778v-5.495a.952.916 0 0 1 .952-.915h5.709a.952.916 0 0 1 .951.915v5.495a.952.916 0 0 1-.951.916h-5.71a.952.916 0 0 1-.95-.916m-125.6-56.778v-5.495a.952.916 0 0 1 .951-.916h5.71a.952.916 0 0 1 .95.916v5.495a.952.916 0 0 1-.95.915h-5.71a.952.916 0 0 1-.951-.915m-.001 56.779v-5.495a.952.916 0 0 1 .952-.916h5.709a.952.916 0 0 1 .951.916v5.495a.952.916 0 0 1-.951.915h-5.71a.952.916 0 0 1-.95-.915" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1007 B

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 100">
<g>
<path d="M3.892 50c0-25.51 21.418-46.19 47.838-46.19h76.54c26.42 0 47.839 20.68 47.839 46.19S154.69 96.19 128.27 96.19H51.73C25.31 96.19 3.892 75.51 3.892 50" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M122.53 19.514V13.97a.957.924 0 0 1 .956-.924h5.74a.957.924 0 0 1 .958.924v5.543a.957.924 0 0 1-.957.924h-5.74a.957.924 0 0 1-.958-.924m.001 66.516v-5.543a.957.924 0 0 1 .957-.924h5.74a.957.924 0 0 1 .957.924v5.543a.957.924 0 0 1-.957.924h-5.74a.957.924 0 0 1-.957-.924M49.816 19.515v-5.543a.957.924 0 0 1 .957-.924h5.74a.957.924 0 0 1 .958.924v5.542a.957.924 0 0 1-.957.924h-5.74a.957.924 0 0 1-.958-.924m0 66.516v-5.543a.957.924 0 0 1 .957-.924h5.74a.957.924 0 0 1 .957.924v5.543a.957.924 0 0 1-.957.923h-5.74a.957.924 0 0 1-.957-.924" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1001 B

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 90">
<g>
<path d="M3.879 17.527c0-7.587 6.39-13.737 14.272-13.737h123.696c7.883 0 14.273 6.15 14.273 13.737v54.947c0 7.587-6.39 13.737-14.273 13.737H18.151c-7.882 0-14.272-6.15-14.272-13.737z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M138.994 19.358v-5.495a.952.916 0 0 1 .951-.915h5.71a.952.916 0 0 1 .95.915v5.495a.952.916 0 0 1-.95.916h-5.71a.952.916 0 0 1-.951-.916m-.001 56.778v-5.495a.952.916 0 0 1 .952-.915h5.709a.952.916 0 0 1 .951.915v5.495a.952.916 0 0 1-.951.916h-5.71a.952.916 0 0 1-.95-.916m-125.6-56.778v-5.495a.952.916 0 0 1 .951-.916h5.71a.952.916 0 0 1 .95.916v5.495a.952.916 0 0 1-.95.915h-5.71a.952.916 0 0 1-.951-.915m-.001 56.779v-5.495a.952.916 0 0 1 .952-.916h5.709a.952.916 0 0 1 .951.916v5.495a.952.916 0 0 1-.951.915h-5.71a.952.916 0 0 1-.95-.915" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 110 110">
<g>
<path d="M3.826 55a51.174 51.174 0 1 0 102.349 0A51.174 51.174 0 1 0 3.826 55" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="m81.005 32.303-3.947-3.947a.93.93 0 0 1 0-1.316l3.947-3.948a.93.93 0 0 1 1.316 0l3.948 3.948a.93.93 0 0 1 0 1.316l-3.948 3.947a.93.93 0 0 1-1.316 0m0 54.897-3.947-3.948a.93.93 0 0 1 0-1.316l3.947-3.947a.93.93 0 0 1 1.316 0l3.948 3.947a.93.93 0 0 1 0 1.316L82.32 87.2a.93.93 0 0 1-1.316 0M27.04 32.304l-3.947-3.948a.93.93 0 0 1 0-1.315l3.948-3.948a.93.93 0 0 1 1.315 0l3.948 3.948a.93.93 0 0 1 0 1.315l-3.948 3.948a.93.93 0 0 1-1.315 0m-.001 54.604-3.947-3.948a.93.93 0 0 1 0-1.316l3.948-3.947a.93.93 0 0 1 1.315 0l3.948 3.947a.93.93 0 0 1 0 1.316l-3.948 3.948a.93.93 0 0 1-1.315 0" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 965 B

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 70">
<g>
<path d="M3.555 48.976c0 9.55 7.125 17.29 15.914 17.29h1.061c8.79 0 15.914-7.74 15.914-17.29V5.966c0-1.233-.92-2.233-2.056-2.233H5.611c-1.135 0-2.056 1-2.056 2.234z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.666 49.12c0 7.003 5.225 12.68 11.67 12.68h1.327c6.445 0 11.67-5.677 11.67-12.68V31.873c0-1.233-.92-2.233-2.056-2.233H9.722c-1.135 0-2.056 1-2.056 2.233zM33.792 3.733c1.465 0 2.652 1.29 2.652 2.882v12.103c0 1.592-1.187 2.882-2.652 2.882H6.208c-1.465 0-2.653-1.29-2.653-2.882V6.615c0-1.591 1.188-2.882 2.653-2.882z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M24.11 50.186c0 2.467-1.84 4.467-4.11 4.467s-4.111-2-4.111-4.467 1.84-4.466 4.11-4.466 4.112 2 4.112 4.466" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1008 B

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 55">
<g>
<path d="M3.555 34.56c0 9.263 7.125 16.774 15.914 16.774h1.061c8.79 0 15.914-7.51 15.914-16.775V5.833c0-1.196-.92-2.166-2.056-2.166H5.611c-1.135 0-2.056.97-2.056 2.166z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.666 34.7c0 6.793 5.225 12.3 11.67 12.3h1.327c6.445 0 11.67-5.507 11.67-12.3V14.5c0-1.197-.92-2.167-2.056-2.167H9.722c-1.135 0-2.056.97-2.056 2.167z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M24.11 35.733c0 2.394-1.84 4.334-4.11 4.334s-4.111-1.94-4.111-4.334 1.84-4.333 4.11-4.333 4.112 1.94 4.112 4.333" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 853 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 28">
<path d="M45.134 10.766H6.18c-1.292 0-2.34.848-2.34 1.894v2.68c0 1.045 1.048 1.893 2.34 1.893h38.954m0-6.467V5.288c0-1.046 1.048-1.894 2.34-1.894h26.704c1.292 0 2.34.848 2.34 1.894v5.478m-31.384 0h31.384m0 0h37.302c1.293 0 2.34.848 2.34 1.894v2.68c0 1.045-1.047 1.893-2.34 1.893H76.518m-31.384 0v5.479c0 1.046 1.048 1.894 2.34 1.894h26.704c1.292 0 2.34-.848 2.34-1.894v-5.479m-31.384 0h31.384" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 567 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 15">
<path d="M35.664 11.4c-.517 0-.936.269-.936.6s.419.6.936.6zm49.553 1.2c.517 0 .936-.269.936-.6s-.42-.6-.936-.6zM6.18 3v.6h107.64V2.4H6.18zm109.98 1.5h-.936v1.8h1.872V4.5zm-2.34 3.3v-.6H6.18v1.2h107.64zM3.84 6.3h.936V4.5H2.904v1.8zm2.34 1.5v-.6c-.775 0-1.404-.403-1.404-.9H2.904c0 1.16 1.467 2.1 3.276 2.1zm109.98-1.5h-.936c0 .497-.629.9-1.404.9v1.2c1.81 0 3.276-.94 3.276-2.1zM113.82 3v.6c.775 0 1.404.403 1.404.9h1.872c0-1.16-1.467-2.1-3.276-2.1zM6.18 3v-.6c-1.81 0-3.276.94-3.276 2.1h1.872c0-.497.629-.9 1.404-.9zm61.308 4.2h-.936V12h1.872V7.2zM52.512 12h.936V7.2h-1.872V12zm-16.848 0v.6h49.553v-1.2H35.664z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 784 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 45">
<path d="M6.336 41.4h227.328a2.418 2.1 0 0 0 2.418-2.1V5.7a2.418 2.1 0 0 0-2.418-2.1H6.336a2.418 2.1 0 0 0-2.418 2.1v33.6a2.418 2.1 0 0 0 2.418 2.1m43.744-5.88V9.48m46.432 26.04V9.48m46.433 26.04V9.48m46.433 26.04V9.48" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 393 B

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 60">
<path d="M93.87 20.25a3.3 3.3 0 0 0-1.072-2.407 3.8 3.8 0 0 0-2.586-.997c-.97 0-1.9.358-2.587.997a3.3 3.3 0 0 0-1.071 2.407c0 2.838 1.225 5.107 3.658 6.81h-.015m0 0 14.58 7.537c.57.295 1.046.726 1.377 1.25.331.523.505 1.12.505 1.727v1.401a3.3 3.3 0 0 1-1.071 2.407 3.8 3.8 0 0 1-2.587.998H77.393a3.8 3.8 0 0 1-2.586-.998 3.3 3.3 0 0 1-1.072-2.407v-1.4c0-.608.174-1.205.505-1.728a3.55 3.55 0 0 1 1.377-1.25zM6.284 3.691h167.433c1.32 0 2.392.981 2.392 2.192v48.23c0 1.212-1.071 2.193-2.392 2.193H6.284c-1.321 0-2.392-.981-2.392-2.192V5.885c0-1.212 1.07-2.193 2.392-2.193" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 743 B

+6 -6
View File
@@ -39,10 +39,12 @@ async def async_setup(hass: HomeAssistant, config) -> bool:
hass.data.setdefault(DOMAIN, {})
hp_ws.async_register(hass)
from .http_api import HouseplanContentView, HouseplanImportPreviewView, HouseplanUploadView
from .frontend_assets import HouseplanFrontendAssetView
hass.http.register_view(HouseplanUploadView())
hass.http.register_view(HouseplanContentView())
hass.http.register_view(HouseplanImportPreviewView())
hass.http.register_view(HouseplanFrontendAssetView())
return True
@@ -98,7 +100,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: HouseplanConfigEntry) ->
static_paths.append(StaticPathConfig(FRONTEND_URL, str(card_path), cache_headers=False))
# NOTE (audit B1): plans and marker files are NO LONGER static.
# They are served by HouseplanContentView, which requires auth.
# Only the card bundle stays public — Lovelace resources must be.
# Only the entry and manifest-gated JS chunks stay public —
# Lovelace modules must be loadable without an auth header.
if static_paths:
await hass.http.async_register_static_paths(static_paths)
except ImportError: # very old HA versions
@@ -236,12 +239,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: HouseplanConfigEntry) ->
hass.bus.async_fire("houseplan_config_updated", {"rev": optimize_revs[0]})
hass.bus.async_fire("houseplan_layout_updated", {"rev": optimize_revs[1]})
if recovered_import:
await recorder.async_refresh()
current = (await data.config_store.async_load() or {}).get("config") or {}
live_ids = {str(marker.get("id")) for marker in current.get("markers") or []}
for marker_id in list(recorder.book.data):
if marker_id not in live_ids:
await recorder.async_delete(marker_id)
await recorder.async_purge_orphans(current)
await recorder.async_refresh()
await async_check_plan_files(hass, entry)
+2 -1
View File
@@ -7,6 +7,7 @@ STORAGE_VIRTUAL_LIGHTS_KEY = f"{DOMAIN}.virtual_lights"
STORAGE_VERSION = 1
STORAGE_MINOR_VERSION = 2
FRONTEND_URL = "/houseplan_files/houseplan-card.js"
FRONTEND_ASSETS_URL = "/houseplan_files/houseplan-assets"
PLANS_URL = "/houseplan_files/plans"
PLANS_DIR = "houseplan/plans" # relative to the HA configuration directory
FILES_URL = "/houseplan_files/files"
@@ -46,7 +47,7 @@ PLAN_ORPHAN_TTL_S = 3600
SCHEDULED_GRACE_S = 30 * 24 * 3600
FILES_DIR = "houseplan/files"
CONF_ADMIN_ONLY = "admin_only"
VERSION = "1.68.1"
VERSION = "1.69.0-beta.3"
# Portable backup format. This is deliberately independent from the Home
# Assistant Store version above: storage migrations and files exported by a
@@ -0,0 +1,113 @@
{
"schema": 1,
"fingerprint": "95bafd2bfb8bfeafdfa45a0f5fc090b71ddc2cc9dbf2fb569fb60941c38ef5e8",
"entry": "houseplan-card.js",
"initialViewFiles": [
"houseplan-assets/houseplan-card-Cz0AWb2g.js",
"houseplan-card.js"
],
"initialViewGzipBytes": 272677,
"lazyFiles": [
"houseplan-assets/de-D6nVPpxU.js",
"houseplan-assets/editor-DREg7QWs.js",
"houseplan-assets/houseplan-editor-runtime-DMApKDuE.js",
"houseplan-assets/houseplan-onboarding-runtime-DyfrTQG3.js",
"houseplan-assets/space-deletion-DeqTZ3B0.js"
],
"lazyGzipBytes": 166287,
"lazyEditorFiles": [
"houseplan-assets/editor-DREg7QWs.js",
"houseplan-assets/houseplan-editor-runtime-DMApKDuE.js",
"houseplan-assets/space-deletion-DeqTZ3B0.js"
],
"lazyEditorGzipBytes": 137185,
"lazyOnboardingFiles": [
"houseplan-assets/houseplan-onboarding-runtime-DyfrTQG3.js",
"houseplan-assets/space-deletion-DeqTZ3B0.js"
],
"lazyOnboardingGzipBytes": 7198,
"lazyLocaleFiles": [
"houseplan-assets/de-D6nVPpxU.js"
],
"lazyLocaleGzipBytes": 22455,
"files": [
{
"path": "houseplan-assets/de-D6nVPpxU.js",
"sha256": "3057f94023d68a1f8f4d7bf69b19465a45c5d017d17e9c8c00b5be2f0c36cbad",
"rawBytes": 73919,
"gzipBytes": 22455,
"isEntry": false,
"imports": [],
"dynamicImports": []
},
{
"path": "houseplan-assets/editor-DREg7QWs.js",
"sha256": "5c08b305112db058ac0ce42b2b6a8e57a6c10d3b2b0b57378b3c3c78a48d351e",
"rawBytes": 3831,
"gzipBytes": 1581,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-Cz0AWb2g.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/houseplan-card-Cz0AWb2g.js",
"sha256": "38d44662e84dae93783754caef2ba13bed030ef173ef54a14f642c89e7f4950c",
"rawBytes": 961010,
"gzipBytes": 271925,
"isEntry": false,
"imports": [],
"dynamicImports": [
"houseplan-assets/de-D6nVPpxU.js",
"houseplan-assets/editor-DREg7QWs.js",
"houseplan-assets/houseplan-editor-runtime-DMApKDuE.js",
"houseplan-assets/houseplan-onboarding-runtime-DyfrTQG3.js"
]
},
{
"path": "houseplan-assets/houseplan-editor-runtime-DMApKDuE.js",
"sha256": "6191dd8b6338053c2aa3544ea2e04db05b31bc65474a9623391823b62dbdd394",
"rawBytes": 522188,
"gzipBytes": 135053,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-Cz0AWb2g.js",
"houseplan-assets/space-deletion-DeqTZ3B0.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/houseplan-onboarding-runtime-DyfrTQG3.js",
"sha256": "c94cd05d64d3764a2d26a0aac8a59e3d2bbe11a923b3d8c4c9891f362dfcbaae",
"rawBytes": 26356,
"gzipBytes": 6647,
"isEntry": false,
"imports": [
"houseplan-assets/houseplan-card-Cz0AWb2g.js",
"houseplan-assets/space-deletion-DeqTZ3B0.js"
],
"dynamicImports": []
},
{
"path": "houseplan-assets/space-deletion-DeqTZ3B0.js",
"sha256": "f4eeaf0367d1b6fe333b35c16ff59f2dc7015f7532fa356aaf7c43db9a0e0827",
"rawBytes": 911,
"gzipBytes": 551,
"isEntry": false,
"imports": [],
"dynamicImports": []
},
{
"path": "houseplan-card.js",
"sha256": "724c889fd050357875c6e38996a179a90ed3172a3822c0b97d17d193fc367ee4",
"rawBytes": 1089,
"gzipBytes": 752,
"isEntry": true,
"imports": [
"houseplan-assets/houseplan-card-Cz0AWb2g.js"
],
"dynamicImports": []
}
]
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,14 @@
globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="95bafd2bfb8bfeafdfa45a0f5fc090b71ddc2cc9dbf2fb569fb60941c38ef5e8";import{c as e,l as o,t,cZ as s,A as a,c_ as i,c$ as l,d0 as n,E as r,b as c}from"./houseplan-card-Cz0AWb2g.js";class f extends e{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1,this._spacesAuthoritative=!1}setConfig(e){this._config=e}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const e=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(e?.config?.spaces||[]).map(e=>({value:e.id,label:e.title||e.id})),this._spacesAuthoritative=!0}catch{this._spaces=[],this._spacesAuthoritative=!1}finally{this._spacesLoading=!1}}}get _lang(){return o(this.hass,this._config?.language)}get _floorToken(){const e=this._config?.floor;return"number"==typeof e?`__houseplan_yaml_floor_index__:${String(e)}`:null}get _formData(){const e={...this._config},o=this._floorToken;return o?e.floor=o:Object.prototype.hasOwnProperty.call(e,"floor")||(e.floor=""),e}get _schema(){const e=this._spaces||[],o=this._lang,a=[{value:"",label:t(o,"editor.floor_none")}],i=this._floorToken;i&&a.push({value:i,label:t(o,"editor.floor_index",{index:String(this._config?.floor)})});const l="string"==typeof this._config?.floor?this._config.floor:"";l&&!e.some(e=>e.value===l)&&a.push({value:l,label:l}),a.push(...e);const n="string"==typeof this._config?.default_floor?this._config.default_floor:"",r=[...e];return n&&!e.some(e=>e.value===n)&&r.unshift({value:n,label:n}),[{name:"title",selector:{text:{}}},{name:"floor",selector:{select:{mode:"dropdown",options:a}}},e.length?{name:"default_floor",selector:{select:{mode:"dropdown",options:r}}}:{name:"default_floor",selector:{text:{}}},{name:"language",selector:{select:{mode:"dropdown",options:s(t(o,"editor.lang_auto"),this._config?.language)}}},{name:"icon_size",selector:{number:{min:1,max:6,step:.1,mode:"box"}}},{name:"show_temperature",selector:{boolean:{}}},{name:"live_states",selector:{boolean:{}}},{name:"show_signal",selector:{boolean:{}}},{name:"kiosk",selector:{boolean:{}}},{name:"cycle",selector:{number:{min:0,max:3600,step:5,mode:"box"}}}]}render(){if(!this.hass||!this._config)return a;const e=i(this,l,o(this.hass,this._config.language));if("cold"===e)return n();if("warm"===e)return r;this._loadSpaces();const s=this._lang,f={title:t(s,"editor.title"),floor:t(s,"editor.floor"),default_floor:t(s,"editor.default_floor"),language:t(s,"editor.language"),icon_size:t(s,"editor.icon_size"),show_temperature:t(s,"editor.show_temperature"),live_states:t(s,"editor.live_states"),show_signal:t(s,"editor.show_signal"),kiosk:t(s,"editor.kiosk"),cycle:t(s,"editor.cycle")},h=this._schema,_=function(e,o,t){if(!t||null===o)return null;const s="string"==typeof e?.default_floor?e.default_floor:"";return!s||o.some(e=>e.value===s)?null:s}(this._config,this._spaces,this._spacesAuthoritative),d=e=>c`<ha-form
.hass=${this.hass}
.data=${this._formData}
.schema=${e}
.computeLabel=${e=>f[e.name]||e.name}
@value-changed=${this._valueChanged}
></ha-form>`;return c`
${d(h.slice(0,3))}
${_?c`<div class="default-floor-error" role="alert"
style="color:var(--error-color,#db4437);margin:-4px 0 12px;overflow-wrap:anywhere">
${t(s,"editor.default_floor_missing",{id:_})}
</div>`:a}
${d(h.slice(3))}
`}_valueChanged(e){const o={...this._config,...e.detail.value};""===o.floor?delete o.floor:o.floor===this._floorToken&&(o.floor=this._config?.floor);const t=new Event("config-changed",{bubbles:!0,composed:!0});t.detail={config:o},this.dispatchEvent(t)}}f.properties={hass:{attribute:!1},_config:{state:!0},_spaces:{state:!0}},customElements.get("houseplan-card-editor")||customElements.define("houseplan-card-editor",f);
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
function e(e,o){return{id:e,title:o,plan_url:null,view_box:[0,0,1,1],rooms:[],wall_segments:[]}}function o(e="file"){const o="draw"===e;return{source:e,showBorders:o,showNames:o,displayTouched:!1}}function r(e,o){if(e.displayTouched)return{...e,source:o};const r="draw"===o;return{...e,source:o,showBorders:r,showNames:r}}function s(e,o,r){return{...e,[o]:r,displayTouched:!0}}function t(e,o,r){const s=(e?.spaces||[]).find(e=>e?.id===r),t=new Set((s?.rooms||[]).map(e=>String(e?.id||"")).filter(Boolean)),n=[...new Set((e?.markers||[]).filter(e=>!0!==e?.removed&&"string"==typeof e?.id).filter(e=>e.space===r||"string"==typeof e.room_id&&t.has(e.room_id)||o?.[e.id]?.s===r).map(e=>e.id))].sort((e,o)=>e.localeCompare(o));return{markerIds:n,count:n.length}}globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="95bafd2bfb8bfeafdfa45a0f5fc090b71ddc2cc9dbf2fb569fb60941c38ef5e8";export{t as a,e as c,o as i,r as s,s as t};

Some files were not shown because too many files have changed in this diff Show More