From dfb9d84b4f1add664f16315b928156d5a98562d6 Mon Sep 17 00:00:00 2001 From: IFOKNR <253658289+ifoknr@users.noreply.github.com> Date: Wed, 28 Oct 2026 13:40:53 +0000 Subject: [PATCH 1/6] WebUI: Material 3 Expressive design Muted dark surfaces and tonal containers with the light periwinkle primary, as in Fastboot Studio for Windows and Mobile. Rounded 26 px cards without borders, pill buttons and a pill indicator in the bottom bar. Battery and storage are slim strips with a thin meter, whose text shrinks or wraps instead of being cut. Small tables (25 px rows, 3 px apart), option cards with a filled radio for the backup sets and the theme, and a 44 x 26 switch. About is a hero card with the icon on a scalloped cookie. The header uses a muted grey version of the icon; the favicon keeps the teal one. A light scheme in the same tones. --- module/webroot/app.js | 29 ++- module/webroot/index.html | 21 +- module/webroot/logo-mono.svg | 1 + module/webroot/style.css | 421 +++++++++++++++++++++-------------- 4 files changed, 296 insertions(+), 176 deletions(-) create mode 100644 module/webroot/logo-mono.svg diff --git a/module/webroot/app.js b/module/webroot/app.js index 9b9098f..4819446 100644 --- a/module/webroot/app.js +++ b/module/webroot/app.js @@ -81,7 +81,7 @@ function size(bytes) { return iso(Math.max(1, Math.round(bytes / 1024)) + ' KiB'); } const sectors = s => (s == null ? null : s * 512); -const celsius = (v, scale) => (v == null ? '–' : (v / scale).toFixed(1) + '°C'); +const celsius = (v, scale) => (v == null ? '–' : iso((v / scale).toFixed(1) + '°C')); let toastTimer; function toast(msg) { @@ -135,7 +135,7 @@ function show(view) { $$('.nav button').forEach(b => b.setAttribute('aria-selected', String(b.dataset.v === view))); $$('.view').forEach(v => { v.hidden = v.id !== 'v-' + view; }); if (view === 'log') Log.kick(); - if (view === 'device') Cpu.kick(); + if (view === 'device') { Cpu.kick(); shrinkStats(); } if (view === 'mods' && !Mods.data) loadMods(); window.scrollTo(0, 0); } @@ -201,11 +201,28 @@ function renderDevice() { // Memory is live in the CPU card below. $('#stats').innerHTML = stats.join(''); $('#stats').style.gridTemplateColumns = stats.length === 1 ? '1fr' : ''; + shrinkStats(); } +// A slim strip: label, note and value on one line, a thin meter along the bottom. function stat(label, value, note, pct, cls) { - return `
${esc(label)}${esc(value)}` + - `${esc(note)}
`; + return `
${esc(label)}${esc(note)}` + + `${esc(value)}
`; +} + +// Text in a strip is never cut: a strip too narrow for its line gets smaller text instead. +function shrinkStats() { + $$('#stats .stat').forEach(el => { + let s = 1; + el.style.fontSize = ''; + el.classList.remove('wrap'); + while (el.scrollWidth > el.clientWidth && s > .8) { + s -= .05; + el.style.fontSize = s.toFixed(2) + 'rem'; + } + // Still too long: the note moves under the label and value. + if (el.scrollWidth > el.clientWidth) el.classList.add('wrap'); + }); } /* The black box: the previous boot's kernel log from pstore. */ @@ -301,7 +318,7 @@ $$('#partF .fbtn').forEach(b => b.addEventListener('click', () => { renderParts(); })); -const SUPER_COLORS = ['#3DD6C4', '#7FA7F5', '#B39CF0', '#F0B341', '#F2615C', '#5BD97A', '#E58FD0', '#9AA6B6']; +const SUPER_COLORS = ['#A8C4FF', '#DCBCE0', '#94D6A0', '#F2C46D', '#BEC6E0', '#FFB4AB', '#C9B7F2', '#8F8F95']; function renderSuper(s) { let total = 0, list = []; @@ -848,7 +865,7 @@ function fit() { const chrome = $('.appbar').offsetHeight + (bottomBar ? nav.offsetHeight : 0); document.documentElement.style.setProperty('--chrome', chrome + 'px'); } -addEventListener('resize', fit); +addEventListener('resize', () => { fit(); shrinkStats(); }); /* ---------------------------------------------------------------- start */ diff --git a/module/webroot/index.html b/module/webroot/index.html index 4b12e1d..ad5e4eb 100644 --- a/module/webroot/index.html +++ b/module/webroot/index.html @@ -11,9 +11,9 @@
- +
-
+
…
@@ -110,7 +110,7 @@

- + -
-

+
+
+ +
Fastboot Studio
+

IFOKNRIFOKNR

-
- - - +
+ + +

diff --git a/module/webroot/logo-mono.svg b/module/webroot/logo-mono.svg new file mode 100644 index 0000000..a2799e7 --- /dev/null +++ b/module/webroot/logo-mono.svg @@ -0,0 +1 @@ + diff --git a/module/webroot/style.css b/module/webroot/style.css index 3fc9b6b..7c1d084 100644 --- a/module/webroot/style.css +++ b/module/webroot/style.css @@ -1,42 +1,56 @@ -/* Colours from the Windows app's Themes/Palette.Dark.xaml and Palette.Light.xaml, so both look - like one product. Dark is the default; light follows the phone, or the choice in Tools. +/* Material 3 Expressive, the same look as Fastboot Studio for Windows (v2.9) and Fastboot Studio + Mobile: muted dark surfaces, tonal containers, a light periwinkle primary, pill buttons and + rounded cards. Dark is the default; light follows the phone, or the choice in Tools. Sizes are in rem: the root grows on tablets, so the whole page scales with the screen. */ :root { - --bg: #0E1116; --rail: #0A0D12; --surface: #161A21; --raised: #1D222B; --hover: #252C37; --line: #2A313C; - --text: #E8ECF2; --dim: #9AA6B6; --faint: #7C8899; - --accent: #3DD6C4; --accent-ink: #0A0D12; --accent-soft: #10251F; --accent-line: #2F6E66; - --warn: #F0B341; --warn-soft: #1C1710; --warn-line: #5C4320; - --danger: #F2615C; --danger-soft: #231315; --danger-line: #5C2B2B; - --ok: #5BD97A; --info: #7FA7F5; --info-soft: #141B2B; --info-line: #2D3E66; - --scrim: rgba(5, 7, 10, .72); --shadow: rgba(0, 0, 0, .55); - --ui: "Roboto", "Noto Sans Arabic", "Noto Kufi Arabic", system-ui, sans-serif; + --bg: #2a2a2c; --nav: #2f2f31; --c0: #232325; --c1: #353537; --c2: #3d3d40; --c3: #47474b; + --line: #4a4a4f; --outline: #8f8f95; + --text: #e5e3e8; --dim: #c4c4cb; --faint: #9a9aa2; + --primary: #a8c4ff; --on-primary: #152c5e; + --pc: #2e4d9d; --on-pc: #dce3ff; + --sc: #3f455a; --on-sc: #dde1f9; --sel: #414a6b; + --tc: #4f4053; --on-tc: #f6dbf8; --tertiary: #dcbce0; + --danger: #ffb4ab; --danger-soft: #6e2a27; --on-danger-soft: #ffdad6; --on-danger: #690005; + --warn: #f2c46d; --warn-soft: #5b4521; --on-warn-soft: #ffdea6; + --ok: #94d6a0; --info: var(--primary); + --scrim: rgba(10, 10, 12, .62); --shadow: rgba(0, 0, 0, .5); + --ui: "Roboto", "Noto Kufi Arabic", "Noto Sans Arabic", system-ui, sans-serif; --mono: "Roboto Mono", "Droid Sans Mono", ui-monospace, monospace; --top: env(safe-area-inset-top, 0px); --bottom: env(safe-area-inset-bottom, 0px); - --rail-w: 6.5rem; + --rail-w: 6rem; + --r-card: 26px; color-scheme: dark; font-size: 16px; } @media (prefers-color-scheme: light) { :root:not([data-theme="dark"]) { - --bg: #F3F5F8; --rail: #E9EDF2; --surface: #FFFFFF; --raised: #EEF1F5; --hover: #E3E8EE; --line: #D3DAE3; - --text: #141A22; --dim: #566273; --faint: #768293; - --accent: #0B8F80; --accent-ink: #FFFFFF; --accent-soft: #E2F4F1; --accent-line: #97D3CA; - --warn: #A86A12; --warn-soft: #FFF5E3; --warn-line: #EBCB8F; - --danger: #C9332E; --danger-soft: #FDEDEC; --danger-line: #F0B6B3; - --ok: #23904A; --info: #2F5FC4; --info-soft: #EAF0FD; --info-line: #B5C8F0; - --scrim: rgba(20, 26, 34, .45); --shadow: rgba(20, 26, 34, .18); + --bg: #f8f7fb; --nav: #efeef3; --c0: #f2f1f6; --c1: #ecebf0; --c2: #e4e3e9; --c3: #dbdae1; + --line: #c6c6ce; --outline: #767680; + --text: #1b1b1f; --dim: #45464f; --faint: #6c6d76; + --primary: #3d5ba9; --on-primary: #ffffff; + --pc: #dce3ff; --on-pc: #10306e; + --sc: #dde1f9; --on-sc: #171c2f; --sel: #d3daf6; + --tc: #f6dbf8; --on-tc: #2e1734; --tertiary: #7b5582; + --danger: #ba1a1a; --danger-soft: #ffdad6; --on-danger-soft: #410002; --on-danger: #ffffff; + --warn: #7c5800; --warn-soft: #ffdea6; --on-warn-soft: #271900; + --ok: #2a6b36; + --scrim: rgba(20, 20, 26, .4); --shadow: rgba(20, 20, 26, .18); color-scheme: light; } } :root[data-theme="light"] { - --bg: #F3F5F8; --rail: #E9EDF2; --surface: #FFFFFF; --raised: #EEF1F5; --hover: #E3E8EE; --line: #D3DAE3; - --text: #141A22; --dim: #566273; --faint: #768293; - --accent: #0B8F80; --accent-ink: #FFFFFF; --accent-soft: #E2F4F1; --accent-line: #97D3CA; - --warn: #A86A12; --warn-soft: #FFF5E3; --warn-line: #EBCB8F; - --danger: #C9332E; --danger-soft: #FDEDEC; --danger-line: #F0B6B3; - --ok: #23904A; --info: #2F5FC4; --info-soft: #EAF0FD; --info-line: #B5C8F0; - --scrim: rgba(20, 26, 34, .45); --shadow: rgba(20, 26, 34, .18); + --bg: #f8f7fb; --nav: #efeef3; --c0: #f2f1f6; --c1: #ecebf0; --c2: #e4e3e9; --c3: #dbdae1; + --line: #c6c6ce; --outline: #767680; + --text: #1b1b1f; --dim: #45464f; --faint: #6c6d76; + --primary: #3d5ba9; --on-primary: #ffffff; + --pc: #dce3ff; --on-pc: #10306e; + --sc: #dde1f9; --on-sc: #171c2f; --sel: #d3daf6; + --tc: #f6dbf8; --on-tc: #2e1734; --tertiary: #7b5582; + --danger: #ba1a1a; --danger-soft: #ffdad6; --on-danger-soft: #410002; --on-danger: #ffffff; + --warn: #7c5800; --warn-soft: #ffdea6; --on-warn-soft: #271900; + --ok: #2a6b36; + --scrim: rgba(20, 20, 26, .4); --shadow: rgba(20, 20, 26, .18); color-scheme: light; } /* Tablets and wide screens: everything a size up. */ @@ -46,221 +60,305 @@ * { box-sizing: border-box; -webkit-tap-highlight-color: transparent; } [hidden] { display: none !important; } html, body { margin: 0; background: var(--bg); color: var(--text); } -body { font: 1rem/1.6 var(--ui); min-height: 100vh; transition: background .2s, color .2s; } +body { font: 1rem/1.55 var(--ui); min-height: 100vh; transition: background .2s, color .2s; } button, input { font: inherit; color: inherit; } -:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } +button { -webkit-user-select: none; user-select: none; } +:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; } .ltr { direction: ltr; unicode-bidi: isolate; } /* ---------- header and navigation */ .appbar { position: sticky; top: 0; z-index: 3; display: flex; align-items: center; justify-content: space-between; gap: .5rem; - padding: calc(var(--top) + .8rem) 1rem .8rem; background: var(--bg); border-bottom: 1px solid var(--line); + padding: calc(var(--top) + .6rem) 1rem .6rem; background: var(--bg); } -/* The app icon, the same as on ifoknr.com and the other Fastboot Studio tools. */ -.brand { display: flex; align-items: center; gap: .7rem; min-width: 0; } -.brand .logo { width: 2.5rem; height: 2.5rem; flex: none; display: block; } -.appbar .title { font-weight: 700; font-size: 1.15rem; line-height: 1.25; display: flex; align-items: center; gap: .5rem; } -.appbar .title .chip { font-size: .7rem; padding: .05rem .45rem; } +.brand { display: flex; align-items: center; gap: .65rem; min-width: 0; } +.brand .logo { width: 2.25rem; height: 2.25rem; flex: none; display: block; border-radius: .7rem; } +.appbar .title { font-weight: 700; font-size: 1.05rem; line-height: 1.25; display: flex; align-items: center; gap: .5rem; } +.appbar .title .chip { font-size: .66rem; padding: .05rem .45rem; } .wm { display: block; height: 1em; overflow: visible; } -.appbar .sub { font-size: .8rem; color: var(--faint); direction: ltr; unicode-bidi: isolate; } +.appbar .sub { font-size: .74rem; color: var(--faint); direction: ltr; unicode-bidi: isolate; } [dir="rtl"] .appbar .sub { text-align: right; } -.bar-end { display: flex; align-items: center; gap: .5rem; } -.iconbtn { width: 2.4rem; height: 2.4rem; border-radius: 50%; border: 1px solid var(--line); background: var(--surface); display: grid; place-items: center; cursor: pointer; padding: 0; } +.bar-end { display: flex; align-items: center; gap: .45rem; } +.iconbtn { width: 2.5rem; height: 2.5rem; border-radius: 50%; border: 0; background: var(--c2); display: grid; place-items: center; cursor: pointer; padding: 0; } .iconbtn svg { width: 1.2rem; height: 1.2rem; fill: none; stroke: var(--text); stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; } /* The button shows what a tap switches to. */ :root[data-dark="1"] .iconbtn .moon, :root:not([data-dark="1"]) .iconbtn .sun { display: none; } -main { padding: 1rem 1rem calc(var(--bottom) + 6rem); max-width: 1500px; margin: 0 auto; } -.view { display: flex; flex-direction: column; gap: .8rem; } +main { padding: .4rem 1rem calc(var(--bottom) + 6.2rem); max-width: 1500px; margin: 0 auto; } +.view { display: flex; flex-direction: column; gap: .7rem; } +/* The navigation bar: a pill indicator in the slate secondary container behind the icon. */ .nav { position: fixed; inset-inline: 0; bottom: 0; z-index: 3; display: grid; grid-template-columns: repeat(6, 1fr); - background: var(--rail); border-top: 1px solid var(--line); padding: .4rem .25rem calc(var(--bottom) + .5rem); + background: var(--nav); padding: .55rem .2rem calc(var(--bottom) + .55rem); } -.nav button { background: none; border: 0; padding: .3rem .1rem; display: flex; flex-direction: column; align-items: center; gap: .15rem; font-size: .75rem; color: var(--faint); cursor: pointer; } -.nav svg { width: 1.35rem; height: 1.35rem; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; } -.nav .ico { padding: .2rem .9rem; border-radius: 999px; display: grid; place-items: center; } -.nav button[aria-selected="true"] { color: var(--accent); } -.nav button[aria-selected="true"] .ico { background: var(--accent-soft); } +.nav button { background: none; border: 0; padding: 0; display: flex; flex-direction: column; align-items: center; gap: .2rem; font-size: .72rem; font-weight: 500; color: var(--dim); cursor: pointer; min-width: 0; } +.nav svg { width: 1.3rem; height: 1.3rem; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; } +.nav .ico { width: 3.3rem; height: 2rem; border-radius: 999px; display: grid; place-items: center; transition: background .2s; } +.nav button[aria-selected="true"] { color: var(--text); font-weight: 700; } +.nav button[aria-selected="true"] .ico { background: var(--sc); color: var(--on-sc); } -/* ---------- cards and text */ -.card { background: var(--surface); border: 1px solid var(--line); border-radius: 1rem; padding: 1rem; min-width: 0; } -.card.flush { padding: .15rem .8rem; } -.card h3 { margin: 0 0 .6rem; font-size: .95rem; font-weight: 600; color: var(--dim); display: flex; align-items: center; justify-content: space-between; gap: .5rem; } +/* ---------- cards and text: tonal surfaces, no borders */ +.card { background: var(--c1); border-radius: var(--r-card); padding: .95rem 1.1rem 1rem; min-width: 0; } +.card.flush { padding: .5rem; } +.card h3 { margin: 0 0 .5rem; font-size: .92rem; font-weight: 600; color: var(--text); display: flex; align-items: center; justify-content: space-between; gap: .5rem; } .card h3.gap { margin-top: 1rem; } -.card.alert { border-color: var(--danger-line); background: var(--danger-soft); } -.card.calm { display: flex; gap: .7rem; align-items: center; padding: .8rem 1rem; } +.card.alert { background: var(--danger-soft); color: var(--on-danger-soft); } +.card.alert h3 span[style] { color: var(--on-danger-soft) !important; } +.card.alert .note { color: var(--on-danger-soft); } +.card.alert .chip.danger { background: var(--danger); color: var(--on-danger); } +.card.alert pre.code { background: rgba(0, 0, 0, .22); border: 0; color: var(--on-danger-soft); } +:root[data-dark="0"] .card.alert pre.code { background: rgba(255, 255, 255, .55); } +.card.alert pre.code .hit { color: inherit; font-weight: 700; } +.card.alert .btn.ghost { background: rgba(0, 0, 0, .2); color: var(--on-danger-soft); } +:root[data-dark="0"] .card.alert .btn.ghost { background: rgba(255, 255, 255, .55); } +.card.calm { display: flex; gap: .7rem; align-items: center; padding: .6rem .7rem .6rem 1.1rem; border-radius: 999px; } +[dir="rtl"] .card.calm { padding: .6rem 1.1rem .6rem .7rem; } +.card.calm .note { margin: 0; } .dot { width: .55rem; height: .55rem; border-radius: 50%; background: var(--ok); flex: none; } -.chip { font-size: .75rem; padding: .1rem .55rem; border-radius: 999px; border: 1px solid var(--accent-line); color: var(--accent); background: var(--accent-soft); white-space: nowrap; font-weight: 500; } -.chip.warn { border-color: var(--warn-line); color: var(--warn); background: var(--warn-soft); } -.chip.danger { border-color: var(--danger-line); color: var(--danger); background: var(--danger-soft); } -.chip.info { border-color: var(--info-line); color: var(--info); background: var(--info-soft); } -.chip.mute { border-color: var(--line); color: var(--dim); background: var(--raised); } +/* Tonal chips. */ +.chip { font-size: .7rem; padding: .12rem .6rem; border-radius: 999px; color: var(--on-pc); background: var(--pc); white-space: nowrap; font-weight: 500; line-height: 1.5; } +.chip.warn { color: var(--on-warn-soft); background: var(--warn-soft); } +.chip.danger { color: var(--on-danger-soft); background: var(--danger-soft); } +.chip.info { color: var(--on-sc); background: var(--sc); } +.chip.mute { color: var(--dim); background: var(--c3); } -.kv { display: grid; grid-template-columns: auto 1fr; gap: .35rem 1rem; margin: 0; font-size: .95rem; } +.kv { display: grid; grid-template-columns: auto 1fr; gap: .28rem 1rem; margin: 0; font-size: .86rem; } .kv dt { color: var(--faint); } .kv dd { margin: 0; text-align: end; direction: ltr; unicode-bidi: isolate; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; } [dir="rtl"] .kv dd { text-align: left; } -.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: .7rem; } -.stat { background: var(--surface); border: 1px solid var(--line); border-radius: 1rem; padding: .8rem 1rem; display: flex; flex-direction: column; gap: .15rem; min-width: 0; } -.stat .l { font-size: .8rem; color: var(--faint); } -.stat .v { font-size: 1.4rem; font-weight: 700; direction: ltr; unicode-bidi: isolate; font-variant-numeric: tabular-nums; } -[dir="rtl"] .stat .v { text-align: right; } -.stat .n { font-size: .8rem; color: var(--dim); } +/* Stats: slim strips, label and value on one line, a thin line along the bottom. No big tiles. */ +.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: .5rem; } +.stat { position: relative; overflow: hidden; background: var(--c1); border-radius: 16px; padding: .5em .85em .62em; display: flex; align-items: baseline; gap: .5em; min-width: 0; font-size: 1rem; } +.stat .l { font-size: .7em; color: var(--dim); white-space: nowrap; } +.stat .n { font-size: .64em; color: var(--faint); white-space: nowrap; } +.stat .v { font-size: .86em; font-weight: 700; direction: ltr; unicode-bidi: isolate; font-variant-numeric: tabular-nums; white-space: nowrap; margin-inline-start: auto; } +.stat.wrap { flex-wrap: wrap; row-gap: 0; } +.stat.wrap .n { order: 3; flex-basis: 100%; white-space: normal; } .stat.good .v { color: var(--ok); } .stat.warn .v { color: var(--warn); } .stat.bad .v { color: var(--danger); } -.meter { height: .4rem; border-radius: .4rem; background: var(--raised); overflow: hidden; margin-top: .3rem; } -.meter i { display: block; height: 100%; width: 0; background: var(--accent); border-radius: .4rem; transition: width .4s ease; } -.meter.big { height: .55rem; margin: 0 0 .7rem; } -.stat.warn .meter i { background: var(--warn); } .stat.bad .meter i { background: var(--danger); } +.meter { height: .38rem; border-radius: 999px; background: var(--c3); overflow: hidden; } +.meter i { display: block; height: 100%; width: 0; background: var(--primary); border-radius: 999px; transition: width .4s ease; } +.meter.big { margin: 0 0 .7rem; } +.stat .meter { position: absolute; inset-inline: .85rem; bottom: .3rem; height: 3px; } +[dir="rtl"] .stat .meter i { margin-inline-start: auto; } +.stat.warn .meter i { background: var(--warn); } .stat.bad .meter i { background: var(--danger); } .stat.good .meter i { background: var(--ok); } -.cores { display: grid; grid-template-columns: repeat(var(--n, 8), 1fr); gap: .45rem; align-items: end; height: 5.5rem; direction: ltr; } +.cores { display: grid; grid-template-columns: repeat(var(--n, 8), 1fr); gap: .45rem; align-items: end; height: 5rem; direction: ltr; } .core { display: flex; flex-direction: column; align-items: center; gap: .3rem; height: 100%; justify-content: flex-end; } -.core .bar { width: 100%; min-height: 3px; border-radius: .3rem .3rem .15rem .15rem; background: linear-gradient(to top, var(--accent-line), var(--accent)); transition: height .6s ease; } -.core.big .bar { background: linear-gradient(to top, var(--warn-line), var(--warn)); } -.core span { font-size: .7rem; color: var(--faint); } -.legend { display: flex; justify-content: space-between; gap: .5rem; font-size: .8rem; color: var(--faint); margin-top: .5rem; direction: ltr; font-variant-numeric: tabular-nums; } +.core .bar { width: 100%; max-width: 1.6rem; min-height: 6px; border-radius: 999px; background: var(--primary); transition: height .6s ease; } +.core.big .bar { background: var(--tertiary); } +.core span { font-size: .66rem; color: var(--faint); } +.legend { display: flex; justify-content: space-between; gap: .5rem; font-size: .74rem; color: var(--faint); margin-top: .5rem; direction: ltr; font-variant-numeric: tabular-nums; } /* ---------- controls */ .row { display: flex; gap: .5rem; align-items: center; } -.search { flex: 1; min-width: 0; background: var(--raised); border: 1px solid var(--line); border-radius: .7rem; padding: .6rem .8rem; font-size: .95rem; } -.filters { display: flex; gap: .45rem; flex-wrap: wrap; } -.fbtn { background: var(--raised); border: 1px solid var(--line); color: var(--dim); font-size: .85rem; padding: .35rem .85rem; border-radius: 999px; cursor: pointer; } -.fbtn[aria-pressed="true"] { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent); } -.fbtn.rules { margin-inline-start: auto; border-color: var(--info-line); color: var(--info); background: var(--info-soft); } -.btn-s { background: var(--raised); border: 1px solid var(--line); border-radius: .7rem; padding: .55rem .9rem; cursor: pointer; font-size: .9rem; color: var(--dim); white-space: nowrap; } -.btn-s[aria-pressed="true"] { color: var(--warn); border-color: var(--warn-line); background: var(--warn-soft); } -.btn-s.danger { color: var(--danger); border-color: var(--danger-line); } -.seg { display: grid; grid-template-columns: 1fr 1fr; background: var(--raised); border: 1px solid var(--line); border-radius: .7rem; padding: .2rem; gap: .2rem; } +.search { flex: 1; min-width: 0; height: 2.75rem; background: var(--c2); border: 0; border-radius: 999px; padding: 0 1.1rem; font-size: .9rem; } +.search::placeholder { color: var(--faint); } +/* One line of chips; a narrow screen scrolls them sideways. */ +.filters { display: flex; gap: .35rem; overflow-x: auto; scrollbar-width: none; } +.filters::-webkit-scrollbar { display: none; } +.fbtn { flex: none; background: none; border: 1px solid var(--line); color: var(--dim); font-size: .78rem; font-weight: 500; height: 2rem; padding: 0 .75rem; border-radius: 10px; cursor: pointer; white-space: nowrap; } +.fbtn[aria-pressed="true"] { background: var(--sc); border-color: var(--sc); color: var(--on-sc); } +.fbtn.rules { margin-inline-start: auto; border-color: var(--pc); color: var(--on-pc); background: var(--pc); border-radius: 999px; } +.btn-s { background: var(--c3); border: 0; border-radius: 999px; height: 2.4rem; padding: 0 1rem; cursor: pointer; font-size: .82rem; font-weight: 500; color: var(--text); white-space: nowrap; } +.btn-s[aria-pressed="true"] { color: var(--on-warn-soft); background: var(--warn-soft); } +.btn-s.danger { color: var(--on-danger-soft); background: var(--danger-soft); } +.btn-s:disabled { opacity: .45; cursor: not-allowed; } +/* A connected button group: the chosen one is filled. */ +.seg { display: grid; grid-template-columns: 1fr 1fr; gap: 3px; } .seg.three { grid-template-columns: 1fr 1fr 1fr; } -.seg button { background: none; border: 0; border-radius: .55rem; padding: .5rem; font-size: .9rem; cursor: pointer; color: var(--dim); } -.seg button[aria-pressed="true"] { background: var(--surface); color: var(--text); box-shadow: 0 0 0 1px var(--line); } +.seg button { background: var(--c2); border: 0; border-radius: 8px; height: 2.6rem; padding: 0 .5rem; font-size: .84rem; font-weight: 500; cursor: pointer; color: var(--text); transition: border-radius .2s, background .2s; } +.seg button:first-child { border-start-start-radius: 999px; border-end-start-radius: 999px; } +.seg button:last-child { border-start-end-radius: 999px; border-end-end-radius: 999px; } +.seg button[aria-pressed="true"] { background: var(--primary); color: var(--on-primary); border-radius: 999px; } /* ---------- partitions */ -.superbar { display: flex; height: 1.1rem; border-radius: .4rem; overflow: hidden; background: var(--raised); direction: ltr; border: 1px solid var(--line); } -.superbar i { display: block; height: 100%; border-inline-end: 1px solid var(--bg); } -.slegend { display: grid; grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr)); gap: .3rem 1.2rem; font-size: .85rem; margin-top: .6rem; } +.superbar { display: flex; gap: 3px; height: .9rem; direction: ltr; } +.superbar i { display: block; height: 100%; border-radius: 4px; min-width: 3px; } +.superbar i:first-child { border-radius: 999px 4px 4px 999px; } +.slegend { display: grid; grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr)); gap: .15rem 1.2rem; font-size: .76rem; margin-top: .65rem; } .slegend div { display: flex; align-items: center; justify-content: space-between; gap: .4rem; min-width: 0; } -.slegend b { font-weight: 400; display: flex; align-items: center; gap: .4rem; direction: ltr; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.slegend b::before { content: ""; width: .55rem; height: .55rem; border-radius: .15rem; background: var(--c); flex: none; } +.slegend b { font-weight: 400; display: flex; align-items: center; gap: .45rem; direction: ltr; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.slegend b::before { content: ""; width: .55rem; height: .55rem; border-radius: 50%; background: var(--c); flex: none; } .slegend span { color: var(--dim); direction: ltr; font-variant-numeric: tabular-nums; white-space: nowrap; } -.ptable { width: 100%; border-collapse: collapse; font-size: .95rem; } -.ptable td { padding: .65rem .3rem; border-bottom: 1px solid var(--line); vertical-align: middle; } -.ptable tr:last-child td { border-bottom: 0; } -.ptable .pn { font-family: var(--mono); font-size: .9rem; direction: ltr; unicode-bidi: isolate; } -.ptable .pd { color: var(--faint); font-family: var(--mono); font-size: .75rem; direction: ltr; unicode-bidi: isolate; } -[dir="rtl"] .ptable .pn, [dir="rtl"] .ptable .pd { text-align: right; } -.ptable .ps { text-align: end; direction: ltr; font-variant-numeric: tabular-nums; white-space: nowrap; } -[dir="rtl"] .ptable .ps { text-align: left; } -.ptable .pt { width: 1%; white-space: nowrap; } +/* Small tables: each row a slim strip, 3 px apart; the card hugs its rows. */ +.ptable { width: 100%; border-collapse: separate; border-spacing: 0 3px; font-size: .74rem; margin: -3px 0; } +.ptable td { padding: 0 .55rem; height: 25px; background: var(--c2); vertical-align: middle; line-height: 1.3; } +.ptable td:first-child { border-start-start-radius: 9px; border-end-start-radius: 9px; } +.ptable td:last-child { border-start-end-radius: 9px; border-end-end-radius: 9px; } +.ptable td.note { background: none; padding: .3rem .2rem; height: auto; } +.ptable .pn, .ptable .pd { display: inline; } +.ptable .pn { font-family: var(--mono); font-size: .74rem; direction: ltr; unicode-bidi: isolate; } +.ptable .pd { color: var(--faint); font-family: var(--mono); font-size: .64rem; direction: ltr; unicode-bidi: isolate; margin: 0 .45rem; } +/* No direction on cells: it would flip their rounded corners. size() already isolates the text. */ +.ptable .ps { text-align: end; font-variant-numeric: tabular-nums; white-space: nowrap; color: var(--dim); } +.ptable .pt { width: 1%; white-space: nowrap; padding: 0 .2rem; } +.ptable .chip { font-size: .62rem; padding: .02rem .5rem; } /* ---------- logs: the log box takes whatever height the screen has left */ /* A fixed height (never below 30rem) so the box scrolls inside instead of growing with its lines; --chrome is the header plus, on phones, the bottom tab bar (set by fit() in app.js). */ #v-log:not([hidden]) { - height: max(30rem, calc(100vh - var(--chrome, 150px) - 2.5rem)); - height: max(30rem, calc(100dvh - var(--chrome, 150px) - 2.5rem)); + height: max(30rem, calc(100vh - var(--chrome, 150px) - 1.5rem)); + height: max(30rem, calc(100dvh - var(--chrome, 150px) - 1.5rem)); } .logbox { - flex: 1 1 0px; min-height: 0; background: var(--rail); border: 1px solid var(--line); border-radius: .8rem; - overflow-y: auto; padding: .5rem; direction: ltr; text-align: left; font: .78rem/1.55 var(--mono); + flex: 1 1 0px; min-height: 0; background: var(--c0); border-radius: 20px; + overflow-y: auto; padding: .6rem; direction: ltr; text-align: left; font: .72rem/1.5 var(--mono); } -.logbox p { margin: 0; padding: .05rem .3rem; border-radius: .2rem; white-space: pre-wrap; word-break: break-word; color: var(--dim); } +.logbox p { margin: 0; padding: .04rem .35rem; border-radius: 6px; white-space: pre-wrap; word-break: break-word; color: var(--dim); } .logbox p.w { color: var(--warn); } -.logbox p.e { color: var(--danger); background: var(--danger-soft); } -.logbox p.s { color: var(--info); cursor: pointer; } -.logbox p.s:active { background: var(--info-soft); } +.logbox p.e { color: var(--on-danger-soft); background: color-mix(in srgb, var(--danger-soft) 70%, transparent); } +.logbox p.s { color: var(--primary); cursor: pointer; } +.logbox p.s:active { background: var(--sc); } .logbox p.gap { color: var(--faint); text-align: center; } .logbox .empty { color: var(--faint); padding: .8rem; } /* ---------- backup */ -.stack { display: flex; flex-direction: column; gap: .8rem; min-width: 0; } -.opt { display: flex; gap: .7rem; align-items: flex-start; padding: .85rem 1rem; border: 1px solid var(--line); border-radius: .8rem; cursor: pointer; background: var(--surface); } -.opt input { margin-top: .35rem; accent-color: var(--accent); width: 1.05rem; height: 1.05rem; } -.opt:has(input:checked) { border-color: var(--accent-line); background: var(--accent-soft); } -.opt b { font-weight: 600; font-size: 1rem; display: block; } -.opt small { color: var(--dim); font-size: .85rem; display: block; line-height: 1.6; } -.btn { border: 0; border-radius: .8rem; padding: .8rem 1rem; font-weight: 600; font-size: 1rem; cursor: pointer; background: var(--accent); color: var(--accent-ink); width: 100%; } -.btn.ghost { background: var(--raised); color: var(--text); border: 1px solid var(--line); } -.btn.danger { background: var(--danger); color: #fff; } +.stack { display: flex; flex-direction: column; gap: .5rem; min-width: 0; } +/* An option card: the chosen one is a filled slate card with a filled radio. */ +.opt { display: flex; gap: .8rem; align-items: center; padding: .75rem 1rem; border-radius: 22px; cursor: pointer; background: var(--c1); transition: background .2s; } +.opt:has(input:checked) { background: var(--sel); } +.opt b { font-weight: 600; font-size: .92rem; display: block; } +.opt small { color: var(--dim); font-size: .76rem; display: block; line-height: 1.5; } +input[type="radio"] { + appearance: none; -webkit-appearance: none; margin: 0; flex: none; width: 1.25rem; height: 1.25rem; border-radius: 50%; + border: 2px solid var(--outline); display: grid; place-items: center; cursor: pointer; +} +input[type="radio"]::after { content: ""; width: .6rem; height: .6rem; border-radius: 50%; background: var(--primary); transform: scale(0); transition: transform .15s; } +input[type="radio"]:checked { border-color: var(--primary); } +input[type="radio"]:checked::after { transform: scale(1); } + +.btn { border: 0; border-radius: 999px; height: 2.85rem; padding: 0 1.4rem; font-weight: 600; font-size: .92rem; cursor: pointer; background: var(--primary); color: var(--on-primary); width: 100%; display: inline-flex; align-items: center; justify-content: center; gap: .5rem; } +.btn.ghost { background: var(--sc); color: var(--on-sc); } +.btn.danger { background: var(--danger); color: var(--on-danger); } .btn:disabled { opacity: .5; cursor: not-allowed; } +.btn.go { height: 3.5rem; border-radius: 20px; font-size: 1rem; margin-top: .2rem; } -.prog { display: flex; flex-direction: column; gap: .45rem; max-height: 22rem; overflow-y: auto; } -.prow { display: grid; grid-template-columns: 1fr auto; gap: 0 .5rem; font-size: .9rem; } +.prog { display: flex; flex-direction: column; gap: .3rem; max-height: 22rem; overflow-y: auto; } +.prow { display: grid; grid-template-columns: 1fr auto; gap: 0 .5rem; font-size: .76rem; } .prow .pn { font-family: var(--mono); direction: ltr; unicode-bidi: isolate; } [dir="rtl"] .prow .pn { text-align: right; } .prow .st { color: var(--faint); direction: ltr; font-variant-numeric: tabular-nums; } .prow .st.ok { color: var(--ok); } .prow .st.bad { color: var(--danger); } -.blist { display: flex; flex-direction: column; gap: .8rem; } -.bitem { display: grid; grid-template-columns: 1fr auto auto; gap: .15rem .5rem; align-items: center; font-size: .9rem; } -.bitem .p { font-family: var(--mono); font-size: .8rem; direction: ltr; unicode-bidi: isolate; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; } +.blist { display: flex; flex-direction: column; gap: 3px; } +.bitem { display: grid; grid-template-columns: 1fr auto auto; gap: 0 .4rem; align-items: center; font-size: .8rem; background: var(--c2); border-radius: 16px; padding: .5rem .5rem .5rem .85rem; } +[dir="rtl"] .bitem { padding: .5rem .85rem .5rem .5rem; } +.bitem .p { font-family: var(--mono); font-size: .72rem; direction: ltr; unicode-bidi: isolate; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; } [dir="rtl"] .bitem .p { text-align: right; } -.bitem .m { color: var(--faint); font-size: .8rem; grid-column: 1; grid-row: 2; } +.bitem .m { color: var(--faint); font-size: .7rem; grid-column: 1; grid-row: 2; } .bitem .p { grid-column: 1; grid-row: 1; } -.bitem button { grid-row: 1 / span 2; } +.bitem button { grid-row: 1 / span 2; height: 2.1rem; padding: 0 .85rem; font-size: .76rem; } /* Links to the Windows app, the group and a private message. */ -.links { display: grid; grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); gap: .5rem; margin-top: .9rem; } -.lbtn { display: flex; align-items: center; gap: .65rem; padding: .65rem .8rem; border: 1px solid var(--line); border-radius: .8rem; background: var(--raised); cursor: pointer; text-align: start; min-width: 0; } -.lbtn:hover { border-color: var(--accent-line); } -.lbtn svg { width: 1.3rem; height: 1.3rem; flex: none; fill: none; stroke: var(--accent); stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; } +.links { display: grid; grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); gap: 3px; margin-top: .8rem; } +.lbtn { display: flex; align-items: center; gap: .7rem; padding: .5rem .8rem .5rem .55rem; border: 0; border-radius: 16px; background: var(--c2); cursor: pointer; text-align: start; min-width: 0; } +[dir="rtl"] .lbtn { padding: .5rem .55rem .5rem .8rem; } +.lbtn:hover { background: var(--c3); } +.lbtn svg { width: 2.1rem; height: 2.1rem; padding: .45rem; border-radius: 50%; background: var(--sc); flex: none; fill: none; stroke: var(--on-sc); stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; } .lbtn span { display: flex; flex-direction: column; min-width: 0; } -.lbtn b { font-size: .9rem; font-weight: 600; } -.lbtn small { font-size: .75rem; color: var(--faint); direction: ltr; unicode-bidi: isolate; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.lbtn b { font-size: .82rem; font-weight: 600; } +.lbtn small { font-size: .68rem; color: var(--faint); direction: ltr; unicode-bidi: isolate; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } [dir="rtl"] .lbtn small { text-align: right; } -/* ---------- modules: one card per conflict, red for files, amber for properties */ -.mgrid { display: grid; gap: .8rem; } +/* ---------- modules: one tinted card per conflict, red for files, amber for properties */ +.mgrid { display: grid; gap: .7rem; } .mgrid:empty { display: none; } -.card.conflict { border-inline-start: 4px solid var(--danger); } -.card.conflict.prop { border-inline-start-color: var(--warn); } -.card.conflict h3 { color: var(--text); } -.mchips { display: flex; flex-wrap: wrap; gap: .35rem; margin: .1rem 0 .5rem; } -.paths { margin: .5rem 0 0; padding: .55rem .7rem; background: var(--rail); border: 1px solid var(--line); border-radius: .6rem; font: .8rem/1.6 var(--mono); direction: ltr; text-align: left; overflow-x: auto; white-space: pre; } -.pvals { display: grid; grid-template-columns: auto 1fr; gap: .25rem .9rem; margin-top: .5rem; font-size: .9rem; } +.card.conflict { background: color-mix(in srgb, var(--danger-soft) 38%, var(--c1)); } +.card.conflict.prop { background: color-mix(in srgb, var(--warn-soft) 38%, var(--c1)); } +.card.conflict h3 .chip.danger { background: var(--danger-soft); } +.mchips { display: flex; flex-wrap: wrap; gap: .3rem; margin: .1rem 0 .4rem; } +.mchips .chip.mute { background: var(--c3); color: var(--text); } +.paths { margin: .5rem 0 0; padding: .55rem .8rem; background: var(--c0); border-radius: 14px; font: .72rem/1.6 var(--mono); direction: ltr; text-align: left; overflow-x: auto; white-space: pre; } +.pvals { display: grid; grid-template-columns: auto 1fr; gap: .2rem .9rem; margin-top: .5rem; font-size: .8rem; } .pvals b { font-weight: 500; } .pvals code { font-family: var(--mono); direction: ltr; unicode-bidi: isolate; text-align: end; } [dir="rtl"] .pvals code { text-align: left; } +.ptable .mn { display: inline; } .ptable .mn b { font-weight: 600; } -.ptable .mn small { color: var(--faint); margin-inline-start: .4rem; } -.ptable .mflags { display: flex; flex-wrap: wrap; gap: .3rem; justify-content: flex-end; } +.ptable .mn small { color: var(--faint); margin: 0 .4rem; font-size: .66rem; } +#modsRows .pd { display: block; margin: 0; } +#modsRows td { height: auto; padding-block: .3rem; } +.ptable .mflags { display: flex; flex-wrap: wrap; gap: .25rem; justify-content: flex-end; } .ptable tr.off td { opacity: .6; } /* ---------- tools */ -.rgrid { display: grid; grid-template-columns: 1fr 1fr; gap: .5rem; } -.rbtn { background: var(--raised); border: 1px solid var(--line); border-radius: .8rem; padding: .8rem; cursor: pointer; text-align: start; display: flex; flex-direction: column; gap: .15rem; } -.rbtn b { font-size: 1rem; font-weight: 600; } -.rbtn small { font-size: .8rem; color: var(--faint); } -.rbtn:disabled { opacity: .4; cursor: not-allowed; } -.check { display: flex; gap: .5rem; align-items: center; font-size: .95rem; padding: .4rem 0 .7rem; } -.check input { accent-color: var(--accent); width: 1.1rem; height: 1.1rem; } -.note { margin: .4rem 0 0; font-size: .85rem; color: var(--dim); line-height: 1.7; } +.rgrid { display: grid; grid-template-columns: 1fr 1fr; gap: 3px; } +.rbtn { background: var(--c2); border: 0; border-radius: 8px; padding: .6rem .85rem; cursor: pointer; text-align: start; display: flex; flex-direction: column; gap: 0; } +.rgrid .rbtn:nth-child(1) { border-start-start-radius: 20px; } +.rgrid .rbtn:nth-child(2) { border-start-end-radius: 20px; } +.rgrid .rbtn:nth-last-child(2) { border-end-start-radius: 20px; } +.rgrid .rbtn:last-child { border-end-end-radius: 20px; } +.rbtn b { font-size: .88rem; font-weight: 600; } +.rbtn small { font-size: .7rem; color: var(--faint); } +.rbtn:disabled { opacity: .45; cursor: not-allowed; } + +/* A setting row with a small switch: 44 × 26, filled when on, outlined with a small knob when off. */ +.check { display: flex; gap: .8rem; align-items: center; justify-content: space-between; font-size: .84rem; padding: .45rem 0 .7rem; cursor: pointer; } +.check input { + appearance: none; -webkit-appearance: none; order: 2; flex: none; margin: 0; width: 44px; height: 26px; border-radius: 999px; + border: 2px solid var(--outline); background: var(--c3); position: relative; cursor: pointer; transition: background .2s, border-color .2s; +} +.check input::after { + content: ""; position: absolute; top: 50%; inset-inline-start: 5px; width: 12px; height: 12px; border-radius: 50%; + background: var(--outline); transform: translateY(-50%); transition: all .2s; +} +.check input:checked { background: var(--primary); border-color: var(--primary); } +.check input:checked::after { inset-inline-start: 20px; width: 18px; height: 18px; background: var(--on-primary); } +.note { margin: .35rem 0 0; font-size: .78rem; color: var(--dim); line-height: 1.65; } .note.warn { color: var(--warn); } .note:empty { display: none; } +/* About: the icon on a scalloped cookie, the name in the Prixma lettering, then IFOKNR. */ +.card.hero { background: var(--pc); color: var(--on-pc); } +.card.hero .kv dt { color: color-mix(in srgb, var(--on-pc) 70%, transparent); } +.card.hero .lbtn { background: color-mix(in srgb, var(--bg) 26%, transparent); } +:root[data-dark="0"] .card.hero .lbtn { background: rgba(255, 255, 255, .5); } +.card.hero .lbtn small { color: color-mix(in srgb, var(--on-pc) 70%, transparent); } +.card.hero .lbtn svg { background: var(--on-pc); stroke: var(--pc); } +.hero-top { display: flex; align-items: center; gap: .9rem; margin: .1rem 0 .8rem; } +.cookie { position: relative; width: 4.4rem; height: 4.4rem; flex: none; display: grid; place-items: center; } +.cookie > svg { position: absolute; inset: 0; width: 100%; height: 100%; fill: color-mix(in srgb, var(--on-pc) 22%, transparent); } +.cookie img { position: relative; width: 2.9rem; height: 2.9rem; border-radius: .85rem; } +.hero-top .name { font-size: 1.35rem; line-height: 1; display: flex; flex-direction: column; gap: .45rem; min-width: 0; } +.hero-top .name small { font-size: .74rem; opacity: .8; } + +/* Theme as option cards. */ +.ocards { display: grid; grid-template-columns: repeat(3, 1fr); gap: .4rem; } +.ocard { display: flex; flex-direction: column; align-items: flex-start; gap: .55rem; padding: .7rem .75rem; border-radius: 18px; background: var(--c2); border: 0; cursor: pointer; text-align: start; font-size: .82rem; font-weight: 500; transition: background .2s; } +.ocard .sw { width: 100%; height: 2.4rem; border-radius: 10px; display: flex; overflow: hidden; } +.ocard .sw i { flex: 1; } +.ocard .lab { display: flex; align-items: center; gap: .45rem; } +.ocard .lab::before { content: ""; width: 1rem; height: 1rem; border-radius: 50%; border: 2px solid var(--outline); box-sizing: border-box; flex: none; } +.ocard[aria-pressed="true"] { background: var(--sel); } +.ocard[aria-pressed="true"] .lab::before { border: 2px solid var(--primary); background: radial-gradient(circle, var(--primary) 0 40%, transparent 45%); } + /* ---------- sheet and toast */ .sheet { position: fixed; inset: 0; z-index: 10; background: var(--scrim); display: flex; align-items: flex-end; justify-content: center; } -.sheet .in { background: var(--surface); border-top: 1px solid var(--line); border-radius: 1.25rem 1.25rem 0 0; padding: 1.2rem 1rem calc(var(--bottom) + 1.2rem); width: 100%; max-width: 52rem; max-height: 85vh; overflow-y: auto; display: flex; flex-direction: column; gap: .8rem; } -.sheet h4 { margin: 0; font-size: 1.1rem; } +.sheet .in { background: var(--c1); border-radius: 28px 28px 0 0; padding: .7rem 1.1rem calc(var(--bottom) + 1.2rem); width: 100%; max-width: 52rem; max-height: 85vh; overflow-y: auto; display: flex; flex-direction: column; gap: .75rem; } +.sheet .in::before { content: ""; width: 2rem; height: 4px; border-radius: 999px; background: var(--outline); align-self: center; opacity: .6; margin-bottom: .2rem; } +.sheet h4 { margin: 0; font-size: 1.15rem; font-weight: 600; } .row2 { display: grid; grid-template-columns: 1fr 1fr; gap: .5rem; } .row2.one { grid-template-columns: 1fr; } -pre.code { margin: 0; padding: .7rem .8rem; background: var(--rail); border: 1px solid var(--line); border-radius: .7rem; overflow-x: auto; direction: ltr; text-align: left; font: .82rem/1.6 var(--mono); white-space: pre; } +pre.code { margin: 0; padding: .65rem .85rem; background: var(--c0); border-radius: 14px; overflow-x: auto; direction: ltr; text-align: left; font: .74rem/1.6 var(--mono); white-space: pre; } pre.code.wrap { white-space: pre-wrap; word-break: break-word; max-height: 50vh; overflow-y: auto; } pre.code .hit { color: var(--danger); } -.toast { position: fixed; inset-inline: 1rem; bottom: calc(var(--bottom) + 5.5rem); z-index: 11; background: var(--raised); border: 1px solid var(--accent-line); padding: .7rem .9rem; border-radius: .8rem; font-size: .9rem; box-shadow: 0 10px 30px -10px var(--shadow); max-width: 40rem; margin-inline: auto; } +.toast { position: fixed; inset-inline: 1rem; bottom: calc(var(--bottom) + 5.6rem); z-index: 11; background: var(--text); color: var(--bg); padding: .75rem 1rem; border-radius: 14px; font-size: .84rem; box-shadow: 0 8px 24px -8px var(--shadow); max-width: 40rem; margin-inline: auto; } /* ---------- tablets: cards side by side */ @media (min-width: 760px) { - main { padding: 1.25rem 1.5rem calc(var(--bottom) + 6.5rem); } - #v-device:not([hidden]), .view.cols:not([hidden]) { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: start; gap: 1rem; } + main { padding: .6rem 1.5rem calc(var(--bottom) + 6.5rem); } + #v-device:not([hidden]), .view.cols:not([hidden]) { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: start; gap: .8rem; } #v-device > #blackbox, #v-device > .wide, #v-device > #stats { grid-column: 1 / -1; } .mgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); } - .view.split:not([hidden]) { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-items: start; gap: 1rem; } + .view.split:not([hidden]) { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-items: start; gap: .8rem; } .sheet { align-items: center; } - .sheet .in { border-radius: 1.25rem; border: 1px solid var(--line); width: calc(100% - 3rem); padding-bottom: 1.2rem; } + .sheet .in { border-radius: 28px; width: calc(100% - 3rem); padding: 1.3rem 1.4rem; } + .sheet .in::before { display: none; } } /* ---------- landscape tablets and wide screens: the tabs move to a side rail */ @@ -268,17 +366,18 @@ pre.code .hit { color: var(--danger); } body { padding-inline-start: var(--rail-w); } .nav { inset-block: 0; inset-inline-start: 0; inset-inline-end: auto; width: var(--rail-w); - grid-template-columns: 1fr; grid-auto-rows: min-content; align-content: start; gap: .4rem; - padding: calc(var(--top) + 5rem) .5rem 1rem; border-top: 0; border-inline-end: 1px solid var(--line); + grid-template-columns: 1fr; grid-auto-rows: min-content; align-content: start; gap: .8rem; + padding: calc(var(--top) + 5rem) .4rem 1rem; background: var(--bg); } - .nav button { padding: .5rem .1rem; font-size: .8rem; } - main { padding: 1.5rem 2rem 2rem; } + .nav button { font-size: .74rem; } + .nav .ico { width: 3.5rem; } + main { padding: .8rem 2rem 2rem; } #v-log:not([hidden]) { - height: max(30rem, calc(100vh - var(--chrome, 80px) - 3.5rem)); - height: max(30rem, calc(100dvh - var(--chrome, 80px) - 3.5rem)); + height: max(30rem, calc(100vh - var(--chrome, 80px) - 2.5rem)); + height: max(30rem, calc(100dvh - var(--chrome, 80px) - 2.5rem)); } .toast { bottom: calc(var(--bottom) + 1.5rem); } - .logbox { font-size: .8rem; } + .logbox { font-size: .74rem; } } @media (min-width: 1200px) { #v-device:not([hidden]) { grid-template-columns: repeat(3, minmax(0, 1fr)); } @@ -286,10 +385,10 @@ pre.code .hit { color: var(--danger); } .view.cols:not([hidden]) { grid-template-columns: repeat(3, minmax(0, 1fr)); } } -@media (prefers-reduced-motion: reduce) { .core .bar, .meter i, body { transition: none; } } +@media (prefers-reduced-motion: reduce) { .core .bar, .meter i, body, .nav .ico, .opt, .seg button, .check input, .check input::after { transition: none; } } /* IFOKNR in the Swamp Ninja lettering, as outlines; one picture per theme. */ -.sig-row { margin: 2px 0 10px; } +.sig-row { margin: 0 0 .7rem; } .sig { height: 18px; width: auto; display: block; } .sig-light { display: none; } :root[data-theme="light"] .sig-dark { display: none; } From bb863b98b367c29b80dfb2b8486482c7550d47e8 Mon Sep 17 00:00:00 2001 From: IFOKNR <253658289+ifoknr@users.noreply.github.com> Date: Wed, 28 Oct 2026 13:45:19 +0000 Subject: [PATCH 2/6] WebUI: the Kernel/Apps and language switches are one pill track with a filled pill inside --- module/webroot/style.css | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/module/webroot/style.css b/module/webroot/style.css index 7c1d084..ed4da5b 100644 --- a/module/webroot/style.css +++ b/module/webroot/style.css @@ -166,13 +166,11 @@ main { padding: .4rem 1rem calc(var(--bottom) + 6.2rem); max-width: 1500px; marg .btn-s[aria-pressed="true"] { color: var(--on-warn-soft); background: var(--warn-soft); } .btn-s.danger { color: var(--on-danger-soft); background: var(--danger-soft); } .btn-s:disabled { opacity: .45; cursor: not-allowed; } -/* A connected button group: the chosen one is filled. */ -.seg { display: grid; grid-template-columns: 1fr 1fr; gap: 3px; } +/* A segmented switch: one pill track, the chosen side is a filled pill inside it. */ +.seg { display: grid; grid-template-columns: 1fr 1fr; gap: 4px; padding: 4px; background: var(--c2); border-radius: 999px; } .seg.three { grid-template-columns: 1fr 1fr 1fr; } -.seg button { background: var(--c2); border: 0; border-radius: 8px; height: 2.6rem; padding: 0 .5rem; font-size: .84rem; font-weight: 500; cursor: pointer; color: var(--text); transition: border-radius .2s, background .2s; } -.seg button:first-child { border-start-start-radius: 999px; border-end-start-radius: 999px; } -.seg button:last-child { border-start-end-radius: 999px; border-end-end-radius: 999px; } -.seg button[aria-pressed="true"] { background: var(--primary); color: var(--on-primary); border-radius: 999px; } +.seg button { background: none; border: 0; border-radius: 999px; height: 2.3rem; padding: 0 .5rem; font-size: .84rem; font-weight: 500; cursor: pointer; color: var(--dim); transition: background .2s, color .2s; } +.seg button[aria-pressed="true"] { background: var(--primary); color: var(--on-primary); font-weight: 600; } /* ---------- partitions */ .superbar { display: flex; gap: 3px; height: .9rem; direction: ltr; } From a0f7aa55c06b4c5efa46454e4922538150860319 Mon Sep 17 00:00:00 2001 From: IFOKNR <253658289+ifoknr@users.noreply.github.com> Date: Wed, 28 Oct 2026 13:53:25 +0000 Subject: [PATCH 3/6] WebUI: a matte grey icon in the header, and a grey About card to match --- module/webroot/logo-mono.svg | 2 +- module/webroot/style.css | 11 +++-------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/module/webroot/logo-mono.svg b/module/webroot/logo-mono.svg index a2799e7..c8228bd 100644 --- a/module/webroot/logo-mono.svg +++ b/module/webroot/logo-mono.svg @@ -1 +1 @@ - + diff --git a/module/webroot/style.css b/module/webroot/style.css index ed4da5b..3995734 100644 --- a/module/webroot/style.css +++ b/module/webroot/style.css @@ -311,16 +311,11 @@ input[type="radio"]:checked::after { transform: scale(1); } .note.warn { color: var(--warn); } .note:empty { display: none; } -/* About: the icon on a scalloped cookie, the name in the Prixma lettering, then IFOKNR. */ -.card.hero { background: var(--pc); color: var(--on-pc); } -.card.hero .kv dt { color: color-mix(in srgb, var(--on-pc) 70%, transparent); } -.card.hero .lbtn { background: color-mix(in srgb, var(--bg) 26%, transparent); } -:root[data-dark="0"] .card.hero .lbtn { background: rgba(255, 255, 255, .5); } -.card.hero .lbtn small { color: color-mix(in srgb, var(--on-pc) 70%, transparent); } -.card.hero .lbtn svg { background: var(--on-pc); stroke: var(--pc); } +/* About: the grey icon on a scalloped cookie, the name in the Prixma lettering, then IFOKNR. */ +.card.hero .lbtn { background: var(--c2); } .hero-top { display: flex; align-items: center; gap: .9rem; margin: .1rem 0 .8rem; } .cookie { position: relative; width: 4.4rem; height: 4.4rem; flex: none; display: grid; place-items: center; } -.cookie > svg { position: absolute; inset: 0; width: 100%; height: 100%; fill: color-mix(in srgb, var(--on-pc) 22%, transparent); } +.cookie > svg { position: absolute; inset: 0; width: 100%; height: 100%; fill: var(--c3); } .cookie img { position: relative; width: 2.9rem; height: 2.9rem; border-radius: .85rem; } .hero-top .name { font-size: 1.35rem; line-height: 1; display: flex; flex-direction: column; gap: .45rem; min-width: 0; } .hero-top .name small { font-size: .74rem; opacity: .8; } From 28c50215169e197ca0632a2bc2a302ef486b3c31 Mon Sep 17 00:00:00 2001 From: IFOKNR <253658289+ifoknr@users.noreply.github.com> Date: Wed, 28 Oct 2026 13:55:38 +0000 Subject: [PATCH 4/6] WebUI: IFOKNR in the violet lettering of the brand --- module/webroot/ifoknr-dark.svg | 2 +- module/webroot/ifoknr-light.svg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/module/webroot/ifoknr-dark.svg b/module/webroot/ifoknr-dark.svg index 17bc917..01b9552 100644 --- a/module/webroot/ifoknr-dark.svg +++ b/module/webroot/ifoknr-dark.svg @@ -1 +1 @@ -IFOKNR \ No newline at end of file +IFOKNR \ No newline at end of file diff --git a/module/webroot/ifoknr-light.svg b/module/webroot/ifoknr-light.svg index 1d57656..36bd9e6 100644 --- a/module/webroot/ifoknr-light.svg +++ b/module/webroot/ifoknr-light.svg @@ -1 +1 @@ -IFOKNR \ No newline at end of file +IFOKNR \ No newline at end of file From 97f51465da3a1c785295cad7e272f0c79a75437f Mon Sep 17 00:00:00 2001 From: IFOKNR <253658289+ifoknr@users.noreply.github.com> Date: Wed, 28 Oct 2026 14:30:11 +0000 Subject: [PATCH 5/6] WebUI: edge to edge in the root manager The page asks for the manager's insets (/internal/insets.css, KernelSU and WebUI X) and turns on edge to edge, so the header runs under the status bar and the tab bar under the navigation bar, padded by the insets. Elsewhere env(safe-area-inset-*) stands in. --- module/webroot/app.js | 2 ++ module/webroot/index.html | 3 +++ module/webroot/style.css | 4 ++-- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/module/webroot/app.js b/module/webroot/app.js index 4819446..488305f 100644 --- a/module/webroot/app.js +++ b/module/webroot/app.js @@ -887,6 +887,8 @@ function loadScript(src) { async function start() { const first = location.hash.slice(1); + // Edge to edge: the header and the tab bar run under the system bars (insets in style.css). + try { if (typeof ksu !== 'undefined' && ksu.enableEdgeToEdge) ksu.enableEdgeToEdge(true); } catch { /* an older manager keeps its bars */ } if (typeof ksu === 'undefined') { // Only for previews in a browser: the module zip leaves demo.js out, so a WebUI host // without the bridge gets the error card below rather than made-up data. diff --git a/module/webroot/index.html b/module/webroot/index.html index ad5e4eb..067b655 100644 --- a/module/webroot/index.html +++ b/module/webroot/index.html @@ -6,6 +6,9 @@ Fastboot Studio Companion + + diff --git a/module/webroot/style.css b/module/webroot/style.css index 3995734..ae079eb 100644 --- a/module/webroot/style.css +++ b/module/webroot/style.css @@ -16,8 +16,8 @@ --scrim: rgba(10, 10, 12, .62); --shadow: rgba(0, 0, 0, .5); --ui: "Roboto", "Noto Kufi Arabic", "Noto Sans Arabic", system-ui, sans-serif; --mono: "Roboto Mono", "Droid Sans Mono", ui-monospace, monospace; - --top: env(safe-area-inset-top, 0px); - --bottom: env(safe-area-inset-bottom, 0px); + --top: var(--window-inset-top, env(safe-area-inset-top, 0px)); + --bottom: var(--window-inset-bottom, env(safe-area-inset-bottom, 0px)); --rail-w: 6rem; --r-card: 26px; color-scheme: dark; From 8fb62c9eff04675560bb69208b0f480440a4552b Mon Sep 17 00:00:00 2001 From: IFOKNR <253658289+ifoknr@users.noreply.github.com> Date: Wed, 28 Oct 2026 17:14:05 +0000 Subject: [PATCH 6/6] WebUI: Fastboot Studio in the Prixma lettering under Companion on About (outlines, one picture per theme) --- module/webroot/fbs-name-dark.svg | 1 + module/webroot/fbs-name-light.svg | 1 + module/webroot/index.html | 2 +- module/webroot/style.css | 11 ++++++++++- 4 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 module/webroot/fbs-name-dark.svg create mode 100644 module/webroot/fbs-name-light.svg diff --git a/module/webroot/fbs-name-dark.svg b/module/webroot/fbs-name-dark.svg new file mode 100644 index 0000000..b1d2e58 --- /dev/null +++ b/module/webroot/fbs-name-dark.svg @@ -0,0 +1 @@ +Fastboot Studio \ No newline at end of file diff --git a/module/webroot/fbs-name-light.svg b/module/webroot/fbs-name-light.svg new file mode 100644 index 0000000..0284210 --- /dev/null +++ b/module/webroot/fbs-name-light.svg @@ -0,0 +1 @@ +Fastboot Studio \ No newline at end of file diff --git a/module/webroot/index.html b/module/webroot/index.html index 067b655..61b8952 100644 --- a/module/webroot/index.html +++ b/module/webroot/index.html @@ -146,7 +146,7 @@

-
Fastboot Studio
+
Fastboot StudioFastboot Studio

IFOKNRIFOKNR

diff --git a/module/webroot/style.css b/module/webroot/style.css index ae079eb..3d356a8 100644 --- a/module/webroot/style.css +++ b/module/webroot/style.css @@ -318,7 +318,16 @@ input[type="radio"]:checked::after { transform: scale(1); } .cookie > svg { position: absolute; inset: 0; width: 100%; height: 100%; fill: var(--c3); } .cookie img { position: relative; width: 2.9rem; height: 2.9rem; border-radius: .85rem; } .hero-top .name { font-size: 1.35rem; line-height: 1; display: flex; flex-direction: column; gap: .45rem; min-width: 0; } -.hero-top .name small { font-size: .74rem; opacity: .8; } +.hero-top .name small { display: block; opacity: .85; } +/* Fastboot Studio in the Prixma lettering, as outlines; one picture per theme, like IFOKNR below. */ +.fbs { height: .62rem; width: auto; display: block; margin-top: .1rem; } +.fbs-light { display: none; } +:root[data-theme="light"] .fbs-dark { display: none; } +:root[data-theme="light"] .fbs-light { display: block; } +@media (prefers-color-scheme: light) { + :root:not([data-theme="dark"]) .fbs-dark { display: none; } + :root:not([data-theme="dark"]) .fbs-light { display: block; } +} /* Theme as option cards. */ .ocards { display: grid; grid-template-columns: repeat(3, 1fr); gap: .4rem; }