android: the handoff — the pending rounds, the pipeline, and what is unverified
Everything written this session that has not been through the device: rounds 43 to 48 (each patch adversarially verified, 43's transient-notice expectation fixed), a partial round 49, the pipeline scripts, the Android Auto design and the icon comparison mock. Also the two harness changes that were never A/B'd — adaptive waits, which may only shorten a declared wait and never lengthen it, and a ship that runs the mock walk and the deploy at the same time. The A/B and its 255-step baseline are in handoff/tools; main stays at what the phone has actually walked.
This commit is contained in:
1 parent
867fd42972
commit
9f0601027c
400 files changed
+26401
-3
No files matched your search
@@ -0,0 +1,376 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>SoulStream Android icon — flat vs concept 04 gradient</title>
|
||||
<style>
|
||||
:root{
|
||||
--ui-bg:#0e1013; --ui-panel:#161a1f; --ui-line:#252b33;
|
||||
--ui-fg:#d2d9e1; --ui-dim:#828e9b; --ui-hi:#6fd3e0;
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
html,body{margin:0;background:var(--ui-bg);color:var(--ui-fg);
|
||||
font:13px/1.45 ui-sans-serif,system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif}
|
||||
a{color:var(--ui-hi)}
|
||||
.wrap{max-width:1180px;margin:0 auto;padding:18px 20px 60px}
|
||||
|
||||
header.top{display:flex;flex-wrap:wrap;align-items:baseline;gap:10px 16px;margin-bottom:14px}
|
||||
header.top h1{font-size:15px;font-weight:650;margin:0;letter-spacing:.2px}
|
||||
header.top .sub{font-size:12px;color:var(--ui-dim)}
|
||||
|
||||
.bar{position:sticky;top:0;z-index:20;display:flex;flex-wrap:wrap;gap:8px 18px;
|
||||
align-items:center;padding:10px 12px;margin:0 0 18px;border:1px solid var(--ui-line);
|
||||
border-radius:10px;background:rgba(16,19,23,.94);backdrop-filter:blur(6px)}
|
||||
.grp{display:flex;align-items:center;gap:7px}
|
||||
.grp>label{font-size:10.5px;text-transform:uppercase;letter-spacing:.9px;color:var(--ui-dim)}
|
||||
.seg{display:flex;border:1px solid var(--ui-line);border-radius:7px;overflow:hidden}
|
||||
.seg button{appearance:none;border:0;background:transparent;color:var(--ui-dim);
|
||||
font:inherit;font-size:12px;padding:5px 9px;cursor:pointer;white-space:nowrap}
|
||||
.seg button+button{border-left:1px solid var(--ui-line)}
|
||||
.seg button:hover{color:var(--ui-fg)}
|
||||
.seg button[aria-pressed="true"]{background:#232c35;color:#eaf3fa}
|
||||
.chk{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--ui-dim);cursor:pointer}
|
||||
|
||||
h2.sec{font-size:11px;text-transform:uppercase;letter-spacing:1.2px;color:var(--ui-dim);
|
||||
margin:26px 0 9px;font-weight:600}
|
||||
|
||||
.band{border-radius:12px;padding:22px 18px 16px;margin-bottom:12px;position:relative;overflow:hidden}
|
||||
.band .bandname{position:absolute;top:8px;left:12px;font-size:10px;letter-spacing:1.1px;
|
||||
text-transform:uppercase;opacity:.55;font-weight:600}
|
||||
.band.dark{background:radial-gradient(125% 105% at 18% -5%,#2e3339 0%,#191c21 52%,#0a0b0d 100%)}
|
||||
.band.dark .bandname{color:#fff}
|
||||
.band.light{background:radial-gradient(125% 105% at 18% -5%,#fdfaf5 0%,#ece5db 52%,#d5cdc1 100%)}
|
||||
.band.light .bandname{color:#2b2722}
|
||||
.band.neutral{background:#12151a;border:1px solid var(--ui-line)}
|
||||
.band.neutral .bandname{color:var(--ui-dim)}
|
||||
|
||||
.cols{display:flex;flex-wrap:wrap;gap:26px;align-items:flex-start;justify-content:center}
|
||||
.col{display:flex;flex-direction:column;align-items:center;gap:9px;min-width:104px}
|
||||
.col.today{opacity:.92}
|
||||
.tilebox{display:flex;align-items:center;justify-content:center;min-height:0}
|
||||
.tag{font-size:12px;font-weight:700;letter-spacing:.4px;text-align:center}
|
||||
.tagsub{font-size:10.5px;line-height:1.35;text-align:center;max-width:150px}
|
||||
.band.dark .tag{color:#eef4fa}.band.dark .tagsub{color:#9aa6b3}
|
||||
.band.light .tag{color:#221f1b}.band.light .tagsub{color:#6a635a}
|
||||
.band.neutral .tag{color:#eef4fa}.band.neutral .tagsub{color:var(--ui-dim)}
|
||||
|
||||
.divider{width:1px;align-self:stretch;background:currentColor;opacity:.16;margin:0 2px}
|
||||
|
||||
.ladder{display:flex;flex-direction:column;gap:14px}
|
||||
.lrow{display:flex;align-items:flex-end;gap:26px;justify-content:center}
|
||||
.lcell{display:flex;flex-direction:column;align-items:center;gap:6px}
|
||||
.lcell .sz{font-size:10px;color:var(--ui-dim)}
|
||||
.lhead{display:flex;gap:26px;justify-content:center;margin-bottom:2px}
|
||||
.lhead span{width:118px;text-align:center;font-size:11px;font-weight:700;letter-spacing:.4px}
|
||||
|
||||
.note{border:1px solid var(--ui-line);border-radius:10px;padding:14px 16px;background:var(--ui-panel);
|
||||
font-size:12.5px;color:#b6c0cb;margin-top:14px}
|
||||
.note b{color:#e6eef6;font-weight:650}
|
||||
.note p{margin:0 0 9px}.note p:last-child{margin:0}
|
||||
table.m{border-collapse:collapse;font:11.5px/1.3 ui-monospace,SFMono-Regular,Consolas,monospace;margin-top:4px}
|
||||
table.m th,table.m td{border:1px solid var(--ui-line);padding:4px 8px;text-align:left}
|
||||
table.m th{color:var(--ui-dim);font-weight:600}
|
||||
.sw{display:inline-block;width:10px;height:10px;border-radius:2px;vertical-align:-1px;margin-right:6px;
|
||||
border:1px solid rgba(255,255,255,.18)}
|
||||
.hidden{display:none}
|
||||
.banner{border-left:2px solid var(--ui-hi);padding:8px 0 8px 12px;margin:0 0 14px;
|
||||
font-size:12.5px;color:#b6c0cb}
|
||||
.banner b{color:#e6eef6}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
|
||||
<header class="top">
|
||||
<h1>SoulStream Android launcher icon — background layer decision</h1>
|
||||
<span class="sub">real kit layers · true 108/72 adaptive geometry · nothing here is shipped</span>
|
||||
</header>
|
||||
|
||||
<div class="bar">
|
||||
<div class="grp"><label>Mask</label><div class="seg" id="segMask"></div></div>
|
||||
<div class="grp"><label>Size</label><div class="seg" id="segSize"></div></div>
|
||||
<div class="grp"><label>Mode</label><div class="seg" id="segMode"></div></div>
|
||||
<label class="chk"><input type="checkbox" id="cbShadow" checked> launcher shadow</label>
|
||||
<label class="chk"><input type="checkbox" id="cbSafe"> safe-zone guides</label>
|
||||
</div>
|
||||
|
||||
<div id="stdBanner" class="banner">
|
||||
<b>A</b> is the kit as delivered. <b>B</b> and <b>C</b> are the same concept-04 gradient, differing only in
|
||||
how the 1024 artwork is mapped onto the 108-unit layer. Reply with a letter.
|
||||
</div>
|
||||
<div id="thmBanner" class="banner hidden">
|
||||
<b>Themed icons discard the background layer entirely.</b> Android 13+ draws only the
|
||||
<code><monochrome></code> drawable, tinted, on a wallpaper-derived system ground — so A, B and C are
|
||||
pixel-identical here. The decision below is not a background decision; it is only whether the monochrome
|
||||
layer exists at all.
|
||||
</div>
|
||||
|
||||
<div id="stage"></div>
|
||||
|
||||
<h2 class="sec">Size ladder — same mask, same wallpaper</h2>
|
||||
<div class="band neutral"><span class="bandname">Neutral ground</span><div id="ladder" class="ladder"></div></div>
|
||||
|
||||
<div class="note" id="notes"></div>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const D = {"flat":"#05080B","fgPath":{"d":"M 471.889 863.531 C 470.033 861.073 467.485 856.812 466.226 854.062 C 464.398 850.070 463.857 847.418 463.544 840.920 C 463.249 834.800 463.512 831.540 464.601 827.793 C 466.399 821.609 469.407 815.555 473.409 810.066 C 476.304 806.095 476.482 805.474 476.562 799.067 C 476.630 793.639 477.162 790.964 479.217 785.732 C 483.406 775.062 489.001 766.958 499.269 756.689 C 504.437 751.520 515.049 742.428 522.967 736.383 C 530.854 730.363 540.573 722.201 544.565 718.246 C 548.557 714.290 553.986 707.799 556.630 703.821 C 566.285 689.294 570.061 677.926 570.109 663.251 C 570.167 645.247 564.288 631.978 550.406 618.778 C 543.711 612.413 537.141 607.939 527.046 602.871 C 511.185 594.907 499.224 590.830 464.632 581.594 C 429.650 572.254 407.140 565.108 384.291 556.087 C 369.696 550.324 349.615 540.740 338.462 534.213 C 320.047 523.435 302.208 509.772 290.291 497.319 C 282.487 489.163 272.391 476.452 268.043 469.309 C 260.079 456.225 252.284 438.708 251.281 431.640 C 250.907 429.006 251.062 428.044 251.860 428.044 C 252.460 428.044 257.063 430.702 262.090 433.950 C 279.955 445.495 298.689 456.033 313.781 463.025 L 321.990 466.828 L 315.175 459.916 C 306.482 451.099 297.522 440.054 290.373 429.342 C 283.846 419.562 275.156 402.180 271.326 391.240 C 268.303 382.605 266.682 375.526 264.934 363.320 C 263.614 354.107 263.278 340.053 264.377 340.053 C 264.755 340.053 271.343 346.050 279.018 353.379 C 305.154 378.337 323.767 394.790 344.545 411.305 C 357.025 421.223 379.468 437.524 391.447 445.369 C 421.469 465.031 452.755 481.034 483.387 492.398 C 493.465 496.137 514.890 502.670 542.833 510.526 C 553.736 513.591 567.823 518.023 574.138 520.375 C 589.578 526.124 605.702 534.093 616.621 541.371 C 626.525 547.973 639.041 559.257 644.889 566.856 C 650.348 573.951 654.995 583.053 657.601 591.759 C 659.560 598.303 659.892 600.978 659.957 610.795 C 660.037 622.827 659.257 627.864 655.632 638.715 C 653.007 646.577 649.606 653.442 644.525 661.136 C 642.375 664.394 640.265 667.630 639.838 668.328 C 639.410 669.026 642.266 666.701 646.183 663.161 C 658.976 651.602 669.377 635.863 674.360 620.525 C 680.887 600.431 681.418 582.436 676.073 562.442 C 672.127 547.683 662.390 530.337 650.734 517.305 C 640.568 505.936 625.125 494.022 607.956 484.301 C 589.833 474.040 573.381 467.503 530.216 453.412 C 512.773 447.718 493.356 440.996 487.066 438.473 C 474.757 433.536 464.611 428.344 455.790 422.469 C 449.119 418.025 438.217 408.277 432.454 401.601 C 422.380 389.933 413.615 372.247 410.336 356.975 C 409.168 351.535 408.804 346.280 408.840 335.400 C 408.881 323.303 409.202 319.739 410.863 312.979 C 414.453 298.361 419.429 287.287 428.148 274.505 C 434.444 265.277 446.706 252.323 456.519 244.535 C 483.937 222.774 516.119 207.844 564.045 194.653 C 579.471 190.408 592.384 187.409 620.816 181.469 C 654.191 174.497 674.652 168.373 699.416 157.943 C 728.416 145.729 764.550 125.102 787.619 107.593 C 791.507 104.642 792.482 104.208 792.476 105.431 C 792.471 106.273 791.534 108.965 790.394 111.414 C 785.297 122.364 772.485 139.362 759.953 151.803 C 750.130 161.555 738.729 170.675 725.744 179.167 C 715.779 185.684 694.819 196.809 683.162 201.768 C 680.040 203.097 677.586 204.285 677.710 204.409 C 678.135 204.834 706.608 197.159 714.223 194.567 C 728.618 189.666 742.169 183.962 756.021 176.972 C 763.653 173.121 770.077 170.148 770.294 170.366 C 771.203 171.274 761.797 182.974 752.317 192.729 C 741.008 204.364 729.933 213.624 716.943 222.304 C 698.650 234.528 680.678 243.412 657.959 251.461 C 642.229 257.034 624.511 261.607 598.311 266.858 C 587.608 269.003 574.363 271.878 568.878 273.248 C 555.300 276.639 537.612 282.530 526.395 287.397 C 507.859 295.440 490.988 307.165 482.040 318.224 C 479.729 321.080 476.651 325.830 475.199 328.779 C 473.747 331.728 471.958 337.085 471.225 340.682 C 470.121 346.096 470.058 348.298 470.860 353.458 C 472.774 365.768 478.100 375.203 489.472 386.432 C 502.963 399.753 527.921 414.250 558.969 426.799 C 566.647 429.902 581.496 435.667 591.966 439.609 C 638.284 457.049 660.959 467.018 682.072 479.225 C 693.287 485.709 708.151 495.840 717.893 503.640 C 729.475 512.914 743.275 526.023 750.422 534.542 C 762.716 549.194 773.237 567.193 778.922 583.298 C 782.840 594.397 785.607 607.564 786.657 620.110 C 787.335 628.206 787.324 633.770 786.618 641.059 C 783.472 673.507 771.319 700.824 748.857 725.937 C 745.773 729.385 740.336 734.824 736.774 738.023 C 710.587 761.547 675.649 780.088 633.589 792.780 C 625.820 795.125 606.899 800.277 591.543 804.231 C 558.928 812.627 544.532 816.705 530.869 821.417 C 510.985 828.275 497.176 835.191 487.628 843.074 C 480.759 848.745 477.646 854.394 477.088 862.205 C 476.860 865.392 476.356 868.000 475.968 868.000 C 475.580 868.000 473.745 865.989 471.889 863.531 Z M 507.752 803.633 C 514.224 802.436 520.624 799.550 524.691 795.993 C 526.496 794.414 530.427 789.316 533.427 784.662 C 536.427 780.009 539.690 775.298 540.676 774.193 L 542.471 772.184 L 540.355 772.Line truncated
|
||||
|
||||
/* ---------- Android adaptive geometry ----------------------------------
|
||||
Layers are 108x108 dp. The launcher mask shows only the central 72x72 dp,
|
||||
i.e. the layer is drawn at 1.5x the on-screen icon size and centre-cropped.
|
||||
Every tile below is an <svg viewBox="18 18 72 72"> over 108-unit artwork,
|
||||
which reproduces exactly that. The 66 dp circle is the content safe zone.
|
||||
------------------------------------------------------------------------*/
|
||||
const VIEWBOX = '18 18 72 72';
|
||||
|
||||
/* Mask outlines in a 0..100 box, mapped into the visible 72 window. */
|
||||
const MASKS = {
|
||||
circle: {label:'Circle', d:'M50,0 A50,50 0 1,1 50,100 A50,50 0 1,1 50,0 Z'},
|
||||
squircle: {label:'Squircle', d:'M50,0 C10.05,0 0,10.05 0,50 C0,89.95 10.05,100 50,100 C89.95,100 100,89.95 100,50 C100,10.05 89.95,0 50,0 Z'},
|
||||
rsquare: {label:'Rounded square',d:'M20,0 L80,0 A20,20 0 0,1 100,20 L100,80 A20,20 0 0,1 80,100 L20,100 A20,20 0 0,1 0,80 L0,20 A20,20 0 0,1 20,0 Z'},
|
||||
teardrop: {label:'Teardrop (aggressive OEM)',d:'M0,20 A20,20 0 0,1 20,0 L50,0 A50,50 0 1,1 0,50 Z'}
|
||||
};
|
||||
|
||||
const SIZES = [
|
||||
{px:24, cap:'24 dp · notification'},
|
||||
{px:32, cap:'32 dp · recents chip'},
|
||||
{px:48, cap:'48 dp · launcher grid'},
|
||||
{px:64, cap:'64 dp · folder / tablet'},
|
||||
{px:112, cap:'112 dp · inspect'}
|
||||
];
|
||||
|
||||
const VARIANTS = [
|
||||
{id:'today', tag:'TODAY', sub:'shipping raster — Tauri placeholder on a white plate', today:true},
|
||||
{id:'flat', tag:'A — FLAT', sub:'kit as delivered · solid ' + D.flat},
|
||||
{id:'gradL', tag:'B — GRADIENT', sub:'concept 04 mapped 1024 → the whole 108 layer'},
|
||||
{id:'gradM', tag:'C — GRADIENT', sub:'concept 04 mapped 1024 → the visible 72 window'}
|
||||
];
|
||||
|
||||
/* ---------- defs ------------------------------------------------------ */
|
||||
function stopsSVG(stops){
|
||||
return stops.map(s =>
|
||||
'<stop offset="'+s.o+'" stop-color="'+s.c+'"'+(s.a!=null?' stop-opacity="'+s.a+'"':'')+'/>'
|
||||
).join('');
|
||||
}
|
||||
function linSVG(id,g){
|
||||
return '<linearGradient id="'+id+'" gradientUnits="userSpaceOnUse" color-interpolation="sRGB" '+
|
||||
'x1="'+g.x1+'" y1="'+g.y1+'" x2="'+g.x2+'" y2="'+g.y2+'">'+stopsSVG(g.stops)+'</linearGradient>';
|
||||
}
|
||||
function radSVG(id,g){
|
||||
return '<radialGradient id="'+id+'" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" '+
|
||||
'gradientTransform="translate('+g.cx+' '+g.cy+') rotate('+g.rot+') scale('+g.r+')">'+
|
||||
stopsSVG(g.stops)+'</radialGradient>';
|
||||
}
|
||||
function buildDefs(){
|
||||
let s = '<svg width="0" height="0" style="position:absolute" aria-hidden="true"><defs>';
|
||||
s += linSVG('fgGrad', D.fgGrad);
|
||||
s += linSVG('surfL', D.gradL.surf) + radSVG('glowL', D.gradL.glow);
|
||||
s += linSVG('surfM', D.gradM.surf) + radSVG('glowM', D.gradM.glow);
|
||||
for (const k in MASKS){
|
||||
s += '<clipPath id="clip-'+k+'" clipPathUnits="userSpaceOnUse">'+
|
||||
'<path transform="translate(18,18) scale(0.72)" d="'+MASKS[k].d+'"/></clipPath>';
|
||||
}
|
||||
s += '</defs></svg>';
|
||||
const el = document.createElement('div'); el.innerHTML = s; document.body.appendChild(el.firstChild);
|
||||
}
|
||||
|
||||
/* ---------- layers ---------------------------------------------------- */
|
||||
const FG = '<path d="'+D.fgPath.d+'" fill="url(#fgGrad)" fill-rule="evenodd" transform="'+D.fgPath.t+'"/>';
|
||||
function MONO(fillVar){
|
||||
return '<path d="'+D.monoPath.d+'" fill="'+fillVar+'" fill-rule="evenodd" transform="'+D.monoPath.t+'"/>';
|
||||
}
|
||||
const RECT = '<rect x="0" y="0" width="108" height="108" ';
|
||||
|
||||
function layers(id){
|
||||
switch(id){
|
||||
case 'flat': return RECT+'fill="'+D.flat+'"/>' + FG;
|
||||
case 'gradL': return RECT+'fill="url(#surfL)"/>' + RECT+'fill="url(#glowL)"/>' + FG;
|
||||
case 'gradM': return RECT+'fill="url(#surfM)"/>' + RECT+'fill="url(#glowM)"/>' + FG;
|
||||
case 'today': /* Launcher3 wraps a legacy raster: corner-extracted plate + icon at ~0.7 */
|
||||
return RECT+'fill="#ffffff"/>' +
|
||||
'<image href="'+D.todayPng+'" x="28.8" y="28.8" width="50.4" height="50.4" '+
|
||||
'preserveAspectRatio="xMidYMid meet"/>';
|
||||
case 'monoLight': return RECT+'fill="'+D.themed.light.ground+'"/>' + MONO(D.themed.light.ink);
|
||||
case 'monoDark': return RECT+'fill="'+D.themed.dark.ground +'"/>' + MONO(D.themed.dark.ink);
|
||||
case 'todayThemed': return layers('today');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
const SAFE = '<g fill="none" stroke="#ff4d6d" stroke-opacity=".85">'+
|
||||
'<circle cx="54" cy="54" r="33" stroke-width=".6" stroke-dasharray="2 2"/>'+
|
||||
'<rect x="18" y="18" width="72" height="72" stroke-width=".6" stroke-opacity=".45"/></g>';
|
||||
|
||||
function tile(variantId, px, mask){
|
||||
const sh = state.shadow
|
||||
? 'filter:drop-shadow(0 '+(px/48).toFixed(2)+'px '+(px/26).toFixed(2)+'px rgba(0,0,0,.30)) '+
|
||||
'drop-shadow(0 0 '+(px/40).toFixed(2)+'px rgba(0,0,0,.14));' : '';
|
||||
return '<svg class="tile" width="'+px+'" height="'+px+'" viewBox="'+VIEWBOX+'" style="'+sh+'display:block">'+
|
||||
'<g clip-path="url(#clip-'+mask+')">'+layers(variantId)+(state.safe?SAFE:'')+'</g></svg>';
|
||||
}
|
||||
|
||||
/* ---------- state + render -------------------------------------------- */
|
||||
const state = {mask:'circle', size:48, mode:'standard', shadow:true, safe:false};
|
||||
|
||||
/* the hash makes any view reproducible: #mask=teardrop&size=112&mode=themed */
|
||||
(function readHash(){
|
||||
for (const kv of location.hash.replace(/^#/,'').split('&')){
|
||||
const [k,v] = kv.split('=');
|
||||
if (k === 'mask' && MASKS[v]) state.mask = v;
|
||||
if (k === 'size' && SIZES.some(s => s.px === +v)) state.size = +v;
|
||||
if (k === 'mode' && (v === 'themed' || v === 'standard')) state.mode = v;
|
||||
if (k === 'shadow') state.shadow = v !== '0';
|
||||
if (k === 'safe') state.safe = v === '1';
|
||||
}
|
||||
})();
|
||||
function writeHash(){
|
||||
history.replaceState(null,'','#mask='+state.mask+'&size='+state.size+'&mode='+state.mode+
|
||||
'&shadow='+(state.shadow?1:0)+'&safe='+(state.safe?1:0));
|
||||
}
|
||||
|
||||
function seg(host, items, key, onpick){
|
||||
host.innerHTML = items.map(i =>
|
||||
'<button data-v="'+i.v+'" aria-pressed="'+(state[key]===i.v)+'">'+i.t+'</button>').join('');
|
||||
host.onclick = e => {
|
||||
const b = e.target.closest('button'); if(!b) return;
|
||||
state[key] = items.find(i => String(i.v) === b.dataset.v).v;
|
||||
[...host.children].forEach(c => c.setAttribute('aria-pressed', c === b));
|
||||
onpick && onpick(); render(); writeHash();
|
||||
};
|
||||
}
|
||||
|
||||
function colHTML(v, px){
|
||||
return '<div class="col'+(v.today?' today':'')+'">'+
|
||||
'<div class="tilebox">'+tile(v.id, px, state.mask)+'</div>'+
|
||||
'<div class="tag">'+v.tag+'</div><div class="tagsub">'+v.sub+'</div></div>';
|
||||
}
|
||||
|
||||
function bandStandard(cls, name){
|
||||
let h = '<div class="band '+cls+'"><span class="bandname">'+name+'</span><div class="cols">';
|
||||
h += colHTML(VARIANTS[0], state.size);
|
||||
h += '<div class="divider"></div>';
|
||||
for (let i=1;i<VARIANTS.length;i++) h += colHTML(VARIANTS[i], state.size);
|
||||
return h + '</div></div>';
|
||||
}
|
||||
|
||||
function bandThemed(cls, name, which){
|
||||
const t = which === 'light' ? D.themed.light : D.themed.dark;
|
||||
const cols = [
|
||||
{id:'todayThemed', tag:'TODAY', sub:'no monochrome layer — the launcher leaves it full-colour among themed neighbours', today:true},
|
||||
{id: which==='light'?'monoLight':'monoDark', tag:'A / B / C', sub:'kit monochrome, tinted '+t.ink+' on system ground '+t.ground}
|
||||
];
|
||||
let h = '<div class="band '+cls+'"><span class="bandname">'+name+'</span><div class="cols">';
|
||||
h += colHTML(cols[0], state.size) + '<div class="divider"></div>' + colHTML(cols[1], state.size);
|
||||
return h + '</div></div>';
|
||||
}
|
||||
|
||||
function render(){
|
||||
const stage = document.getElementById('stage');
|
||||
const themed = state.mode === 'themed';
|
||||
document.getElementById('stdBanner').classList.toggle('hidden', themed);
|
||||
document.getElementById('thmBanner').classList.toggle('hidden', !themed);
|
||||
|
||||
if (!themed){
|
||||
stage.innerHTML = bandStandard('dark','Dark wallpaper') + bandStandard('light','Light wallpaper');
|
||||
} else {
|
||||
stage.innerHTML = bandThemed('light','Light theme · wallpaper-derived ground','light') +
|
||||
bandThemed('dark','Dark theme · wallpaper-derived ground','dark');
|
||||
}
|
||||
|
||||
const list = themed
|
||||
? [{id:'todayThemed',tag:'TODAY'},{id:'monoLight',tag:'THEMED · LIGHT'},{id:'monoDark',tag:'THEMED · DARK'}]
|
||||
: VARIANTS;
|
||||
let lh = '<div class="lhead">' + list.map(v=>'<span>'+v.tag+'</span>').join('') + '</div>';
|
||||
for (const s of SIZES){
|
||||
lh += '<div class="lrow">' + list.map(v =>
|
||||
'<div class="lcell" style="width:118px"><div class="tilebox">'+tile(v.id, s.px, state.mask)+
|
||||
'</div><div class="sz">'+s.cap+'</div></div>').join('') + '</div>';
|
||||
}
|
||||
document.getElementById('ladder').innerHTML = lh;
|
||||
}
|
||||
|
||||
/* ---------- measurement: how much background does the falcon leave? ---- */
|
||||
function coverage(maskKey){
|
||||
const N = 512, c = document.createElement('canvas');
|
||||
c.width = c.height = N; const x = c.getContext('2d');
|
||||
const k = N/72; // 72 visible units -> N px
|
||||
// mask
|
||||
x.save(); x.scale(k,k); x.translate(-18,-18);
|
||||
x.translate(18,18); x.scale(0.72,0.72);
|
||||
x.fill(new Path2D(MASKS[maskKey].d)); x.restore();
|
||||
const maskData = x.getImageData(0,0,N,N).data;
|
||||
let maskPx = 0; for (let i=3;i<maskData.length;i+=4) if (maskData[i]>127) maskPx++;
|
||||
// falcon inside the mask
|
||||
x.globalCompositeOperation = 'source-in';
|
||||
x.save(); x.scale(k,k); x.translate(-18,-18);
|
||||
const t = D.fgPath.tParsed; // [tx,ty,s,tx2,ty2]
|
||||
x.translate(t[0],t[1]); x.scale(t[2],t[2]); x.translate(t[3],t[4]);
|
||||
x.fill(new Path2D(D.fgPath.d),'evenodd'); x.restore();
|
||||
const fgData = x.getImageData(0,0,N,N).data;
|
||||
let fgPx = 0; for (let i=3;i<fgData.length;i+=4) if (fgData[i]>127) fgPx++;
|
||||
// the falcon on its own, to see how much the mask shaves off
|
||||
const c2 = document.createElement('canvas'); c2.width = c2.height = N;
|
||||
const y = c2.getContext('2d');
|
||||
y.save(); y.scale(k,k); y.translate(-18,-18);
|
||||
y.translate(t[0],t[1]); y.scale(t[2],t[2]); y.translate(t[3],t[4]);
|
||||
y.fill(new Path2D(D.fgPath.d),'evenodd'); y.restore();
|
||||
const allData = y.getImageData(0,0,N,N).data;
|
||||
let allPx = 0; for (let i=3;i<allData.length;i+=4) if (allData[i]>127) allPx++;
|
||||
return {mask:maskPx, fg:fgPx, pct: 100*fgPx/maskPx, kept: 100*fgPx/allPx,
|
||||
maskOfSquare: 100*maskPx/(N*N)};
|
||||
}
|
||||
|
||||
/* ---------- notes ----------------------------------------------------- */
|
||||
function notes(){
|
||||
const cov = coverage(state.mask);
|
||||
const rows = D.samples.map(r =>
|
||||
'<tr><td>'+r.where+'</td>'+
|
||||
r.v.map(v=>'<td><span class="sw" style="background:'+v+'"></span>'+v+'</td>').join('')+'</tr>').join('');
|
||||
document.getElementById('notes').innerHTML =
|
||||
'<p><b>Geometry.</b> Layers are 108 dp; the mask shows the central 72 dp, so every layer is drawn at '+
|
||||
'1.5× the icon size and centre-cropped — that is what the <code>viewBox="18 18 72 72"</code> on each '+
|
||||
'tile reproduces. The foreground and monochrome paths, including the kit\'s v1.1 optical offset '+
|
||||
'<code>translate(-40.740741 20.370370)</code>, are copied verbatim from the kit SVGs and are '+
|
||||
'<b>identical in A, B and C</b>. Only the background layer changes.</p>'+
|
||||
'<p><b>Composited background colour</b> (surface + glow, under the falcon, sampled in layer units):</p>'+
|
||||
'<table class="m"><tr><th>point in the visible 72 window</th><th>A flat</th><th>B layer-fit</th>'+
|
||||
'<th>C mask-fit</th></tr>'+rows+'</table>'+
|
||||
'<p style="margin-top:10px"><b>How much background is even visible?</b> Measured live on this page: '+
|
||||
'under the '+MASKS[state.mask].label.toLowerCase()+' mask the falcon covers only <b>'+
|
||||
cov.pct.toFixed(1)+'%</b> of the masked area, so the background owns the other '+
|
||||
(100-cov.pct).toFixed(1)+'%. That mask keeps '+cov.maskOfSquare.toFixed(1)+'% of the 72 dp square and '+
|
||||
cov.kept.toFixed(1)+'% of the falcon (the falcon\'s painted box is 46.6 × 65.8 of the 72 window, '+
|
||||
'so even a circle shaves only the extreme tips).</p>'+
|
||||
'<p><b>B vs C.</b> B maps the 1024 artwork onto the whole 108 layer, so the visible 72 window sees only '+
|
||||
'the middle two-thirds of the gradient. C maps it onto the visible 72 window, so the masked result matches '+
|
||||
'the desktop tile corner for corner — but C\'s outer 18 dp of bleed is flat padding, which is what a '+
|
||||
'launcher reveals when it parallaxes the layers on a swipe. Under a circle the two are almost the same '+
|
||||
'(#0F3643 vs #0E3442 at the NW rim); they separate only where a mask keeps the corners.</p>'+
|
||||
'<p><b>Honesty notes.</b> 1 dp = 1 CSS px here; on a Pixel-class screen a 48 dp icon is ~132 physical '+
|
||||
'pixels but only ~7.6 mm across. Mask outlines are the standard circle / squircle / rounded-square / '+
|
||||
'teardrop families — OEM paths vary slightly. The launcher shadow is a Launcher3-style ambient+key pair, '+
|
||||
'approximated in CSS, applied identically to every tile. Themed-icon ground and ink are representative '+
|
||||
'Material You tones; the real values are derived from the user\'s wallpaper. The TODAY column is the '+
|
||||
'actual shipping <code>mipmap-xxxhdpi/ic_launcher.png</code> with Launcher3\'s legacy plating '+
|
||||
'(corner-extracted plate, icon at 0.7) applied.</p>';
|
||||
}
|
||||
|
||||
/* ---------- boot ------------------------------------------------------ */
|
||||
buildDefs();
|
||||
seg(document.getElementById('segMask'),
|
||||
Object.keys(MASKS).map(k=>({v:k,t:MASKS[k].label})), 'mask', notes);
|
||||
seg(document.getElementById('segSize'),
|
||||
SIZES.map(s=>({v:s.px,t:s.px+' dp'})), 'size');
|
||||
seg(document.getElementById('segMode'),
|
||||
[{v:'standard',t:'Standard'},{v:'themed',t:'Themed (13+)'}], 'mode');
|
||||
document.getElementById('cbShadow').checked = state.shadow;
|
||||
document.getElementById('cbSafe').checked = state.safe;
|
||||
document.getElementById('cbShadow').onchange = e => { state.shadow = e.target.checked; render(); writeHash(); };
|
||||
document.getElementById('cbSafe').onchange = e => { state.safe = e.target.checked; render(); writeHash(); };
|
||||
render(); notes(); writeHash();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user