Files
Murine-launcher/docs/icon-shapes.html
T

660 lines
55 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
layout: page
title: Icon shapes
subtitle: Pick a preset or draw your own shape, then send it to Murine
permalink: /icon-shapes/
sitemap: false # keep it out of sitemap.xml
noindex: true # and out of search engines
---
<style>
.page-body { max-width: 960px; }
.se-presets { display: grid; grid-template-columns: repeat(auto-fill, minmax(92px, 1fr)); gap: 10px; margin: 1rem 0 2rem; }
.se-preset { display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 12px 6px 10px;
border: 1px solid var(--md-outline-variant); border-radius: var(--md-shape-md);
background: var(--md-surface-container-low); color: var(--md-on-surface); cursor: pointer;
font: inherit; font-size: .78rem; line-height: 1.2; }
.se-preset:hover { background: var(--md-surface-container-high); }
.se-preset > svg { width: 48px; height: 48px; fill: var(--md-primary); }
.se-preset[hidden] { display: none; }
#se-presets { margin-bottom: 12px; }
.se-presets-more { display: block; margin: 0 auto 2rem; }
.se-editor { display: grid; grid-template-columns: minmax(0, 1fr) 280px; gap: 24px; align-items: start; }
@media (max-width: 760px) { .se-editor { grid-template-columns: 1fr; } }
#se-canvas { width: 100%; aspect-ratio: 1; display: block; touch-action: manipulation; user-select: none;
background: var(--md-surface-container-lowest); border: 1px solid var(--md-outline-variant);
border-radius: var(--md-shape-lg); outline: none; }
/* Focus ring only when the canvas was reached with the keyboard: clicks and taps focus it too (so Delete
and the arrow keys work), and Chrome would then show the ring as soon as a key is pressed. */
#se-canvas:focus-visible:not([data-pointer]) { box-shadow: 0 0 0 2px var(--md-primary); }
.se-grid-line { stroke: var(--md-outline-variant); stroke-width: .15; }
.se-grid-mid { stroke: var(--md-outline); stroke-width: .25; }
.se-bounds { fill: none; stroke: var(--md-outline); stroke-width: .35; stroke-dasharray: 1.5 1; }
/* Every shape is filled on its own: the others softly and behind, the selected one stronger and on top. */
.se-stage { --se-fill-active: color-mix(in srgb, var(--md-primary) 50%, transparent);
--se-fill-other: color-mix(in srgb, var(--md-primary-container) 85%, transparent); }
[data-theme="dark"] .se-stage { --se-fill-active: color-mix(in srgb, var(--md-primary-container) 85%, transparent);
--se-fill-other: color-mix(in srgb, var(--md-primary-container) 30%, transparent); }
.se-line { fill: var(--se-fill-other); stroke: var(--md-outline); stroke-width: .35; stroke-dasharray: 1.2 1; }
.se-line.active { fill: var(--se-fill-active); }
.se-line.copy { stroke-dasharray: .4 .8; }
.se-line.active { stroke: var(--md-primary); stroke-width: .5; stroke-dasharray: none; }
.se-line.active.copy { stroke-width: .35; stroke-dasharray: 1.2 1; }
#se-back-fill { filter: url(#se-cap-light); }
[data-theme="dark"] #se-back-fill { filter: url(#se-cap-dark); }
#se-back-fill .se-line { stroke: none; }
#se-back-stroke .se-line { fill: none; }
.se-move-box { fill: transparent; stroke: var(--md-primary); stroke-width: .3; stroke-dasharray: 1 1; cursor: move; }
.se-resize { fill: var(--md-surface-container-lowest); stroke: var(--md-primary); stroke-width: .5; }
.se-resize.selected { fill: var(--md-primary); }
.se-move-box, .se-resize { touch-action: none; }
.se-outline { fill: none; stroke: var(--md-on-surface-variant); stroke-width: .25; stroke-dasharray: 1 1; }
.se-handle { cursor: grab; outline: none; }
.se-handle circle:not(.se-hit) { fill: var(--md-surface-container-lowest); stroke: var(--md-primary); stroke-width: .7; }
.se-handle.selected circle:not(.se-hit), .se-handle:focus-visible circle:not(.se-hit) { fill: var(--md-primary); }
.se-hit { fill: transparent; }
.se-add { cursor: copy; opacity: .55; }
.se-add:hover { opacity: 1; }
.se-handle, .se-add { touch-action: none; }
.se-add circle:not(.se-hit) { fill: var(--md-surface-container-lowest); stroke: var(--md-outline); stroke-width: .35; }
.se-add path { stroke: var(--md-on-surface-variant); stroke-width: .45; }
.se-panel fieldset { border: 1px solid var(--md-outline-variant); border-radius: var(--md-shape-md);
padding: 10px 14px 12px; margin: 0 0 12px; }
.se-panel legend { padding: 0 6px; font-weight: 600; font-size: .85rem; }
/* Corner size and smoothing as sliders, or (.se-typed, with the legend's toggle) as number fields */
#se-corner-set legend { display: flex; align-items: center; gap: 6px; }
.se-corner-type { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 44px; height: 30px; padding: 0;
border: 1px solid var(--md-outline-variant); border-radius: var(--md-shape-full); background: transparent; color: var(--md-primary);
font: inherit; font-size: .78rem; font-weight: 600; letter-spacing: -.02em; cursor: pointer; }
.se-corner-type:hover { background: var(--md-surface-container); }
.se-corner-type svg { width: 22px; height: 22px; }
/* fingers: an invisible margin around it that still counts as the button */
@media (pointer: coarse) { .se-corner-type::after { content: ''; position: absolute; inset: -8px -4px; } }
.se-ct-slide, .se-typed .se-ct-num, .se-num { display: none; }
.se-typed .se-ct-slide { display: block; }
#se-corner-set.se-typed .se-num { display: inline-flex; align-items: center; gap: 4px; }
#se-corner-set.se-typed :is(#se-size-v, #se-smooth-v, #se-size-track, #se-smooth, .se-scale) { display: none; }
.se-panel .se-num, .se-panel .se-num input { user-select: text; -webkit-user-select: text; }
.se-unit { min-width: 1.2em; } /* the same for both fields, so they line up */
#se-size-v.se-over { color: var(--md-tertiary); cursor: help; }
.se-panel label { display: flex; align-items: center; justify-content: space-between; gap: 8px; user-select: none; -webkit-user-select: none;
font-size: .88rem; margin: 4px 0; }
.se-panel input[type=range] { width: 100%; accent-color: var(--md-primary); }
/* Tinted end of the size slider: past it, the selected corner shrinks its neighbours' rounding.
8px is about half the native thumb, so the tint starts under the thumb's centre. */
/* Native range inputs ignore their own background, so the tint sits on a wrapper behind the input. */
.se-size-track { display: flex; }
.se-size-track.has-zone { background: linear-gradient(to right, transparent calc(8px + (100% - 16px) * var(--squeeze)),
color-mix(in srgb, var(--md-tertiary) 55%, transparent) 0) 0 100% / 100% 4px no-repeat; }
.se-size-track input { margin-bottom: 6px; }
.se-panel .se-size-warn { color: var(--md-on-surface-variant); font-size: .78rem; line-height: 1.4; min-height: 2.8em; margin: 0 0 4px; }
.se-panel .se-size-warn.active { color: var(--md-tertiary); }
.se-handle.squeezed circle:not(.se-hit) { stroke: var(--md-tertiary);
fill: color-mix(in srgb, var(--md-tertiary) 35%, var(--md-surface-container-lowest)); }
/* filled means selected (as above), red means a warning: both, a solid red dot */
.se-handle.squeezed.selected circle:not(.se-hit), .se-handle.squeezed:focus-visible circle:not(.se-hit) { fill: var(--md-tertiary); }
.se-panel input[type=number], .se-panel select { width: 5.5em; font: inherit; padding: 2px 6px;
color: var(--md-on-surface); background: var(--md-surface-container); border: 1px solid var(--md-outline-variant);
border-radius: var(--md-shape-xs); }
.se-panel input[type=checkbox] { accent-color: var(--md-primary); }
.se-scale { display: flex; justify-content: space-between; font-size: .72rem; color: var(--md-on-surface-variant); margin-top: -4px; }
.se-row { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.se-btn { font: inherit; font-size: .82rem; padding: 6px 12px; border-radius: var(--md-shape-full); cursor: pointer;
border: 1px solid var(--md-outline); background: transparent; color: var(--md-primary); }
.se-btn:hover:not(:disabled) { background: var(--md-surface-container); }
.se-btn:disabled { opacity: .4; cursor: default; }
.se-toolbar { margin: 0 0 12px; }
/* Top-left pill: X / Y of the selected point (and its delete button) or of the selected Move-mode corner.
Same height, border and shadow as the mirror pill. */
.se-pill { position: absolute; left: 10px; top: 10px; display: flex; align-items: center; gap: 8px; padding: 3px 3px 3px 12px;
border-radius: var(--md-shape-full); border: 1px solid var(--md-outline-variant);
background: var(--md-surface-container-low); box-shadow: var(--md-shadow); font-size: .82rem; }
.se-pill:has(#se-remove[hidden]) { padding-right: 8px; }
#se-rot-pill { padding: 3px 10px 3px 3px; }
#se-rot-pill:has(#se-turn-mode:last-of-type[aria-pressed="false"]) { padding-right: 3px; }
.se-pill-part { display: flex; align-items: center; gap: 8px; }
.se-pill svg { width: 20px; height: 20px; }
.se-pill[hidden], .se-pill [hidden] { display: none; }
.se-pill label { display: flex; align-items: center; gap: 4px; color: var(--md-on-surface-variant); }
.se-pill input { width: 4.2em; height: 28px; padding: 0 6px; font: inherit; color: var(--md-on-surface);
background: var(--md-surface-container); border: 1px solid var(--md-outline-variant); border-radius: var(--md-shape-xs); }
.se-pill button { width: 34px; height: 34px; display: inline-flex; align-items: center; justify-content: center; padding: 0;
border: 0; border-radius: var(--md-shape-full); background: transparent; color: var(--md-primary); cursor: pointer; }
.se-pill button:hover:not(:disabled):not([aria-pressed="true"]) { background: var(--md-surface-container-high); }
.se-pill button:disabled { opacity: .35; cursor: default; }
.se-pill button[aria-pressed="true"] { background: var(--md-primary-container); color: var(--md-on-primary-container); }
.se-pill .material-symbols-outlined { font-size: 20px; }
.se-pill input[type=range] { width: 120px; height: auto; padding: 0; border: 0; background: none; accent-color: var(--md-primary); }
#se-turn-val { min-width: 4ch; text-align: right; color: var(--md-on-surface-variant); font-variant-numeric: tabular-nums; }
@media (pointer: fine) { .se-pill input { width: calc(6ch + 22px); } } /* room for "-12.50" plus the spin arrows */
.se-hint { font-size: .8rem; color: var(--md-on-surface-variant); line-height: 1.5; margin: 8px 0 0; }
.se-result { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 20px; align-items: center; margin-top: 1rem; }
@media (max-width: 640px) { .se-result { grid-template-columns: 1fr; } }
.se-previews { display: flex; align-items: end; gap: 14px; }
.se-preview { fill: url(#se-icon-fill); }
#se-out { width: 100%; min-height: 6.5em; font-family: ui-monospace, monospace; font-size: .78rem; resize: vertical;
color: var(--md-on-surface); background: var(--md-surface-container); border: 1px solid var(--md-outline-variant);
border-radius: var(--md-shape-sm); padding: 8px; }
.se-warn { color: var(--md-error); font-size: .88rem; margin: 6px 0 0; }
.se-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 10px; }
.se-actions .btn-filled, .se-actions .btn-outlined { padding: 10px 22px; }
.se-note { margin: 0 0 12px; padding: 10px 14px; border-radius: var(--md-shape-md); background: var(--md-surface-container); }
.se-panel fieldset:disabled { opacity: .45; }
.se-disabled { opacity: .4; pointer-events: none; }
#se-use, #se-use:hover { color: var(--md-on-primary); text-decoration: none; }
/* The share dialog reuses the contact dialog's classes; undo the prose styles of .page-body around it. */
#se-share { line-height: 1.5; }
#se-share h2 { margin: 0; }
#se-share .se-card-title { display: block; font-size: 1rem; font-weight: 500; margin-bottom: 2px; }
#se-share .se-card-sub { display: block; font-size: .8125rem; color: var(--md-on-surface-variant); }
#se-share .contact-modal-desc { margin: 0 0 24px; }
.se-share-card { width: 100%; border: 0; font: inherit; text-align: left; cursor: pointer; }
.se-share-card[hidden] { display: none; }
.se-share-card:disabled { opacity: .45; cursor: default; box-shadow: none; }
.se-dev { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
.se-dev[hidden] { display: none; }
.se-dev .se-share-card { flex-direction: column; justify-content: center; gap: 2px; padding: 10px 6px; text-align: center; }
.se-dev .material-symbols-outlined { color: var(--md-primary); }
/* Canvas overlay (Move mode): fit this shape top right, mirroring bottom left. */
.se-left { min-width: 0; }
.se-stage { position: relative; }
.se-docs { margin-top: 40px; font-size: .9rem; }
.se-docs summary { cursor: pointer; font-weight: 500; color: var(--md-primary); }
.se-docs-links a { overflow-wrap: anywhere; }
/* "Hide tools": everything floating over the canvas goes (the canvas is the stage's only svg child) */
.se-stage.se-bare > :not(svg) { display: none !important; }
.se-title-row { display: flex; align-items: center; gap: 16px; margin: 40px 0 16px; }
.page-body .se-title-row h2 { margin: 0; }
.se-shake { animation: se-shake .35s; }
@keyframes se-shake { 20%, 60% { transform: translateX(-6px); } 40%, 80% { transform: translateX(6px); } }
@media (prefers-reduced-motion: reduce) { .se-shake { animation: none; } }
.se-stage-btn, .se-mirror button { display: inline-flex; align-items: center; justify-content: center; padding: 0;
border-radius: var(--md-shape-full); color: var(--md-primary); cursor: pointer; }
.se-stage-tools { position: absolute; top: 10px; right: 10px; display: flex; flex-direction: column; gap: 6px; }
.se-stage-btn { width: 40px; height: 40px;
border: 1px solid var(--md-outline-variant); background: var(--md-surface-container-low); box-shadow: var(--md-shadow); }
.se-mirror { position: absolute; left: 10px; bottom: 10px; display: flex; gap: 2px; padding: 3px;
border-radius: var(--md-shape-full); border: 1px solid var(--md-outline-variant);
background: var(--md-surface-container-low); box-shadow: var(--md-shadow); }
.se-mirror button { width: 34px; height: 34px; border: 0; background: transparent; }
.se-mirror button:disabled { opacity: .35; cursor: default; pointer-events: none; }
.se-mirror button[aria-pressed="true"] { background: var(--md-primary-container); color: var(--md-on-primary-container); }
.se-mirror svg { width: 20px; height: 20px; }
.se-corner-wrap { position: relative; display: flex; }
.se-corner-pick { position: absolute; left: 50%; bottom: calc(100% + 10px); transform: translateX(-50%); display: grid;
grid-template-columns: 34px 34px; gap: 2px; padding: 3px; border-radius: 18px; border: 1px solid var(--md-outline-variant);
background: var(--md-surface-container-low); box-shadow: var(--md-shadow); }
.se-corner-pick[hidden] { display: none; }
.se-pivot { display: flex; gap: 2px; padding-left: 5px; margin-left: 3px; border-left: 1px solid var(--md-outline-variant); }
.se-pivot-ghost { pointer-events: none; fill: none; stroke: var(--md-primary); stroke-width: .55; stroke-linecap: round; opacity: .8; transition: opacity .6s; }
.se-pivot-ghost.off { opacity: 0; }
.se-pivot-mark { pointer-events: none; fill: none; stroke: var(--md-primary); stroke-width: .6; stroke-linecap: round; stroke-linejoin: round; }
.se-stage-btn:hover, .se-mirror button:hover:not([aria-pressed="true"]) { background: var(--md-surface-container-high); }
.se-stage-tools[hidden], .se-mirror[hidden], .se-stage-btn[hidden] { display: none; }
/* Bottom right: the "how it works" button and its bubble, which opens above it */
.se-help-btn { position: absolute; right: 10px; bottom: 10px; width: 36px; height: 36px; }
.se-help-btn[aria-expanded="true"] { background: var(--md-primary-container); color: var(--md-on-primary-container); }
.se-help { position: absolute; right: 10px; bottom: 54px; width: min(340px, calc(100% - 20px)); max-height: calc(100% - 64px);
overflow-y: auto; padding: 10px 14px; border-radius: var(--md-shape-md); border: 1px solid var(--md-outline-variant);
background: var(--md-surface-container-low); box-shadow: var(--md-shadow); font-size: .8rem; line-height: 1.5;
color: var(--md-on-surface-variant); z-index: 2; }
.se-help[hidden] { display: none; }
.se-help p { margin: 0; }
.se-help p + p { margin-top: 6px; }
.se-help strong { color: var(--md-on-surface); font-weight: 500; }
.se-touch { display: none; }
@media (hover: none) { .se-kb { display: none; } .se-touch { display: inline; } }
@media (max-width: 365px) { /* no room beside the mirror pill (267px wide), nor under the right-hand column: above the pill */
.se-help-btn { bottom: 56px; right: auto; left: 10px; }
.se-help { bottom: 100px; right: auto; left: 10px; max-height: calc(100% - 110px); }
}
.se-stage-btn svg { width: 22px; height: 22px; }
/* Shape cards under the canvas. */
.se-cards { display: flex; gap: 10px; overflow-x: auto; padding: 12px 2px 6px; }
.se-card { position: relative; flex: 0 0 104px; display: flex; flex-direction: column; align-items: center;
border: 1px solid var(--md-outline-variant); border-radius: var(--md-shape-md); background: var(--md-surface-container-low); }
.se-card.active { border-color: var(--md-primary); box-shadow: 0 0 0 1px var(--md-primary); }
.se-card-pick { display: flex; flex-direction: column; align-items: center; gap: 4px; width: 100%; padding: 12px 6px 8px;
border: 0; background: transparent; color: var(--md-on-surface); font: inherit; font-size: .75rem; cursor: pointer;
border-radius: var(--md-shape-md); user-select: none; -webkit-user-select: none; -webkit-touch-callout: none; }
.se-card-pick svg { width: 56px; height: 56px; }
.se-card-pick path { fill: var(--md-primary); }
.se-card-pick path.copy { fill-opacity: .35; }
.se-card-box { fill: none; stroke: var(--md-outline-variant); stroke-width: 2; stroke-dasharray: 6 4; }
.se-card-del { position: absolute; top: 2px; right: 2px; width: 28px; height: 28px; display: flex; align-items: center;
justify-content: center; padding: 0; border: 0; border-radius: 50%; background: transparent;
color: var(--md-on-surface-variant); cursor: pointer; }
.se-card-del:hover { background: var(--md-surface-container-highest); color: var(--md-error); }
.se-card-del .material-symbols-outlined, .se-card-modes .material-symbols-outlined { font-size: 18px; }
.se-card-modes { display: flex; gap: 4px; padding: 0 6px 8px; }
.se-card-modes button { width: 38px; height: 30px; display: flex; align-items: center; justify-content: center; padding: 0;
border: 1px solid var(--md-outline-variant); border-radius: var(--md-shape-full); background: transparent;
color: var(--md-on-surface-variant); cursor: pointer; }
.se-card-modes button[aria-pressed="true"] { background: var(--md-primary-container); color: var(--md-on-primary-container); border-color: transparent; }
.se-card-modes button:disabled { opacity: .35; cursor: default; }
.se-card-add { justify-content: center; min-height: 104px; border-style: dashed; color: var(--md-primary); cursor: pointer; font: inherit; }
.se-card-add .material-symbols-outlined { font-size: 32px; }
.se-card-add:hover { background: var(--md-surface-container-high); }
.se-note-out { color: var(--md-on-surface-variant); font-size: .85rem; margin: 6px 0 0; }
/* The "add a shape" dialog reuses the contact dialog's classes too. */
#se-add { line-height: 1.5; }
#se-add h2 { margin: 0; }
#se-add .contact-modal { max-width: 640px; }
#se-add .contact-modal-desc { margin: 0 0 16px; }
/* The dialog never grows past the screen: only the grid scrolls, so the buttons above it and the bar
under it stay in view (the whole dialog scrolls only if even a two-row grid doesn't fit). */
#se-add .contact-modal { max-height: 100%; display: flex; flex-direction: column; overflow-y: auto; }
#se-add .contact-modal > * { flex: none; }
#se-add .contact-modal > .se-presets { flex: 1 1 auto; min-height: 220px; margin: 0; overflow-y: auto; align-content: start; } /* two rows of tiles; extra height stays empty instead of stretching them */
@media (max-width: 480px) { #se-add { padding: 12px; } }
/* On a very short screen the whole dialog scrolls: the bar then sticks to its bottom edge. */
#se-add .se-pick-bar { position: sticky; bottom: 0; padding: 8px 0; margin-top: 4px; background: var(--md-surface-container-low); }
.se-import { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; margin: 0 0 16px; }
.se-import p { margin: 0; }
#se-import-choice[hidden], #se-import-msg[hidden], #se-import-unload[hidden], .se-pick-bar[hidden] { display: none; }
.se-preset.picked { border-color: var(--md-primary); background: var(--md-primary-container); box-shadow: inset 0 0 0 1px var(--md-primary); }
.se-preset:disabled { opacity: .45; cursor: default; }
.se-preset { -webkit-touch-callout: none; user-select: none; } /* long-press picks, it doesn't open the callout or select text */
.se-preset .se-tile-ghost { opacity: .18; }
.se-seg { display: inline-flex; margin: 8px 0; border: 1px solid var(--md-outline-variant); border-radius: var(--md-shape-full); overflow: hidden; }
.se-seg[hidden] { display: none; }
.se-seg button { font: inherit; font-size: .82rem; padding: 6px 14px; border: 0; background: transparent; color: var(--md-on-surface); cursor: pointer; }
.se-seg button + button { border-left: 1px solid var(--md-outline-variant); }
.se-seg button[aria-pressed="true"] { background: var(--md-primary-container); color: var(--md-on-primary-container); }
.se-pick-bar { display: flex; align-items: center; justify-content: flex-end; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
#se-pick-count { margin-right: auto; font-size: .85rem; color: var(--md-on-surface-variant); }
/* The buttons stay on one row together: if they don't fit next to the count, they move under it as a group. */
.se-pick-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 8px; margin-left: auto; }
.se-btn-primary { background: var(--md-primary); color: var(--md-on-primary); border-color: var(--md-primary); }
.se-btn-primary:hover:not(:disabled) { background: var(--md-primary); filter: brightness(1.08); }
/* Saved shapes: the Presets / Custom switch is the section's title; custom tiles get rename and delete corners. */
.page-body h2.se-lib-title { margin: 40px 0 0; }
.se-seg-title { margin: 0; }
.se-seg-title button { font: inherit; padding: 4px 18px; } /* the size of the other section titles */
.se-seg-title button[aria-pressed="false"] { color: var(--md-on-surface-variant); }
.se-presets[hidden], .se-presets-more[hidden], .se-saved[hidden] { display: none; }
#se-add-lib { margin: 0; }
.se-saved { position: relative; display: flex; }
.se-saved > .se-preset { flex: 1; min-width: 0; padding-top: 22px; }
.se-saved > .se-preset span { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.se-tile-btn { position: absolute; top: 2px; width: 28px; height: 28px; display: flex; align-items: center; justify-content: center;
padding: 0; border: 0; border-radius: 50%; background: transparent; color: var(--md-on-surface-variant); cursor: pointer; }
.se-tile-btn .material-symbols-outlined { font-size: 18px; }
.se-tile-edit { left: 2px; }
.se-tile-del { right: 2px; }
.se-tile-btn:hover { background: var(--md-surface-container-highest); color: var(--md-primary); }
.se-tile-del:hover { color: var(--md-error); }
.se-card-save { right: auto; left: 2px; }
.se-card-save:hover { color: var(--md-primary); }
.se-presets > .se-hint { grid-column: 1 / -1; margin: 0; }
.se-custom-msg { display: flex; align-items: center; gap: 8px; margin: -4px 0 16px; }
.se-custom-msg[hidden] { display: none; }
.se-name-modal { max-width: 400px; }
.se-name-input { display: block; width: 100%; box-sizing: border-box; margin: 4px 0 4px; padding: 10px 12px; font: inherit;
color: var(--md-on-surface); background: var(--md-surface-container-lowest); border: 1px solid var(--md-outline);
border-radius: var(--md-shape-sm, 8px); }
.se-name-input:focus { outline: 2px solid var(--md-primary); outline-offset: -1px; }
#se-name .se-pick-actions { margin-top: 16px; }
#se-measure { position: absolute; width: 0; height: 0; overflow: hidden; }
</style>
<h2 class="se-lib-title"><span id="se-lib" class="se-seg se-seg-title" role="group" aria-label="Shapes to start from">
<button type="button" data-lib="presets" aria-pressed="true">Presets</button><button type="button" data-lib="custom" aria-pressed="false" title="Shapes you saved, kept in this browser">Custom</button>
</span></h2>
<div id="se-presets" class="se-presets"></div>
<div id="se-custom" class="se-presets" hidden></div>
<p id="se-custom-msg" class="se-hint se-custom-msg" role="status" hidden><span></span> <button type="button" class="se-btn" id="se-custom-undo" hidden>Undo</button></p>
<button id="se-presets-more" class="se-btn se-presets-more" type="button" aria-expanded="false" aria-controls="se-presets">More shapes</button>
<div class="se-title-row">
<h2>Editor</h2>
<button id="se-tools-toggle" class="se-btn" type="button" aria-pressed="false" title="Hide the buttons and pills floating over the canvas">Hide tools</button>
</div>
<div class="se-editor">
<div class="se-left">
<div class="se-stage">
<svg id="se-canvas" viewBox="-20 -20 140 140" tabindex="0" aria-label="Shape editor. In Edit mode drag points to move them and the plus marks to add points; in Move mode drag the shape or its corners.">
<g id="se-grid"></g>
<rect class="se-bounds" x="0" y="0" width="100" height="100"/>
<defs>
<!-- The other shapes' fills add up where they overlap, but only up to a cap, so a pile of them never
reaches the selected shape's colour: alpha becomes min(alpha, cap). -->
<filter id="se-cap-light" filterUnits="userSpaceOnUse" x="-1000" y="-1000" width="2200" height="2200"><feComponentTransfer><feFuncA type="table" tableValues="0 .1 .2 .3 .4 .5 .6 .7 .8 .9 .9"/></feComponentTransfer></filter>
<filter id="se-cap-dark" filterUnits="userSpaceOnUse" x="-1000" y="-1000" width="2200" height="2200"><feComponentTransfer><feFuncA type="table" tableValues="0 .5 .5"/></feComponentTransfer></filter>
</defs>
<g id="se-lines"><g id="se-back-fill"></g><g id="se-back-stroke"></g><g id="se-front"></g></g>
<polygon id="se-outline" class="se-outline"/>
<g id="se-stretch-mark" class="se-pivot-mark" style="display: none"><circle r=".8" fill="var(--md-primary)" stroke="none"/><path/></g>
<g id="se-adders"></g>
<g id="se-handles"></g>
<g id="se-move" style="display: none">
<rect id="se-move-box" class="se-move-box"/>
<!-- corners in the order the script fills them: top left, top right, bottom right, bottom left -->
<rect class="se-resize" width="4" height="4" style="cursor: nwse-resize"/>
<rect class="se-resize" width="4" height="4" style="cursor: nesw-resize"/>
<rect class="se-resize" width="4" height="4" style="cursor: nwse-resize"/>
<rect class="se-resize" width="4" height="4" style="cursor: nesw-resize"/>
<!-- sides, in the same order: top, right, bottom, left -->
<rect class="se-resize" width="6" height="2.2" rx="1.1" style="cursor: ns-resize"/>
<rect class="se-resize" width="2.2" height="6" rx="1.1" style="cursor: ew-resize"/>
<rect class="se-resize" width="6" height="2.2" rx="1.1" style="cursor: ns-resize"/>
<rect class="se-resize" width="2.2" height="6" rx="1.1" style="cursor: ew-resize"/>
<!-- where a turn around the middle of the box pivots: that middle moves as the shape turns -->
<g id="se-pivot-ghost" class="se-pivot-ghost off"><circle r="1.8" stroke-dasharray=".7 .7"/><path d="M0 -4V-2.2M0 2.2V4M-4 0H-2.2M2.2 0H4"/></g>
<g id="se-pivot-mark" class="se-pivot-mark"><circle r="1.8"/><path d="M0 -4V-2.2M0 2.2V4M-4 0H-2.2M2.2 0H4"/><circle r=".5" fill="var(--md-primary)" stroke="none"/><path id="se-stretch-arrows"/></g>
</g>
</svg>
<button id="se-help-btn" class="se-stage-btn se-help-btn" type="button" title="How the editor works" aria-label="How the editor works" aria-expanded="false" aria-controls="se-help"><span class="material-symbols-outlined" aria-hidden="true">&#xe88e;</span></button>
<div id="se-help" class="se-help" role="note" hidden>
<!-- Keys and Ctrl only where there's likely a keyboard: a primary pointer that can hover (mouse, trackpad). -->
<p><strong>Edit mode:</strong> drag a point to move it, drag a <strong>+</strong> to add one.
<span class="se-kb">With a point selected, arrow keys nudge it, Delete removes it, Esc deselects.
Ctrl+click (or long-press) more points to select several: they move, nudge and delete together.</span>
<span class="se-touch">Tap a point to type its position or remove it; tap empty space to deselect.
Long-press more points to select several: they move together.</span></p>
<p><strong>Move mode:</strong> drag the shape, or a corner to resize it; <span class="se-kb">click a corner to type its
position. Arrow keys move the shape, Delete removes it.</span><span class="se-touch">tap a corner to type its position.</span>
The first card, <strong>Global</strong>, moves, turns, resizes or flips the whole shape at once; its reset button
takes that back.</p>
<p><strong>Several shapes:</strong> <span class="se-kb">Ctrl+click (or long-press) a shape or its card to move them
together; Esc stops.</span><span class="se-touch">long-press a shape or its card to move them together, then tap
cards to add or remove more. Tap outside the group to stop.</span></p>
<p><strong>Saving:</strong> the save button on a card keeps that shape (on Global, the whole shape) under
Custom, above the editor and in Add a shape. It stays in this browser.</p>
</div>
<div id="se-fit-tools" class="se-stage-tools" hidden>
<button id="se-fit-shape" class="se-stage-btn" type="button" title="Fit this shape to the box" aria-label="Fit this shape to the box"><span class="material-symbols-outlined" aria-hidden="true">&#xea10;</span></button>
<button id="se-fit-quarter" class="se-stage-btn" type="button" title="Fit this shape into the top-left quarter" aria-label="Fit this shape into the top-left quarter"><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="3.5" y="3.5" width="17" height="17" rx="2" fill="none" stroke="currentColor" stroke-width="1.8" stroke-dasharray="3 2"/><rect x="5.5" y="5.5" width="6.5" height="6.5" rx="1.5" fill="currentColor"/></svg></button>
<button id="se-center" class="se-stage-btn" type="button" title="Centre this shape" aria-label="Centre this shape"><span class="material-symbols-outlined" aria-hidden="true">&#xe3dc;</span></button>
<button id="se-flip" class="se-stage-btn" type="button" title="Flip left to right, around the pivot" aria-label="Flip left to right"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3v18" fill="none" stroke="currentColor" stroke-width="1.8" stroke-dasharray="2.5 2.5"/><path d="M9.5 6 3.5 18h6z" fill="currentColor"/><path d="M14.5 6l6 12h-6z" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg></button>
<button id="se-unstretch" class="se-stage-btn" type="button" title="Remove the stretch (shrinks the long side)" aria-label="Remove the stretch" hidden><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3v18" stroke-dasharray="2.5 2.5"/><path d="M3 12h6M6.5 9.5 9 12l-2.5 2.5M21 12h-6M17.5 9.5 15 12l2.5 2.5"/></svg></button>
</div>
<div id="se-mirror" class="se-mirror" role="group" aria-label="Mirror" hidden>
<button type="button" data-mirror="none" title="No mirroring" aria-label="No mirroring"><svg viewBox="0 0 20 20" fill="currentColor"><path d="M6 4l6 6-6 6z"/></svg></button>
<button type="button" data-mirror="h" title="Mirror left and right" aria-label="Mirror left and right"><svg viewBox="0 0 20 20" fill="currentColor"><path d="M10 2v16" stroke="currentColor" stroke-width="1.2" stroke-dasharray="2 1.6" fill="none"/><path d="M2 5l5 5-5 5zM18 5l-5 5 5 5z"/></svg></button>
<button type="button" data-mirror="v" title="Mirror top and bottom" aria-label="Mirror top and bottom"><svg viewBox="0 0 20 20" fill="currentColor"><path d="M2 10h16" stroke="currentColor" stroke-width="1.2" stroke-dasharray="2 1.6" fill="none"/><path d="M5 2l5 5 5-5zM5 18l5-5 5 5z"/></svg></button>
<button type="button" data-mirror="both" title="Mirror both ways" aria-label="Mirror both ways"><svg viewBox="0 0 20 20" fill="currentColor"><path d="M10 2v16M2 10h16" stroke="currentColor" stroke-width="1.2" stroke-dasharray="2 1.6" fill="none"/><path d="M2 2h5l-5 5zM18 2h-5l5 5zM2 18h5l-5-5zM18 18h-5l5-5z"/></svg></button>
<span class="se-pivot" role="group" aria-label="Centre point, for rotating and centring">
<button type="button" data-pivot="box" title="Rotate and centre around the middle of the box" aria-label="Middle of the box"><svg viewBox="0 0 24 24"><rect x="3.5" y="3.5" width="17" height="17" rx="2" fill="none" stroke="currentColor" stroke-width="1.8" stroke-dasharray="3 2"/><circle cx="12" cy="12" r="2.4" fill="currentColor"/></svg></button>
<span class="se-corner-wrap">
<button type="button" data-pivot="corner" title="Rotate and centre around a corner of the box" aria-label="A corner of the box"><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="3.5" y="3.5" width="17" height="17" rx="2" fill="none" stroke="currentColor" stroke-width="1.8" stroke-dasharray="3 2"/><circle id="se-corner-dot" cx="6" cy="6" r="2.6" fill="currentColor"/></svg></button>
<span id="se-corner-pick" class="se-corner-pick" role="group" aria-label="Which corner" hidden>
<button type="button" data-corner="0" aria-label="Top left corner"><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="3.5" y="3.5" width="17" height="17" rx="2" fill="none" stroke="currentColor" stroke-width="1.8" stroke-dasharray="3 2"/><circle cx="6" cy="6" r="2.6" fill="currentColor"/></svg></button><button type="button" data-corner="1" aria-label="Top right corner"><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="3.5" y="3.5" width="17" height="17" rx="2" fill="none" stroke="currentColor" stroke-width="1.8" stroke-dasharray="3 2"/><circle cx="18" cy="6" r="2.6" fill="currentColor"/></svg></button><button type="button" data-corner="3" aria-label="Bottom left corner"><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="3.5" y="3.5" width="17" height="17" rx="2" fill="none" stroke="currentColor" stroke-width="1.8" stroke-dasharray="3 2"/><circle cx="6" cy="18" r="2.6" fill="currentColor"/></svg></button><button type="button" data-corner="2" aria-label="Bottom right corner"><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="3.5" y="3.5" width="17" height="17" rx="2" fill="none" stroke="currentColor" stroke-width="1.8" stroke-dasharray="3 2"/><circle cx="18" cy="18" r="2.6" fill="currentColor"/></svg></button>
</span>
</span>
<button type="button" data-pivot="mass" title="Rotate and centre around the centre of mass" aria-label="Centre of mass"><svg viewBox="0 0 24 24"><path d="M12 3.5 21 19.5H3Z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><circle cx="12" cy="14.2" r="1.6" fill="currentColor"/><path d="M12 10.6v1.6M12 16.2v1.6M8.4 14.2h1.6M14 14.2h1.6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg></button>
</span>
</div>
<div id="se-rot-pill" class="se-pill" role="group" aria-label="Position and rotation" hidden>
<button id="se-pos-mode" type="button" aria-pressed="true" title="Position of the centre point" aria-label="Position"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" aria-hidden="true"><circle cx="12" cy="12" r="4.5"/><path d="M12 2.5v4M12 17.5v4M2.5 12h4M17.5 12h4"/><circle cx="12" cy="12" r="1" fill="currentColor" stroke="none"/></svg></button>
<span id="se-pos-fields" class="se-pill-part">
<label>X <input id="se-px" type="number" step="0.5" inputmode="decimal" aria-label="Centre point X"></label>
<label>Y <input id="se-py" type="number" step="0.5" inputmode="decimal" aria-label="Centre point Y"></label>
</span>
<button id="se-turn-mode" type="button" aria-pressed="false" title="Rotation (tap again to type the angle)" aria-label="Rotation"><span class="material-symbols-outlined" aria-hidden="true">&#xe41a;</span></button>
<input id="se-turn" type="range" min="0" max="359" step="1" aria-label="Rotation in degrees, clockwise"><output id="se-turn-val" for="se-turn"></output>
<label id="se-turn-type" hidden><input id="se-turn-num" type="number" step="1" aria-label="Rotation in degrees, clockwise">°</label>
</div>
<div id="se-point-pill" class="se-pill" role="group" aria-label="Point" hidden>
<label>X <input id="se-x" type="number" step="0.5" inputmode="decimal"></label>
<label>Y <input id="se-y" type="number" step="0.5" inputmode="decimal"></label>
<button id="se-remove" type="button" title="Remove point" aria-label="Remove point"><span class="material-symbols-outlined" aria-hidden="true">&#xe92e;</span></button>
</div>
</div>
<div id="se-cards" class="se-cards" role="list" aria-label="Shapes"></div>
</div>
<div class="se-panel">
<div class="se-row se-toolbar">
<button id="se-undo" class="se-btn" type="button" title="Undo (Ctrl+Z)">Undo</button>
<button id="se-redo" class="se-btn" type="button" title="Redo (Ctrl+Y or Ctrl+Shift+Z)">Redo</button>
<button id="se-fit" class="se-btn" type="button" title="Scale and centre the shapes until their longest side touches the box (the Global card has its own Fit)">Fit to box</button>
<button id="se-reset" class="se-btn" type="button">Reset</button>
</div>
<p id="se-fixed-note" class="se-hint se-note" hidden>This shape has a fixed outline (ready-made or imported): you can move, resize, stretch and
mirror it, but not edit its points. Generate replaces it with an editable one.</p>
<fieldset id="se-corner-set">
<legend><span id="se-corner-legend">All corners</span>
<button id="se-corner-type" class="se-corner-type" type="button" aria-pressed="false" title="Type the values instead" aria-label="Type the values instead"><span class="se-ct-num" aria-hidden="true">123</span><svg class="se-ct-slide" viewBox="0 0 24 24" aria-hidden="true"><path d="M3 12h18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><circle cx="9" cy="12" r="4" fill="currentColor"/></svg></button></legend>
<label for="se-size">Size <span id="se-size-v"></span><span class="se-num"><input id="se-size-n" type="number" min="0" max="500" step="0.1" placeholder="mixed" aria-label="Corner size"><span class="se-unit"></span></span></label>
<div id="se-size-track" class="se-size-track"><input id="se-size" type="range" min="0" max="50" step="0.1"></div>
<p id="se-size-warn" class="se-size-warn" style="visibility: hidden"></p>
<label for="se-smooth">Smoothing <span id="se-smooth-v"></span><span class="se-num"><input id="se-smooth-n" type="number" min="0" max="100" step="1" placeholder="mixed" aria-label="Smoothing, in percent"><span class="se-unit">%</span></span></label>
<input id="se-smooth" type="range" min="0" max="100" step="1">
<div class="se-scale"><span>Cut</span><span>Round</span><span>Squircle</span></div>
<div class="se-row"><button id="se-apply-all" class="se-btn" type="button">Apply to all corners</button></div>
</fieldset>
<fieldset>
<legend>Grid</legend>
<label>Show grid <input id="se-show-grid" type="checkbox" checked></label>
<label>Snap to grid <input id="se-snap" type="checkbox" checked></label>
<label>Step
<select id="se-step">
<option>2.5</option><option selected>5</option><option>10</option><option>12.5</option><option>25</option>
</select>
</label>
</fieldset>
<fieldset>
<legend>Polygon / star</legend>
<label>Sides <input id="se-sides" type="number" min="3" max="24" value="6"></label>
<label>Inner radius % <input id="se-inner" type="number" min="10" max="100" value="100"></label>
<label>Rotation° <input id="se-rot" type="number" step="15" value="0"></label>
<label>Tip corner size <input id="se-gen-r" type="number" min="0" max="50" step="1" value="14"></label>
<label>Inner corner size <input id="se-gen-ir" type="number" min="0" max="50" step="1" value="6"></label>
<label>Smoothing % <input id="se-gen-s" type="number" min="0" max="100" step="5" value="50"></label>
<div class="se-row"><button id="se-gen" class="se-btn" type="button">Generate</button></div>
</fieldset>
</div>
</div>
<h2>Result</h2>
<div class="se-result">
<div class="se-previews" aria-hidden="true">
<svg width="0" height="0"><defs><linearGradient id="se-icon-fill" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#C1556A"/><stop offset="1" stop-color="#962CA0"/></linearGradient></defs></svg>
<svg class="se-preview" width="72" height="72" viewBox="0 0 100 100"><path/></svg>
<svg class="se-preview" width="48" height="48" viewBox="0 0 100 100"><path/></svg>
<svg class="se-preview" width="32" height="32" viewBox="0 0 100 100"><path/></svg>
</div>
<div>
<textarea id="se-out" readonly aria-label="Path data" spellcheck="false"></textarea>
<p id="se-warn" class="se-warn" hidden></p>
<p id="se-note" class="se-note-out" hidden></p>
<div class="se-actions">
<a id="se-use" class="btn-filled" href="#">Use in Murine</a>
<button id="se-share-open" class="btn-outlined" type="button" aria-haspopup="dialog">Share</button>
</div>
</div>
</div>
<details class="se-docs">
<summary>Link format</summary>
<p>An edit link opens an icon in this editor. It's the editor's URL with the icon in its fragment, in one of two forms:</p>
<ul>
<li><code>https://www.murinelauncher.app/icon-shapes/#edit=DATA</code>: the icon's JSON (below), compressed with raw
DEFLATE (no zlib header or checksum: <code>zlib.compressobj(9, zlib.DEFLATED, -15)</code> in Python,
<code>new CompressionStream('deflate-raw')</code> in JavaScript), then base64url-encoded without <code>=</code> padding.
This is the preferred form: it's what <strong>Share</strong> produces and it keeps links short.</li>
<li><code>https://www.murinelauncher.app/icon-shapes/#edit-raw=JSON</code>: the same JSON, uncompressed and
percent-encoded (<code>encodeURIComponent</code> in JavaScript, <code>urllib.parse.quote(json, safe='')</code> in
Python). Use it when compressing isn't possible.</li>
</ul>
<p>Opening either one loads the icon (the previous one stays a step back in Undo); <strong>Share</strong> then gives the
short link and the link that sends the shape to Murine. A link that doesn't follow the format below is ignored, and
the editor keeps the icon it had.</p>
<h3>Checking a link</h3>
<p>A link written by hand or by a script is worth opening before it's shared: the canvas shows the shape, and anything the
launcher can't use (shapes that don't touch, crossing edges) is reported in red under <strong>Result</strong>, which
also disables <strong>Use in Murine</strong>. Other messages are just information: a corner whose size shrinks its
neighbours, for instance, is fine if the shape looks as intended. Small fixes can be made right there. <strong>Share</strong> then has the finished links: the address of its <strong>Edit link</strong> entry is the
compressed edit link (clicking the entry copies it), and <strong>Use in Murine</strong> points to the import link.</p>
<h3>The JSON</h3>
<pre><code>{"shapes": [
{"pts": [{"x": 50, "y": 4, "r": 12, "s": 0.6}, {"x": 97.6, "y": 38.5, "r": 12, "s": 0.6}, …],
"mirror": "none"}
]}</code></pre>
<ul>
<li>The icon is drawn in a 100 × 100 box: <code>x</code> grows to the right, <code>y</code> grows <em>down</em>, and
<code>(0, 0)</code> is the top-left corner. The launcher scales this box to the icon's size, so the shape should fill it
and stay inside it.</li>
<li><code>shapes</code>: 1 to 20 shapes. The icon is all of them together, so they must overlap or touch and make a single
outline; gaps they enclose are filled in.</li>
<li><code>pts</code>: the corners of one shape, 3 to 200, in order around it (either direction). Its edges must not cross
each other.</li>
<li><code>r</code> (optional, default 0): how far along each edge the corner's rounding reaches, in box units; 0 is a sharp
corner. Where edges are too short for it, it's reduced automatically (along with its neighbours'), which is fine
as long as the result looks right.</li>
<li><code>s</code> (optional, default 0): the rounding's profile, 0 to 1: 0 cuts the corner straight, 0.5 is a circular
arc, 1 is the smoothest (squircle-like).</li>
<li><code>mirror</code> (optional, default <code>"none"</code>): <code>"h"</code> adds a copy mirrored left to right
(around <code>x = 50</code>), <code>"v"</code> one mirrored top to bottom (around <code>y = 50</code>),
<code>"both"</code> three copies, one per quarter.</li>
</ul>
<p>A shape can also be an SVG outline instead of corners, for free-form curves. It can be mixed with corner shapes in the
same icon, and takes <code>mirror</code> too:</p>
<pre><code>{"path": "M 50 0 C 77.6 0 100 22.4 100 50 C 100 77.6 77.6 100 50 100 C 22.4 100 0 77.6 0 50 C 0 22.4 22.4 0 50 0 Z"}</code></pre>
<ul>
<li><code>path</code>: one closed outline in the same 100 × 100 box, written in a strict form: it starts with
<code>M x y</code>, continues with <code>L x y</code> (lines) and <code>C x1 y1 x2 y2 x y</code> (cubic curves) only,
and ends with <code>Z</code>; absolute coordinates, every command letter and number separated by a single space, no
commas. Other commands (relative ones, <code>H</code>/<code>V</code>, <code>S</code>, <code>Q</code>, <code>A</code>)
and holes or extra outlines aren't accepted, and the outline must not cross itself. To use an existing SVG file,
<strong>Add a shape → Import SVG</strong> converts it, and <strong>Share</strong> then gives the link.</li>
<li><code>a</code>, <code>x</code>, <code>y</code> (optional): the outline is drawn scaled by <code>a</code> (default 1)
and then moved by <code>x</code>, <code>y</code> (default 0).</li>
<li><code>ang</code> (optional, default 0): a turn in degrees, clockwise, around the outline's centre.</li>
</ul>
<p>Next to <code>shapes</code>, an optional <code>whole</code> transforms the icon as a whole, after its shapes are
merged (mirrored copies included), without changing them: <code>{"shapes": [...], "whole": {"a": 0.9, "x": 5, "y": 5,
"ang": 30, "flip": true}}</code>. In order: <code>flip</code> (optional, default false) turns the icon over left to
right within the 100 × 100 box; <code>a</code> (default 1) scales it and <code>x</code>, <code>y</code> (default 0)
move it; <code>ang</code> (default 0) turns it clockwise, in degrees, around the middle of where the box now is.
In the editor it's the <strong>Global</strong> card.</p>
<h3>Example</h3>
<p>A rounded pentagon: <code>{"shapes":[{"pts":[{"x":50,"y":4,"r":12,"s":0.6},{"x":97.6,"y":38.5,"r":12,"s":0.6},{"x":79.4,"y":94.5,"r":12,"s":0.6},{"x":20.6,"y":94.5,"r":12,"s":0.6},{"x":2.4,"y":38.5,"r":12,"s":0.6}]}]}</code></p>
<ul class="se-docs-links">
<li>Compressed: <a href="#edit=q1YqzkgsSC1WsoquVioogdAVSlamBnoGOkqVSlYmILpIycrQSEcJKGugZ1arA1Zhaa5nBlZhbKFnil2JuaWeCViJpQkuJUYGUFPwKIEags2eWCAEAA">https://www.murinelauncher.app/icon-shapes/#edit=q1YqzkgsSC1WsoquVioogdAVSlamBnoGOkqVSlYmILpIycrQSEcJKGugZ1arA1Zhaa5nBlZhbKFnil2JuaWeCViJpQkuJUYGUFPwKIEags2eWCAEAA</a></li>
<li>Raw: <a href="#edit-raw=%7B%22shapes%22%3A%5B%7B%22pts%22%3A%5B%7B%22x%22%3A50.0%2C%22y%22%3A4.0%2C%22r%22%3A12%2C%22s%22%3A0.6%7D%2C%7B%22x%22%3A97.6%2C%22y%22%3A38.5%2C%22r%22%3A12%2C%22s%22%3A0.6%7D%2C%7B%22x%22%3A79.4%2C%22y%22%3A94.5%2C%22r%22%3A12%2C%22s%22%3A0.6%7D%2C%7B%22x%22%3A20.6%2C%22y%22%3A94.5%2C%22r%22%3A12%2C%22s%22%3A0.6%7D%2C%7B%22x%22%3A2.4%2C%22y%22%3A38.5%2C%22r%22%3A12%2C%22s%22%3A0.6%7D%5D%7D%5D%7D">https://www.murinelauncher.app/icon-shapes/#edit-raw=%7B%22shapes%22%3A%5B%7B%22pts%22%3A%5B%7B%22x%22%3A50.0%2C%22y%22%3A4.0%2C%22r%22%3A12%2C%22s%22%3A0.6%7D%2C%7B%22x%22%3A97.6%2C%22y%22%3A38.5%2C%22r%22%3A12%2C%22s%22%3A0.6%7D%2C%7B%22x%22%3A79.4%2C%22y%22%3A94.5%2C%22r%22%3A12%2C%22s%22%3A0.6%7D%2C%7B%22x%22%3A20.6%2C%22y%22%3A94.5%2C%22r%22%3A12%2C%22s%22%3A0.6%7D%2C%7B%22x%22%3A2.4%2C%22y%22%3A38.5%2C%22r%22%3A12%2C%22s%22%3A0.6%7D%5D%7D%5D%7D</a></li>
</ul>
</details>
<div class="contact-modal-overlay" id="se-share" aria-hidden="true">
<div class="contact-modal" role="dialog" aria-modal="true" aria-labelledby="se-share-title">
<div class="contact-modal-header">
<h2 id="se-share-title">Share shape</h2>
<button class="contact-modal-close" type="button" aria-label="Close" data-close>
<span class="material-symbols-outlined">&#xE5CD;</span> <!-- close -->
</button>
</div>
<p class="contact-modal-desc">Copy the shape in the form you need.</p>
<div class="contact-cards">
<!-- a link, so its address is the edit link itself (copy link address works too); a click copies it -->
<a class="contact-card se-share-card" id="se-share-edit" href="#" data-copy="edit">
<span class="contact-card-icon"><span class="material-symbols-outlined">&#xE40A;</span></span> <!-- palette -->
<span class="contact-card-info"><span class="se-card-title">Edit link</span><span class="se-card-sub">Reopen this shape in the editor, on any device</span></span>
</a>
<button class="contact-card se-share-card" type="button" data-copy="import" id="se-share-import">
<span class="contact-card-icon"><span class="material-symbols-outlined">&#xE32C;</span></span> <!-- smartphone -->
<span class="contact-card-info"><span class="se-card-title">Import link</span><span class="se-card-sub">Open it on your phone to apply the shape in Murine</span></span>
</button>
<button class="contact-card se-share-card" type="button" data-copy="path">
<span class="contact-card-icon"><span class="material-symbols-outlined">&#xE86F;</span></span> <!-- code -->
<span class="contact-card-info"><span class="se-card-title">Path data</span><span class="se-card-sub">The raw SVG path, 0 to 100 box</span></span>
</button>
<button class="contact-card se-share-card" type="button" id="se-share-native" hidden>
<span class="contact-card-icon"><svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.31 2.92 2.92 2.92s2.92-1.31 2.92-2.92-1.31-2.92-2.92-2.92z"/></svg></span>
<span class="contact-card-info"><span class="se-card-title">Send edit link</span><span class="se-card-sub">Through another app</span></span>
</button>
<!-- typing "devmode" with the dialog open shows this row (see devKeys in shape-editor.js) -->
<div id="se-dev" class="se-dev" hidden>
<button class="contact-card se-share-card" type="button" data-copy="json" data-done="Copied">
<span class="material-symbols-outlined" aria-hidden="true">&#xE86F;</span>
<span class="se-card-title">JSON</span><span class="se-card-sub">Link data</span>
</button>
<button class="contact-card se-share-card" type="button" data-copy="yaml" data-done="Copied">
<span class="material-symbols-outlined" aria-hidden="true">&#xE86F;</span>
<span class="se-card-title">YAML</span><span class="se-card-sub">Preset entry</span>
</button>
<button class="contact-card se-share-card" type="button" data-copy="yamljson" data-done="Copied">
<span class="material-symbols-outlined" aria-hidden="true">&#xE86F;</span>
<span class="se-card-title">YAML-JSON</span><span class="se-card-sub">Inline preset</span>
</button>
</div>
</div>
</div>
</div>
<div class="contact-modal-overlay" id="se-add" aria-hidden="true">
<div class="contact-modal" role="dialog" aria-modal="true" aria-labelledby="se-add-title">
<div class="contact-modal-header">
<h2 id="se-add-title">Add a shape</h2>
<button class="contact-modal-close" type="button" aria-label="Close" data-close>
<span class="material-symbols-outlined">&#xE5CD;</span> <!-- close -->
</button>
</div>
<p id="se-add-desc" class="contact-modal-desc"></p>
<div class="se-import">
<div class="se-row">
<span id="se-add-lib" class="se-seg" role="group" aria-label="Show in the grid">
<button type="button" data-lib="presets">Presets</button><button type="button" data-lib="custom" title="Shapes you saved, kept in this browser">Custom</button>
</span>
<button id="se-import-btn" class="se-btn" type="button" title="Import the shapes of an SVG file">Import SVG…</button>
</div>
<input id="se-import-file" type="file" accept=".svg,image/svg+xml" hidden>
<p id="se-import-msg" class="se-hint" role="status" hidden></p>
<div id="se-import-choice" hidden>
<p id="se-import-count" class="se-hint"></p>
<div id="se-import-view" class="se-seg" role="group" aria-label="Show in the grid">
<button type="button" data-view="pieces">Pieces</button><button type="button" data-view="merged">Merged</button>
</div>
<div class="se-row">
<button id="se-import-separate" class="se-btn" type="button">Add all separately</button>
<button id="se-import-merge" class="se-btn" type="button">Merge all</button>
</div>
</div>
</div>
<div id="se-add-grid" class="se-presets"></div>
<div id="se-pick-bar" class="se-pick-bar" hidden>
<span id="se-pick-count"></span>
<span class="se-pick-actions">
<button id="se-pick-clear" class="se-btn" type="button">Cancel</button>
<button id="se-pick-merge" class="se-btn" type="button" hidden>Merge</button>
<button id="se-pick-bake" class="se-btn" type="button" hidden>Bake</button>
<button id="se-pick-add" class="se-btn se-btn-primary" type="button">Add</button>
</span>
</div>
</div>
</div>
<div class="contact-modal-overlay" id="se-name" aria-hidden="true">
<div class="contact-modal se-name-modal" role="dialog" aria-modal="true" aria-labelledby="se-name-title">
<div class="contact-modal-header">
<h2 id="se-name-title">Save shape</h2>
<button class="contact-modal-close" type="button" aria-label="Close" data-close>
<span class="material-symbols-outlined">&#xE5CD;</span> <!-- close -->
</button>
</div>
<form id="se-name-form">
<label for="se-name-input" class="se-hint">Name</label>
<input id="se-name-input" class="se-name-input" type="text" maxlength="40" required autocomplete="off">
<p id="se-name-msg" class="se-hint" role="status" hidden></p>
<div class="se-pick-actions">
<button class="se-btn" type="button" data-close>Cancel</button>
<button id="se-name-ok" class="se-btn se-btn-primary" type="submit">Save</button>
</div>
</form>
</div>
</div>
<svg id="se-measure" viewBox="0 0 100 100" aria-hidden="true"><path id="se-measure-path"/></svg>
<script src="{{ '/assets/js/polygon-clipping.min.js' | relative_url }}?v={{ site.version }}"></script>
<script id="se-presets-data" type="application/json">{{ site.data.icon_shapes | jsonify }}</script>
<script src="{{ '/assets/js/shape-editor.js' | relative_url }}?v={{ site.version }}"></script>