feat(honeybar): the HoneyOS bar, loaded at runtime
A strip along the top that leads to the other apps of the same system. It is served by HoneyOS; InkNote only says where to find it, through INKNOTE_HONEYBAR_ORIGIN. Empty means no bar and no change at all. The origin is served by the API rather than built into the page, because an image is pulled ready-made and a service worker keeps whole responses: anything frozen into index.html would outlive the setting. Layout follows the bar through --honeybar-h. The body has overflow:hidden, so the surplus height would not have scrolled but been cut off at the bottom; and #root padded itself around the notch that the bar now covers, which would have counted that gap twice. Unit 263/263, all fourteen browser suites green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2ea448f091
commit
00b1d4469f
12 files changed
+144
-5
No files matched your search
+14
-1
@@ -38,7 +38,20 @@
|
||||
</script>
|
||||
<style>
|
||||
body { margin: 0; padding: 0; overflow: hidden; overscroll-behavior: none; }
|
||||
#root { padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left); height: 100vh; }
|
||||
/* --honeybar-h is set by the HoneyOS bar while it is on the page, and is
|
||||
0 when it is not. The bar sits above the app and pushes it down, so the
|
||||
app has that much less room; body has overflow:hidden, which means the
|
||||
difference would not scroll but simply be cut off at the bottom.
|
||||
|
||||
It also covers the notch itself, so the padding above must give way —
|
||||
otherwise the gap is counted twice. max() keeps that from going
|
||||
negative when there is no bar. */
|
||||
#root {
|
||||
box-sizing: border-box;
|
||||
padding: max(0px, calc(env(safe-area-inset-top) - var(--honeybar-h, 0px)))
|
||||
env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
|
||||
height: calc(100vh - var(--honeybar-h, 0px));
|
||||
}
|
||||
#splash { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center; flex-direction: column; gap: 16px; background: #0d1117; z-index: 9999; transition: opacity 0.3s; }
|
||||
#splash.hide { opacity: 0; pointer-events: none; }
|
||||
#splash .logo { width: 64px; height: 64px; border-radius: 18px; background: linear-gradient(135deg, #388bfd, #a855f7); display: flex; align-items: center; justify-content: center; font-size: 32px; animation: pulse 1.5s infinite; }
|
||||
|
||||
@@ -933,7 +933,7 @@ export default function App() {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
height: "100vh",
|
||||
height: "calc(100vh - var(--honeybar-h, 0px))",
|
||||
background: T.bg,
|
||||
color: T.text,
|
||||
fontFamily: FONT_UI,
|
||||
@@ -1000,7 +1000,7 @@ export default function App() {
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
height: "100vh",
|
||||
height: "calc(100vh - var(--honeybar-h, 0px))",
|
||||
width: "100vw",
|
||||
overflow: "hidden",
|
||||
background: T.bg,
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Load the HoneyOS bar, if this instance is configured to have one.
|
||||
*
|
||||
* The bar is a strip along the top of every app that belongs to the same
|
||||
* system: it says where you are and takes you to the others. It lives on the
|
||||
* HoneyOS server, so it stays in step everywhere without InkNote being rebuilt.
|
||||
*
|
||||
* Deliberately fire-and-forget. If HoneyOS is slow, down, or simply not
|
||||
* configured, nothing here throws and nothing here waits — InkNote is not the
|
||||
* kind of app that may fail to start because a decoration is missing. Offline
|
||||
* there is no bar at all, which is the right trade for an offline-first app.
|
||||
*/
|
||||
export async function mountHoneybar() {
|
||||
// The bar mounts itself on load; a second script tag would mean two bars.
|
||||
if (document.querySelector("script[data-honeybar]")) return;
|
||||
|
||||
let origin, key;
|
||||
try {
|
||||
const r = await fetch("/api/public/config");
|
||||
if (!r.ok) return;
|
||||
const cfg = (await r.json()).honeybar;
|
||||
if (!cfg?.origin) return;
|
||||
({ origin, key } = cfg);
|
||||
} catch {
|
||||
return; // offline, or no server — either way, no bar
|
||||
}
|
||||
|
||||
const el = document.createElement("script");
|
||||
el.dataset.honeybar = "";
|
||||
el.defer = true;
|
||||
el.src = origin.replace(/\/+$/, "") + "/honeybar/honeybar.js";
|
||||
// honeybar.js reads its settings from document.currentScript, which is set
|
||||
// for a script inserted this way just as it is for one written in the HTML.
|
||||
el.dataset.app = "inknote";
|
||||
if (key) el.dataset.key = key;
|
||||
document.head.appendChild(el);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import NoteApp from "./App.jsx";
|
||||
import { mountHoneybar } from "./honeybar.js";
|
||||
|
||||
// Hide splash screen
|
||||
const hideSplash = () => {
|
||||
@@ -19,6 +20,9 @@ ReactDOM.createRoot(document.getElementById("root")).render(
|
||||
|
||||
setTimeout(hideSplash, 200);
|
||||
|
||||
// Not awaited: the bar is a decoration and must never hold up the app.
|
||||
mountHoneybar();
|
||||
|
||||
// Register service worker
|
||||
if ("serviceWorker" in navigator) {
|
||||
window.addEventListener("load", async () => {
|
||||
|
||||
@@ -81,7 +81,7 @@ export function AuthScreen({ onLogin, dark, initialError = "" }) {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
minHeight: "100vh",
|
||||
minHeight: "calc(100vh - var(--honeybar-h, 0px))",
|
||||
background: T.bg,
|
||||
fontFamily: "'DM Sans',sans-serif",
|
||||
}}
|
||||
|
||||
@@ -157,7 +157,7 @@ export function Sidebar({
|
||||
zIndex: isMobile ? 900 : 1,
|
||||
width: isMobile ? (open ? "100vw" : 0) : open ? 260 : 0,
|
||||
minWidth: isMobile ? 0 : open ? 260 : 0,
|
||||
height: isMobile ? "100vh" : "auto",
|
||||
height: isMobile ? "calc(100vh - var(--honeybar-h, 0px))" : "auto",
|
||||
background: T.sidebar,
|
||||
borderRight: isMobile ? "none" : `1px solid ${T.border}`,
|
||||
display: "flex",
|
||||
|
||||
Reference in new issue
Block a user