From 52d70cd38943706e4773377506a5307fc9989334 Mon Sep 17 00:00:00 2001 From: SandObserver <260779319+SandObserver@users.noreply.github.com> Date: Wed, 23 Sep 2026 18:55:44 -0400 Subject: [PATCH 1/2] Restyle the home screen and app icons on the iOS 27 kit --- ui/admin/index.html | 2 +- ui/css/dashboard.css | 205 +++++++++++++++-------------- ui/index.html | 4 +- ui/js/admin-app-form.js | 8 +- ui/js/admin-auth.js | 4 +- ui/js/admin-color-control.js | 4 +- ui/js/admin-drag.js | 2 +- ui/js/admin-list.js | 8 +- ui/js/admin-settings.js | 8 +- ui/js/admin-shared.js | 2 +- ui/js/admin-widget-form.js | 8 +- ui/js/admin.js | 18 +-- ui/js/background.js | 2 +- ui/js/dashboard.js | 15 +-- ui/js/listbox.js | 2 +- ui/js/mobile-metrics.js | 2 +- ui/js/spotlight.js | 2 +- ui/js/ui.js | 21 +-- ui/js/utils.js | 2 + ui/js/widget-config-form.js | 8 +- ui/test/glass-rim.test.mjs | 4 +- ui/test/pagination.test.mjs | 1 + ui/test/smooth-corner.test.mjs | 2 +- ui/test/spotlight-dismiss.test.mjs | 2 +- ui/test/type-scale.test.mjs | 10 +- 25 files changed, 176 insertions(+), 170 deletions(-) diff --git a/ui/admin/index.html b/ui/admin/index.html index 4d5b83c9..2af15435 100644 --- a/ui/admin/index.html +++ b/ui/admin/index.html @@ -292,6 +292,6 @@

General

- + diff --git a/ui/css/dashboard.css b/ui/css/dashboard.css index bb1d37f7..3742dd57 100644 --- a/ui/css/dashboard.css +++ b/ui/css/dashboard.css @@ -2,8 +2,8 @@ :root { --glass-bg: #1C1C1E; --glass-border: rgba(255,255,255,.09); - --glass-surface-bg: rgba(155,155,158,.09); - --glass-surface-blur: blur(14px) saturate(160%); + --glass-surface-bg: rgba(51,51,51,.33); + --glass-surface-blur: blur(12px) saturate(180%); --glass-rim-light: rgba(255,255,255,.30); --glass-rim-dark: rgba(255,255,255,.07); --label-color: rgba(255,255,255,.90); @@ -209,13 +209,13 @@ /* No blend modes here. Safari blends before it clips the rounded edge and leaks a light stroke while the tile scales. */ .plate { - --icon-band: calc(var(--pw,60px) * .018); - --icon-light: radial-gradient(60% var(--icon-band) at 50% 0,oklch(from var(--tc,var(--sy-gray)) calc(l + .14) calc(c * 1.15) h),transparent), - radial-gradient(60% var(--icon-band) at 50% 100%,oklch(from var(--tc,var(--sy-gray)) calc(l + .06) c h),transparent); - --icon-shade: radial-gradient(calc(var(--icon-band) * .7) 30% at 0 50%,oklch(from var(--tc,var(--sy-gray)) calc(l - min(c, .2) * .5) c h),transparent), - radial-gradient(calc(var(--icon-band) * .7) 30% at 100% 50%,oklch(from var(--tc,var(--sy-gray)) calc(l - min(c, .2) * .5) c h),transparent); - --icon-body: linear-gradient(oklch(from var(--tc,var(--sy-gray)) calc(l + .04 * (1 - l) + min(c, .2) * .1) c h),transparent 50%,oklch(from var(--tc,var(--sy-gray)) calc(l - .06 * (1 - l) - min(c, .2) * .2) c h)); - background-image:var(--icon-light), var(--icon-shade), var(--icon-body); + --icon-rim: radial-gradient(46% 2% at 50% 0,rgba(255,255,255,.6),rgba(255,255,255,.25) 45%,transparent), + radial-gradient(46% 1.6% at 50% 100%,rgba(255,255,255,.35),rgba(255,255,255,.14) 45%,transparent); + --icon-shade: radial-gradient(1.2% 25% at 0 50%,rgba(0,0,0,.19),transparent), + radial-gradient(1.2% 25% at 100% 50%,rgba(0,0,0,.19),transparent); + --icon-body: linear-gradient(oklch(from var(--tc,var(--sy-gray)) calc(l + .03 + l * .05) c h) 4%,var(--tc,var(--sy-gray)) 50%,oklch(from var(--tc,var(--sy-gray)) calc(l - .03 - l * .05) c h) 96%); + background-image:var(--icon-rim), var(--icon-shade), var(--icon-body); + box-shadow:inset 0 .5px 0 rgba(255,255,255,.18), inset 0 -.5px 0 rgba(255,255,255,.1); } .iwrap img, .dwrap img { object-fit:contain; position:relative; z-index:3 } .iwrap .fb, .dwrap .fb, @@ -257,15 +257,15 @@ /* ── Badge — reference size, scaled by --sc (desktop overrides below) ── */ .badge { position:absolute; - top:calc(-7px * var(--sc,1)); inset-inline-end:calc(-7px * var(--sc,1)); + top:calc(-11px * var(--sc,1)); inset-inline-end:calc(-11px * var(--sc,1)); min-width:calc(24px * var(--sc,1)); height:calc(24px * var(--sc,1)); - border-radius:50px; padding:0 calc(6px * var(--sc,1)); + border-radius:50px; padding:0 calc(7px * var(--sc,1)); display:none; align-items:center; justify-content:center; - font-size:calc(var(--fs-footnote) * var(--sc,1));letter-spacing:var(--tr-footnote); font-weight:800; color:var(--on-fill); line-height:1; + font-size:calc(var(--fs-callout) * var(--sc,1));letter-spacing:var(--tr-callout); font-weight:500; color:var(--on-fill); line-height:1; white-space:nowrap; - max-width:calc(100% + 7px * var(--sc,1)); + max-width:calc(100% + 11px * var(--sc,1)); gap:0; z-index:20; pointer-events:none; -webkit-touch-callout:none; touch-action:manipulation; @@ -291,7 +291,7 @@ /* Both pills are pseudo-elements at negative depth. That is the only order where one paints under the other and both stay under the label, and it needs the element's own background to stay clear. */ - .badge.has-more { background:none; inset-inline-end:calc(-7px * var(--sc,1) + var(--badge-peek)) } + .badge.has-more { background:none; inset-inline-end:calc(-11px * var(--sc,1) + var(--badge-peek)) } /* The badge is transparent to the pointer so a click reaches the tile. One with a popover has to take it, or nothing can hover what it opens. */ .badge.has-pop { pointer-events:auto } @@ -310,7 +310,7 @@ box-shadow:0 0 0 1.5px var(--badge-stack-edge,rgba(0,0,0,.55)); } .badge.has-more::after { z-index:-1; background:var(--badge-bg,var(--badge-blue)) } - body:not(.is-mob) .badge.has-more { inset-inline-end:calc(-7px + var(--badge-peek)) } + body:not(.is-mob) .badge.has-more { inset-inline-end:calc(-9px + var(--badge-peek)) } /* ── Badge popover — the values the pill has no room for ── */ /* In the top layer now. The popover defaults have to be undone or they @@ -319,37 +319,37 @@ .badge-pop { inset:auto; margin:0; position:fixed; z-index:900; min-width:124px; max-width:min(300px,calc(100vw - 16px)); - padding:5px; border-radius:11px; - background:rgba(28,30,42,.96); - border:1px solid rgba(255,255,255,.16); - box-shadow:0 18px 44px rgba(0,0,0,.6),0 0 0 .5px rgba(255,255,255,.08); - -webkit-backdrop-filter:blur(24px) saturate(160%); - backdrop-filter:blur(24px) saturate(160%); + padding:10px 8px; border-radius:22px; + background:rgba(30,30,32,.72); + border:.5px solid rgba(255,255,255,.12); + box-shadow:0 8px 48px rgba(0,0,0,.45); + -webkit-backdrop-filter:blur(32px) saturate(180%); + backdrop-filter:blur(32px) saturate(180%); color:#fff; /* SC 1.4.13 asks that hover content be hoverable. pointer-events:none put it out of reach and left the outside-pointerdown guard unreachable. */ pointer-events:auto; } .badge-pop-row { - display:flex; align-items:flex-start; gap:7px; padding:2px 6px; - font-size:var(--fs-caption-1);line-height:var(--lh-caption-1);letter-spacing:var(--tr-caption-1); + display:flex; align-items:flex-start; gap:8px; padding:3px 8px; + font-size:var(--fs-subheadline);line-height:var(--lh-subheadline);letter-spacing:var(--tr-subheadline); } .badge-pop-dot { width:7px; height:7px; border-radius:50%; flex-shrink:0; margin-top:.35em; background:var(--badge-blue) } /* Two lines, then clip. German and Persian labels run longer than English. The value stays on one line beside it. */ .badge-pop-name { - flex:1; min-width:0; color:rgba(255,255,255,.78); + flex:1; min-width:0; color:rgba(235,235,245,.7); overflow-wrap:anywhere; display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:2; overflow:hidden; } - .badge-pop-val { font-weight:700; flex-shrink:0; font-variant-numeric:tabular-nums } + .badge-pop-val { font-weight:600; flex-shrink:0; font-variant-numeric:tabular-nums } /* Desktop: restore compact badge size */ body:not(.is-mob) .badge { --badge-peek:6px; - max-width:calc(100% + 7px); - top:-7px; inset-inline-end:-7px; - min-width:20px; height:20px; padding:0 5px; - font-size:var(--fs-caption-2);line-height:var(--lh-caption-2);letter-spacing:var(--tr-caption-2); font-weight:700; + max-width:calc(100% + 9px); + top:-9px; inset-inline-end:-9px; + min-width:22px; height:22px; padding:0 6px; + font-size:var(--fs-subheadline);line-height:1;letter-spacing:var(--tr-subheadline); font-weight:500; } @@ -357,15 +357,14 @@ #dots { position:fixed; bottom:160px; left:50%; /* desktop only — mobile overrides bottom inline */ transform:translateX(-50%); - /* 24px between centres: an interior dot's hit area is bounded by its - neighbours', so the pitch is its width. */ + /* Keep 24px between centres. Less fails the WCAG 2.5.8 target size. */ display:flex; gap:16px; z-index:500; /* The tone is sampled after the first paint. Until it lands the bar is a white tint, which over a light wallpaper is invisible, so it carries an edge that reads on either ground. */ - background:rgba(255,255,255,.28); border:1px solid rgba(0,0,0,.22); - -webkit-backdrop-filter:blur(12px) saturate(140%); backdrop-filter:blur(12px) saturate(140%); - border-radius:24px; padding:10px 18px; + background:rgba(255,255,255,.14); border:.5px solid rgba(0,0,0,.18); + -webkit-backdrop-filter:blur(24px) saturate(160%); backdrop-filter:blur(24px) saturate(160%); + border-radius:999px; padding:8px 12px; align-items:center; } /* State by shape as well as fill. A tint over a wallpaper the project does @@ -384,11 +383,12 @@ bright, leave what is dark. The tone is sampled from the wallpaper behind the bar, by the same pass that inks the icon labels. */ #dots[data-tone="dark"] { - background:rgba(0,0,0,.35); border-color:rgba(255,255,255,.30); + background:rgba(0,0,0,.30); border-color:rgba(255,255,255,.30); --dots-ink:rgba(0,0,0,.85); } #dots[data-tone="dark"] .dot { border-color:rgba(0,0,0,.55) } #dots[hidden] { display:none } + #dots:empty { display:none } /* Mirrored by the no-dock reserve in desktopSlots(). */ body.no-dock:not(.is-mob) #dots { bottom:24px } @@ -399,10 +399,10 @@ display:none; /* shown by buildMobile */ align-items:center; justify-content:center; - background:rgba(60,60,67,.72); - -webkit-backdrop-filter:blur(20px) saturate(160%); backdrop-filter:blur(20px) saturate(160%); - border-radius:calc(17px * var(--sc,1)); /* half of pillH = capsule ends */ - border:0.5px solid rgba(255,255,255,.10); + background:var(--glass-surface-bg); + -webkit-backdrop-filter:var(--glass-surface-blur); backdrop-filter:var(--glass-surface-blur); + border-radius:999px; + border:0.5px solid rgba(255,255,255,.22); cursor:pointer; -webkit-tap-highlight-color:transparent; -webkit-user-select:none; user-select:none; @@ -421,11 +421,11 @@ pointer-events:none; } #mob-search-pill .msp-icon { - font-size:calc(var(--fs-footnote) * var(--sc,1));letter-spacing:var(--tr-footnote); color:rgba(255,255,255,.60); line-height:1; + font-size:calc(12px * var(--sc,1)); font-weight:600; color:#FFFFFF; line-height:1; font-style:normal; } #mob-search-pill .msp-label { - font-size:calc(var(--fs-subheadline) * var(--sc,1));line-height:var(--lh-subheadline);letter-spacing:var(--tr-subheadline); font-weight:400; color:rgba(255,255,255,.80); + font-size:calc(12px * var(--sc,1));line-height:1; font-weight:600; color:#FFFFFF; white-space:nowrap; } /* Dots row — shown when paging */ @@ -442,10 +442,10 @@ /* Pill dots — must match JS _pdotSz/_pdotGap, which size the paging pill */ #mob-search-pill .msp-dot { width:calc(8px * var(--sc,1)); height:calc(8px * var(--sc,1)); border-radius:50%; - background:rgba(255,255,255,.38); flex-shrink:0; - transition:background .18s,transform .18s; + background:rgba(255,255,255,.35); flex-shrink:0; + transition:background .18s; } - #mob-search-pill .msp-dot.on { background:rgba(255,255,255,.90); transform:scale(1.2) } + #mob-search-pill .msp-dot.on { background:#FFFFFF } /* State transitions */ #mob-search-pill.paging .msp-search { opacity:0; pointer-events:none } #mob-search-pill.paging .msp-dots { opacity:1; pointer-events:auto } @@ -548,19 +548,21 @@ /* Search bar as a floating card at the top */ body:not(.is-mob) #sbox { width:min(680px,92vw); - background:rgba(28,28,32,.92); - border:1px solid rgba(255,255,255,.14); border-radius:18px; - box-shadow:0 20px 60px rgba(0,0,0,.60); overflow:hidden; + background:rgba(30,30,32,.72); + -webkit-backdrop-filter:blur(32px) saturate(180%); backdrop-filter:blur(32px) saturate(180%); + border:.5px solid rgba(255,255,255,.12); border-radius:999px; + box-shadow:0 8px 48px rgba(0,0,0,.45); overflow:hidden; flex-shrink:0; padding:0; order:-1; } /* Results as a separate card below the search bar */ body:not(.is-mob) #sres { width:min(680px,92vw); max-height:60vh; overflow-y:auto; - padding:6px 0; margin-top:8px; flex:none; min-height:0; - background:rgba(28,28,32,.92); - border:1px solid rgba(255,255,255,.14); border-radius:18px; - box-shadow:0 20px 60px rgba(0,0,0,.40); + padding:10px 8px; margin-top:10px; flex:none; min-height:0; + background:rgba(30,30,32,.72); + -webkit-backdrop-filter:blur(32px) saturate(180%); backdrop-filter:blur(32px) saturate(180%); + border:.5px solid rgba(255,255,255,.12); border-radius:34px; + box-shadow:0 8px 48px rgba(0,0,0,.45); } body:not(.is-mob) #sres:empty { display:none; } @@ -568,8 +570,7 @@ .spot-bar { display:flex; align-items:center; - gap:10px; padding:14px 18px; - border-bottom:1px solid rgba(255,255,255,.08); + gap:10px; padding:13px 22px; } .spot-field { display:contents; } body.is-mob #spot .spot-bar { @@ -578,9 +579,10 @@ } body.is-mob #spot .spot-field { display:flex; align-items:center; gap:8px; - flex:1; min-width:0; min-height:44px; - background:rgba(118,118,128,.30); - border-radius:12px; padding:0 12px; + flex:1; min-width:0; min-height:48px; + background:rgba(30,30,32,.72); + box-shadow:inset 0 0 0 .5px rgba(255,255,255,.12); + border-radius:999px; padding:0 16px; box-sizing:border-box; } .spot-icon { @@ -632,14 +634,13 @@ /* ── Spotlight result rows ── */ .sr-section { - font-size:var(--fs-footnote);line-height:var(--lh-footnote); font-weight:600; color:rgba(255,255,255,.42); - padding:10px 16px 4px; letter-spacing:0.04em; text-transform:uppercase; - border-bottom:0.5px solid rgba(255,255,255,.08); + font-size:var(--fs-subheadline);line-height:var(--lh-subheadline);letter-spacing:var(--tr-subheadline); font-weight:600; color:rgba(235,235,245,.6); + padding:6px 12px 6px; } .sr { display:flex; align-items:center; - gap:14px; padding:12px 16px; cursor:pointer; text-decoration:none; + gap:14px; padding:10px 12px; border-radius:22px; cursor:pointer; text-decoration:none; transition:background .12s; } .sr:hover, .sr:active, .sr.sel { background:rgba(255,255,255,.07) } @@ -732,9 +733,9 @@ .folder-dot { border-radius:50%; cursor:pointer; flex-shrink:0; background:rgba(255,255,255,.35); - transition:background .2s,transform .2s; + transition:background .2s; } - .folder-dot.on { background:var(--control-knob); transform:scale(1.25) } + .folder-dot.on { background:var(--control-knob) } /* ── Folder overlay — desktop ── */ .folder-overlay { @@ -791,8 +792,8 @@ .dyn-grid { display:grid; grid-template-columns:repeat(6,var(--iw)); gap:24px; justify-content:center; } .dyn-mob-btn { flex:1; height:var(--rh); background:none; border:none; padding:0; display:flex; flex-direction:column; align-items:center; justify-content:center; cursor:pointer; flex-shrink:0; -webkit-tap-highlight-color:transparent; -webkit-appearance:none; } .dyn-mob-icon { display:flex; flex-direction:column; align-items:center; justify-content:center; width:var(--cw); height:var(--rh); cursor:pointer; text-decoration:none; flex-shrink:0; -webkit-tap-highlight-color:transparent; } - .dyn-mob-label { font-weight:500; color:rgba(255,255,255,.88); text-align:center; margin-top:2px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; text-shadow:0 1px 3px rgba(0,0,0,.65); font-size:var(--lfs); width:var(--lw); } - .dyn-fold-label { font-weight:500; color:rgba(255,255,255,.90); text-align:center; margin-top:3px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; text-shadow:0 1px 3px rgba(0,0,0,.70); font-size:var(--lfs); width:var(--lw); } + .dyn-mob-label { font-weight:500; color:#FFFFFF; text-align:center; margin-top:5px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; text-shadow:0 1px 3px rgba(0,0,0,.65); font-size:var(--lfs); width:var(--lw); } + .dyn-fold-label { font-weight:500; color:#FFFFFF; text-align:center; margin-top:5px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; text-shadow:0 1px 3px rgba(0,0,0,.70); font-size:var(--lfs); width:var(--lw); } .dyn-fold-wrap { position:absolute; inset:0; pointer-events:none; border-radius:var(--br); display:grid; grid-template-columns:repeat(3,1fr); grid-template-rows:repeat(3,1fr); gap:var(--gap); padding:var(--pad); overflow:hidden; } .dyn-fold-cell { display:flex; align-items:center; justify-content:center; z-index:1; pointer-events:none; min-width:0; min-height:0; } .dyn-clip { overflow:hidden; flex-shrink:0; width:var(--gw); height:var(--gh); } @@ -843,20 +844,21 @@ margin-block-end: var(--sp-32); } .empty-state-title { - margin: 0 0 var(--sp-10); font-weight: 600; color: var(--label-primary); - font-size:var(--fs-title-1);line-height:var(--lh-title-1);letter-spacing:var(--tr-title-1); + margin: 0; font-weight: 600; color: var(--label-primary); + font-size:var(--fs-title-2);line-height:var(--lh-title-2);letter-spacing:var(--tr-title-2); } .empty-state-sub { - margin: 0; max-width: 38ch; color: var(--label-secondary); - font-size:var(--fs-callout);line-height:var(--lh-callout);letter-spacing:var(--tr-callout); + margin: 0; max-width: 38ch; color: var(--label-secondary); font-weight: 500; + font-size:var(--fs-title-2);line-height:var(--lh-title-2);letter-spacing:var(--tr-title-2); } .empty-state-btn { pointer-events: auto; - margin-block-start: var(--sp-32); padding: var(--sp-12) var(--sp-32); - border-radius: 22px; - background: rgba(255,255,255,.12); border: 1px solid rgba(255,255,255,.18); - color: var(--label-primary); text-decoration: none; - font-size:var(--fs-subheadline);line-height:var(--lh-subheadline);letter-spacing:var(--tr-subheadline); + margin-block-start: var(--sp-32); padding: 15px var(--sp-32); + border-radius: 999px; min-height: 52px; + background: var(--glass-surface-bg); border: .5px solid rgba(255,255,255,.22); + -webkit-backdrop-filter: var(--glass-surface-blur); backdrop-filter: var(--glass-surface-blur); + color: var(--label-primary); text-decoration: none; font-weight: 500; + font-size:var(--fs-body);line-height:var(--lh-body);letter-spacing:var(--tr-body); } .empty-state-hint { pointer-events: auto; @@ -876,8 +878,9 @@ .api-error-title { font-size:var(--fs-body);line-height:var(--lh-body);letter-spacing:var(--tr-body); font-weight: 500; margin: 0; } .api-error-sub { font-size:var(--fs-footnote);line-height:var(--lh-footnote);letter-spacing:var(--tr-footnote); color: rgba(255,255,255,.4); margin: 0; } .api-error-btn { - margin-top: 8px; padding: 10px 24px; border-radius: 20px; - background: rgba(255,255,255,.12); border: 1px solid rgba(255,255,255,.18); + margin-top: 8px; padding: 13px 24px; border-radius: 999px; min-height: 48px; + background: var(--glass-surface-bg); border: .5px solid rgba(255,255,255,.22); + -webkit-backdrop-filter: var(--glass-surface-blur); backdrop-filter: var(--glass-surface-blur); font-weight: 500; color: var(--label-primary); font-size:var(--fs-subheadline);line-height:var(--lh-subheadline);letter-spacing:var(--tr-subheadline); cursor: pointer; font-family: inherit; } @@ -959,46 +962,46 @@ body.is-mob .spot-field:focus-within { outline:2px solid var(--accent); outline- margin: 0; border: 0; color: inherit; position: fixed; inset: 0; z-index: 10001; display: flex; align-items: center; justify-content: center; - background: rgba(0,0,0,.5); - -webkit-backdrop-filter: blur(20px) saturate(140%); backdrop-filter: blur(20px) saturate(140%); + background: rgba(0,0,0,.48); font-family: inherit; padding: 24px; } .setup-prompt::backdrop { background: none } .setup-card { - width: 100%; max-width: 360px; display: flex; flex-direction: column; gap: 12px; - padding: 24px; border-radius: 20px; - background: rgba(40,40,44,.7); border: 1px solid rgba(255,255,255,.14); - -webkit-backdrop-filter: blur(24px) saturate(140%); backdrop-filter: blur(24px) saturate(140%); + width: 100%; max-width: 340px; display: flex; flex-direction: column; gap: 10px; + padding: 22px 14px 14px; border-radius: 34px; + background: rgba(30,30,32,.82); border: .5px solid rgba(255,255,255,.12); + -webkit-backdrop-filter: blur(32px) saturate(180%); backdrop-filter: blur(32px) saturate(180%); + box-shadow: 0 8px 48px rgba(0,0,0,.45); color: var(--label-primary); } -.setup-title { font-size:var(--fs-body);line-height:var(--lh-body);letter-spacing:var(--tr-body); font-weight: 600; margin: 0; } -.setup-sub { font-size:var(--fs-footnote);letter-spacing:var(--tr-footnote); line-height: 1.4; color: rgba(255,255,255,.55); margin: 0; } +.setup-title { font-size:var(--fs-headline);line-height:var(--lh-headline);letter-spacing:var(--tr-headline); font-weight: var(--fw-headline); margin: 0; padding-inline: 8px; } +.setup-sub { font-size:var(--fs-subheadline);letter-spacing:var(--tr-subheadline); line-height: 1.4; color: rgba(235,235,245,.7); margin: 0 0 6px; padding-inline: 8px; } .setup-pw { - width: 100%; box-sizing: border-box; padding: 11px 14px; border-radius: 12px; - background: rgba(0,0,0,.25); border: 1px solid rgba(255,255,255,.16); - color: var(--label-primary); font-size:var(--fs-subheadline);line-height:var(--lh-subheadline);letter-spacing:var(--tr-subheadline); font-family: inherit; outline: none; + width: 100%; box-sizing: border-box; min-height: 52px; padding: 0 16px; border-radius: 26px; + background: rgba(118,118,128,.2); border: 0; + color: var(--label-primary); font-size:var(--fs-body);line-height:var(--lh-body);letter-spacing:var(--tr-body); font-weight: 500; font-family: inherit; outline: none; } -.setup-pw:focus { border-color: var(--accent); } +.setup-pw:focus { box-shadow: 0 0 0 2px var(--accent); } .setup-field { position: relative; display: flex; align-items: center; } -.setup-field .setup-pw { padding-inline-end: 44px; } +.setup-field .setup-pw { padding-inline-end: 48px; } .setup-reveal { - position: absolute; inset-inline-end: 6px; display: flex; align-items: center; justify-content: center; - width: 32px; height: 32px; padding: 0; border: none; border-radius: 8px; - background: none; color: rgba(255,255,255,.55); cursor: pointer; + position: absolute; inset-inline-end: 8px; display: flex; align-items: center; justify-content: center; + width: 36px; height: 36px; padding: 0; border: none; border-radius: 50%; + background: none; color: rgba(235,235,245,.6); cursor: pointer; } .setup-reveal:hover { color: rgba(255,255,255,.9); } .setup-reveal[aria-pressed="true"] { color: var(--accent); } -.setup-bars { display: flex; gap: 4px; } +.setup-bars { display: flex; gap: 4px; padding-inline: 8px; } .setup-bars .pwbar { flex: 1; height: 4px; border-radius: 2px; background: rgba(255,255,255,.1); transition: background .15s; } -.setup-hint { font-size:var(--fs-caption-1);line-height:var(--lh-caption-1);letter-spacing:var(--tr-caption-1); min-height: 14px; color: rgba(255,255,255,.5); } -.setup-err { display: none; font-size:var(--fs-footnote);line-height:var(--lh-footnote);letter-spacing:var(--tr-footnote); color: var(--danger); } -.setup-btns { display: flex; gap: 10px; margin-top: 4px; } +.setup-hint { font-size:var(--fs-caption-1);line-height:var(--lh-caption-1);letter-spacing:var(--tr-caption-1); min-height: 14px; color: rgba(235,235,245,.6); padding-inline: 8px; } +.setup-err { display: none; font-size:var(--fs-footnote);line-height:var(--lh-footnote);letter-spacing:var(--tr-footnote); color: var(--danger); padding-inline: 8px; } +.setup-btns { display: flex; gap: 8px; margin-top: 6px; } .setup-btn { - flex: 1; padding: 10px; border-radius: 14px; font-size:var(--fs-subheadline);line-height:var(--lh-subheadline);letter-spacing:var(--tr-subheadline); cursor: pointer; font-family: inherit; - border: 1px solid rgba(255,255,255,.18); + flex: 1; min-height: 48px; padding: 13px 16px; border-radius: 24px; border: 0; cursor: pointer; font-family: inherit; + font-size:var(--fs-headline);line-height:var(--lh-headline);letter-spacing:var(--tr-headline); font-weight: var(--fw-headline); } -.setup-btn-skip { background: rgba(255,255,255,.1); color: rgba(255,255,255,.85); } -.setup-btn-set { background: var(--accent); border-color: var(--accent); color: var(--label-primary); } +.setup-btn-skip { background: rgba(118,118,128,.32); color: var(--label-primary); } +.setup-btn-set { background: var(--accent); color: var(--on-fill); } .setup-btn-set:disabled { opacity: .4; cursor: default; } /* ── RTL ── @@ -1046,13 +1049,13 @@ body.is-mob .spot-field:focus-within { outline:2px solid var(--accent); outline- .sy-as{stroke:var(--icon-accent,currentColor)} @media (forced-colors:active){.sy-icon{--icon-accent:CanvasText}} -.plate.plate-clear { background-image:none } +.plate.plate-clear { background-image:none; box-shadow:none } html[data-theme="light"] { --bg-base: #E5E5EA; --glass-bg: #FFFFFF; --glass-border: rgba(0,0,0,.06); - --glass-surface-bg: rgba(255,255,255,.4); + --glass-surface-bg: rgba(153,153,153,.33); --glass-surface-blur: blur(24px) saturate(180%); --glass-rim-light: rgba(255,255,255,1); --glass-rim-dark: rgba(255,255,255,.4); diff --git a/ui/index.html b/ui/index.html index 277fc2d0..70ced654 100644 --- a/ui/index.html +++ b/ui/index.html @@ -26,7 +26,7 @@ Stackyard - +

Stackyard

@@ -55,5 +55,5 @@ - + diff --git a/ui/js/admin-app-form.js b/ui/js/admin-app-form.js index e517e290..ed71d7e1 100644 --- a/ui/js/admin-app-form.js +++ b/ui/js/admin-app-form.js @@ -1,4 +1,4 @@ -import { clr, el, inp as inpById, q as qSel, qa, qi, tgt } from '/js/utils.js?v=b6231666'; +import { clr, el, inp as inpById, q as qSel, qa, qi, tgt } from '/js/utils.js?v=01662438'; import { html, raw, setHtml } from '/js/html.js?v=c71f8903'; import { loadLocalIcons, resolveIcon, iconChain, cdnIconRef, splitIconRef } from '/js/icons.js?v=9c8c550c'; import { state } from '/js/admin-state.js?v=831e219e'; @@ -21,10 +21,10 @@ import { reveal, setTogDisabled, swapContent, -} from '/js/admin-shared.js?v=d4f4cd3c'; -import { createListbox } from '/js/listbox.js?v=95ec8aab'; +} from '/js/admin-shared.js?v=e477e574'; +import { createListbox } from '/js/listbox.js?v=fd1151b6'; import { MAX_LABELS } from '/js/badge-logic.js?v=9e6d9d4b'; -import { renderColorControl, BADGE_DEFAULT } from '/js/admin-color-control.js?v=6e070d2e'; +import { renderColorControl, BADGE_DEFAULT } from '/js/admin-color-control.js?v=ee39fb36'; import { badgeErrorAdvice, TONE } from '/js/admin-error.js?v=e4ea7820'; import { fluidHoverClear, fluidHoverKb } from '/js/fluid-hover.js?v=cb886e86'; import { iconSvg } from '/js/icon-set.js?v=ba9434d9'; diff --git a/ui/js/admin-auth.js b/ui/js/admin-auth.js index f5110660..bf7063da 100644 --- a/ui/js/admin-auth.js +++ b/ui/js/admin-auth.js @@ -1,7 +1,7 @@ -import { apiGet, apiPost } from '/js/admin-shared.js?v=d4f4cd3c'; +import { apiGet, apiPost } from '/js/admin-shared.js?v=e477e574'; import { t } from '/js/i18n.js?v=1f1ea9c1'; import { pwStrength } from '/js/password-strength.js?v=42f45ac7'; -import { el, inp as inpById, qa } from '/js/utils.js?v=b6231666'; +import { el, inp as inpById, qa } from '/js/utils.js?v=01662438'; export async function checkAuth(onLogin) { try { diff --git a/ui/js/admin-color-control.js b/ui/js/admin-color-control.js index 3b5482d6..5b2f0b64 100644 --- a/ui/js/admin-color-control.js +++ b/ui/js/admin-color-control.js @@ -1,7 +1,7 @@ -import { PE_SVG, initInlineEdit, toast, reveal } from '/js/admin-shared.js?v=d4f4cd3c'; +import { PE_SVG, initInlineEdit, toast, reveal } from '/js/admin-shared.js?v=e477e574'; import { t } from '/js/i18n.js?v=1f1ea9c1'; import { html, raw, setHtml } from '/js/html.js?v=c71f8903'; -import { qa, q } from '/js/utils.js?v=b6231666'; +import { qa, q } from '/js/utils.js?v=01662438'; import { iconSvg } from '/js/icon-set.js?v=ba9434d9'; import { HUE_NAMES, ROLE_NAMES, TILE_KEYWORDS, pageTheme, tileColor } from '/js/palette.js?v=3fb8ae43'; diff --git a/ui/js/admin-drag.js b/ui/js/admin-drag.js index 54eb2188..052a3b00 100644 --- a/ui/js/admin-drag.js +++ b/ui/js/admin-drag.js @@ -12,7 +12,7 @@ import { applyDrop, canJoinFolder, folderRowZone } from '/js/admin-drag-logic.js?v=6b767e76'; import { state } from '/js/admin-state.js?v=831e219e'; import { snapshotItems } from '/js/admin-save-logic.js?v=60a82419'; -import { qa } from '/js/utils.js?v=b6231666'; +import { qa } from '/js/utils.js?v=01662438'; /** @type {(before: unknown) => void} */ let _save = () => {}; diff --git a/ui/js/admin-list.js b/ui/js/admin-list.js index 5262bcbd..0a76ccae 100644 --- a/ui/js/admin-list.js +++ b/ui/js/admin-list.js @@ -14,12 +14,12 @@ import { collapsedFolders, filter, state } from '/js/admin-state.js?v=831e219e'; import { snapshotItems } from '/js/admin-save-logic.js?v=60a82419'; import { reorderItems } from '/js/admin-logic.js?v=5356a1b3'; -import { initDrag, wireRowDrag } from '/js/admin-drag.js?v=64aea034'; -import { paintIcon } from '/js/admin-shared.js?v=d4f4cd3c'; -import { clr, el, qa, setUserText } from '/js/utils.js?v=b6231666'; +import { initDrag, wireRowDrag } from '/js/admin-drag.js?v=70ee4100'; +import { paintIcon } from '/js/admin-shared.js?v=e477e574'; +import { clr, el, qa, setUserText } from '/js/utils.js?v=01662438'; import { html, raw, setHtml } from '/js/html.js?v=c71f8903'; import { t } from '/js/i18n.js?v=1f1ea9c1'; -import { sizeLabel } from '/js/admin-widget-form.js?v=6a06debe'; +import { sizeLabel } from '/js/admin-widget-form.js?v=bb851134'; import { widgetGlyph } from '/js/widget-glyphs.js?v=ad1869d7'; import { iconSvg } from '/js/icon-set.js?v=ba9434d9'; diff --git a/ui/js/admin-settings.js b/ui/js/admin-settings.js index 00f2d237..d7d92361 100644 --- a/ui/js/admin-settings.js +++ b/ui/js/admin-settings.js @@ -1,4 +1,4 @@ -import { toast, apiGet, apiPost, reveal, swapContent } from '/js/admin-shared.js?v=d4f4cd3c'; +import { toast, apiGet, apiPost, reveal, swapContent } from '/js/admin-shared.js?v=e477e574'; import { pwStrength } from '/js/password-strength.js?v=42f45ac7'; import { t } from '/js/i18n.js?v=1f1ea9c1'; import { @@ -9,9 +9,9 @@ import { BLOCK, } from '/js/admin-logic.js?v=5356a1b3'; import { confirmText } from '/js/modal.js?v=11fa1eff'; -import { el, inp, setUserText } from '/js/utils.js?v=b6231666'; -import { renderColorControl } from '/js/admin-color-control.js?v=6e070d2e'; -import { BACKDROP } from '/js/background.js?v=1fec6318'; +import { el, inp, setUserText } from '/js/utils.js?v=01662438'; +import { renderColorControl } from '/js/admin-color-control.js?v=ee39fb36'; +import { BACKDROP } from '/js/background.js?v=09beead7'; /* Mirrors the server's rule: auth cannot be switched on with no password. */ let _passwordSet = false; diff --git a/ui/js/admin-shared.js b/ui/js/admin-shared.js index b364d783..ae116d26 100644 --- a/ui/js/admin-shared.js +++ b/ui/js/admin-shared.js @@ -1,6 +1,6 @@ /* Stateless helpers shared by the admin modules. Mutable state stays out. */ import { recoversSession, toastHoldMs } from '/js/admin-logic.js?v=5356a1b3'; -import { el, q } from '/js/utils.js?v=b6231666'; +import { el, q } from '/js/utils.js?v=01662438'; import { t } from '/js/i18n.js?v=1f1ea9c1'; import { iconChain } from '/js/icons.js?v=9c8c550c'; import { iconSvg } from '/js/icon-set.js?v=ba9434d9'; diff --git a/ui/js/admin-widget-form.js b/ui/js/admin-widget-form.js index 868008e4..02ad4e33 100644 --- a/ui/js/admin-widget-form.js +++ b/ui/js/admin-widget-form.js @@ -1,11 +1,11 @@ import { state } from '/js/admin-state.js?v=831e219e'; -import { PE_SVG, initInlineEdit, swapContent } from '/js/admin-shared.js?v=d4f4cd3c'; -import { createListbox } from '/js/listbox.js?v=95ec8aab'; -import { renderWidgetConfigForm } from '/js/widget-config-form.js?v=7692d3e2'; +import { PE_SVG, initInlineEdit, swapContent } from '/js/admin-shared.js?v=e477e574'; +import { createListbox } from '/js/listbox.js?v=fd1151b6'; +import { renderWidgetConfigForm } from '/js/widget-config-form.js?v=152a4103'; import { html, raw, setHtml } from '/js/html.js?v=c71f8903'; import { sizesForView, widgetConfigMode, rejectionLines, carriesTypedValues } from '/js/admin-logic.js?v=5356a1b3'; import { t } from '/js/i18n.js?v=1f1ea9c1'; -import { q, qi, qa } from '/js/utils.js?v=b6231666'; +import { q, qi, qa } from '/js/utils.js?v=01662438'; import { iconSvg } from '/js/icon-set.js?v=ba9434d9'; const SIZES_WITH_ICONS = new Set(['small', 'medium', 'large', 'xlarge']); diff --git a/ui/js/admin.js b/ui/js/admin.js index b8d3ab6e..0dfe2096 100644 --- a/ui/js/admin.js +++ b/ui/js/admin.js @@ -1,6 +1,6 @@ -import { buildAppForm, buildFolderForm, captureActLabels, serializeKvRows } from '/js/admin-app-form.js?v=127079a8'; -import { checkAuth, requireLogin, wirePasswordStrength } from '/js/admin-auth.js?v=dbccdb3f'; -import { initList, render, syncFilterUI } from '/js/admin-list.js?v=da79755f'; +import { buildAppForm, buildFolderForm, captureActLabels, serializeKvRows } from '/js/admin-app-form.js?v=8a42d8e0'; +import { checkAuth, requireLogin, wirePasswordStrength } from '/js/admin-auth.js?v=a73706fe'; +import { initList, render, syncFilterUI } from '/js/admin-list.js?v=e00006a8'; import { resolveAdminSection } from '/js/admin-logic.js?v=5356a1b3'; import { buildAppItem, @@ -10,7 +10,7 @@ import { snapshotItems, upsertItem, } from '/js/admin-save-logic.js?v=60a82419'; -import { loadSettings, settingsDirty, showBgFields, showWallpaperFile } from '/js/admin-settings.js?v=429a28d8'; +import { loadSettings, settingsDirty, showBgFields, showWallpaperFile } from '/js/admin-settings.js?v=f87eb909'; import { apiGet, apiPost, @@ -19,12 +19,12 @@ import { reveal, setReauthHandler, toast, -} from '/js/admin-shared.js?v=d4f4cd3c'; +} from '/js/admin-shared.js?v=e477e574'; import { collapsedFolders, filter, state } from '/js/admin-state.js?v=831e219e'; -import { buildWidgetForm } from '/js/admin-widget-form.js?v=6a06debe'; +import { buildWidgetForm } from '/js/admin-widget-form.js?v=bb851134'; import { initFluidHover } from '/js/fluid-hover.js?v=cb886e86'; import { initGlideSelect, syncGlideSelect } from '/js/glide-select.js?v=8b39e9d0'; -import { createListbox } from '/js/listbox.js?v=95ec8aab'; +import { createListbox } from '/js/listbox.js?v=fd1151b6'; import { html, raw, setHtml } from '/js/html.js?v=c71f8903'; import { initI18n, LANGUAGES, t } from '/js/i18n.js?v=1f1ea9c1'; import { loadLocalIcons } from '/js/icons.js?v=9c8c550c'; @@ -49,8 +49,8 @@ import { watchSystemTheme, writeMode, } from '/js/theme.js?v=eeafa4b5'; -import { el, inp, q, qa, clr, setUserText, tgt } from '/js/utils.js?v=b6231666'; -import { applyBackground, resolveBackground } from '/js/background.js?v=1fec6318'; +import { el, inp, q, qa, clr, setUserText, tgt } from '/js/utils.js?v=01662438'; +import { applyBackground, resolveBackground } from '/js/background.js?v=09beead7'; import { parseYamlTolerant, YamlLiteError } from '/js/yaml-lite.js?v=6ebb564c'; ensureSprite(); diff --git a/ui/js/background.js b/ui/js/background.js index d8c03942..ff312266 100644 --- a/ui/js/background.js +++ b/ui/js/background.js @@ -1,5 +1,5 @@ // @ts-check -import { cssColor, sanitizeCssUrl } from '/js/utils.js?v=b6231666'; +import { cssColor, sanitizeCssUrl } from '/js/utils.js?v=01662438'; import { loadWallpaper, saveWallpaper } from '/js/wallpaper-cache.js?v=c5f8a3e6'; export const BACKDROP = '#0d1117'; diff --git a/ui/js/dashboard.js b/ui/js/dashboard.js index dde3f38b..6a3d9857 100644 --- a/ui/js/dashboard.js +++ b/ui/js/dashboard.js @@ -11,6 +11,7 @@ import { } from '/js/widget-types.js?v=9264dee5'; import { el, + ICON_R, isDashboardEmpty, mk, mkWrap as _mkWrap, @@ -23,9 +24,9 @@ import { setUserText, teardownWidgets, titleWhenTruncated, -} from '/js/utils.js?v=b6231666'; +} from '/js/utils.js?v=01662438'; import { initFluidHover } from '/js/fluid-hover.js?v=cb886e86'; -import { initSpotlight } from '/js/spotlight.js?v=a59d932e'; +import { initSpotlight } from '/js/spotlight.js?v=3d038c35'; import { html, setHtml, raw } from '/js/html.js?v=c71f8903'; import { initI18n, t, currentLang } from '/js/i18n.js?v=1f1ea9c1'; import { pwStrength, passwordMismatch } from '/js/password-strength.js?v=42f45ac7'; @@ -39,7 +40,7 @@ import { buildMobile, resetMobileChrome, mkFolderGlyph, -} from '/js/ui.js?v=5d62b8d2'; +} from '/js/ui.js?v=d078716c'; import { badgeMinimum, badgeSignature, computeBadgeVisual, readBadgeUpdate } from '/js/badge-logic.js?v=9e6d9d4b'; import { formatNumber } from '/js/format-number.js?v=4a5ccef4'; import { closeBadgePopover, wireBadgePopover } from '/js/badge-popover.js?v=aa52b1a3'; @@ -51,7 +52,7 @@ import { landingAfterSetup, restorePage, } from '/js/dashboard-logic.js?v=0d519f8b'; -import { applyBackground, BACKDROP, resolveBackground } from '/js/background.js?v=1fec6318'; +import { applyBackground, BACKDROP, resolveBackground } from '/js/background.js?v=09beead7'; import { jitter } from '/js/jitter.js?v=4eeef4c9'; import { isMobileLayout, onLayoutChange } from '/js/layout.js?v=e9f4b607'; import { startWakeLock } from '/js/wake-lock.js?v=6b9591cf'; @@ -68,10 +69,6 @@ let MOB = isMobileLayout(); const wCols = { d: WIDGET_COLS.desktop, m: WIDGET_COLS.mobile }; const wRows = { d: WIDGET_ROWS.desktop, m: WIDGET_ROWS.mobile }; const WH = { d: WIDGET_HEIGHTS }; -/* An app icon's corner is 22.37% of its width. A fixed radius only lands on that - at one icon size, and the grid draws two: 72 with a label under it, 78 - without. */ -const ICON_R = 0.2237; /* A widget tile's corner, at design size. WIDGET_DESIGN's small is 170 square against the reference's 165, so this is its 28 unchanged. */ const WIDGET_R = 28; @@ -427,7 +424,7 @@ function buildDesktop() { dk.replaceChildren(); dock.forEach(item => dk.appendChild(mkDock(item))); dk.hidden = !dock.length; - observeGlass(dk, 45, 0.2); + observeGlass(dk, 38, 0.6); } /* Every page is mounted at once, so widgets the user has swiped away from keep diff --git a/ui/js/listbox.js b/ui/js/listbox.js index 2f4e7524..ba914c58 100644 --- a/ui/js/listbox.js +++ b/ui/js/listbox.js @@ -9,7 +9,7 @@ import { nextActiveIndex } from '/js/admin-logic.js?v=5356a1b3'; import { fluidHoverClear, fluidHoverKb } from '/js/fluid-hover.js?v=cb886e86'; import { html, raw, setHtml } from '/js/html.js?v=c71f8903'; -import { qa } from '/js/utils.js?v=b6231666'; +import { qa } from '/js/utils.js?v=01662438'; import { iconSvg } from '/js/icon-set.js?v=ba9434d9'; const CHEV = iconSvg('chevrons', 22, 'solid', 'dd-chev'); diff --git a/ui/js/mobile-metrics.js b/ui/js/mobile-metrics.js index 43eb2837..a4966be9 100644 --- a/ui/js/mobile-metrics.js +++ b/ui/js/mobile-metrics.js @@ -41,7 +41,7 @@ export function mobileMetrics(vw, docked = true) { const sc = Math.min(vw / BASE_VW, MAX_SC); const sm = Math.round(18 * sc); const dh = docked ? Math.round(108 * sc) : 0; - const pillH = Math.round(34 * sc); + const pillH = Math.round(30 * sc); const pillGap = Math.round(10 * sc); /* Reserving less lets the pill sit on top of the last row. */ const dz = pillGap + pillH + Math.round(8 * sc); diff --git a/ui/js/spotlight.js b/ui/js/spotlight.js index ad616126..8b0097cd 100644 --- a/ui/js/spotlight.js +++ b/ui/js/spotlight.js @@ -1,5 +1,5 @@ import { fluidHoverClear, fluidHoverKb } from '/js/fluid-hover.js?v=cb886e86'; -import { mk, clr, el, inp as inpById, q, qa, setUserText } from '/js/utils.js?v=b6231666'; +import { mk, clr, el, inp as inpById, q, qa, setUserText } from '/js/utils.js?v=01662438'; import { t } from '/js/i18n.js?v=1f1ea9c1'; /* Attached to the window so a re-open can undo the previous one. */ diff --git a/ui/js/ui.js b/ui/js/ui.js index 7fc869ed..71e7e93b 100644 --- a/ui/js/ui.js +++ b/ui/js/ui.js @@ -6,6 +6,7 @@ import { isDashboardEmpty, renderEmptyState, mkWrap as _mkWrap, + ICON_R, mountScaledWidget, pageDir, teardownWidgets, @@ -13,10 +14,10 @@ import { q, qa, setUserText, -} from '/js/utils.js?v=b6231666'; +} from '/js/utils.js?v=01662438'; import { t, currentLang } from '/js/i18n.js?v=1f1ea9c1'; import { toneForColor } from '/js/label-contrast.js?v=c1ac6fb8'; -import { mobileMetrics, gridColumnWidth, gridCellCount } from '/js/mobile-metrics.js?v=ab5fe77e'; +import { mobileMetrics, gridColumnWidth, gridCellCount } from '/js/mobile-metrics.js?v=349f4300'; import { smoothRectPath } from '/js/smooth-corner.js?v=b7dda7e1'; import { mkGlassRim, observeGlass } from '/js/glass-rim.js?v=3faec233'; @@ -110,7 +111,7 @@ export function mkFolderGlyph(folder, size) { const wrap = mk('div'); wrap.className = 'dyn-fold-wrap glass-surface'; css(wrap, { - '--br': Math.round(size * 0.24) + 'px', + '--br': Math.round(size * ICON_R) + 'px', '--gap': Math.round(size * 0.04) + 'px', '--pad': Math.round(size * 0.1) + 'px', }); @@ -311,7 +312,7 @@ function mFolder(item, cw, rh, isz, ir, im, sc) { css(box, { '--sz': eff + 'px' }); box.style.pointerEvents = 'none'; const wrap = mkFolderGlyph(item, eff); - wrap.appendChild(mkGlassRim(eff, eff, smoothRectPath(eff, eff, Math.round(eff * 0.24), 0))); + wrap.appendChild(mkGlassRim(eff, eff, smoothRectPath(eff, eff, Math.round(eff * ICON_R), 0))); box.appendChild(wrap); const fb_ = mk('div'); fb_.className = 'badge'; @@ -322,7 +323,7 @@ function mFolder(item, cw, rh, isz, ir, im, sc) { const l = mk('div'); l.className = 'dyn-fold-label'; l.dir = 'auto'; - css(l, { '--lfs': Math.max(9, Math.round(9 * sc)) + 'px', '--lw': cw - 4 + 'px' }); + css(l, { '--lfs': Math.max(11, Math.round(12 * sc)) + 'px', '--lw': cw - 4 + 'px' }); setUserText(l, item.label || t('type.folder')); a.appendChild(l); } @@ -641,7 +642,7 @@ export function buildMobile() { css(document.body, { '--mgw': Math.round(gridW) + 'px' }); const maxIsz = Math.round(74 * sc); const isz = Math.round(Math.min(cw * 0.9, rh * 0.8, maxIsz)); - const ir = Math.round(isz * 0.225), + const ir = Math.round(isz * ICON_R), im = Math.round(isz * 0.64); const showLabel = S().showLabels?.ios === true; /* The reference draws a 66 icon and a 28 widget corner, so a widget's corner @@ -705,7 +706,7 @@ export function buildMobile() { function mIcon(item) { const eff = showLabel ? Math.round(isz * 0.82) : isz; - const er = Math.round(eff * 0.225), + const er = Math.round(eff * ICON_R), em = Math.round(eff * 0.64); const a = item.system === 'settings' @@ -720,7 +721,7 @@ export function buildMobile() { const l = mk('div'); l.className = 'dyn-mob-label'; l.dir = 'auto'; - css(l, { '--lfs': Math.max(9, Math.round(9 * sc)) + 'px', '--lw': '100%' }); + css(l, { '--lfs': Math.max(11, Math.round(12 * sc)) + 'px', '--lw': '100%' }); setUserText(l, item.label || item.id); a.appendChild(l); } @@ -786,7 +787,7 @@ export function buildMobile() { const maxDockW = vw - Math.round(18 * sc); const dockPad = Math.round(14 * sc); const dockIconSz = Math.round(Math.min(isz, ((maxDockW - Math.round(28 * sc)) / 4) * 0.85)); - const dockIr = Math.round(dockIconSz * 0.225), + const dockIr = Math.round(dockIconSz * ICON_R), dockIm = Math.round(dockIconSz * 0.64); const dockGap = Math.round(9 * sc); const dockW = Math.min(maxDockW, Math.round((PHONE_W - 18) * sc)); @@ -796,7 +797,7 @@ export function buildMobile() { dk.hidden = !dock.length; dk.style.cssText = `position:fixed;left:50%;bottom:${dockGap}px;transform:translateX(-50%);width:${dockW}px;height:${dh}px;padding:0 ${dockPad}px;gap:${dockIconGap}px;z-index:400;`; dk.replaceChildren(); - observeGlass(dk, Math.round(40 * sc), 0.2); + observeGlass(dk, Math.round(38 * sc), 0.6); dock.forEach(item => { const a = mk('a', { href: item.href, target: '_blank', rel: 'noreferrer noopener' }); a.className = 'dyn-dock-icon'; diff --git a/ui/js/utils.js b/ui/js/utils.js index 1c00a8cd..202ec1b5 100644 --- a/ui/js/utils.js +++ b/ui/js/utils.js @@ -122,6 +122,8 @@ export const qa = (sel, root = document) => /** @type {HTMLElement[]} */ ([...ro export const tgt = e => /** @type {HTMLInputElement} */ (e.target); /* breg is passed in to avoid a circular import. */ +export const ICON_R = 0.26; + export function mkWrap(item, sz, r, isz, cls, breg) { const w = mk('div'); w.className = cls ? `plate ${cls}` : 'plate'; diff --git a/ui/js/widget-config-form.js b/ui/js/widget-config-form.js index f099a624..ce13d9ed 100644 --- a/ui/js/widget-config-form.js +++ b/ui/js/widget-config-form.js @@ -3,9 +3,9 @@ import { t } from '/js/i18n.js?v=1f1ea9c1'; import { html, raw, setHtml } from '/js/html.js?v=c71f8903'; -import { reveal, wireInlineEdit } from '/js/admin-shared.js?v=d4f4cd3c'; -import { createListbox } from '/js/listbox.js?v=95ec8aab'; -import { renderColorControl } from '/js/admin-color-control.js?v=6e070d2e'; +import { reveal, wireInlineEdit } from '/js/admin-shared.js?v=e477e574'; +import { createListbox } from '/js/listbox.js?v=fd1151b6'; +import { renderColorControl } from '/js/admin-color-control.js?v=ee39fb36'; import { seedCarried, applyOptionSet, @@ -15,7 +15,7 @@ import { visibleFieldFlags, } from '/js/admin-logic.js?v=5356a1b3'; import { optionsErrorAdvice, TONE } from '/js/admin-error.js?v=e4ea7820'; -import { qi } from '/js/utils.js?v=b6231666'; +import { qi } from '/js/utils.js?v=01662438'; const PE = ''; diff --git a/ui/test/glass-rim.test.mjs b/ui/test/glass-rim.test.mjs index ec84b166..ca5c2857 100644 --- a/ui/test/glass-rim.test.mjs +++ b/ui/test/glass-rim.test.mjs @@ -9,12 +9,12 @@ const read = p => fs.readFileSync(path.join(root, p), 'utf8'); test('every dock and folder surface shares one edge', () => { const ui = read('js/ui.js'); - assert.match(ui, /observeGlass\(dk, Math\.round\(40 \* sc\), 0\.2\)/); + assert.match(ui, /observeGlass\(dk, Math\.round\(38 \* sc\), 0\.6\)/); assert.match(ui, /mkGlassRim\(boxW, boxH, boxD\)/); assert.match(ui, /mkGlassRim\(eff, eff,/); assert.match(ui, /mkGlassRim\(iw, iw,/); assert.match(ui, /observeGlass\(box, 28, 0\.2\)/); - assert.match(read('js/dashboard.js'), /observeGlass\(dk, 45, 0\.2\)/); + assert.match(read('js/dashboard.js'), /observeGlass\(dk, 38, 0\.6\)/); }); test('the open phone folder is clipped to the rim path', () => { diff --git a/ui/test/pagination.test.mjs b/ui/test/pagination.test.mjs index 37676ade..00b05009 100644 --- a/ui/test/pagination.test.mjs +++ b/ui/test/pagination.test.mjs @@ -208,4 +208,5 @@ test('the pager is hidden when there is one page', () => { test('the hidden pager is not painted', () => { const css = read('css/dashboard.css'); assert.match(css, /#dots\[hidden\] \{ display:none \}/, 'display:flex on #dots beats the hidden attribute'); + assert.match(css, /#dots:empty \{ display:none \}/, 'an empty bar shows on the error screen'); }); diff --git a/ui/test/smooth-corner.test.mjs b/ui/test/smooth-corner.test.mjs index aea13e68..43b90009 100644 --- a/ui/test/smooth-corner.test.mjs +++ b/ui/test/smooth-corner.test.mjs @@ -51,6 +51,6 @@ test('the corner never reaches past half the short side', () => { test('the mobile dock draws the smooth shape at phone width', () => { const ui = read('js/ui.js'); assert.match(ui, /const dockW = Math\.min\(maxDockW, Math\.round\(\(PHONE_W - 18\) \* sc\)\)/); - assert.match(ui, /observeGlass\(dk, Math\.round\(40 \* sc\), 0\.2\)/); + assert.match(ui, /observeGlass\(dk, Math\.round\(38 \* sc\), 0\.6\)/); assert.doesNotMatch(ui, /border-radius:\$\{Math\.round\(44 \* sc\)\}px/, 'the dock is back to a circular corner'); }); diff --git a/ui/test/spotlight-dismiss.test.mjs b/ui/test/spotlight-dismiss.test.mjs index 1132eb0b..fc55868b 100644 --- a/ui/test/spotlight-dismiss.test.mjs +++ b/ui/test/spotlight-dismiss.test.mjs @@ -63,7 +63,7 @@ test('Cancel carries the tint, not a plain white label', () => { test('the field keeps the search-bar height and the body type scale', () => { const rule = css.match(/body\.is-mob #spot \.spot-field \{([^}]*)\}/); assert.ok(rule, 'the phone field rule is gone'); - assert.match(rule[1], /min-height:44px/); + assert.match(rule[1], /min-height:48px/); assert.match(css, /body\.is-mob #spot #sin \{[^}]*font-size:var\(--fs-body\)/); }); diff --git a/ui/test/type-scale.test.mjs b/ui/test/type-scale.test.mjs index 05101783..c49a3ecf 100644 --- a/ui/test/type-scale.test.mjs +++ b/ui/test/type-scale.test.mjs @@ -127,6 +127,7 @@ for (const [step, scale] of Object.entries(STEPS)) { purpose are listed, with the reason. */ const OWN_LINE_HEIGHT = new Set([ '.badge', // a count in a fixed circle + 'body:not(.is-mob) .badge', '#mob-search-pill .msp-icon', '.setup-sub', '.grp-tip', // prose, deliberately looser than the style @@ -150,16 +151,17 @@ test('a rule using a size token takes its leading from the scale', () => { /* Radii that have to follow the thing they round. - An app icon's corner is 22.37% of its width, and the grid draws two widths: - 72 with a label under it and 78 without. A fixed radius lands on the ratio at - one of them and is wrong at the other. + An app icon's corner is 26% of its width, the kit's icon grid, and the grid + draws two widths: 72 with a label under it and 78 without. A fixed radius + lands on the ratio at one of them and is wrong at the other. A widget tile's corner is 28 at design size, and WIDGET_DESIGN's small is 170 square against the reference's 165, so it is the reference value unchanged. Desktop tiles keep one size at every width, so the corner does too. */ test('icon and tile radii are derived, not literal', () => { const dash = fs.readFileSync(path.join(cssDir, '..', 'js', 'dashboard.js'), 'utf8'); - assert.match(dash, /const ICON_R = 0\.2237;/); + const utils = fs.readFileSync(path.join(cssDir, '..', 'js', 'utils.js'), 'utf8'); + assert.match(utils, /export const ICON_R = 0\.26;/); assert.match(dash, /mkWrap\(item, iw, Math\.round\(iw \* ICON_R\)/, 'the grid icon derives its corner'); assert.match(dash, /mkWrap\(item, 78, Math\.round\(78 \* ICON_R\)/, 'the dock icon derives its corner'); assert.match(dash, /const WIDGET_R = 28;/); From 1d8cb64941acb64f6d77f8ac546985699bf3a257 Mon Sep 17 00:00:00 2001 From: SandObserver <260779319+SandObserver@users.noreply.github.com> Date: Wed, 23 Sep 2026 20:13:29 -0400 Subject: [PATCH 2/2] Reserve room for the moved badge, match folder and dock corners, keep the phone dot size cue, shadow the pill label, and start the pager hidden --- ui/admin/index.html | 2 +- ui/css/dashboard.css | 19 +++++++++---------- ui/index.html | 6 +++--- ui/js/admin-app-form.js | 8 ++++---- ui/js/admin-auth.js | 4 ++-- ui/js/admin-color-control.js | 4 ++-- ui/js/admin-drag.js | 2 +- ui/js/admin-list.js | 8 ++++---- ui/js/admin-settings.js | 8 ++++---- ui/js/admin-shared.js | 2 +- ui/js/admin-widget-form.js | 8 ++++---- ui/js/admin.js | 18 +++++++++--------- ui/js/background.js | 2 +- ui/js/dashboard.js | 8 ++++---- ui/js/listbox.js | 2 +- ui/js/spotlight.js | 2 +- ui/js/ui.js | 8 ++++---- ui/js/utils.js | 2 +- ui/js/widget-config-form.js | 8 ++++---- ui/test/folder-overlay.test.mjs | 4 ++-- ui/test/pagination.test.mjs | 6 +++++- ui/test/type-scale.test.mjs | 2 ++ 22 files changed, 69 insertions(+), 64 deletions(-) diff --git a/ui/admin/index.html b/ui/admin/index.html index 2af15435..21ec66c9 100644 --- a/ui/admin/index.html +++ b/ui/admin/index.html @@ -292,6 +292,6 @@

General

- + diff --git a/ui/css/dashboard.css b/ui/css/dashboard.css index 3742dd57..bfefeeab 100644 --- a/ui/css/dashboard.css +++ b/ui/css/dashboard.css @@ -388,7 +388,6 @@ } #dots[data-tone="dark"] .dot { border-color:rgba(0,0,0,.55) } #dots[hidden] { display:none } - #dots:empty { display:none } /* Mirrored by the no-dock reserve in desktopSlots(). */ body.no-dock:not(.is-mob) #dots { bottom:24px } @@ -422,10 +421,10 @@ } #mob-search-pill .msp-icon { font-size:calc(12px * var(--sc,1)); font-weight:600; color:#FFFFFF; line-height:1; - font-style:normal; + font-style:normal; text-shadow:var(--label-shadow); } #mob-search-pill .msp-label { - font-size:calc(12px * var(--sc,1));line-height:1; font-weight:600; color:#FFFFFF; + font-size:calc(12px * var(--sc,1));line-height:1; font-weight:600; color:#FFFFFF; text-shadow:var(--label-shadow); white-space:nowrap; } /* Dots row — shown when paging */ @@ -443,9 +442,9 @@ #mob-search-pill .msp-dot { width:calc(8px * var(--sc,1)); height:calc(8px * var(--sc,1)); border-radius:50%; background:rgba(255,255,255,.35); flex-shrink:0; - transition:background .18s; + transition:background .18s,transform .18s; } - #mob-search-pill .msp-dot.on { background:#FFFFFF } + #mob-search-pill .msp-dot.on { background:#FFFFFF; transform:scale(1.2) } /* State transitions */ #mob-search-pill.paging .msp-search { opacity:0; pointer-events:none } #mob-search-pill.paging .msp-dots { opacity:1; pointer-events:auto } @@ -733,9 +732,9 @@ .folder-dot { border-radius:50%; cursor:pointer; flex-shrink:0; background:rgba(255,255,255,.35); - transition:background .2s; + transition:background .2s,transform .2s; } - .folder-dot.on { background:var(--control-knob) } + .folder-dot.on { background:var(--control-knob); transform:scale(1.25) } /* ── Folder overlay — desktop ── */ .folder-overlay { @@ -1066,7 +1065,7 @@ html[data-theme="light"] .widget[data-card="translucent"], html[data-theme="light"] .mob-widget-card[data-card="translucent"] { background:#FFFFFF } html[data-theme="light"] #mob-search-pill { background:rgba(255,255,255,.72); border-color:rgba(0,0,0,.08) } html[data-theme="light"] #mob-search-pill .msp-icon, -html[data-theme="light"] #mob-search-pill .msp-label { color:var(--pill-ink-light) } +html[data-theme="light"] #mob-search-pill .msp-label { color:var(--pill-ink-light); text-shadow:none } html[data-theme="light"] #mob-search-pill .msp-dot { background:rgba(60,60,67,.3) } html[data-theme="light"] #mob-search-pill .msp-dot.on { background:rgba(0,0,0,.8) } html[data-theme="light"] #spot, @@ -1108,11 +1107,11 @@ html[data-theme="light"] .api-error-btn { background:rgba(0,0,0,.06); border-col html[data-theme="light"] .api-error-screen { color:rgba(0,0,0,.7) } html[data-theme="light"] .api-error-sub { color:rgba(0,0,0,.45) } html[data-theme="light"] .folder-overlay .ilabel { color:var(--label-color-dark); text-shadow:var(--label-shadow-dark) } -html[data-theme="light"] #dock:not(.mdock) { border-radius:45px; box-shadow:var(--glass-lift) } +html[data-theme="light"] #dock:not(.mdock) { border-radius:38px; box-shadow:var(--glass-lift) } html[data-theme="light"] .folder-icon-grid, html[data-theme="light"] .dyn-fold-wrap, html[data-theme="light"] .plate-clear { box-shadow:0 4px 14px rgba(0,0,0,.12) } -html[data-theme="light"] #dock.mdock { border-radius:calc(40px * var(--sc,1)); box-shadow:var(--glass-lift) } +html[data-theme="light"] #dock.mdock { border-radius:calc(38px * var(--sc,1)); box-shadow:var(--glass-lift) } html[data-theme="light"] .widget[data-appearance="dark"], html[data-theme="light"] .mob-widget-card[data-appearance="dark"] { --glass-bg:var(--card-fixed-dark); --glass-border:rgba(255,255,255,.09); --card-dark:var(--card-fixed-dark); diff --git a/ui/index.html b/ui/index.html index 70ced654..270f3d17 100644 --- a/ui/index.html +++ b/ui/index.html @@ -26,12 +26,12 @@ Stackyard - +

Stackyard

- +
@@ -55,5 +55,5 @@
- + diff --git a/ui/js/admin-app-form.js b/ui/js/admin-app-form.js index ed71d7e1..76906926 100644 --- a/ui/js/admin-app-form.js +++ b/ui/js/admin-app-form.js @@ -1,4 +1,4 @@ -import { clr, el, inp as inpById, q as qSel, qa, qi, tgt } from '/js/utils.js?v=01662438'; +import { clr, el, inp as inpById, q as qSel, qa, qi, tgt } from '/js/utils.js?v=ed0f1802'; import { html, raw, setHtml } from '/js/html.js?v=c71f8903'; import { loadLocalIcons, resolveIcon, iconChain, cdnIconRef, splitIconRef } from '/js/icons.js?v=9c8c550c'; import { state } from '/js/admin-state.js?v=831e219e'; @@ -21,10 +21,10 @@ import { reveal, setTogDisabled, swapContent, -} from '/js/admin-shared.js?v=e477e574'; -import { createListbox } from '/js/listbox.js?v=fd1151b6'; +} from '/js/admin-shared.js?v=d218efee'; +import { createListbox } from '/js/listbox.js?v=80606092'; import { MAX_LABELS } from '/js/badge-logic.js?v=9e6d9d4b'; -import { renderColorControl, BADGE_DEFAULT } from '/js/admin-color-control.js?v=ee39fb36'; +import { renderColorControl, BADGE_DEFAULT } from '/js/admin-color-control.js?v=96364a80'; import { badgeErrorAdvice, TONE } from '/js/admin-error.js?v=e4ea7820'; import { fluidHoverClear, fluidHoverKb } from '/js/fluid-hover.js?v=cb886e86'; import { iconSvg } from '/js/icon-set.js?v=ba9434d9'; diff --git a/ui/js/admin-auth.js b/ui/js/admin-auth.js index bf7063da..d8df95be 100644 --- a/ui/js/admin-auth.js +++ b/ui/js/admin-auth.js @@ -1,7 +1,7 @@ -import { apiGet, apiPost } from '/js/admin-shared.js?v=e477e574'; +import { apiGet, apiPost } from '/js/admin-shared.js?v=d218efee'; import { t } from '/js/i18n.js?v=1f1ea9c1'; import { pwStrength } from '/js/password-strength.js?v=42f45ac7'; -import { el, inp as inpById, qa } from '/js/utils.js?v=01662438'; +import { el, inp as inpById, qa } from '/js/utils.js?v=ed0f1802'; export async function checkAuth(onLogin) { try { diff --git a/ui/js/admin-color-control.js b/ui/js/admin-color-control.js index 5b2f0b64..1972d2cc 100644 --- a/ui/js/admin-color-control.js +++ b/ui/js/admin-color-control.js @@ -1,7 +1,7 @@ -import { PE_SVG, initInlineEdit, toast, reveal } from '/js/admin-shared.js?v=e477e574'; +import { PE_SVG, initInlineEdit, toast, reveal } from '/js/admin-shared.js?v=d218efee'; import { t } from '/js/i18n.js?v=1f1ea9c1'; import { html, raw, setHtml } from '/js/html.js?v=c71f8903'; -import { qa, q } from '/js/utils.js?v=01662438'; +import { qa, q } from '/js/utils.js?v=ed0f1802'; import { iconSvg } from '/js/icon-set.js?v=ba9434d9'; import { HUE_NAMES, ROLE_NAMES, TILE_KEYWORDS, pageTheme, tileColor } from '/js/palette.js?v=3fb8ae43'; diff --git a/ui/js/admin-drag.js b/ui/js/admin-drag.js index 052a3b00..dc886785 100644 --- a/ui/js/admin-drag.js +++ b/ui/js/admin-drag.js @@ -12,7 +12,7 @@ import { applyDrop, canJoinFolder, folderRowZone } from '/js/admin-drag-logic.js?v=6b767e76'; import { state } from '/js/admin-state.js?v=831e219e'; import { snapshotItems } from '/js/admin-save-logic.js?v=60a82419'; -import { qa } from '/js/utils.js?v=01662438'; +import { qa } from '/js/utils.js?v=ed0f1802'; /** @type {(before: unknown) => void} */ let _save = () => {}; diff --git a/ui/js/admin-list.js b/ui/js/admin-list.js index 0a76ccae..0f4a1b7b 100644 --- a/ui/js/admin-list.js +++ b/ui/js/admin-list.js @@ -14,12 +14,12 @@ import { collapsedFolders, filter, state } from '/js/admin-state.js?v=831e219e'; import { snapshotItems } from '/js/admin-save-logic.js?v=60a82419'; import { reorderItems } from '/js/admin-logic.js?v=5356a1b3'; -import { initDrag, wireRowDrag } from '/js/admin-drag.js?v=70ee4100'; -import { paintIcon } from '/js/admin-shared.js?v=e477e574'; -import { clr, el, qa, setUserText } from '/js/utils.js?v=01662438'; +import { initDrag, wireRowDrag } from '/js/admin-drag.js?v=0a411097'; +import { paintIcon } from '/js/admin-shared.js?v=d218efee'; +import { clr, el, qa, setUserText } from '/js/utils.js?v=ed0f1802'; import { html, raw, setHtml } from '/js/html.js?v=c71f8903'; import { t } from '/js/i18n.js?v=1f1ea9c1'; -import { sizeLabel } from '/js/admin-widget-form.js?v=bb851134'; +import { sizeLabel } from '/js/admin-widget-form.js?v=e17ed1b7'; import { widgetGlyph } from '/js/widget-glyphs.js?v=ad1869d7'; import { iconSvg } from '/js/icon-set.js?v=ba9434d9'; diff --git a/ui/js/admin-settings.js b/ui/js/admin-settings.js index d7d92361..8a3fca31 100644 --- a/ui/js/admin-settings.js +++ b/ui/js/admin-settings.js @@ -1,4 +1,4 @@ -import { toast, apiGet, apiPost, reveal, swapContent } from '/js/admin-shared.js?v=e477e574'; +import { toast, apiGet, apiPost, reveal, swapContent } from '/js/admin-shared.js?v=d218efee'; import { pwStrength } from '/js/password-strength.js?v=42f45ac7'; import { t } from '/js/i18n.js?v=1f1ea9c1'; import { @@ -9,9 +9,9 @@ import { BLOCK, } from '/js/admin-logic.js?v=5356a1b3'; import { confirmText } from '/js/modal.js?v=11fa1eff'; -import { el, inp, setUserText } from '/js/utils.js?v=01662438'; -import { renderColorControl } from '/js/admin-color-control.js?v=ee39fb36'; -import { BACKDROP } from '/js/background.js?v=09beead7'; +import { el, inp, setUserText } from '/js/utils.js?v=ed0f1802'; +import { renderColorControl } from '/js/admin-color-control.js?v=96364a80'; +import { BACKDROP } from '/js/background.js?v=848da677'; /* Mirrors the server's rule: auth cannot be switched on with no password. */ let _passwordSet = false; diff --git a/ui/js/admin-shared.js b/ui/js/admin-shared.js index ae116d26..264cf35a 100644 --- a/ui/js/admin-shared.js +++ b/ui/js/admin-shared.js @@ -1,6 +1,6 @@ /* Stateless helpers shared by the admin modules. Mutable state stays out. */ import { recoversSession, toastHoldMs } from '/js/admin-logic.js?v=5356a1b3'; -import { el, q } from '/js/utils.js?v=01662438'; +import { el, q } from '/js/utils.js?v=ed0f1802'; import { t } from '/js/i18n.js?v=1f1ea9c1'; import { iconChain } from '/js/icons.js?v=9c8c550c'; import { iconSvg } from '/js/icon-set.js?v=ba9434d9'; diff --git a/ui/js/admin-widget-form.js b/ui/js/admin-widget-form.js index 02ad4e33..9288fd5c 100644 --- a/ui/js/admin-widget-form.js +++ b/ui/js/admin-widget-form.js @@ -1,11 +1,11 @@ import { state } from '/js/admin-state.js?v=831e219e'; -import { PE_SVG, initInlineEdit, swapContent } from '/js/admin-shared.js?v=e477e574'; -import { createListbox } from '/js/listbox.js?v=fd1151b6'; -import { renderWidgetConfigForm } from '/js/widget-config-form.js?v=152a4103'; +import { PE_SVG, initInlineEdit, swapContent } from '/js/admin-shared.js?v=d218efee'; +import { createListbox } from '/js/listbox.js?v=80606092'; +import { renderWidgetConfigForm } from '/js/widget-config-form.js?v=0dbce7d5'; import { html, raw, setHtml } from '/js/html.js?v=c71f8903'; import { sizesForView, widgetConfigMode, rejectionLines, carriesTypedValues } from '/js/admin-logic.js?v=5356a1b3'; import { t } from '/js/i18n.js?v=1f1ea9c1'; -import { q, qi, qa } from '/js/utils.js?v=01662438'; +import { q, qi, qa } from '/js/utils.js?v=ed0f1802'; import { iconSvg } from '/js/icon-set.js?v=ba9434d9'; const SIZES_WITH_ICONS = new Set(['small', 'medium', 'large', 'xlarge']); diff --git a/ui/js/admin.js b/ui/js/admin.js index 0dfe2096..526501f4 100644 --- a/ui/js/admin.js +++ b/ui/js/admin.js @@ -1,6 +1,6 @@ -import { buildAppForm, buildFolderForm, captureActLabels, serializeKvRows } from '/js/admin-app-form.js?v=8a42d8e0'; -import { checkAuth, requireLogin, wirePasswordStrength } from '/js/admin-auth.js?v=a73706fe'; -import { initList, render, syncFilterUI } from '/js/admin-list.js?v=e00006a8'; +import { buildAppForm, buildFolderForm, captureActLabels, serializeKvRows } from '/js/admin-app-form.js?v=097f38ca'; +import { checkAuth, requireLogin, wirePasswordStrength } from '/js/admin-auth.js?v=fbd8a6cb'; +import { initList, render, syncFilterUI } from '/js/admin-list.js?v=1c8871af'; import { resolveAdminSection } from '/js/admin-logic.js?v=5356a1b3'; import { buildAppItem, @@ -10,7 +10,7 @@ import { snapshotItems, upsertItem, } from '/js/admin-save-logic.js?v=60a82419'; -import { loadSettings, settingsDirty, showBgFields, showWallpaperFile } from '/js/admin-settings.js?v=f87eb909'; +import { loadSettings, settingsDirty, showBgFields, showWallpaperFile } from '/js/admin-settings.js?v=b5694a5f'; import { apiGet, apiPost, @@ -19,12 +19,12 @@ import { reveal, setReauthHandler, toast, -} from '/js/admin-shared.js?v=e477e574'; +} from '/js/admin-shared.js?v=d218efee'; import { collapsedFolders, filter, state } from '/js/admin-state.js?v=831e219e'; -import { buildWidgetForm } from '/js/admin-widget-form.js?v=bb851134'; +import { buildWidgetForm } from '/js/admin-widget-form.js?v=e17ed1b7'; import { initFluidHover } from '/js/fluid-hover.js?v=cb886e86'; import { initGlideSelect, syncGlideSelect } from '/js/glide-select.js?v=8b39e9d0'; -import { createListbox } from '/js/listbox.js?v=fd1151b6'; +import { createListbox } from '/js/listbox.js?v=80606092'; import { html, raw, setHtml } from '/js/html.js?v=c71f8903'; import { initI18n, LANGUAGES, t } from '/js/i18n.js?v=1f1ea9c1'; import { loadLocalIcons } from '/js/icons.js?v=9c8c550c'; @@ -49,8 +49,8 @@ import { watchSystemTheme, writeMode, } from '/js/theme.js?v=eeafa4b5'; -import { el, inp, q, qa, clr, setUserText, tgt } from '/js/utils.js?v=01662438'; -import { applyBackground, resolveBackground } from '/js/background.js?v=09beead7'; +import { el, inp, q, qa, clr, setUserText, tgt } from '/js/utils.js?v=ed0f1802'; +import { applyBackground, resolveBackground } from '/js/background.js?v=848da677'; import { parseYamlTolerant, YamlLiteError } from '/js/yaml-lite.js?v=6ebb564c'; ensureSprite(); diff --git a/ui/js/background.js b/ui/js/background.js index ff312266..7f38775c 100644 --- a/ui/js/background.js +++ b/ui/js/background.js @@ -1,5 +1,5 @@ // @ts-check -import { cssColor, sanitizeCssUrl } from '/js/utils.js?v=01662438'; +import { cssColor, sanitizeCssUrl } from '/js/utils.js?v=ed0f1802'; import { loadWallpaper, saveWallpaper } from '/js/wallpaper-cache.js?v=c5f8a3e6'; export const BACKDROP = '#0d1117'; diff --git a/ui/js/dashboard.js b/ui/js/dashboard.js index 6a3d9857..8ab6161c 100644 --- a/ui/js/dashboard.js +++ b/ui/js/dashboard.js @@ -24,9 +24,9 @@ import { setUserText, teardownWidgets, titleWhenTruncated, -} from '/js/utils.js?v=01662438'; +} from '/js/utils.js?v=ed0f1802'; import { initFluidHover } from '/js/fluid-hover.js?v=cb886e86'; -import { initSpotlight } from '/js/spotlight.js?v=3d038c35'; +import { initSpotlight } from '/js/spotlight.js?v=6cac6066'; import { html, setHtml, raw } from '/js/html.js?v=c71f8903'; import { initI18n, t, currentLang } from '/js/i18n.js?v=1f1ea9c1'; import { pwStrength, passwordMismatch } from '/js/password-strength.js?v=42f45ac7'; @@ -40,7 +40,7 @@ import { buildMobile, resetMobileChrome, mkFolderGlyph, -} from '/js/ui.js?v=d078716c'; +} from '/js/ui.js?v=42f30621'; import { badgeMinimum, badgeSignature, computeBadgeVisual, readBadgeUpdate } from '/js/badge-logic.js?v=9e6d9d4b'; import { formatNumber } from '/js/format-number.js?v=4a5ccef4'; import { closeBadgePopover, wireBadgePopover } from '/js/badge-popover.js?v=aa52b1a3'; @@ -52,7 +52,7 @@ import { landingAfterSetup, restorePage, } from '/js/dashboard-logic.js?v=0d519f8b'; -import { applyBackground, BACKDROP, resolveBackground } from '/js/background.js?v=09beead7'; +import { applyBackground, BACKDROP, resolveBackground } from '/js/background.js?v=848da677'; import { jitter } from '/js/jitter.js?v=4eeef4c9'; import { isMobileLayout, onLayoutChange } from '/js/layout.js?v=e9f4b607'; import { startWakeLock } from '/js/wake-lock.js?v=6b9591cf'; diff --git a/ui/js/listbox.js b/ui/js/listbox.js index ba914c58..83c04291 100644 --- a/ui/js/listbox.js +++ b/ui/js/listbox.js @@ -9,7 +9,7 @@ import { nextActiveIndex } from '/js/admin-logic.js?v=5356a1b3'; import { fluidHoverClear, fluidHoverKb } from '/js/fluid-hover.js?v=cb886e86'; import { html, raw, setHtml } from '/js/html.js?v=c71f8903'; -import { qa } from '/js/utils.js?v=01662438'; +import { qa } from '/js/utils.js?v=ed0f1802'; import { iconSvg } from '/js/icon-set.js?v=ba9434d9'; const CHEV = iconSvg('chevrons', 22, 'solid', 'dd-chev'); diff --git a/ui/js/spotlight.js b/ui/js/spotlight.js index 8b0097cd..321e2f61 100644 --- a/ui/js/spotlight.js +++ b/ui/js/spotlight.js @@ -1,5 +1,5 @@ import { fluidHoverClear, fluidHoverKb } from '/js/fluid-hover.js?v=cb886e86'; -import { mk, clr, el, inp as inpById, q, qa, setUserText } from '/js/utils.js?v=01662438'; +import { mk, clr, el, inp as inpById, q, qa, setUserText } from '/js/utils.js?v=ed0f1802'; import { t } from '/js/i18n.js?v=1f1ea9c1'; /* Attached to the window so a re-open can undo the previous one. */ diff --git a/ui/js/ui.js b/ui/js/ui.js index 71e7e93b..2eb5255c 100644 --- a/ui/js/ui.js +++ b/ui/js/ui.js @@ -14,7 +14,7 @@ import { q, qa, setUserText, -} from '/js/utils.js?v=01662438'; +} from '/js/utils.js?v=ed0f1802'; import { t, currentLang } from '/js/i18n.js?v=1f1ea9c1'; import { toneForColor } from '/js/label-contrast.js?v=c1ac6fb8'; import { mobileMetrics, gridColumnWidth, gridCellCount } from '/js/mobile-metrics.js?v=349f4300'; @@ -373,13 +373,13 @@ export function openFolderMobile(folder, isz, _ir, _im, sc) { const innerW = boxW - padH * 2, gap = Math.round(14 * ptScale); const folderIconW = Math.min(Math.floor((innerW - gap * 2) / 3), isz); - const folderIr = Math.round(folderIconW * 0.22), + const folderIr = Math.round(folderIconW * ICON_R), folderIm = Math.round(folderIconW * 0.64); const gridInnerW = folderIconW * 3 + gap * 2, gridH = folderIconW * 3 + gap * 2; - /* A badge sits 7px outside its icon corner. Without this inset the outer + /* A badge sits 11px outside its icon corner. Without this inset the outer icons' badges cross into the next page and the viewport shows them. */ - const badgeOvh = Math.min(Math.ceil(7 * (sc || 1)), padH, padVT, padVB); + const badgeOvh = Math.min(Math.ceil(11 * (sc || 1)), padH, padVT, padVB); const pageW = gridInnerW + badgeOvh * 2, pageH = gridH + badgeOvh * 2; const dotSz = Math.round(7 * ptScale); diff --git a/ui/js/utils.js b/ui/js/utils.js index 202ec1b5..a44eb379 100644 --- a/ui/js/utils.js +++ b/ui/js/utils.js @@ -121,9 +121,9 @@ export const qa = (sel, root = document) => /** @type {HTMLElement[]} */ ([...ro @param {Event} e @returns {HTMLInputElement} */ export const tgt = e => /** @type {HTMLInputElement} */ (e.target); -/* breg is passed in to avoid a circular import. */ export const ICON_R = 0.26; +/* breg is passed in to avoid a circular import. */ export function mkWrap(item, sz, r, isz, cls, breg) { const w = mk('div'); w.className = cls ? `plate ${cls}` : 'plate'; diff --git a/ui/js/widget-config-form.js b/ui/js/widget-config-form.js index ce13d9ed..e032a528 100644 --- a/ui/js/widget-config-form.js +++ b/ui/js/widget-config-form.js @@ -3,9 +3,9 @@ import { t } from '/js/i18n.js?v=1f1ea9c1'; import { html, raw, setHtml } from '/js/html.js?v=c71f8903'; -import { reveal, wireInlineEdit } from '/js/admin-shared.js?v=e477e574'; -import { createListbox } from '/js/listbox.js?v=fd1151b6'; -import { renderColorControl } from '/js/admin-color-control.js?v=ee39fb36'; +import { reveal, wireInlineEdit } from '/js/admin-shared.js?v=d218efee'; +import { createListbox } from '/js/listbox.js?v=80606092'; +import { renderColorControl } from '/js/admin-color-control.js?v=96364a80'; import { seedCarried, applyOptionSet, @@ -15,7 +15,7 @@ import { visibleFieldFlags, } from '/js/admin-logic.js?v=5356a1b3'; import { optionsErrorAdvice, TONE } from '/js/admin-error.js?v=e4ea7820'; -import { qi } from '/js/utils.js?v=01662438'; +import { qi } from '/js/utils.js?v=ed0f1802'; const PE = ''; diff --git a/ui/test/folder-overlay.test.mjs b/ui/test/folder-overlay.test.mjs index 26b591b6..d2ba6144 100644 --- a/ui/test/folder-overlay.test.mjs +++ b/ui/test/folder-overlay.test.mjs @@ -43,7 +43,7 @@ test('a pointer click on the scrim closes the folder', () => { test('the page is inset by the badge overhang', () => { assert.match( overlay, - /badgeOvh = Math\.min\(Math\.ceil\(7 \* \(sc \|\| 1\)\)/, + /badgeOvh = Math\.min\(Math\.ceil\(11 \* \(sc \|\| 1\)\)/, 'the inset must follow the badge scale', ); assert.match(overlay, /pageW = gridInnerW \+ badgeOvh \* 2/); @@ -57,7 +57,7 @@ test('the inset comes out of the box padding, so the icons keep their place', () }); test('the inset never exceeds the padding it is taken from', () => { - assert.match(overlay, /Math\.min\(Math\.ceil\(7 \* \(sc \|\| 1\)\), padH, padVT, padVB\)/); + assert.match(overlay, /Math\.min\(Math\.ceil\(11 \* \(sc \|\| 1\)\), padH, padVT, padVB\)/); }); test('the icon grid itself is unchanged', () => { diff --git a/ui/test/pagination.test.mjs b/ui/test/pagination.test.mjs index 00b05009..6216a69f 100644 --- a/ui/test/pagination.test.mjs +++ b/ui/test/pagination.test.mjs @@ -208,5 +208,9 @@ test('the pager is hidden when there is one page', () => { test('the hidden pager is not painted', () => { const css = read('css/dashboard.css'); assert.match(css, /#dots\[hidden\] \{ display:none \}/, 'display:flex on #dots beats the hidden attribute'); - assert.match(css, /#dots:empty \{ display:none \}/, 'an empty bar shows on the error screen'); + assert.match( + read('index.html'), + /