diff --git a/ui/admin/index.html b/ui/admin/index.html index 4d5b83c9..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 bb1d37f7..bfefeeab 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,7 +383,7 @@ 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) } @@ -399,10 +398,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 +420,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-style:normal; + font-size:calc(12px * var(--sc,1)); font-weight:600; color:#FFFFFF; line-height:1; + font-style:normal; text-shadow:var(--label-shadow); } #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; text-shadow:var(--label-shadow); white-space:nowrap; } /* Dots row — shown when paging */ @@ -442,10 +441,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; + background:rgba(255,255,255,.35); flex-shrink:0; transition:background .18s,transform .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; 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 } @@ -548,19 +547,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 +569,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 +578,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 +633,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) } @@ -791,8 +791,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 +843,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 +877,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 +961,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 +1048,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); @@ -1063,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, @@ -1105,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 277fc2d0..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 e517e290..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=b6231666'; +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=d4f4cd3c'; -import { createListbox } from '/js/listbox.js?v=95ec8aab'; +} 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=6e070d2e'; +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 f5110660..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=d4f4cd3c'; +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=b6231666'; +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 3b5482d6..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=d4f4cd3c'; +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=b6231666'; +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 54eb2188..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=b6231666'; +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 5262bcbd..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=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=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=6a06debe'; +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 00f2d237..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=d4f4cd3c'; +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=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=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 b364d783..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=b6231666'; +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 868008e4..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=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=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=b6231666'; +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 b8d3ab6e..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=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=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=429a28d8'; +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=d4f4cd3c'; +} 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=6a06debe'; +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=95ec8aab'; +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=b6231666'; -import { applyBackground, resolveBackground } from '/js/background.js?v=1fec6318'; +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 d8c03942..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=b6231666'; +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 dde3f38b..8ab6161c 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=ed0f1802'; import { initFluidHover } from '/js/fluid-hover.js?v=cb886e86'; -import { initSpotlight } from '/js/spotlight.js?v=a59d932e'; +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'; @@ -39,7 +40,7 @@ import { buildMobile, resetMobileChrome, mkFolderGlyph, -} from '/js/ui.js?v=5d62b8d2'; +} 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'; @@ -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=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'; @@ -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..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=b6231666'; +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/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..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=b6231666'; +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 7fc869ed..2eb5255c 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=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=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); } @@ -372,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); @@ -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..a44eb379 100644 --- a/ui/js/utils.js +++ b/ui/js/utils.js @@ -121,6 +121,8 @@ export const qa = (sel, root = document) => /** @type {HTMLElement[]} */ ([...ro @param {Event} e @returns {HTMLInputElement} */ export const tgt = e => /** @type {HTMLInputElement} */ (e.target); +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'); diff --git a/ui/js/widget-config-form.js b/ui/js/widget-config-form.js index f099a624..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=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=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=b6231666'; +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/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..6216a69f 100644 --- a/ui/test/pagination.test.mjs +++ b/ui/test/pagination.test.mjs @@ -208,4 +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( + read('index.html'), + /