Device
Volume
~/Downloads; .apk files install as Android apps;
a game's .zip, folder or .exe becomes a Steam library title.diff --git a/docs/vr-utilities.md b/docs/vr-utilities.md index 0e73669..1c289b4 100644 --- a/docs/vr-utilities.md +++ b/docs/vr-utilities.md @@ -26,7 +26,7 @@ it twice reuses the existing process. | Compositor CPU | OpenVR compositor render CPU milliseconds, not game CPU time. | | System CPU | `/proc/stat` busy-time delta across the sample, with guest time counted once and iowait treated as idle. | | GPU clock | `3d00000.gpu/cur_freq`, converted from Hz to MHz; frequency is not load. | -| Hottest sensor / battery | Existing thermal-zone and battery sysfs reads from `frame_status.py`. | +| Hottest sensor / battery | Existing thermal-zone and battery sysfs reads from `frame_status.py`. In the headset HUD only: the app shows them once, in the battery menu at the top. | OpenVR uses background application mode, which does not start SteamVR or keep it running. This mode also returned live timing in a read-only device probe. diff --git a/ui/index.html b/ui/index.html index 0d7a686..09870da 100644 --- a/ui/index.html +++ b/ui/index.html @@ -27,8 +27,10 @@ } * { box-sizing: border-box; } [hidden] { display: none !important; } /* class display rules would otherwise win */ - html { scroll-behavior: smooth; scroll-padding-top: calc(76px + env(safe-area-inset-top)); } - body { margin: 0; color: var(--text); min-height: 100vh; padding-bottom: calc(46px + env(safe-area-inset-bottom)); + /* On a computer the header and the Activity bar are the window's chrome: the page scrolls in #scroll + between them, so neither covers anything. Phones scroll the window (see the phone rules below). */ + body { margin: 0; color: var(--text); height: 100vh; display: flex; flex-direction: column; overflow: hidden; + padding-bottom: calc(42px + env(safe-area-inset-bottom)); font: 14px/1.45 "Motiva Sans", -apple-system, "Segoe UI", Arial, sans-serif; background: radial-gradient(1400px 700px at 15% -15%, #2a475e 0%, transparent 60%), linear-gradient(180deg, var(--bg) 0%, #16202d 40%, var(--bg-deep) 100%) fixed; } @@ -38,13 +40,14 @@ ::-webkit-scrollbar-track { background: transparent; } /* ---- top bar (Steam's STORE / LIBRARY / COMMUNITY strip) ---- */ - header { position: sticky; top: 0; z-index: 20; display: flex; align-items: center; gap: 30px; + #scroll { flex: 1; min-height: 0; overflow: auto; scroll-behavior: smooth; scroll-padding-top: 12px; } + header { position: sticky; top: 0; z-index: 20; display: flex; align-items: center; gap: 30px; flex: none; min-width: 0; height: calc(64px + env(safe-area-inset-top)); padding: env(safe-area-inset-top) calc(28px + env(safe-area-inset-right)) 0 calc(28px + env(safe-area-inset-left)); background: rgba(23, 29, 37, .94); backdrop-filter: blur(12px); box-shadow: 0 1px 0 rgba(255,255,255,.04), 0 6px 18px rgba(0,0,0,.35); } - .brand { display: flex; align-items: center; gap: 11px; color: var(--bright); text-decoration: none; } + .brand { display: flex; align-items: center; gap: 11px; color: var(--bright); text-decoration: none; flex: none; } .brand svg { width: 30px; height: 30px; } .brand b { font-weight: 700; font-size: 15px; letter-spacing: 2.5px; } - nav { display: flex; gap: 4px; height: 100%; } + nav { display: flex; gap: 4px; height: 100%; flex: none; } nav a { display: flex; align-items: center; padding: 0 13px; height: 100%; color: var(--text); text-decoration: none; font-weight: 500; font-size: 15px; letter-spacing: 1.2px; text-transform: uppercase; border-bottom: 3px solid transparent; transition: color .15s; } @@ -58,10 +61,17 @@ .dot.off { background: var(--bad); } main { max-width: 1480px; margin: 0 auto; padding: 22px calc(28px + env(safe-area-inset-right)) 30px calc(28px + env(safe-area-inset-left)); display: flex; flex-direction: column; gap: 26px; } - .grid-top { display: grid; grid-template-columns: minmax(0, 1.7fr) minmax(330px, 1fr); gap: 22px; } + .grid-top { display: grid; grid-template-columns: minmax(0, 1.7fr) minmax(330px, 1fr); gap: 22px; align-items: start; } .grid-3 { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 22px; } - @media (max-width: 1150px) { .grid-top, .grid-3 { grid-template-columns: 1fr; } header { gap: 16px; padding: env(safe-area-inset-top) calc(18px + env(safe-area-inset-right)) 0 calc(18px + env(safe-area-inset-left)); } nav a { padding: 0 10px; } } - @media (max-width: 820px) { .brand b { display: none; } } + /* The header gives way as the window narrows: the wordmark goes, then the tabs tighten, then the + connection pill drops its second line. It fits down to the app's 760 px minimum, with room for + the macOS window buttons the app adds on the left. */ + header > button:not(.pill), header > .chip { flex: none; } + header > button:not(.pill):not(.chip) { width: 36px; padding: 0; justify-content: center; } + @media (max-width: 1250px) { .brand b { display: none; } } + @media (max-width: 1150px) { .grid-top, .grid-3 { grid-template-columns: 1fr; } header { gap: 14px; padding: env(safe-area-inset-top) calc(18px + env(safe-area-inset-right)) 0 calc(18px + env(safe-area-inset-left)); } nav a { padding: 0 10px; } } + @media (max-width: 960px) { header { gap: 10px; } nav { gap: 0; } nav a { padding: 0 8px; font-size: 13px; letter-spacing: .4px; } + .brand, #refreshAll { display: none; } .pill .pt span { display: none; } } nav svg { display: none; } html { -webkit-text-size-adjust: 100%; } body { -webkit-tap-highlight-color: transparent; } @@ -87,7 +97,7 @@ .wait::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--dim); flex: none; } /* ---- connection pill, its details dialog, and the Devices tab (frame_link.py) ---- */ - .pill { height: 40px; padding: 0 12px; gap: 9px; max-width: 420px; min-width: 190px; flex: 0 1 auto; background: var(--btn); } + .pill { height: 40px; padding: 0 12px; gap: 9px; max-width: 320px; min-width: 0; flex: 0 1 auto; background: var(--btn); } .pill .dot { flex: none; } .pill .dot.wait { background: var(--warn); box-shadow: 0 0 6px rgba(217,162,58,.7); animation: pulse 1s ease-in-out infinite; } @keyframes pulse { 50% { opacity: .35; } } @@ -149,8 +159,9 @@ /* ---- panels and section headers ---- */ .panel { background: linear-gradient(180deg, rgba(42,47,56,.72) 0%, rgba(30,35,41,.9) 100%); border-radius: 4px; padding: 18px; box-shadow: 0 8px 24px rgba(0,0,0,.25); min-width: 0; } - .shelf-head { display: flex; align-items: baseline; gap: 12px; margin: 0 0 14px; } - .shelf-head h2 { margin: 0; font-size: 13px; font-weight: 700; letter-spacing: 2px; text-transform: uppercase; color: var(--text); } + /* Buttons in a heading row wrap onto a line of their own rather than squeeze the heading. */ + .shelf-head { display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px 12px; margin: 0 0 14px; } + .shelf-head h2 { margin: 0; font-size: 13px; font-weight: 700; letter-spacing: 2px; text-transform: uppercase; color: var(--text); white-space: nowrap; } .shelf-head .count { color: var(--muted); font-size: 13px; } .shelf-head .spacer { flex: 1; } .sub { color: var(--muted); font-size: 12.5px; } @@ -179,6 +190,11 @@ border-radius: 3px; padding: 9px 11px; font: inherit; } textarea { resize: vertical; min-height: 76px; } input:focus, textarea:focus { outline: none; border-color: var(--blue); background: rgba(0,0,0,.4); } + /* Every drop-down dark, like the fields; the panels' own rules below still size them. */ + select { background: rgba(0,0,0,.28); color: var(--text); border: 1px solid transparent; border-radius: 3px; + padding: 7px 8px; font: inherit; font-size: 13px; max-width: 100%; min-width: 0; } + select:focus { outline: none; border-color: var(--blue); } + option { background: #1e2329; color: var(--text); } .row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; } /* ---- headset view ---- */ @@ -223,14 +239,18 @@ @keyframes spin { to { transform: rotate(360deg); } } .toolbar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-bottom: 12px; } - /* ---- device ---- */ - .battery { display: flex; gap: 16px; align-items: center; padding: 16px; border-radius: 3px; background: rgba(0,0,0,.22); } - .battery svg { width: 74px; height: 38px; flex: none; } - .battery .pct { font-size: 34px; font-weight: 300; color: var(--bright); line-height: 1; font-variant-numeric: tabular-nums; } - .battery .state { font-weight: 700; font-size: 13px; letter-spacing: 1.2px; text-transform: uppercase; margin-top: 6px; } - .battery .state.charging { color: var(--green-hi); } .battery .state.full { color: var(--green); } - .battery .state.low { color: var(--bad); } .battery .state.draining { color: var(--muted); } - .battery .detail { color: var(--muted); font-size: 12.5px; margin-top: 2px; } + /* ---- the headset's details: a menu under the battery chip ---- */ + .pop { position: fixed; inset: auto; margin: 0; width: min(390px, calc(100vw - 24px)); max-height: calc(100vh - 90px); overflow: auto; + padding: 16px; border: 1px solid rgba(255,255,255,.1); border-radius: 6px; background: #1e2329; color: var(--text); + box-shadow: 0 20px 60px rgba(0,0,0,.6); -webkit-app-region: no-drag; } + .pop .shelf-head { margin-bottom: 10px; } + .pop .stats { margin-top: 12px; } + .pop .chips { margin-top: 12px; } + .batt .state { font-weight: 700; font-size: 12.5px; letter-spacing: 1.2px; text-transform: uppercase; } + .batt .state.charging { color: var(--green-hi); } .batt .state.full { color: var(--green); } + .batt .state.low { color: var(--bad); } .batt .state.draining { color: var(--text); } + .batt .detail { color: var(--muted); font-size: 12.5px; margin-top: 2px; } + button.chip { height: 32px; padding: 0 12px; font-size: 13px; } .stats { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1px; margin-top: 14px; border-radius: 3px; overflow: hidden; } .stat { background: rgba(0,0,0,.18); padding: 10px 12px; min-width: 0; } .stat .k { color: var(--muted); font-size: 11px; letter-spacing: 1px; text-transform: uppercase; } @@ -433,11 +453,14 @@ /* ---- phones, upright or on their side: tabs move to a bottom bar, as in iOS apps. Every edge keeps clear of the safe area (notch or Dynamic Island, rounded corners, home indicator); env() is zero on desktops. ---- */ @media (max-width: 640px), (max-height: 500px) and (hover: none) { - html { scroll-padding-top: calc(64px + env(safe-area-inset-top)); } + html { scroll-behavior: smooth; scroll-padding-top: calc(64px + env(safe-area-inset-top)); } + body { height: auto; display: block; overflow: visible; } + #scroll { overflow: visible; } /* No blur here: it would make the header the containing block of the fixed tab bar. */ header { height: calc(52px + env(safe-area-inset-top)); padding: env(safe-area-inset-top) calc(14px + env(safe-area-inset-right)) 0 calc(14px + env(safe-area-inset-left)); gap: 10px; backdrop-filter: none; background: rgb(23, 29, 37); } .brand svg { width: 26px; height: 26px; } + .brand { display: flex; } #refreshAll { display: inline-flex; } .pill .pt span { display: block; } /* the tabs are at the bottom here */ header .chip { height: 30px; padding: 0 10px; } nav { position: fixed; left: 0; right: 0; bottom: 0; z-index: 25; gap: 0; height: calc(58px + env(safe-area-inset-bottom)); padding: 0 env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left); @@ -648,7 +671,7 @@
- — + @@ -656,7 +679,21 @@ +~/Downloads; .apk files install as Android apps;
a game's .zip, folder or .exe becomes a Steam library title.