/* ArozOS Office Suite - start-up splash ===================================== What an app shows while it starts and opens its document, so the wait has a face and says what is happening ("Opening report.docx...", "Laying out the pages..."). Two looks, chosen by where the app runs: window inside a web desktop float window: the window starts small, a coloured card with the app name, moving dots and the status line in the corner - and grows to its working size, about its own centre, once the document is ready page a full browser tab (or the mobile desktop, whose windows fill the screen): a plain page with the app icon in the middle and the status under it Include it as the FIRST element of
, so it covers the page before anything else has drawn: data-size is the window's working size in window mode. The splash brings its own styles: it paints before any stylesheet of the app has arrived, so an app should load its stylesheets and scripts after this tag (in ) rather than in , where they would hold the first paint back. The framework (office.js) feeds it status text and calls done() when the document is on screen, when a dialog needs the person, or when something failed; a start that never finishes is let go after a while so the app is never locked behind it. OfficeSplash.status("Opening report.docx...") OfficeSplash.done() OfficeSplash.active() -> true while it is showing */ var OfficeSplash = (function () { "use strict"; var SPLASH_W = 400, SPLASH_H = 240; // the window while it starts var GIVE_UP_MS = 60000; var script = document.currentScript; var appName = (script && script.getAttribute("data-app")) || "Office"; var icon = (script && script.getAttribute("data-icon")) || ""; var sizeAttr = ((script && script.getAttribute("data-size")) || "1080x700").split("x"); var workW = parseInt(sizeAttr[0], 10) || 1080, workH = parseInt(sizeAttr[1], 10) || 700; var root = null, statusEl = null, finished = false, giveUp = null; // each app's colour for the window card var APP_COLOURS = { docs: "#1f6fbf", sheets: "#1e8a3c", slides: "#cf6c16" }; var CSS = [ ".of-splash {", " position: fixed;", " inset: 0;", " z-index: 5000;", " box-sizing: border-box;", " font-family: \"Segoe UI\", \"Helvetica Neue\", Arial, sans-serif;", " user-select: none;", " -webkit-user-select: none;", " transition: opacity 0.22s ease;", "}", ".of-splash.of-splash-out { opacity: 0; pointer-events: none; }", ".of-splash-mark { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 1.8; }", ".of-splash-window {", " background: var(--of-splash-bg, #1f6fbf);", " color: #ffffff;", "}", ".of-splash-brand {", " position: absolute;", " left: 12px;", " top: 9px;", " display: flex;", " align-items: center;", " gap: 6px;", " font-size: 12px;", " opacity: 0.95;", "}", ".of-splash-mid {", " position: absolute;", " left: 0;", " right: 0;", " top: 50%;", " transform: translateY(-58%);", " text-align: center;", "}", ".of-splash-name {", " font-size: 46px;", " font-weight: 300;", " line-height: 1.1;", " letter-spacing: 0.5px;", "}", ".of-splash-window .of-splash-status {", " position: absolute;", " left: 12px;", " right: 12px;", " bottom: 9px;", " font-size: 12px;", " white-space: nowrap;", " overflow: hidden;", " text-overflow: ellipsis;", " opacity: 0.95;", "}", ".of-splash-dots {", " position: relative;", " width: 90px;", " height: 5px;", " margin: 12px auto 0;", "}", ".of-splash-dots span {", " position: absolute;", " top: 0;", " left: 0;", " width: 4px;", " height: 4px;", " border-radius: 50%;", " background: currentColor;", " opacity: 0;", " animation: of-splash-dot 2.4s infinite cubic-bezier(0.4, 0, 0.6, 1);", "}", ".of-splash-dots span:nth-child(2) { animation-delay: 0.14s; }", ".of-splash-dots span:nth-child(3) { animation-delay: 0.28s; }", ".of-splash-dots span:nth-child(4) { animation-delay: 0.42s; }", ".of-splash-dots span:nth-child(5) { animation-delay: 0.56s; }", "@keyframes of-splash-dot {", " 0% { left: 0; opacity: 0; }", " 15% { opacity: 1; }", " 35% { left: 45%; }", " 65% { left: 55%; }", " 85% { opacity: 1; }", " 100% { left: 100%; opacity: 0; }", "}", ".of-splash-page {", " background: #ffffff;", " color: #5f6368;", "}", ".of-splash-page.of-splash-dark { background: #1f2328; color: #b8bec6; }", ".of-splash-center {", " position: absolute;", " left: 0;", " right: 0;", " top: 50%;", " transform: translateY(-60%);", " text-align: center;", "}", ".of-splash-icon {", " width: 88px;", " height: 88px;", " display: block;", " margin: 0 auto 22px;", "}", ".of-splash-page .of-splash-status {", " font-size: 13px;", " min-height: 1.4em;", " padding: 0 16px;", "}", ".of-splash-foot {", " position: absolute;", " left: 0;", " right: 0;", " bottom: 22px;", " display: flex;", " align-items: center;", " justify-content: center;", " gap: 7px;", " font-size: 13px;", "}", "@media (prefers-reduced-motion: reduce) {", " .of-splash-dots span { animation: none; opacity: 0.8; }", " .of-splash-dots span:nth-child(1) { left: 30%; }", " .of-splash-dots span:nth-child(2) { left: 40%; }", " .of-splash-dots span:nth-child(3) { left: 50%; }", " .of-splash-dots span:nth-child(4) { left: 60%; }", " .of-splash-dots span:nth-child(5) { left: 70%; }", " .of-splash { transition: none; }", "}", "@media print {", " .of-splash { display: none !important; }", "}" ].join("\n"); function injectStyle() { var st = document.createElement("style"); st.setAttribute("data-of-splash", ""); var app = document.body ? document.body.getAttribute("data-officeapp") : ""; st.textContent = ".of-splash{--of-splash-bg:" + (APP_COLOURS[app] || "#1f6fbf") + ";}\n" + CSS; (document.head || document.documentElement).appendChild(st); } // the web desktop's float window this page sits in, when it can be sized // (the mobile desktop has float windows too, but they fill the screen) function floatWindow() { try { var fe = window.frameElement; if (!fe || typeof fe.closest !== "function") return null; var fw = fe.closest(".floatWindow"); if (!fw || !/px$/.test(fw.style.width || "")) return null; if (!window.parent || typeof window.parent.setFloatWindowSize !== "function") return null; return fw; } catch (e) { return null; // not same-origin: not a desktop window } } /* The window is sized through ao_module.js once it has loaded; the splash comes up before it, and then asks the desktop directly with the same calls ao_module makes. */ function setWindowSize(fw, w, h) { if (typeof ao_module_setWindowSize === "function") ao_module_setWindowSize(w, h); else window.parent.setFloatWindowSize(fw.getAttribute("windowId"), w, h); } function setResizable(fw, on) { if (typeof ao_module_setFixedWindowSize === "function") { if (on) ao_module_setResizableWindowSize(); else ao_module_setFixedWindowSize(); } else if (typeof window.parent.setFloatWindowResizePolicy === "function") { window.parent.setFloatWindowResizePolicy(fw.getAttribute("windowId"), !!on); } } function el(tag, cls, text) { var n = document.createElement(tag); if (cls) n.className = cls; if (text) n.textContent = text; return n; } // the suite mark: four rounded squares function mark(cls) { var ns = "http://www.w3.org/2000/svg"; var svg = document.createElementNS(ns, "svg"); svg.setAttribute("viewBox", "0 0 24 24"); svg.setAttribute("class", cls); svg.setAttribute("aria-hidden", "true"); [[3.5, 3.5], [13.5, 3.5], [3.5, 13.5], [13.5, 13.5]].forEach(function (p) { var r = document.createElementNS(ns, "rect"); r.setAttribute("x", p[0]); r.setAttribute("y", p[1]); r.setAttribute("width", 7); r.setAttribute("height", 7); r.setAttribute("rx", 1.5); svg.appendChild(r); }); return svg; } function dots() { var d = el("div", "of-splash-dots"); for (var i = 0; i < 5; i++) d.appendChild(el("span")); return d; } function build() { injectStyle(); var fw = floatWindow(); root = el("div", "of-splash of-noprint " + (fw ? "of-splash-window" : "of-splash-page")); root.setAttribute("role", "status"); root.setAttribute("aria-live", "polite"); var dark = false; try { dark = localStorage.getItem("office_theme") === "dark"; } catch (e) { } if (dark) root.classList.add("of-splash-dark"); if (fw) { var brand = el("div", "of-splash-brand"); brand.appendChild(mark("of-splash-mark")); brand.appendChild(el("span", "", "ArozOS Office")); root.appendChild(brand); var mid = el("div", "of-splash-mid"); mid.appendChild(el("div", "of-splash-name", appName)); mid.appendChild(dots()); root.appendChild(mid); statusEl = el("div", "of-splash-status", "Loading..."); root.appendChild(statusEl); // start small, like a splash; grown back in done() try { setResizable(fw, false); setWindowSize(fw, SPLASH_W, SPLASH_H); } catch (e) { } } else { var center = el("div", "of-splash-center"); if (icon) { var img = el("img", "of-splash-icon"); img.src = icon; img.alt = ""; center.appendChild(img); } statusEl = el("div", "of-splash-status", "Loading " + appName + "..."); center.appendChild(statusEl); root.appendChild(center); var foot = el("div", "of-splash-foot"); foot.appendChild(mark("of-splash-mark")); foot.appendChild(el("span", "", "ArozOS Office")); root.appendChild(foot); } document.body.insertBefore(root, document.body.firstChild); giveUp = setTimeout(done, GIVE_UP_MS); } function status(msg) { if (finished || !statusEl || !msg) return; statusEl.textContent = msg; } // grow the window to its working size around the splash's centre, kept // on the desktop function growWindow() { var fw = floatWindow(); if (!fw) return; // the person already made it bigger (maximized it) while it started if (fw.offsetWidth > SPLASH_W + 40 || fw.offsetHeight > SPLASH_H + 40) { setResizable(fw, true); return; } try { var pw = window.parent.innerWidth, ph = window.parent.innerHeight; var w = Math.min(workW, pw), h = Math.min(workH, ph); var left = parseFloat(fw.style.left) || 0, top = parseFloat(fw.style.top) || 0; var cx = left + fw.offsetWidth / 2, cy = top + fw.offsetHeight / 2; fw.style.left = Math.max(0, Math.min(pw - w, Math.round(cx - w / 2))) + "px"; fw.style.top = Math.max(0, Math.min(ph - h, Math.round(cy - h / 2))) + "px"; setWindowSize(fw, w, h); setResizable(fw, true); } catch (e) { } } function done() { if (finished || !root) return; finished = true; clearTimeout(giveUp); if (root.classList.contains("of-splash-window")) growWindow(); root.classList.add("of-splash-out"); var r = root; setTimeout(function () { if (r.parentNode) r.parentNode.removeChild(r); }, 260); // the layout changed size under everything that measured it try { window.dispatchEvent(new Event("resize")); } catch (e) { } } function active() { return !!root && !finished; } if (document.body) build(); else document.addEventListener("DOMContentLoaded", build); return { status: status, done: done, active: active }; })();