@junoput01/junoui 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +251 -0
- package/README.md +23 -19
- package/dist/css/juno-custom-media.css +32 -0
- package/dist/css/juno.css +1355 -40
- package/dist/icons/inline.js +22 -0
- package/docs/accessibility.md +188 -0
- package/docs/boot-shell.md +295 -0
- package/docs/components/README.md +71 -0
- package/docs/components/accordion.md +49 -0
- package/docs/components/alert.md +46 -0
- package/docs/components/avatar.md +45 -0
- package/docs/components/badge.md +35 -0
- package/docs/components/breadcrumb.md +31 -0
- package/docs/components/button.md +32 -0
- package/docs/components/card.md +41 -0
- package/docs/components/checkbox.md +41 -0
- package/docs/components/chip.md +44 -0
- package/docs/components/divider.md +32 -0
- package/docs/components/dock.md +215 -0
- package/docs/components/drawer.md +100 -0
- package/docs/components/field.md +44 -0
- package/docs/components/fold-slot.md +31 -0
- package/docs/components/gauge.md +48 -0
- package/docs/components/icon-loader.md +122 -0
- package/docs/components/icon.md +70 -0
- package/docs/components/input.md +45 -0
- package/docs/components/list.md +76 -0
- package/docs/components/load-state.md +131 -0
- package/docs/components/loader.md +88 -0
- package/docs/components/menu.md +54 -0
- package/docs/components/modal.md +52 -0
- package/docs/components/navbar.md +53 -0
- package/docs/components/pagination.md +42 -0
- package/docs/components/pillbar.md +211 -0
- package/docs/components/popover.md +43 -0
- package/docs/components/rail.md +54 -0
- package/docs/components/readout.md +39 -0
- package/docs/components/reload.md +41 -0
- package/docs/components/segmented.md +46 -0
- package/docs/components/select.md +33 -0
- package/docs/components/skeleton.md +45 -0
- package/docs/components/slider.md +48 -0
- package/docs/components/spark.md +51 -0
- package/docs/components/status.md +30 -0
- package/docs/components/stepper.md +46 -0
- package/docs/components/switch.md +38 -0
- package/docs/components/table.md +126 -0
- package/docs/components/tabs.md +61 -0
- package/docs/components/thumb.md +81 -0
- package/docs/components/toast.md +48 -0
- package/docs/components/toggle-button.md +40 -0
- package/docs/components/tooltip.md +63 -0
- package/docs/design-guidelines.md +173 -0
- package/docs/flutter.md +42 -0
- package/docs/getting-started.md +93 -0
- package/docs/integration.md +95 -0
- package/docs/ios-conformance.md +224 -0
- package/docs/layout.md +271 -0
- package/docs/native.md +52 -0
- package/docs/roadmap.md +106 -0
- package/docs/tokens-reference.md +223 -0
- package/docs/web.md +95 -0
- package/package.json +7 -3
- package/src/css/base.css +171 -1
- package/src/css/components/dock.css +335 -0
- package/src/css/components/drawer.css +40 -3
- package/src/css/components/fold-slot.css +44 -0
- package/src/css/components/icon-loader.css +67 -0
- package/src/css/components/icon.css +6 -4
- package/src/css/components/load-state.css +136 -0
- package/src/css/components/loader.css +6 -0
- package/src/css/components/menu.css +4 -0
- package/src/css/components/modal.css +24 -3
- package/src/css/components/navbar.css +5 -1
- package/src/css/components/pillbar.css +222 -7
- package/src/css/components/rail.css +8 -0
- package/src/css/components/reload.css +48 -0
- package/src/css/components/skeleton.css +41 -13
- package/src/css/components/tabs.css +5 -0
- package/src/css/components/thumb.css +63 -1
- package/src/css/components/toast.css +5 -1
- package/src/css/density.css +22 -0
- package/src/css/layout.css +95 -5
- package/src/css/utilities.css +4 -1
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// junoui — inline icon sprite injector. Generated; do not edit.
|
|
2
|
+
// Fixes Safari dropping external <use href="file.svg#id"> refs: this injects
|
|
3
|
+
// the sprite into the document so you reference icons same-document instead:
|
|
4
|
+
// import '@junoput01/junoui/icons/inline';
|
|
5
|
+
// <svg class="juno-icon"><use href="#juno-i-gear" /></svg>
|
|
6
|
+
const SPRITE = "<svg xmlns=\"http://www.w3.org/2000/svg\" style=\"display:none\" fill=\"currentColor\">\n <symbol id=\"juno-i-arrow-clockwise\" viewBox=\"0 0 256 256\"><path d=\"M244,56v48a12,12,0,0,1-12,12H184a12,12,0,1,1,0-24H201.1l-19-17.38c-.13-.12-.26-.24-.38-.37A76,76,0,1,0,127,204h1a75.53,75.53,0,0,0,52.15-20.72,12,12,0,0,1,16.49,17.45A99.45,99.45,0,0,1,128,228h-1.37A100,100,0,1,1,198.51,57.06L220,76.72V56a12,12,0,0,1,24,0Z\"/></symbol>\n <symbol id=\"juno-i-arrow-down\" viewBox=\"0 0 256 256\"><path d=\"M208.49,152.49l-72,72a12,12,0,0,1-17,0l-72-72a12,12,0,0,1,17-17L116,187V40a12,12,0,0,1,24,0V187l51.51-51.52a12,12,0,0,1,17,17Z\"/></symbol>\n <symbol id=\"juno-i-arrow-left\" viewBox=\"0 0 256 256\"><path d=\"M228,128a12,12,0,0,1-12,12H69l51.52,51.51a12,12,0,0,1-17,17l-72-72a12,12,0,0,1,0-17l72-72a12,12,0,0,1,17,17L69,116H216A12,12,0,0,1,228,128Z\"/></symbol>\n <symbol id=\"juno-i-arrow-right\" viewBox=\"0 0 256 256\"><path d=\"M224.49,136.49l-72,72a12,12,0,0,1-17-17L187,140H40a12,12,0,0,1,0-24H187L135.51,64.48a12,12,0,0,1,17-17l72,72A12,12,0,0,1,224.49,136.49Z\"/></symbol>\n <symbol id=\"juno-i-arrow-up\" viewBox=\"0 0 256 256\"><path d=\"M208.49,120.49a12,12,0,0,1-17,0L140,69V216a12,12,0,0,1-24,0V69L64.49,120.49a12,12,0,0,1-17-17l72-72a12,12,0,0,1,17,0l72,72A12,12,0,0,1,208.49,120.49Z\"/></symbol>\n <symbol id=\"juno-i-arrows-clockwise\" viewBox=\"0 0 256 256\"><path d=\"M228,48V96a12,12,0,0,1-12,12H168a12,12,0,0,1,0-24h19l-7.8-7.8a75.55,75.55,0,0,0-53.32-22.26h-.43A75.49,75.49,0,0,0,72.39,75.57,12,12,0,1,1,55.61,58.41a99.38,99.38,0,0,1,69.87-28.47H126A99.42,99.42,0,0,1,196.2,59.23L204,67V48a12,12,0,0,1,24,0ZM183.61,180.43a75.49,75.49,0,0,1-53.09,21.63h-.43A75.55,75.55,0,0,1,76.77,179.8L69,172H88a12,12,0,0,0,0-24H40a12,12,0,0,0-12,12v48a12,12,0,0,0,24,0V189l7.8,7.8A99.42,99.42,0,0,0,130,226.06h.56a99.38,99.38,0,0,0,69.87-28.47,12,12,0,0,0-16.78-17.16Z\"/></symbol>\n <symbol id=\"juno-i-arrows-out\" viewBox=\"0 0 256 256\"><path d=\"M220,48V96a12,12,0,0,1-24,0V77l-35.51,35.52a12,12,0,0,1-17-17L179,60H160a12,12,0,0,1,0-24h48A12,12,0,0,1,220,48ZM95.51,143.51,60,179V160a12,12,0,0,0-24,0v48a12,12,0,0,0,12,12H96a12,12,0,0,0,0-24H77l35.52-35.51a12,12,0,0,0-17-17ZM208,148a12,12,0,0,0-12,12v19l-35.51-35.52a12,12,0,0,0-17,17L179,196H160a12,12,0,0,0,0,24h48a12,12,0,0,0,12-12V160A12,12,0,0,0,208,148ZM77,60H96a12,12,0,0,0,0-24H48A12,12,0,0,0,36,48V96a12,12,0,0,0,24,0V77l35.51,35.52a12,12,0,0,0,17-17Z\"/></symbol>\n <symbol id=\"juno-i-bell\" viewBox=\"0 0 256 256\"><path d=\"M225.29,165.93C216.61,151,212,129.57,212,104a84,84,0,0,0-168,0c0,25.58-4.59,47-13.27,61.93A20.08,20.08,0,0,0,30.66,186,19.77,19.77,0,0,0,48,196H84.18a44,44,0,0,0,87.64,0H208a19.77,19.77,0,0,0,17.31-10A20.08,20.08,0,0,0,225.29,165.93ZM128,212a20,20,0,0,1-19.6-16h39.2A20,20,0,0,1,128,212ZM54.66,172C63.51,154,68,131.14,68,104a60,60,0,0,1,120,0c0,27.13,4.48,50,13.33,68Z\"/></symbol>\n <symbol id=\"juno-i-calendar-blank\" viewBox=\"0 0 256 256\"><path d=\"M208,28H188V24a12,12,0,0,0-24,0v4H92V24a12,12,0,0,0-24,0v4H48A20,20,0,0,0,28,48V208a20,20,0,0,0,20,20H208a20,20,0,0,0,20-20V48A20,20,0,0,0,208,28ZM68,52a12,12,0,0,0,24,0h72a12,12,0,0,0,24,0h16V76H52V52ZM52,204V100H204V204Z\"/></symbol>\n <symbol id=\"juno-i-caret-down\" viewBox=\"0 0 256 256\"><path d=\"M216.49,104.49l-80,80a12,12,0,0,1-17,0l-80-80a12,12,0,0,1,17-17L128,159l71.51-71.52a12,12,0,0,1,17,17Z\"/></symbol>\n <symbol id=\"juno-i-caret-left\" viewBox=\"0 0 256 256\"><path d=\"M168.49,199.51a12,12,0,0,1-17,17l-80-80a12,12,0,0,1,0-17l80-80a12,12,0,0,1,17,17L97,128Z\"/></symbol>\n <symbol id=\"juno-i-caret-right\" viewBox=\"0 0 256 256\"><path d=\"M184.49,136.49l-80,80a12,12,0,0,1-17-17L159,128,87.51,56.49a12,12,0,1,1,17-17l80,80A12,12,0,0,1,184.49,136.49Z\"/></symbol>\n <symbol id=\"juno-i-caret-up\" viewBox=\"0 0 256 256\"><path d=\"M216.49,168.49a12,12,0,0,1-17,0L128,97,56.49,168.49a12,12,0,0,1-17-17l80-80a12,12,0,0,1,17,0l80,80A12,12,0,0,1,216.49,168.49Z\"/></symbol>\n <symbol id=\"juno-i-chat-circle\" viewBox=\"0 0 256 256\"><path d=\"M128,20A108,108,0,0,0,31.85,177.23L21,209.66A20,20,0,0,0,46.34,235l32.43-10.81A108,108,0,1,0,128,20Zm0,192a84,84,0,0,1-42.06-11.27,12,12,0,0,0-6-1.62,12.1,12.1,0,0,0-3.8.62l-29.79,9.93,9.93-29.79a12,12,0,0,0-1-9.81A84,84,0,1,1,128,212Z\"/></symbol>\n <symbol id=\"juno-i-check-circle\" viewBox=\"0 0 256 256\"><path d=\"M176.49,95.51a12,12,0,0,1,0,17l-56,56a12,12,0,0,1-17,0l-24-24a12,12,0,1,1,17-17L112,143l47.51-47.52A12,12,0,0,1,176.49,95.51ZM236,128A108,108,0,1,1,128,20,108.12,108.12,0,0,1,236,128Zm-24,0a84,84,0,1,0-84,84A84.09,84.09,0,0,0,212,128Z\"/></symbol>\n <symbol id=\"juno-i-check\" viewBox=\"0 0 256 256\"><path d=\"M232.49,80.49l-128,128a12,12,0,0,1-17,0l-56-56a12,12,0,1,1,17-17L96,183,215.51,63.51a12,12,0,0,1,17,17Z\"/></symbol>\n <symbol id=\"juno-i-clock\" viewBox=\"0 0 256 256\"><path d=\"M128,20A108,108,0,1,0,236,128,108.12,108.12,0,0,0,128,20Zm0,192a84,84,0,1,1,84-84A84.09,84.09,0,0,1,128,212Zm68-84a12,12,0,0,1-12,12H128a12,12,0,0,1-12-12V72a12,12,0,0,1,24,0v44h44A12,12,0,0,1,196,128Z\"/></symbol>\n <symbol id=\"juno-i-cloud-arrow-down\" viewBox=\"0 0 256 256\"><path d=\"M192.49,167.51a12,12,0,0,1,0,17l-32,32a12,12,0,0,1-17,0l-32-32a12,12,0,1,1,17-17L140,179V128a12,12,0,0,1,24,0v51l11.51-11.52A12,12,0,0,1,192.49,167.51ZM160,36A92.08,92.08,0,0,0,79,84.37,68,68,0,1,0,72,220H84a12,12,0,0,0,0-24H72a44,44,0,0,1-1.81-87.95A91.7,91.7,0,0,0,68,128a12,12,0,0,0,24,0,68,68,0,0,1,136,0,67.27,67.27,0,0,1-7.25,30.59,12,12,0,1,0,21.42,10.82A91.08,91.08,0,0,0,252,128,92.1,92.1,0,0,0,160,36Z\"/></symbol>\n <symbol id=\"juno-i-cloud\" viewBox=\"0 0 256 256\"><path d=\"M160,36A92.09,92.09,0,0,0,79,84.36,68,68,0,1,0,72,220h88a92,92,0,0,0,0-184Zm0,160H72a44,44,0,0,1-1.82-88A91.86,91.86,0,0,0,68,128a12,12,0,0,0,24,0,68,68,0,1,1,68,68Z\"/></symbol>\n <symbol id=\"juno-i-copy\" viewBox=\"0 0 256 256\"><path d=\"M216,28H88A12,12,0,0,0,76,40V76H40A12,12,0,0,0,28,88V216a12,12,0,0,0,12,12H168a12,12,0,0,0,12-12V180h36a12,12,0,0,0,12-12V40A12,12,0,0,0,216,28ZM156,204H52V100H156Zm48-48H180V88a12,12,0,0,0-12-12H100V52H204Z\"/></symbol>\n <symbol id=\"juno-i-cpu\" viewBox=\"0 0 256 256\"><path d=\"M156,88H100a12,12,0,0,0-12,12v56a12,12,0,0,0,12,12h56a12,12,0,0,0,12-12V100A12,12,0,0,0,156,88Zm-12,56H112V112h32Zm88-4H220V116h12a12,12,0,0,0,0-24H220V56a20,20,0,0,0-20-20H164V24a12,12,0,0,0-24,0V36H116V24a12,12,0,0,0-24,0V36H56A20,20,0,0,0,36,56V92H24a12,12,0,0,0,0,24H36v24H24a12,12,0,0,0,0,24H36v36a20,20,0,0,0,20,20H92v12a12,12,0,0,0,24,0V220h24v12a12,12,0,0,0,24,0V220h36a20,20,0,0,0,20-20V164h12a12,12,0,0,0,0-24Zm-36,56H60V60H196Z\"/></symbol>\n <symbol id=\"juno-i-dots-three-vertical\" viewBox=\"0 0 256 256\"><path d=\"M112,60a16,16,0,1,1,16,16A16,16,0,0,1,112,60Zm16,52a16,16,0,1,0,16,16A16,16,0,0,0,128,112Zm0,68a16,16,0,1,0,16,16A16,16,0,0,0,128,180Z\"/></symbol>\n <symbol id=\"juno-i-dots-three\" viewBox=\"0 0 256 256\"><path d=\"M144,128a16,16,0,1,1-16-16A16,16,0,0,1,144,128ZM60,112a16,16,0,1,0,16,16A16,16,0,0,0,60,112Zm136,0a16,16,0,1,0,16,16A16,16,0,0,0,196,112Z\"/></symbol>\n <symbol id=\"juno-i-download\" viewBox=\"0 0 256 256\"><path d=\"M71.51,88.49a12,12,0,0,1,17-17L116,99V24a12,12,0,0,1,24,0V99l27.51-27.52a12,12,0,0,1,17,17l-48,48a12,12,0,0,1-17,0ZM224,116H188a12,12,0,0,0,0,24h32v56H36V140H68a12,12,0,0,0,0-24H32a20,20,0,0,0-20,20v64a20,20,0,0,0,20,20H224a20,20,0,0,0,20-20V136A20,20,0,0,0,224,116Zm-20,52a16,16,0,1,0-16,16A16,16,0,0,0,204,168Z\"/></symbol>\n <symbol id=\"juno-i-envelope\" viewBox=\"0 0 256 256\"><path d=\"M224,44H32A12,12,0,0,0,20,56V192a20,20,0,0,0,20,20H216a20,20,0,0,0,20-20V56A12,12,0,0,0,224,44Zm-96,83.72L62.85,68h130.3ZM92.79,128,44,172.72V83.28Zm17.76,16.28,9.34,8.57a12,12,0,0,0,16.22,0l9.34-8.57L193.15,188H62.85ZM163.21,128,212,83.28v89.44Z\"/></symbol>\n <symbol id=\"juno-i-eye-slash\" viewBox=\"0 0 256 256\"><path d=\"M56.88,31.93A12,12,0,1,0,39.12,48.07l16,17.65C20.67,88.66,5.72,121.58,5,123.13a12.08,12.08,0,0,0,0,9.75c.37.82,9.13,20.26,28.49,39.61C59.37,198.34,92,212,128,212a131.34,131.34,0,0,0,51-10l20.09,22.1a12,12,0,0,0,17.76-16.14ZM128,188c-29.59,0-55.47-10.73-76.91-31.88A130.69,130.69,0,0,1,29.52,128c5.27-9.31,18.79-29.9,42-44.29l90.09,99.11A109.33,109.33,0,0,1,128,188Zm123-55.12c-.36.81-9,20-28,39.16a12,12,0,1,1-17-16.9A130.48,130.48,0,0,0,226.48,128a130.36,130.36,0,0,0-21.57-28.12C183.46,78.73,157.59,68,128,68c-3.35,0-6.7.14-10,.42a12,12,0,1,1-2-23.91c3.93-.34,8-.51,12-.51,36,0,68.63,13.67,94.49,39.52,19.35,19.35,28.11,38.8,28.48,39.61A12.08,12.08,0,0,1,251,132.88Z\"/></symbol>\n <symbol id=\"juno-i-eye\" viewBox=\"0 0 256 256\"><path d=\"M251,123.13c-.37-.81-9.13-20.26-28.48-39.61C196.63,57.67,164,44,128,44S59.37,57.67,33.51,83.52C14.16,102.87,5.4,122.32,5,123.13a12.08,12.08,0,0,0,0,9.75c.37.82,9.13,20.26,28.49,39.61C59.37,198.34,92,212,128,212s68.63-13.66,94.48-39.51c19.36-19.35,28.12-38.79,28.49-39.61A12.08,12.08,0,0,0,251,123.13Zm-46.06,33C183.47,177.27,157.59,188,128,188s-55.47-10.73-76.91-31.88A130.36,130.36,0,0,1,29.52,128,130.45,130.45,0,0,1,51.09,99.89C72.54,78.73,98.41,68,128,68s55.46,10.73,76.91,31.89A130.36,130.36,0,0,1,226.48,128,130.45,130.45,0,0,1,204.91,156.12ZM128,84a44,44,0,1,0,44,44A44.05,44.05,0,0,0,128,84Zm0,64a20,20,0,1,1,20-20A20,20,0,0,1,128,148Z\"/></symbol>\n <symbol id=\"juno-i-file\" viewBox=\"0 0 256 256\"><path d=\"M216.49,79.52l-56-56A12,12,0,0,0,152,20H56A20,20,0,0,0,36,40V216a20,20,0,0,0,20,20H200a20,20,0,0,0,20-20V88A12,12,0,0,0,216.49,79.52ZM160,57l23,23H160ZM60,212V44h76V92a12,12,0,0,0,12,12h48V212Z\"/></symbol>\n <symbol id=\"juno-i-film-strip\" viewBox=\"0 0 256 256\"><path d=\"M216,36H40A20,20,0,0,0,20,56V200a20,20,0,0,0,20,20H216a20,20,0,0,0,20-20V56A20,20,0,0,0,216,36ZM44,100h72v56H44Zm96-24V60h24V76Zm-24,0H92V60h24Zm0,104v16H92V180Zm24,0h24v16H140Zm0-24V100h72v56Zm72-80H188V60h24ZM68,60V76H44V60ZM44,180H68v16H44Zm144,16V180h24v16Z\"/></symbol>\n <symbol id=\"juno-i-floppy-disk\" viewBox=\"0 0 256 256\"><path d=\"M222.14,69.17,186.83,33.86A19.86,19.86,0,0,0,172.69,28H48A20,20,0,0,0,28,48V208a20,20,0,0,0,20,20H208a20,20,0,0,0,20-20V83.31A19.86,19.86,0,0,0,222.14,69.17ZM164,204H92V160h72Zm40,0H188V156a20,20,0,0,0-20-20H88a20,20,0,0,0-20,20v48H52V52H171l33,33ZM164,84a12,12,0,0,1-12,12H96a12,12,0,0,1,0-24h56A12,12,0,0,1,164,84Z\"/></symbol>\n <symbol id=\"juno-i-folder\" viewBox=\"0 0 256 256\"><path d=\"M216,68H133.39l-26-29.29a20,20,0,0,0-15-6.71H40A20,20,0,0,0,20,52V200.62A19.41,19.41,0,0,0,39.38,220H216.89A19.13,19.13,0,0,0,236,200.89V88A20,20,0,0,0,216,68ZM44,56H90.61l10.67,12H44ZM212,196H44V92H212Z\"/></symbol>\n <symbol id=\"juno-i-funnel\" viewBox=\"0 0 256 256\"><path d=\"M234.29,47.91A20,20,0,0,0,216,36H40A20,20,0,0,0,25.2,69.45l.12.14L92,140.75V216a20,20,0,0,0,31.1,16.64l32-21.33A20,20,0,0,0,164,194.66V140.75l66.67-71.16.12-.14A20,20,0,0,0,234.29,47.91Zm-91,79.89A12,12,0,0,0,140,136v56.52l-24,16V136a12,12,0,0,0-3.25-8.2L49.23,60H206.77Z\"/></symbol>\n <symbol id=\"juno-i-gear\" viewBox=\"0 0 256 256\"><path d=\"M128,76a52,52,0,1,0,52,52A52.06,52.06,0,0,0,128,76Zm0,80a28,28,0,1,1,28-28A28,28,0,0,1,128,156Zm92-27.21v-1.58l14-17.51a12,12,0,0,0,2.23-10.59A111.75,111.75,0,0,0,225,71.89,12,12,0,0,0,215.89,66L193.61,63.5l-1.11-1.11L190,40.1A12,12,0,0,0,184.11,31a111.67,111.67,0,0,0-27.23-11.27A12,12,0,0,0,146.3,22L128.79,36h-1.58L109.7,22a12,12,0,0,0-10.59-2.23A111.75,111.75,0,0,0,71.89,31.05,12,12,0,0,0,66,40.11L63.5,62.39,62.39,63.5,40.1,66A12,12,0,0,0,31,71.89,111.67,111.67,0,0,0,19.77,99.12,12,12,0,0,0,22,109.7l14,17.51v1.58L22,146.3a12,12,0,0,0-2.23,10.59,111.75,111.75,0,0,0,11.29,27.22A12,12,0,0,0,40.11,190l22.28,2.48,1.11,1.11L66,215.9A12,12,0,0,0,71.89,225a111.67,111.67,0,0,0,27.23,11.27A12,12,0,0,0,109.7,234l17.51-14h1.58l17.51,14a12,12,0,0,0,10.59,2.23A111.75,111.75,0,0,0,184.11,225a12,12,0,0,0,5.91-9.06l2.48-22.28,1.11-1.11L215.9,190a12,12,0,0,0,9.06-5.91,111.67,111.67,0,0,0,11.27-27.23A12,12,0,0,0,234,146.3Zm-24.12-4.89a70.1,70.1,0,0,1,0,8.2,12,12,0,0,0,2.61,8.22l12.84,16.05A86.47,86.47,0,0,1,207,166.86l-20.43,2.27a12,12,0,0,0-7.65,4,69,69,0,0,1-5.8,5.8,12,12,0,0,0-4,7.65L166.86,207a86.47,86.47,0,0,1-10.49,4.35l-16.05-12.85a12,12,0,0,0-7.5-2.62c-.24,0-.48,0-.72,0a70.1,70.1,0,0,1-8.2,0,12.06,12.06,0,0,0-8.22,2.6L99.63,211.33A86.47,86.47,0,0,1,89.14,207l-2.27-20.43a12,12,0,0,0-4-7.65,69,69,0,0,1-5.8-5.8,12,12,0,0,0-7.65-4L49,166.86a86.47,86.47,0,0,1-4.35-10.49l12.84-16.05a12,12,0,0,0,2.61-8.22,70.1,70.1,0,0,1,0-8.2,12,12,0,0,0-2.61-8.22L44.67,99.63A86.47,86.47,0,0,1,49,89.14l20.43-2.27a12,12,0,0,0,7.65-4,69,69,0,0,1,5.8-5.8,12,12,0,0,0,4-7.65L89.14,49a86.47,86.47,0,0,1,10.49-4.35l16.05,12.85a12.06,12.06,0,0,0,8.22,2.6,70.1,70.1,0,0,1,8.2,0,12,12,0,0,0,8.22-2.6l16.05-12.85A86.47,86.47,0,0,1,166.86,49l2.27,20.43a12,12,0,0,0,4,7.65,69,69,0,0,1,5.8,5.8,12,12,0,0,0,7.65,4L207,89.14a86.47,86.47,0,0,1,4.35,10.49l-12.84,16.05A12,12,0,0,0,195.88,123.9Z\"/></symbol>\n <symbol id=\"juno-i-hard-drives\" viewBox=\"0 0 256 256\"><path d=\"M208,36H48A20,20,0,0,0,28,56V200a20,20,0,0,0,20,20H208a20,20,0,0,0,20-20V56A20,20,0,0,0,208,36Zm-4,24v56H52V60ZM52,196V140H204v56ZM160,88a16,16,0,1,1,16,16A16,16,0,0,1,160,88Zm32,80a16,16,0,1,1-16-16A16,16,0,0,1,192,168Z\"/></symbol>\n <symbol id=\"juno-i-heart\" viewBox=\"0 0 256 256\"><path d=\"M178,36c-20.09,0-37.92,7.93-50,21.56C115.92,43.93,98.09,36,78,36a66.08,66.08,0,0,0-66,66c0,72.34,105.81,130.14,110.31,132.57a12,12,0,0,0,11.38,0C138.19,232.14,244,174.34,244,102A66.08,66.08,0,0,0,178,36Zm-5.49,142.36A328.69,328.69,0,0,1,128,210.16a328.69,328.69,0,0,1-44.51-31.8C61.82,159.77,36,131.42,36,102A42,42,0,0,1,78,60c17.8,0,32.7,9.4,38.89,24.54a12,12,0,0,0,22.22,0C145.3,69.4,160.2,60,178,60a42,42,0,0,1,42,42C220,131.42,194.18,159.77,172.51,178.36Z\"/></symbol>\n <symbol id=\"juno-i-hexagon\" viewBox=\"0 0 256 256\"><path d=\"M225.6,62.64l-88-48.17a19.91,19.91,0,0,0-19.2,0l-88,48.17A20,20,0,0,0,20,80.19v95.62a20,20,0,0,0,10.4,17.55l88,48.17a19.89,19.89,0,0,0,19.2,0l88-48.17A20,20,0,0,0,236,175.81V80.19A20,20,0,0,0,225.6,62.64ZM212,173.44l-84,46-84-46V82.56l84-46,84,46Z\"/></symbol>\n <symbol id=\"juno-i-house\" viewBox=\"0 0 256 256\"><path d=\"M222.14,105.85l-80-80a20,20,0,0,0-28.28,0l-80,80A19.86,19.86,0,0,0,28,120v96a12,12,0,0,0,12,12h64a12,12,0,0,0,12-12V164h24v52a12,12,0,0,0,12,12h64a12,12,0,0,0,12-12V120A19.86,19.86,0,0,0,222.14,105.85ZM204,204H164V152a12,12,0,0,0-12-12H104a12,12,0,0,0-12,12v52H52V121.65l76-76,76,76Z\"/></symbol>\n <symbol id=\"juno-i-images\" viewBox=\"0 0 256 256\"><path d=\"M160,88a16,16,0,1,1,16,16A16,16,0,0,1,160,88Zm76-32V160a20,20,0,0,1-20,20H204v20a20,20,0,0,1-20,20H40a20,20,0,0,1-20-20V88A20,20,0,0,1,40,68H60V56A20,20,0,0,1,80,36H216A20,20,0,0,1,236,56ZM180,180H80a20,20,0,0,1-20-20V92H44V196H180Zm-21.66-24L124,121.66,89.66,156ZM212,60H84v67.72l25.86-25.86a20,20,0,0,1,28.28,0L192.28,156H212Z\"/></symbol>\n <symbol id=\"juno-i-info\" viewBox=\"0 0 256 256\"><path d=\"M108,84a16,16,0,1,1,16,16A16,16,0,0,1,108,84Zm128,44A108,108,0,1,1,128,20,108.12,108.12,0,0,1,236,128Zm-24,0a84,84,0,1,0-84,84A84.09,84.09,0,0,0,212,128Zm-72,36.68V132a20,20,0,0,0-20-20,12,12,0,0,0-4,23.32V168a20,20,0,0,0,20,20,12,12,0,0,0,4-23.32Z\"/></symbol>\n <symbol id=\"juno-i-link\" viewBox=\"0 0 256 256\"><path d=\"M117.18,188.74a12,12,0,0,1,0,17l-5.12,5.12A58.26,58.26,0,0,1,70.6,228h0A58.62,58.62,0,0,1,29.14,127.92L63.89,93.17a58.64,58.64,0,0,1,98.56,28.11,12,12,0,1,1-23.37,5.44,34.65,34.65,0,0,0-58.22-16.58L46.11,144.89A34.62,34.62,0,0,0,70.57,204h0a34.41,34.41,0,0,0,24.49-10.14l5.11-5.12A12,12,0,0,1,117.18,188.74ZM226.83,45.17a58.65,58.65,0,0,0-82.93,0l-5.11,5.11a12,12,0,0,0,17,17l5.12-5.12a34.63,34.63,0,1,1,49,49L175.1,145.86A34.39,34.39,0,0,1,150.61,156h0a34.63,34.63,0,0,1-33.69-26.72,12,12,0,0,0-23.38,5.44A58.64,58.64,0,0,0,150.56,180h.05a58.28,58.28,0,0,0,41.47-17.17l34.75-34.75a58.62,58.62,0,0,0,0-82.91Z\"/></symbol>\n <symbol id=\"juno-i-list\" viewBox=\"0 0 256 256\"><path d=\"M228,128a12,12,0,0,1-12,12H40a12,12,0,0,1,0-24H216A12,12,0,0,1,228,128ZM40,76H216a12,12,0,0,0,0-24H40a12,12,0,0,0,0,24ZM216,180H40a12,12,0,0,0,0,24H216a12,12,0,0,0,0-24Z\"/></symbol>\n <symbol id=\"juno-i-lock-open\" viewBox=\"0 0 256 256\"><path d=\"M208,76H100V56a28,28,0,0,1,28-28c13.51,0,25.65,9.62,28.24,22.39a12,12,0,1,0,23.52-4.78C174.87,21.5,153.1,4,128,4A52.06,52.06,0,0,0,76,56V76H48A20,20,0,0,0,28,96V208a20,20,0,0,0,20,20H208a20,20,0,0,0,20-20V96A20,20,0,0,0,208,76Zm-4,128H52V100H204Zm-92-52a16,16,0,1,1,16,16A16,16,0,0,1,112,152Z\"/></symbol>\n <symbol id=\"juno-i-lock\" viewBox=\"0 0 256 256\"><path d=\"M208,76H180V56A52,52,0,0,0,76,56V76H48A20,20,0,0,0,28,96V208a20,20,0,0,0,20,20H208a20,20,0,0,0,20-20V96A20,20,0,0,0,208,76ZM100,56a28,28,0,0,1,56,0V76H100ZM204,204H52V100H204Zm-60-52a16,16,0,1,1-16-16A16,16,0,0,1,144,152Z\"/></symbol>\n <symbol id=\"juno-i-magnifying-glass\" viewBox=\"0 0 256 256\"><path d=\"M232.49,215.51,185,168a92.12,92.12,0,1,0-17,17l47.53,47.54a12,12,0,0,0,17-17ZM44,112a68,68,0,1,1,68,68A68.07,68.07,0,0,1,44,112Z\"/></symbol>\n <symbol id=\"juno-i-minus\" viewBox=\"0 0 256 256\"><path d=\"M228,128a12,12,0,0,1-12,12H40a12,12,0,0,1,0-24H216A12,12,0,0,1,228,128Z\"/></symbol>\n <symbol id=\"juno-i-moon\" viewBox=\"0 0 256 256\"><path d=\"M236.37,139.4a12,12,0,0,0-12-3A84.07,84.07,0,0,1,119.6,31.59a12,12,0,0,0-15-15A108.86,108.86,0,0,0,49.69,55.07,108,108,0,0,0,136,228a107.09,107.09,0,0,0,64.93-21.69,108.86,108.86,0,0,0,38.44-54.94A12,12,0,0,0,236.37,139.4Zm-49.88,47.74A84,84,0,0,1,68.86,69.51,84.93,84.93,0,0,1,92.27,48.29Q92,52.13,92,56A108.12,108.12,0,0,0,200,164q3.87,0,7.71-.27A84.79,84.79,0,0,1,186.49,187.14Z\"/></symbol>\n <symbol id=\"juno-i-pencil-simple\" viewBox=\"0 0 256 256\"><path d=\"M230.14,70.54,185.46,25.85a20,20,0,0,0-28.29,0L33.86,149.17A19.85,19.85,0,0,0,28,163.31V208a20,20,0,0,0,20,20H92.69a19.86,19.86,0,0,0,14.14-5.86L230.14,98.82a20,20,0,0,0,0-28.28ZM91,204H52V165l84-84,39,39ZM192,103,153,64l18.34-18.34,39,39Z\"/></symbol>\n <symbol id=\"juno-i-play\" viewBox=\"0 0 256 256\"><path d=\"M234.49,111.07,90.41,22.94A20,20,0,0,0,60,39.87V216.13a20,20,0,0,0,30.41,16.93l144.08-88.13a19.82,19.82,0,0,0,0-33.86ZM84,208.85V47.15L216.16,128Z\"/></symbol>\n <symbol id=\"juno-i-plus\" viewBox=\"0 0 256 256\"><path d=\"M228,128a12,12,0,0,1-12,12H140v76a12,12,0,0,1-24,0V140H40a12,12,0,0,1,0-24h76V40a12,12,0,0,1,24,0v76h76A12,12,0,0,1,228,128Z\"/></symbol>\n <symbol id=\"juno-i-puzzle-piece\" viewBox=\"0 0 256 256\"><path d=\"M222.41,155.16a12,12,0,0,0-11.56-.69A16,16,0,0,1,188,139,16.2,16.2,0,0,1,202.8,124a15.83,15.83,0,0,1,8,1.5A12,12,0,0,0,228,114.7V72a20,20,0,0,0-20-20H176a40.15,40.15,0,0,0-12.62-29.16,39.67,39.67,0,0,0-29.94-10.76,40.08,40.08,0,0,0-37.34,37C96,50.07,96,51,96,52H64A20,20,0,0,0,44,72v28a40.15,40.15,0,0,0-29.16,12.62A40,40,0,0,0,41.1,179.9a28.3,28.3,0,0,0,2.9.1v28a20,20,0,0,0,20,20H208a20,20,0,0,0,20-20V165.31A12,12,0,0,0,222.41,155.16ZM204,204H68V165.31a12,12,0,0,0-17.15-10.84A15.9,15.9,0,0,1,42.8,156,16.2,16.2,0,0,1,28,141.06a16,16,0,0,1,22.82-15.52A12,12,0,0,0,68,114.7V76h42.7a12,12,0,0,0,10.83-17.15A15.9,15.9,0,0,1,120,50.8,16.19,16.19,0,0,1,134.94,36a16,16,0,0,1,15.53,22.81A12,12,0,0,0,161.31,76H204v24c-1,0-1.93,0-2.9.11A40,40,0,0,0,204,180h0Z\"/></symbol>\n <symbol id=\"juno-i-question\" viewBox=\"0 0 256 256\"><path d=\"M144,180a16,16,0,1,1-16-16A16,16,0,0,1,144,180Zm92-52A108,108,0,1,1,128,20,108.12,108.12,0,0,1,236,128Zm-24,0a84,84,0,1,0-84,84A84.09,84.09,0,0,0,212,128ZM128,64c-24.26,0-44,17.94-44,40v4a12,12,0,0,0,24,0v-4c0-8.82,9-16,20-16s20,7.18,20,16-9,16-20,16a12,12,0,0,0-12,12v8a12,12,0,0,0,23.73,2.56C158.31,137.88,172,122.37,172,104,172,81.94,152.26,64,128,64Z\"/></symbol>\n <symbol id=\"juno-i-sliders-horizontal\" viewBox=\"0 0 256 256\"><path d=\"M40,92H70.06a36,36,0,0,0,67.88,0H216a12,12,0,0,0,0-24H137.94a36,36,0,0,0-67.88,0H40a12,12,0,0,0,0,24Zm64-24A12,12,0,1,1,92,80,12,12,0,0,1,104,68Zm112,96H201.94a36,36,0,0,0-67.88,0H40a12,12,0,0,0,0,24h94.06a36,36,0,0,0,67.88,0H216a12,12,0,0,0,0-24Zm-48,24a12,12,0,1,1,12-12A12,12,0,0,1,168,188Z\"/></symbol>\n <symbol id=\"juno-i-sliders\" viewBox=\"0 0 256 256\"><path d=\"M68,102.06V40a12,12,0,0,0-24,0v62.06a36,36,0,0,0,0,67.88V216a12,12,0,0,0,24,0V169.94a36,36,0,0,0,0-67.88ZM56,148a12,12,0,1,1,12-12A12,12,0,0,1,56,148ZM164,88a36.07,36.07,0,0,0-24-33.94V40a12,12,0,0,0-24,0V54.06a36,36,0,0,0,0,67.88V216a12,12,0,0,0,24,0V121.94A36.07,36.07,0,0,0,164,88Zm-36,12a12,12,0,1,1,12-12A12,12,0,0,1,128,100Zm108,68a36.07,36.07,0,0,0-24-33.94V40a12,12,0,0,0-24,0v94.06a36,36,0,0,0,0,67.88V216a12,12,0,0,0,24,0V201.94A36.07,36.07,0,0,0,236,168Zm-36,12a12,12,0,1,1,12-12A12,12,0,0,1,200,180Z\"/></symbol>\n <symbol id=\"juno-i-squares-four\" viewBox=\"0 0 256 256\"><path d=\"M100,36H56A20,20,0,0,0,36,56v44a20,20,0,0,0,20,20h44a20,20,0,0,0,20-20V56A20,20,0,0,0,100,36ZM96,96H60V60H96ZM200,36H156a20,20,0,0,0-20,20v44a20,20,0,0,0,20,20h44a20,20,0,0,0,20-20V56A20,20,0,0,0,200,36Zm-4,60H160V60h36Zm-96,40H56a20,20,0,0,0-20,20v44a20,20,0,0,0,20,20h44a20,20,0,0,0,20-20V156A20,20,0,0,0,100,136Zm-4,60H60V160H96Zm104-60H156a20,20,0,0,0-20,20v44a20,20,0,0,0,20,20h44a20,20,0,0,0,20-20V156A20,20,0,0,0,200,136Zm-4,60H160V160h36Z\"/></symbol>\n <symbol id=\"juno-i-star\" viewBox=\"0 0 256 256\"><path d=\"M243,96a20.33,20.33,0,0,0-17.74-14l-56.59-4.57L146.83,24.62a20.36,20.36,0,0,0-37.66,0L87.35,77.44,30.76,82A20.45,20.45,0,0,0,19.1,117.88l43.18,37.24-13.2,55.7A20.37,20.37,0,0,0,79.57,233L128,203.19,176.43,233a20.39,20.39,0,0,0,30.49-22.15l-13.2-55.7,43.18-37.24A20.43,20.43,0,0,0,243,96ZM172.53,141.7a12,12,0,0,0-3.84,11.86L181.58,208l-47.29-29.08a12,12,0,0,0-12.58,0L74.42,208l12.89-54.4a12,12,0,0,0-3.84-11.86L41.2,105.24l55.4-4.47a12,12,0,0,0,10.13-7.38L128,41.89l21.27,51.5a12,12,0,0,0,10.13,7.38l55.4,4.47Z\"/></symbol>\n <symbol id=\"juno-i-sun\" viewBox=\"0 0 256 256\"><path d=\"M116,36V20a12,12,0,0,1,24,0V36a12,12,0,0,1-24,0Zm80,92a68,68,0,1,1-68-68A68.07,68.07,0,0,1,196,128Zm-24,0a44,44,0,1,0-44,44A44.05,44.05,0,0,0,172,128ZM51.51,68.49a12,12,0,1,0,17-17l-12-12a12,12,0,0,0-17,17Zm0,119-12,12a12,12,0,0,0,17,17l12-12a12,12,0,1,0-17-17ZM196,72a12,12,0,0,0,8.49-3.51l12-12a12,12,0,0,0-17-17l-12,12A12,12,0,0,0,196,72Zm8.49,115.51a12,12,0,0,0-17,17l12,12a12,12,0,0,0,17-17ZM48,128a12,12,0,0,0-12-12H20a12,12,0,0,0,0,24H36A12,12,0,0,0,48,128Zm80,80a12,12,0,0,0-12,12v16a12,12,0,0,0,24,0V220A12,12,0,0,0,128,208Zm108-92H220a12,12,0,0,0,0,24h16a12,12,0,0,0,0-24Z\"/></symbol>\n <symbol id=\"juno-i-trash\" viewBox=\"0 0 256 256\"><path d=\"M216,48H180V36A28,28,0,0,0,152,8H104A28,28,0,0,0,76,36V48H40a12,12,0,0,0,0,24h4V208a20,20,0,0,0,20,20H192a20,20,0,0,0,20-20V72h4a12,12,0,0,0,0-24ZM100,36a4,4,0,0,1,4-4h48a4,4,0,0,1,4,4V48H100Zm88,168H68V72H188ZM116,104v64a12,12,0,0,1-24,0V104a12,12,0,0,1,24,0Zm48,0v64a12,12,0,0,1-24,0V104a12,12,0,0,1,24,0Z\"/></symbol>\n <symbol id=\"juno-i-upload-simple\" viewBox=\"0 0 256 256\"><path d=\"M228,144v64a12,12,0,0,1-12,12H40a12,12,0,0,1-12-12V144a12,12,0,0,1,24,0v52H204V144a12,12,0,0,1,24,0ZM96.49,80.49,116,61v83a12,12,0,0,0,24,0V61l19.51,19.52a12,12,0,1,0,17-17l-40-40a12,12,0,0,0-17,0l-40,40a12,12,0,1,0,17,17Z\"/></symbol>\n <symbol id=\"juno-i-upload\" viewBox=\"0 0 256 256\"><path d=\"M188,184a16,16,0,1,1,16-16A16,16,0,0,1,188,184Zm36-68H180a12,12,0,0,0,0,24h40v56H36V140H76a12,12,0,0,0,0-24H32a20,20,0,0,0-20,20v64a20,20,0,0,0,20,20H224a20,20,0,0,0,20-20V136A20,20,0,0,0,224,116ZM88.49,80.49,116,53v75a12,12,0,0,0,24,0V53l27.51,27.52a12,12,0,1,0,17-17l-48-48a12,12,0,0,0-17,0l-48,48a12,12,0,1,0,17,17Z\"/></symbol>\n <symbol id=\"juno-i-user\" viewBox=\"0 0 256 256\"><path d=\"M234.38,210a123.36,123.36,0,0,0-60.78-53.23,76,76,0,1,0-91.2,0A123.36,123.36,0,0,0,21.62,210a12,12,0,1,0,20.77,12c18.12-31.32,50.12-50,85.61-50s67.49,18.69,85.61,50a12,12,0,0,0,20.77-12ZM76,96a52,52,0,1,1,52,52A52.06,52.06,0,0,1,76,96Z\"/></symbol>\n <symbol id=\"juno-i-users\" viewBox=\"0 0 256 256\"><path d=\"M125.18,156.94a64,64,0,1,0-82.36,0,100.23,100.23,0,0,0-39.49,32,12,12,0,0,0,19.35,14.2,76,76,0,0,1,122.64,0,12,12,0,0,0,19.36-14.2A100.33,100.33,0,0,0,125.18,156.94ZM44,108a40,40,0,1,1,40,40A40,40,0,0,1,44,108Zm206.1,97.67a12,12,0,0,1-16.78-2.57A76.31,76.31,0,0,0,172,172a12,12,0,0,1,0-24,40,40,0,1,0-10.3-78.67,12,12,0,1,1-6.16-23.19,64,64,0,0,1,57.64,110.8,100.23,100.23,0,0,1,39.49,32A12,12,0,0,1,250.1,205.67Z\"/></symbol>\n <symbol id=\"juno-i-warning-circle\" viewBox=\"0 0 256 256\"><path d=\"M128,20A108,108,0,1,0,236,128,108.12,108.12,0,0,0,128,20Zm0,192a84,84,0,1,1,84-84A84.09,84.09,0,0,1,128,212Zm-12-80V80a12,12,0,0,1,24,0v52a12,12,0,0,1-24,0Zm28,40a16,16,0,1,1-16-16A16,16,0,0,1,144,172Z\"/></symbol>\n <symbol id=\"juno-i-warning\" viewBox=\"0 0 256 256\"><path d=\"M240.26,186.1,152.81,34.23h0a28.74,28.74,0,0,0-49.62,0L15.74,186.1a27.45,27.45,0,0,0,0,27.71A28.31,28.31,0,0,0,40.55,228h174.9a28.31,28.31,0,0,0,24.79-14.19A27.45,27.45,0,0,0,240.26,186.1Zm-20.8,15.7a4.46,4.46,0,0,1-4,2.2H40.55a4.46,4.46,0,0,1-4-2.2,3.56,3.56,0,0,1,0-3.73L124,46.2a4.77,4.77,0,0,1,8,0l87.44,151.87A3.56,3.56,0,0,1,219.46,201.8ZM116,136V104a12,12,0,0,1,24,0v32a12,12,0,0,1-24,0Zm28,40a16,16,0,1,1-16-16A16,16,0,0,1,144,176Z\"/></symbol>\n <symbol id=\"juno-i-x-circle\" viewBox=\"0 0 256 256\"><path d=\"M168.49,104.49,145,128l23.52,23.51a12,12,0,0,1-17,17L128,145l-23.51,23.52a12,12,0,0,1-17-17L111,128,87.51,104.49a12,12,0,0,1,17-17L128,111l23.51-23.52a12,12,0,0,1,17,17ZM236,128A108,108,0,1,1,128,20,108.12,108.12,0,0,1,236,128Zm-24,0a84,84,0,1,0-84,84A84.09,84.09,0,0,0,212,128Z\"/></symbol>\n <symbol id=\"juno-i-x\" viewBox=\"0 0 256 256\"><path d=\"M208.49,191.51a12,12,0,0,1-17,17L128,145,64.49,208.49a12,12,0,0,1-17-17L111,128,47.51,64.49a12,12,0,0,1,17-17L128,111l63.51-63.52a12,12,0,0,1,17,17L145,128Z\"/></symbol>\n</svg>\n";
|
|
7
|
+
|
|
8
|
+
/** Inject the sprite into `doc` once (id-guarded, safe to call repeatedly). */
|
|
9
|
+
export function installJunoIcons(doc = typeof document !== 'undefined' ? document : undefined) {
|
|
10
|
+
if (!doc || doc.getElementById('juno-icon-sprite')) return;
|
|
11
|
+
const holder = doc.createElement('div');
|
|
12
|
+
holder.id = 'juno-icon-sprite';
|
|
13
|
+
holder.style.display = 'none';
|
|
14
|
+
holder.setAttribute('aria-hidden', 'true');
|
|
15
|
+
holder.innerHTML = SPRITE;
|
|
16
|
+
(doc.body || doc.documentElement).prepend(holder);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Auto-install on import in a browser (no-op server-side / pre-hydration).
|
|
20
|
+
if (typeof document !== 'undefined') installJunoIcons();
|
|
21
|
+
|
|
22
|
+
export default installJunoIcons;
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# Accessibility
|
|
2
|
+
|
|
3
|
+
Accessibility is a primary goal of junoui, not an afterthought. The design system
|
|
4
|
+
ships the **look and the accessibility contract**; the _interaction behavior_ (focus
|
|
5
|
+
trapping, ARIA state updates, keyboard handlers) lives in your app or a sibling
|
|
6
|
+
`junoui-<framework>` package — but this page states exactly what junoui guarantees, to
|
|
7
|
+
which published standard, and what you must wire up.
|
|
8
|
+
|
|
9
|
+
## Standards we build to
|
|
10
|
+
|
|
11
|
+
junoui targets the international web accessibility standards and references the
|
|
12
|
+
specific criteria it meets — so a claim can be checked, not just trusted:
|
|
13
|
+
|
|
14
|
+
- **[WCAG 2.2](https://www.w3.org/TR/WCAG22/)** (W3C Recommendation) — **AA across the
|
|
15
|
+
board, AAA where it matters** (see the specific success criteria cited below).
|
|
16
|
+
- **[WAI-ARIA 1.2](https://www.w3.org/TR/wai-aria-1.2/)** roles/states/properties, and
|
|
17
|
+
the **[ARIA Authoring Practices Guide](https://www.w3.org/WAI/ARIA/apg/patterns/)**
|
|
18
|
+
for the keyboard/interaction patterns in [the contract table](#the-aria-contract-per-component).
|
|
19
|
+
|
|
20
|
+
Criteria referenced on this page, by number:
|
|
21
|
+
|
|
22
|
+
| SC | Name | Level | Where |
|
|
23
|
+
| ---------- | --------------------------- | ----- | --------------------------------- |
|
|
24
|
+
| **1.4.1** | Use of Color | A | [Color](#color) |
|
|
25
|
+
| **1.4.3** | Contrast (Minimum) | AA | [Color](#color) |
|
|
26
|
+
| **1.4.6** | Contrast (Enhanced) | AAA | [Color](#color) |
|
|
27
|
+
| **1.4.11** | Non-text Contrast | AA | [Focus](#focus) |
|
|
28
|
+
| **2.4.7** | Focus Visible | AA | [Focus](#focus) |
|
|
29
|
+
| **2.4.11** | Focus Appearance | AA | [Focus](#focus) |
|
|
30
|
+
| **2.2.2** | Pause, Stop, Hide | A | [Motion](#motion) |
|
|
31
|
+
| **2.3.3** | Animation from Interactions | AAA | [Motion](#motion) |
|
|
32
|
+
| **2.5.8** | Target Size (Minimum) | AA | [Targets](#targets) |
|
|
33
|
+
| **2.5.5** | Target Size (Enhanced) | AAA | [Targets](#targets) |
|
|
34
|
+
| **1.4.12** | Text Spacing | AA | [Density](#density--text-spacing) |
|
|
35
|
+
|
|
36
|
+
This coverage is expected to grow: junoui adds standards references as components and
|
|
37
|
+
tokens land, and never removes a guarantee without a semver-major note.
|
|
38
|
+
|
|
39
|
+
## Color
|
|
40
|
+
|
|
41
|
+
- **Never make color the only signal** (WCAG **1.4.1** Use of Color, A). Every status
|
|
42
|
+
pairs color with a text label, icon, or shape. A badge says `WARNING`; a dot sits
|
|
43
|
+
beside its word.
|
|
44
|
+
- The **colorblind** palette (IBM Carbon universal set) is distinguishable across
|
|
45
|
+
deuteranopia, protanopia, and tritanopia. On the dark base–card surfaces (`s0`–`s2`)
|
|
46
|
+
every status role passes WCAG **1.4.6** Contrast (Enhanced), AAA (≥ 7:1); on the
|
|
47
|
+
selected/overlay surface (`s3`) it holds AA (≈ 7:1, ≥ 4.5:1). Use it for
|
|
48
|
+
accessibility-critical or universal audiences.
|
|
49
|
+
- Contrast (measured — regenerate with `node scripts/audit-contrast.mjs`):
|
|
50
|
+
- `data` (values) meets **1.4.6** (AAA, ≥ 7:1) on **every** surface `s0`–`s3`, both
|
|
51
|
+
themes and all three palettes (dark ≈ 13–17:1).
|
|
52
|
+
- `label` (secondary text) meets **1.4.3** Contrast (Minimum), AA (≥ 4.5:1) on **every**
|
|
53
|
+
surface `s0`–`s3` (dark ≈ 4.6–5.9:1). Never use it for values.
|
|
54
|
+
- Status roles (`nominal`/`active`/`caution`/`warning`/`target`) meet **1.4.3** AA on
|
|
55
|
+
`s0`–`s3`; most reach AAA on `s0`–`s2`.
|
|
56
|
+
- `muted` and `data-dim` are **decorative / WCAG-exempt roles** — disabled controls,
|
|
57
|
+
placeholders, faint tick labels. They intentionally sit below 4.5:1; never carry
|
|
58
|
+
meaningful text in them (a real timestamp or value uses `label`/`data`, not `data-dim`).
|
|
59
|
+
Exact values: [tokens-reference.md](./tokens-reference.md).
|
|
60
|
+
- **Surfaces & borders (dark).** Adjacent surface fills differ by only ~1.05:1, so a
|
|
61
|
+
block reads against its background through its **border**, not its fill. The dark
|
|
62
|
+
`border` / `border-strong` tokens are lifted (≈ 1.5:1 / 2.2:1 vs the base surface) so
|
|
63
|
+
cards, panels and dividers stay perceptible. Container boundaries are decorative and
|
|
64
|
+
exempt from **1.4.11** Non-text Contrast. Component-_identifying_ edges use the
|
|
65
|
+
`--juno-control-edge` family instead: input / checkbox / slider / avatar edges
|
|
66
|
+
(`-strong`, = `label`) clear ≥ 3:1 (dark ≈ 5.3:1) as **1.4.11** requires; table
|
|
67
|
+
gridlines (`--juno-control-edge`, = `muted`, ≈ 2.9:1) are supplementary rules — table
|
|
68
|
+
structure is carried by `<th>`/alignment, not the line — so they fall under the
|
|
69
|
+
decorative exemption.
|
|
70
|
+
|
|
71
|
+
## Focus
|
|
72
|
+
|
|
73
|
+
- A visible focus ring is defined in the base layer for links, buttons, inputs, and
|
|
74
|
+
anything with `[tabindex]`: 2px solid `--juno-active`, 2px offset. It tracks the
|
|
75
|
+
theme and is never removed (WCAG **2.4.7** Focus Visible, AA).
|
|
76
|
+
- The ring's size, offset and `active`-role contrast satisfy **2.4.11** Focus
|
|
77
|
+
Appearance (AA, WCAG 2.2) and **1.4.11** Non-text Contrast (AA) against adjacent
|
|
78
|
+
surfaces.
|
|
79
|
+
- Don't suppress `:focus-visible`. If you build a custom control, give it a tabstop
|
|
80
|
+
and let the ring apply.
|
|
81
|
+
- Text inputs / select / textarea swap the outline for an equivalent visible signal:
|
|
82
|
+
an `active` border + 1px ring. Never remove focus indication outright.
|
|
83
|
+
|
|
84
|
+
## Motion
|
|
85
|
+
|
|
86
|
+
- All animations respect `prefers-reduced-motion: reduce` (base layer slows them to
|
|
87
|
+
near-static). Loader pulses and the `--live` dot honor it automatically — covering
|
|
88
|
+
WCAG **2.2.2** Pause, Stop, Hide (A) and **2.3.3** Animation from Interactions (AAA).
|
|
89
|
+
- Use the `determinate` loaders when progress is known — less motion, more trust.
|
|
90
|
+
- **Script can read the preference too.** A CSS media query never reaches imperative
|
|
91
|
+
JS (a `scrollIntoView({behavior})` choice, a rAF-driven transform). The base layer
|
|
92
|
+
exposes the same preference as a custom property, so one read decides:
|
|
93
|
+
|
|
94
|
+
```js
|
|
95
|
+
const motion = getComputedStyle(document.documentElement)
|
|
96
|
+
.getPropertyValue('--juno-motion')
|
|
97
|
+
.trim(); // 'auto' | 'none'
|
|
98
|
+
el.scrollIntoView({ behavior: motion === 'none' ? 'auto' : 'smooth' });
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`--juno-motion-scale` (`1` | `0`) is the multiplier form, for CSS that authors a
|
|
102
|
+
duration as `calc(var(--juno-motion-duration-base) * var(--juno-motion-scale))`.
|
|
103
|
+
|
|
104
|
+
## Targets
|
|
105
|
+
|
|
106
|
+
- `--juno-size-tap-min` = 24px — WCAG **2.5.8** Target Size (Minimum), AA (WCAG 2.2).
|
|
107
|
+
- `--juno-size-tap-comfortable` = 44px — WCAG **2.5.5** Target Size (Enhanced), AAA;
|
|
108
|
+
recommended for primary actions on touch.
|
|
109
|
+
- `.juno-btn` already sets `min-height: var(--juno-size-tap-min)`. The other
|
|
110
|
+
touch surfaces — `.juno-menu__item` (dock overflow lands here on phones) and
|
|
111
|
+
`.juno-navbar__actions > *` — do too, so nothing routed onto a phone falls
|
|
112
|
+
under the minimum. Don't use `.juno-btn--sm` on a touch surface: it is
|
|
113
|
+
deliberately below the tap minimum (see `button.css`).
|
|
114
|
+
- **On coarse pointers this is automatic:** under `@media (pointer: coarse)` the
|
|
115
|
+
base layer raises `--juno-size-tap-min` to the comfortable 44px, so every
|
|
116
|
+
control sized off the tap minimum grows on touch devices. A cascade override —
|
|
117
|
+
the token values themselves don't change.
|
|
118
|
+
- Hover-revealed affordances get a touch fallback: table row actions stay
|
|
119
|
+
visible under `@media (hover: none)`.
|
|
120
|
+
|
|
121
|
+
## Forced colors / high contrast
|
|
122
|
+
|
|
123
|
+
- The base layer opts components into the system palette under
|
|
124
|
+
`@media (forced-colors: active)`: borders fall back to `CanvasText`, focus to
|
|
125
|
+
`Highlight`. Badges keep their fill (`forced-color-adjust: none`) so status stays
|
|
126
|
+
legible.
|
|
127
|
+
|
|
128
|
+
## Writing direction (RTL)
|
|
129
|
+
|
|
130
|
+
- Components use **CSS logical properties** (`margin-inline`, `padding-inline`,
|
|
131
|
+
`inset`, `min-inline-size`) so they mirror correctly under `dir="rtl"` with no
|
|
132
|
+
extra CSS. Avoid reintroducing physical `left`/`right` in consuming code.
|
|
133
|
+
|
|
134
|
+
## Density & text spacing
|
|
135
|
+
|
|
136
|
+
- Type tokens set line-height and spacing with enough headroom that user or
|
|
137
|
+
browser-imposed **text spacing** (line-height 1.5×, paragraph/letter/word spacing)
|
|
138
|
+
doesn't clip or overlap content — WCAG **1.4.12** Text Spacing (AA).
|
|
139
|
+
- Compact density shrinks padding, never a control's `min-height` (the tap target
|
|
140
|
+
under [Targets](#targets)) and never the type scale — readability holds at every
|
|
141
|
+
density.
|
|
142
|
+
|
|
143
|
+
## The ARIA contract (per component)
|
|
144
|
+
|
|
145
|
+
junoui gives structure + style; you add roles/state:
|
|
146
|
+
|
|
147
|
+
| Component | You must add |
|
|
148
|
+
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
149
|
+
| Badge / status dot | Text label in the DOM (not color alone). For live values, `aria-live="polite"`. |
|
|
150
|
+
| Button | Use a real `<button>`; `disabled` for disabled (not just the class). |
|
|
151
|
+
| Card | Heading semantics (`<h2>`…) in `__head` if it titles a region. |
|
|
152
|
+
| Loader (indeterminate) | `role="status"` + `aria-label="Loading"`. |
|
|
153
|
+
| Loader (determinate) | `role="progressbar"` + `aria-valuenow/min/max`. |
|
|
154
|
+
| Input / select / textarea | A `<label>` tied by `for`/`id`. Errors: `aria-invalid="true"` + `aria-describedby` → the error text. Required: `aria-required`. |
|
|
155
|
+
| Checkbox / radio | Real `<input>` inside its `<label>`; radios share a `name`. State is native `checked`. |
|
|
156
|
+
| Switch | `role="switch"` on the input; app keeps `checked` + `aria-checked` in sync. |
|
|
157
|
+
| Toggle button | Real `<button>` with `aria-pressed`; app flips it. Legend text is in the DOM, not color alone. |
|
|
158
|
+
| Slider | Value-driven `div`: add `role="slider"`, `tabindex`, `aria-valuenow/min/max` (+ `aria-valuetext` for units); app wires drag + arrow/Page keys and keeps `--juno-slider-pct` in sync. |
|
|
159
|
+
| Modal / drawer / sheet | Native `<dialog>` + `showModal()` (focus-trap, ESC, inert background, scrim-click come free). Name it via `aria-labelledby` → the title. **`<dialog>` is the only supported root** — a hand-rolled `<div>` overlay (own scrim, own positioning) loses focus-trap and `inert` silently; there is no CSS-only substitute. Bottom sheet (`.juno-drawer--bottom`): the grab handle (`.juno-sheet__handle`) is decorative, `aria-hidden="true"` — it carries no label and no drag behavior; dragging to resize/dismiss is the app's responsibility, not junoui's. |
|
|
160
|
+
| Tooltip | Trigger needs a tabstop; bubble `role="tooltip"` + `aria-describedby` so it reveals on focus, not only hover. Promote to `popover="hint"` to escape ancestor clipping (top layer). |
|
|
161
|
+
| Popover | Native `popover` + `popovertarget` button (top layer; ESC + outside-click dismiss + `aria-expanded` come free). Keep `aria-haspopup`; move focus in on open, restore on close. |
|
|
162
|
+
| Menu / dropdown | `role="menu"` + `role="menuitem"`; native `popover` + `popovertarget` (free dismiss/ESC/`aria-expanded`), items `popovertargetaction="hide"`. App wires arrow-key roving focus + Enter. |
|
|
163
|
+
| Table / data grid | Real `<table>`/`<th>`/`<td>` (use `scope`). Sortable headers: `aria-sort` per column, kept in sync as you reorder. Selected rows: `aria-selected="true"`. Truncated cells: a `title` (or tooltip) with the full value. |
|
|
164
|
+
| Alert | `role="alert"` for urgent (assertive), `role="status"` for passive. Icon is `aria-hidden`; the message text carries the meaning. Dismiss is a real `<button>` with `aria-label`. |
|
|
165
|
+
| Toast | Stack is a live region: `aria-live="polite"` (`assertive` for errors). Each toast `role="status"`; keep it short. Dismiss `<button>` + `aria-label`; don't rely on the auto-timeout alone for critical info. |
|
|
166
|
+
| Tabs | `role="tablist"` + `role="tab"` (each `aria-controls` its `role="tabpanel"`, which is `aria-labelledby` the tab). App keeps one `aria-selected="true"`, hides the rest, moves roving `tabindex`, and wires ← / → / Home / End. |
|
|
167
|
+
| Accordion | Native `<details>`/`<summary>` — open state, focus, and Enter/Space are built in. For one-open-at-a-time, give the `<details>` a shared `name`. No ARIA to add. |
|
|
168
|
+
| Icon | Decorative: `aria-hidden="true"`. Meaningful (icon-only control): `role="img"` + `aria-label`, or label the surrounding control. Never the only signal for status. |
|
|
169
|
+
| Skeleton | Wrap the loading region in `aria-busy="true"` (+ `aria-live="polite"`); swap in real content when loaded. The shimmer itself is decorative. |
|
|
170
|
+
| Reload | Refetch-over-content: `role="status"` + `aria-label="Reloading"` announces politely (not `alert`). Keep `pointer-events: none` so it never blocks the content it floats over. First-paint uses the skeleton instead. |
|
|
171
|
+
| Load state — shimmer | Work in progress with no ETA: `aria-busy="true"` on the region (+ `aria-live="polite"` if it isn't otherwise announced). The band is decorative. |
|
|
172
|
+
| Load state — fault | `role="status"` on `.juno-fault` itself — per-region and polite. NOT `role="alert"`; that is `.juno-alert`'s job for page-level failures. Always pair the icon with text. |
|
|
173
|
+
| Load state — empty | No extra role — static content. "Nothing here" is not an error and must not announce as one. |
|
|
174
|
+
| Avatar | `<img>` needs a meaningful `alt`. The status ring is decorative — pair it with a `title` / visible label so status isn't color-only. |
|
|
175
|
+
| Divider | Semantic break: use `<hr>`. Purely decorative: a `<span>`/`<div>` (no role needed). |
|
|
176
|
+
| Chip / tag | Toggle chip: real `<button>` + `aria-pressed`. Removable: the `__remove` `<button>` needs an `aria-label` ("Remove X"). Meaning is in the text, not color. |
|
|
177
|
+
| Breadcrumb | `<nav aria-label="Breadcrumb">` around an `<ol>`; mark the last crumb `aria-current="page"` and drop its `href`. |
|
|
178
|
+
| Pagination | `<nav aria-label="Pagination">`; current page `aria-current="page"`; icon-only prev/next need `aria-label`; disable the ends with `disabled`. |
|
|
179
|
+
| Stepper | Real `<ol>`; set `data-state` per step and `aria-current="step"` on the active one. State drives the color — don't rely on color alone. |
|
|
180
|
+
| Rail / dock | `<nav aria-label="Primary">` of real links; active item `aria-current="page"`. Collapsed rail items (icons-only) need `aria-label`/`title`. When both navs render (rail + dock swap), the hidden one leaves the tree via `display:none`. Pill variant (`--pill`) hides labels — **every item needs an `aria-label`**. Section-loading arc gets `role="status"` + `aria-label`. The dock's `--collapsible` variant: the `__knob` needs an `aria-label`; `data-juno-collapsed` hides tray items from the tab order, so move focus to the knob before setting it if focus was inside. |
|
|
181
|
+
| Pillbar | `<nav aria-label>` when it navigates; links get `aria-current="page"`, toggle buttons `aria-pressed`. Icon-only items need `aria-label`. The `__input` search field needs its own `aria-label` / visually-hidden `<label>` — a placeholder is not a label. The `--collapsible` variant's `__toggle` carries `aria-expanded` + `aria-label`; collapsing hides tray items from the tab order, so move focus to the toggle before collapsing if it was inside. Never render both a dock and a pillbar on one screen. |
|
|
182
|
+
| Navbar | Back is a real `<a>` (parent route) or `<button>` (history); `aria-label="Back"` when the caret has no text. Title is usually the screen's `<h1>`. The bar itself needs no role — it's a `<header>`. |
|
|
183
|
+
| List | Rows that navigate are `<a>` (or `<button>`) wrapping the whole row — never a bare chevron target. Keep `<ul>`/`<li>` when rows form a set. A trailing switch/checkbox is its own labeled control; don't nest it inside a link row. |
|
|
184
|
+
|
|
185
|
+
These are interaction concerns — junoui can't enforce them in CSS, so they're your
|
|
186
|
+
responsibility (or the widget package's). Each row follows the matching
|
|
187
|
+
[WAI-ARIA Authoring Practices](https://www.w3.org/WAI/ARIA/apg/patterns/) pattern;
|
|
188
|
+
when in doubt, implement the APG keyboard model for that role.
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
# Boot shell — fast first paint, everything else in the background
|
|
2
|
+
|
|
3
|
+
How a junoui app gets from a cold tap to a usable screen in under a frame's
|
|
4
|
+
worth of network, and loads everything else behind that first paint. This is
|
|
5
|
+
the pattern the Nexora web client ships (reference implementation:
|
|
6
|
+
`nexora/web/index.html`, `src/prefetch.ts`, `src/sw.ts`); the pieces are
|
|
7
|
+
junoui's, the ordering rules are what this page adds.
|
|
8
|
+
|
|
9
|
+
The measured budget it buys (Nexora, production build, cold cache, mid-tier
|
|
10
|
+
phone emulation): **first paint ~100 ms** from the HTML alone, ten requests and
|
|
11
|
+
~150 kB compressed to an interactive library, and every later screen arriving
|
|
12
|
+
from a background-warmed cache instead of a tap-time download.
|
|
13
|
+
|
|
14
|
+
## The boot ladder
|
|
15
|
+
|
|
16
|
+
A bundle-rendered app paints nothing until its JS is fetched, parsed and run —
|
|
17
|
+
on a phone that is a black screen for however long the network takes. The fix
|
|
18
|
+
is not one trick but a ladder; each rung paints strictly earlier than the one
|
|
19
|
+
below it, and each hands over to the next without moving a pixel:
|
|
20
|
+
|
|
21
|
+
| Rung | Paints | Powered by |
|
|
22
|
+
| ---------------------- | ----------------------------------------- | --------------------------------------- |
|
|
23
|
+
| 1. Pre-bundle shell | App chrome + skeleton, first HTML parse | Inline `<style>` + static markup, no JS |
|
|
24
|
+
| 2. Cache-aware chrome | Cached content at first framework commit | Storage probe before the bundle |
|
|
25
|
+
| 3. Default screen only | The one screen users land on | Route-level code splitting |
|
|
26
|
+
| 4. Background warming | Nothing — pre-loads what a tap needs next | Idle + intent prefetch, link-class gate |
|
|
27
|
+
| 5. Offline shell | Rung 1–3 with the network gone | App-shell service worker |
|
|
28
|
+
|
|
29
|
+
The invariant across all five: **content gets the network first.** Nothing
|
|
30
|
+
speculative — a chunk, a font half, a screen nobody opened — may run before the
|
|
31
|
+
first screenful of real content has been requested.
|
|
32
|
+
|
|
33
|
+
## Rung 1 — the pre-bundle shell
|
|
34
|
+
|
|
35
|
+
The first HTML parse paints the product frame: background, rail or dock, top
|
|
36
|
+
bar, and a shimmering tile wall — before any stylesheet or script arrives.
|
|
37
|
+
That means an inline `<style>` and static skeleton markup in `index.html`,
|
|
38
|
+
mirroring the geometry of the real [`.juno-app-shell`](./layout.md) that will
|
|
39
|
+
replace it. (In-app loading states use [`.juno-skeleton`](./components/skeleton.md);
|
|
40
|
+
this rung hand-rolls the same shimmer only because `juno.css` has not arrived
|
|
41
|
+
yet.)
|
|
42
|
+
|
|
43
|
+
`juno.css` is not loaded yet at this point in the document — that is the whole
|
|
44
|
+
point — so the shell uses **token literals**: the handful of `--juno-*` values
|
|
45
|
+
it needs, written out by hand. For the standard palette that is:
|
|
46
|
+
|
|
47
|
+
| Token | Dark | Light |
|
|
48
|
+
| --------------- | --------------------- | --------------------- |
|
|
49
|
+
| `--juno-s0` | `oklch(11% 0.010 85)` | `oklch(97% 0.008 85)` |
|
|
50
|
+
| `--juno-s1` | `oklch(15% 0.012 85)` | `oklch(93% 0.010 85)` |
|
|
51
|
+
| `--juno-s2` | `oklch(19% 0.012 85)` | `oklch(89% 0.010 85)` |
|
|
52
|
+
| `--juno-border` | `oklch(30% 0.012 85)` | `oklch(82% 0.010 85)` |
|
|
53
|
+
|
|
54
|
+
Minimal skeleton (adapt the boxes to your shell — the rule is that every box
|
|
55
|
+
mirrors a real chrome element at its real size, so the React/Vue/whatever
|
|
56
|
+
commit replaces it without anything jumping):
|
|
57
|
+
|
|
58
|
+
```html
|
|
59
|
+
<style>
|
|
60
|
+
/* Token literals — juno.css is not here yet. Each mirrors a --juno-* token;
|
|
61
|
+
keep them asserted against the consumed build (see "Guarding the copies"). */
|
|
62
|
+
html {
|
|
63
|
+
background: oklch(11% 0.01 85); /* --juno-s0, dark */
|
|
64
|
+
}
|
|
65
|
+
html[data-juno-mode='light'] {
|
|
66
|
+
background: oklch(97% 0.008 85);
|
|
67
|
+
}
|
|
68
|
+
.boot {
|
|
69
|
+
position: fixed;
|
|
70
|
+
inset: 0;
|
|
71
|
+
display: flex;
|
|
72
|
+
--b-s1: oklch(15% 0.012 85);
|
|
73
|
+
--b-s2: oklch(19% 0.012 85);
|
|
74
|
+
--b-border: oklch(30% 0.012 85);
|
|
75
|
+
}
|
|
76
|
+
.boot-rail {
|
|
77
|
+
flex: 0 0 180px;
|
|
78
|
+
background: var(--b-s1);
|
|
79
|
+
border-right: 1px solid var(--b-border);
|
|
80
|
+
}
|
|
81
|
+
.boot-wall {
|
|
82
|
+
flex: 1;
|
|
83
|
+
display: grid;
|
|
84
|
+
gap: 10px;
|
|
85
|
+
padding: 14px 16px;
|
|
86
|
+
align-content: start;
|
|
87
|
+
/* match the real wall's column math or tiles resnap at handover */
|
|
88
|
+
grid-template-columns: repeat(auto-fill, minmax(min(108px, 100%), 1fr));
|
|
89
|
+
}
|
|
90
|
+
.boot-wall i {
|
|
91
|
+
aspect-ratio: 1;
|
|
92
|
+
border-radius: 6px;
|
|
93
|
+
background: var(--b-s2);
|
|
94
|
+
animation: boot-pulse 1.4s ease-in-out infinite;
|
|
95
|
+
}
|
|
96
|
+
@keyframes boot-pulse {
|
|
97
|
+
0%,
|
|
98
|
+
100% {
|
|
99
|
+
opacity: 1;
|
|
100
|
+
}
|
|
101
|
+
50% {
|
|
102
|
+
opacity: 0.45;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
</style>
|
|
106
|
+
...
|
|
107
|
+
<body>
|
|
108
|
+
<div id="root">
|
|
109
|
+
<div class="boot" aria-hidden="true">
|
|
110
|
+
<div class="boot-rail"></div>
|
|
111
|
+
<div class="boot-wall"><i></i><i></i><i></i>…</div>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
</body>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Rules that make this work rather than merely exist:
|
|
118
|
+
|
|
119
|
+
- **The frame is real chrome, not a wireframe.** Grey bands where the rail,
|
|
120
|
+
top bar and dock will be read as a loading page. The same boxes styled
|
|
121
|
+
exactly like their live counterparts — the dock as its floating pill with
|
|
122
|
+
the active item's bubble already filled, the rail as brand + one row per
|
|
123
|
+
destination with the accent edge on the active one, the top bar as its
|
|
124
|
+
chips and search field — read as the app, already open. The shimmer is
|
|
125
|
+
confined to the slots whose content genuinely is not there yet: icons,
|
|
126
|
+
labels, thumbnails. Placeholders are for content, never for chrome.
|
|
127
|
+
- **No real text in the shell.** The fonts arrive with the bundle, and a
|
|
128
|
+
system-font word swapping to the real face at handover is exactly the kind
|
|
129
|
+
of jump the shell exists to prevent. Label and wordmark slots are bars.
|
|
130
|
+
- **Every literal mirrors one real element.** Safe-area padding, tile column
|
|
131
|
+
minimums (`--juno-tile-min`), tap-target sizes (`--juno-size-tap-comfortable`
|
|
132
|
+
= 44px), dock geometry — if the skeleton reserves a different amount than the
|
|
133
|
+
real chrome, the layout jumps by exactly the difference at handover, which
|
|
134
|
+
reads worse than a plain spinner.
|
|
135
|
+
- **The framework replaces `#root`'s children at its first commit** — no
|
|
136
|
+
removal code, no fade choreography. The skeleton needs zero JS.
|
|
137
|
+
- **`aria-hidden="true"`** on the whole skeleton: it is paint, not content.
|
|
138
|
+
- Honor `data-juno-mode` in the literals too (both modes in the snippet above),
|
|
139
|
+
or a light-mode user gets a dark flash.
|
|
140
|
+
|
|
141
|
+
## Rung 2 — cache-aware chrome
|
|
142
|
+
|
|
143
|
+
The pulsing skeleton is right exactly once: on a true first run, when the
|
|
144
|
+
device holds nothing to draw. Every later launch should hydrate its first
|
|
145
|
+
framework commit from local cache (a persisted window of content in
|
|
146
|
+
`localStorage`/IndexedDB) — and shimmering in front of data the device already
|
|
147
|
+
has is a loading state in front of nothing.
|
|
148
|
+
|
|
149
|
+
So a tiny inline script — before the shell's markup — probes storage and
|
|
150
|
+
stamps the answer on the root element:
|
|
151
|
+
|
|
152
|
+
```html
|
|
153
|
+
<script>
|
|
154
|
+
(function () {
|
|
155
|
+
var warm = false;
|
|
156
|
+
try {
|
|
157
|
+
var win = JSON.parse(localStorage.getItem('app.contentCache') || 'null');
|
|
158
|
+
warm = !!(win && win.tiles && win.tiles.length);
|
|
159
|
+
} catch (e) {
|
|
160
|
+
/* unreadable storage = first run */
|
|
161
|
+
}
|
|
162
|
+
document.documentElement.setAttribute('data-boot', warm ? 'warm' : 'cold');
|
|
163
|
+
})();
|
|
164
|
+
</script>
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
```css
|
|
168
|
+
/* warm: chrome outlines only — the real content is about to commit */
|
|
169
|
+
html[data-boot='warm'] .boot-wall > i {
|
|
170
|
+
display: none;
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Two contracts, both load-bearing:
|
|
175
|
+
|
|
176
|
+
- **Fail to `cold`.** A broken read produces today's behaviour (a skeleton),
|
|
177
|
+
never a wrongly quiet screen. The key strings are duplicated from the app's
|
|
178
|
+
cache module by necessity — no module has loaded yet — so drift must land on
|
|
179
|
+
the harmless side.
|
|
180
|
+
- **This is chrome, never content, and never authorization.** The probe decides
|
|
181
|
+
which skeleton to paint. The app's own gated read path is still the only
|
|
182
|
+
thing that may put cached data on screen — a cache read is an authorization
|
|
183
|
+
boundary, and this script must never become one.
|
|
184
|
+
|
|
185
|
+
## Rung 3 — ship only the default screen
|
|
186
|
+
|
|
187
|
+
The first bundle contains the screen users land on and nothing else. Every
|
|
188
|
+
other screen is a route-level lazy chunk, fetched on first navigation:
|
|
189
|
+
|
|
190
|
+
```jsx
|
|
191
|
+
const Settings = lazy(() => import('./screens/Settings'));
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
The shell stays painted while a chunk loads — `Suspense` (or your framework's
|
|
195
|
+
equivalent) drops a [`.juno-arc`](./components/loader.md) into the main
|
|
196
|
+
region, and a nav destination that is loading gets `.juno-icon-loader` (the
|
|
197
|
+
icon ringed by the spinning arc) instead of a bare spinner. Heavy libraries a
|
|
198
|
+
rare media kind needs (3D, panorama) split again below the screen level, so
|
|
199
|
+
"Settings" never pays for three.js.
|
|
200
|
+
|
|
201
|
+
## Rung 4 — background warming
|
|
202
|
+
|
|
203
|
+
Lazy chunks trade boot weight for a spinner at tap time — the worst moment for
|
|
204
|
+
one, because the user just pointed at a specific thing. The trade only
|
|
205
|
+
completes when the chunks arrive **in the background, before the tap**:
|
|
206
|
+
|
|
207
|
+
- **After content, never before.** Warming starts once the first page of real
|
|
208
|
+
content has landed (or provably isn't coming: no backend configured,
|
|
209
|
+
connection failed). Content gets the network first — that is the ordering
|
|
210
|
+
rule, and it should be a pure, testable predicate in code.
|
|
211
|
+
- **Idle trigger:** one chunk per `requestIdleCallback` slot, ordered by how
|
|
212
|
+
likely a phone user is to reach each screen — never the whole list in one
|
|
213
|
+
burst.
|
|
214
|
+
- **Intent trigger:** first `pointerdown` on anything that leads to a lazy
|
|
215
|
+
screen warms that screen immediately — insurance for when idle never comes.
|
|
216
|
+
- **Gate on the link class.** `Save-Data`, metered connections, or an explicit
|
|
217
|
+
user setting mean speculative bytes are not yours to spend. One module owns
|
|
218
|
+
that judgement; warming asks it, never decides itself.
|
|
219
|
+
- **Don't warm everything.** Screens that are large and rarely reached from a
|
|
220
|
+
phone stay cold on purpose. Warming is a bet; size the stake to the odds.
|
|
221
|
+
|
|
222
|
+
The same triggers and the same gate apply to warming **data** (next pages of
|
|
223
|
+
content, thumbnails around the viewport) — same budget, same ordering rule.
|
|
224
|
+
|
|
225
|
+
## Rung 5 — the offline shell
|
|
226
|
+
|
|
227
|
+
An app-shell service worker precaches rungs 1–3 (`js`, `css`, `html`, `woff2`,
|
|
228
|
+
icon `svg`) so a cold start with no network still paints the full ladder.
|
|
229
|
+
Boundaries that keep it honest:
|
|
230
|
+
|
|
231
|
+
- **Precache the shell, not the long tail.** The rarely-used heavy chunks from
|
|
232
|
+
rung 3 are excluded — precaching them would make every install download
|
|
233
|
+
hundreds of kB to speed up a path almost nobody takes.
|
|
234
|
+
- **Content requests bypass the worker entirely** when the app has its own
|
|
235
|
+
authenticated content cache — two caches for one byte is a purge bug waiting
|
|
236
|
+
to happen.
|
|
237
|
+
- **Hand over on cold start only** (no mid-session `skipWaiting`), and ship a
|
|
238
|
+
kill switch (`?sw=off`) — a cached shell can otherwise hide fresh code from
|
|
239
|
+
every reload.
|
|
240
|
+
|
|
241
|
+
### Fonts
|
|
242
|
+
|
|
243
|
+
Fonts block the first _text_ paint, so they ride the critical path: subset by
|
|
244
|
+
`unicode-range` (basic Latin separate from accented extensions) and let the
|
|
245
|
+
service worker precache all halves for later sessions. Nexora's split took the
|
|
246
|
+
critical-path font bytes from 72 kB to 28 kB with identical glyph coverage.
|
|
247
|
+
|
|
248
|
+
## Guarding the copies
|
|
249
|
+
|
|
250
|
+
The pattern requires literal duplicates — token values, cache key strings,
|
|
251
|
+
safe-area expressions — in a file no bundler touches. Prose ("keep in sync")
|
|
252
|
+
does not keep them in sync; tests do:
|
|
253
|
+
|
|
254
|
+
- **Assert literals against the consumed build.** A unit test reads
|
|
255
|
+
`node_modules/junoui/dist/css/juno.css` (or `dist/json/tokens.json`) and the
|
|
256
|
+
app's `index.html`, and fails when a literal no longer matches its token —
|
|
257
|
+
including after a junoui version bump, which is exactly when it drifts.
|
|
258
|
+
- **Fail toward the harmless side.** Every probe in rung 2 is written so an
|
|
259
|
+
error lands on `cold`/absent — the pre-existing behaviour — never on a wrong
|
|
260
|
+
positive.
|
|
261
|
+
- **Verify through the server, not the repo.** First-paint claims are measured
|
|
262
|
+
from a served build with a cold cache (headless browser, CPU throttled), not
|
|
263
|
+
inferred from bundle sizes. A dev server is the wrong instrument: it serves
|
|
264
|
+
the unbundled module graph (Nexora: 80 requests where the build makes 10) and
|
|
265
|
+
its numbers say nothing about what users get.
|
|
266
|
+
|
|
267
|
+
## Serving the ladder
|
|
268
|
+
|
|
269
|
+
None of the rungs exist on a dev server: it ships the unbundled module graph,
|
|
270
|
+
React's dev build, no minification, and it cannot register a service worker.
|
|
271
|
+
The dev server is the workshop; every demo anyone judges the app's speed by
|
|
272
|
+
serves the **production build**. A standing demo that still picks up every
|
|
273
|
+
change is three processes under one supervisor:
|
|
274
|
+
|
|
275
|
+
```sh
|
|
276
|
+
vite build --watch # rebuilds dist/ a few seconds after any source change
|
|
277
|
+
vite preview --host # serves dist/ (with the app's API proxy)
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
plus whatever restart loop keeps them alive. Properties that fall out:
|
|
281
|
+
|
|
282
|
+
- **Rebuild is content-addressed.** Hashed asset names mean an unchanged
|
|
283
|
+
module keeps its URL — reloads after a rebuild refetch only what changed.
|
|
284
|
+
- **The watch build skips type-checking.** Fine for a demo (CI still gates);
|
|
285
|
+
know that a type error will not stop the demo from rebuilding.
|
|
286
|
+
- **Each rebuild empties the output directory.** Anything placed in `dist/`
|
|
287
|
+
by hand (a connect page, a fixture) needs a guard that puts it back.
|
|
288
|
+
- **Secure context or no service worker.** On real devices the SW half of the
|
|
289
|
+
ladder needs HTTPS; plain-http demos still get the pre-bundle shell, warm
|
|
290
|
+
boot and the content caches — relaunches paint instantly and refetch only
|
|
291
|
+
code.
|
|
292
|
+
|
|
293
|
+
Measured on the reference implementation the day this section landed, same
|
|
294
|
+
port that previously ran the dev server: cold = 13 requests; relaunch = 12 of
|
|
295
|
+
13 served by the service worker, first-paint 32 ms.
|