agent-zero/plugins/_office/webui/office-panel.html
Alessandro df9523433d Improve Office canvas setup and dashboard UX
Replace the raw Collabora setup log with a simple Office setup progress state, redesign the Office dashboard around document cards with lightweight previews, and keep backend WOPI sessions aligned with visible Office tabs. Also preserve the restored Office canvas surface across window refreshes and add regression coverage for the new behavior.
2026-04-28 07:17:04 +02:00

992 lines
32 KiB
HTML

<html>
<head>
<script type="module">
import { store } from "/plugins/_office/webui/office-store.js";
</script>
</head>
<body>
<div class="office-panel" x-data x-create="$store.office.onMount($el, xAttrs($el) || {})" x-destroy="$store.office.cleanup()">
<template x-if="$store.office">
<div class="office-shell">
<div class="office-toolbar">
<button type="button" class="office-button" title="Open" @click="$store.office.openPrompt()">
<span class="material-symbols-outlined">folder_open</span>
<span>Open</span>
</button>
<button type="button" class="office-button" title="New document" @click="$store.office.create('document')">
<span class="material-symbols-outlined">note_add</span>
<span>Doc</span>
</button>
<button type="button" class="office-button" title="New spreadsheet" @click="$store.office.create('spreadsheet')">
<span class="material-symbols-outlined">table</span>
<span>Sheet</span>
</button>
<button type="button" class="office-button" title="New presentation" @click="$store.office.create('presentation')">
<span class="material-symbols-outlined">slideshow</span>
<span>Presentation</span>
</button>
<span class="office-toolbar-spacer"></span>
<span
class="office-health-pill"
:class="`is-${$store.office.status?.state || 'unknown'}`"
:title="$store.office.healthTitle()"
:aria-label="$store.office.healthTitle()"
>
<span class="office-health-dot"></span>
<span x-show="$store.office.status?.state !== 'healthy'" x-text="$store.office.healthText()"></span>
</span>
<button type="button" class="office-icon-button" title="Save" @click="$store.office.save()" :disabled="!$store.office.session">
<span class="material-symbols-outlined">save</span>
</button>
<button
type="button"
class="office-icon-button"
title="Close file"
aria-label="Close file"
:disabled="!$store.office.session"
@click="$confirmClick($event, () => $store.office.closeFile())"
>
<span class="material-symbols-outlined">close</span>
</button>
<button type="button" class="office-icon-button" title="Refresh status" @click="$store.office.refresh()">
<span class="material-symbols-outlined">refresh</span>
</button>
</div>
<div class="office-tabs" x-show="$store.office.tabs.length" role="tablist" aria-label="Open Office files" style="display: none;">
<template x-for="tab in $store.office.tabs" :key="tab.tab_id">
<div class="office-tab-shell" :class="{ 'is-active': $store.office.isActiveTab(tab) }">
<button
type="button"
class="office-tab"
role="tab"
:aria-selected="$store.office.isActiveTab(tab).toString()"
:title="$store.office.tabLabel(tab)"
@click="$store.office.selectTab(tab.tab_id)"
>
<span class="material-symbols-outlined office-tab-icon" aria-hidden="true" x-text="$store.office.tabIcon(tab)"></span>
<span class="office-tab-title" x-text="$store.office.tabTitle(tab)"></span>
</button>
<button
type="button"
class="office-tab-close"
:title="'Close ' + $store.office.tabLabel(tab)"
:aria-label="'Close ' + $store.office.tabLabel(tab)"
:disabled="$store.office.loading"
@click.stop="$confirmClick($event, () => $store.office.closeTab(tab.tab_id))"
>
<span class="material-symbols-outlined">close</span>
</button>
</div>
</template>
</div>
<div class="office-status-line" x-show="$store.office.message || $store.office.error || $store.office.loading" style="display: none;">
<span class="material-symbols-outlined" :class="{ spinning: $store.office.loading }" x-text="$store.office.loading ? 'progress_activity' : ($store.office.error ? 'error' : 'check_circle')"></span>
<span x-text="$store.office.error || $store.office.message || 'Working...'"></span>
</div>
<div class="office-body">
<div class="office-bootstrap" x-show="!$store.office.session && (!$store.office.status || !$store.office.status.healthy)" style="display: none;">
<div class="office-setup-mark" :class="{ 'is-busy': $store.office.isSetupBusy(), 'is-alert': $store.office.isSetupBlocked() }">
<span class="material-symbols-outlined" :class="{ spinning: $store.office.isSetupBusy() }" x-text="$store.office.setupIcon()"></span>
</div>
<div class="office-setup-copy">
<span>Agent Zero Office</span>
<strong x-text="$store.office.setupTitle()"></strong>
<p x-text="$store.office.setupMessage()"></p>
</div>
<div class="office-setup-progress" :class="{ 'is-paused': !$store.office.isSetupBusy() }" aria-hidden="true">
<span></span>
</div>
<div class="office-bootstrap-actions" x-show="$store.office.showSetupActions()" style="display: none;">
<button type="button" class="office-button" @click="$store.office.retry()" x-show="$store.office.isSetupBlocked()" style="display: none;">
<span class="material-symbols-outlined">restart_alt</span>
<span>Retry</span>
</button>
<button type="button" class="office-button" @click="$store.office.refresh()">
<span class="material-symbols-outlined">sync</span>
<span>Refresh</span>
</button>
</div>
</div>
<div class="office-start" x-show="!$store.office.session && $store.office.status?.healthy" style="display: none;">
<section class="office-dashboard-section" aria-label="Create Office file">
<div class="office-dashboard-heading">Create</div>
<div class="office-template-grid">
<button type="button" class="office-create-tile" @click="$store.office.create('document')">
<span class="material-symbols-outlined">article</span>
<span>Document</span>
</button>
<button type="button" class="office-create-tile" @click="$store.office.create('spreadsheet')">
<span class="material-symbols-outlined">table_chart</span>
<span>Spreadsheet</span>
</button>
<button type="button" class="office-create-tile" @click="$store.office.create('presentation')">
<span class="material-symbols-outlined">co_present</span>
<span>Presentation</span>
</button>
</div>
</section>
<section class="office-dashboard-section" x-show="$store.office.openCards().length" aria-label="Open Office files" style="display: none;">
<div class="office-dashboard-heading">Open files</div>
<div class="office-card-grid">
<template x-for="doc in $store.office.openCards()" :key="doc.tab_id">
<button type="button" class="office-document-card is-open" :title="doc.path" @click="$store.office.selectTab(doc.tab_id)">
<span class="office-card-badge">Open</span>
<div class="office-card-preview" :class="`is-${$store.office.previewKind(doc)}`">
<template x-if="$store.office.previewKind(doc) === 'spreadsheet' && $store.office.hasPreview(doc)">
<div class="office-sheet-preview">
<template x-for="(row, rowIndex) in $store.office.previewRows(doc)" :key="rowIndex">
<div class="office-sheet-row">
<template x-for="(cell, cellIndex) in row" :key="cellIndex">
<span x-text="cell"></span>
</template>
</div>
</template>
</div>
</template>
<template x-if="$store.office.previewKind(doc) === 'presentation' && $store.office.hasPreview(doc)">
<div class="office-slide-preview">
<template x-for="(slide, index) in $store.office.previewSlides(doc)" :key="index">
<div class="office-slide-line">
<strong x-text="slide.title"></strong>
<span x-text="(slide.lines || []).join(' / ')"></span>
</div>
</template>
</div>
</template>
<template x-if="$store.office.previewKind(doc) === 'document' && $store.office.hasPreview(doc)">
<div class="office-page-preview">
<template x-for="(line, index) in $store.office.previewLines(doc)" :key="index">
<span x-text="line"></span>
</template>
</div>
</template>
<template x-if="!$store.office.hasPreview(doc)">
<div class="office-preview-fallback">
<span class="material-symbols-outlined" x-text="$store.office.tabIcon(doc)"></span>
</div>
</template>
</div>
<span class="office-card-title" x-text="$store.office.dashboardTitle(doc)"></span>
<small x-text="$store.office.dashboardMeta(doc)"></small>
</button>
</template>
</div>
</section>
<section class="office-dashboard-section" x-show="$store.office.recentCards().length" aria-label="Recent Office files" style="display: none;">
<div class="office-dashboard-heading">Recent files</div>
<div class="office-card-grid">
<template x-for="doc in $store.office.recentCards()" :key="doc.file_id">
<button type="button" class="office-document-card" :title="doc.path" @click="$store.office.openPath(doc.path)">
<div class="office-card-preview" :class="`is-${$store.office.previewKind(doc)}`">
<template x-if="$store.office.previewKind(doc) === 'spreadsheet' && $store.office.hasPreview(doc)">
<div class="office-sheet-preview">
<template x-for="(row, rowIndex) in $store.office.previewRows(doc)" :key="rowIndex">
<div class="office-sheet-row">
<template x-for="(cell, cellIndex) in row" :key="cellIndex">
<span x-text="cell"></span>
</template>
</div>
</template>
</div>
</template>
<template x-if="$store.office.previewKind(doc) === 'presentation' && $store.office.hasPreview(doc)">
<div class="office-slide-preview">
<template x-for="(slide, index) in $store.office.previewSlides(doc)" :key="index">
<div class="office-slide-line">
<strong x-text="slide.title"></strong>
<span x-text="(slide.lines || []).join(' / ')"></span>
</div>
</template>
</div>
</template>
<template x-if="$store.office.previewKind(doc) === 'document' && $store.office.hasPreview(doc)">
<div class="office-page-preview">
<template x-for="(line, index) in $store.office.previewLines(doc)" :key="index">
<span x-text="line"></span>
</template>
</div>
</template>
<template x-if="!$store.office.hasPreview(doc)">
<div class="office-preview-fallback">
<span class="material-symbols-outlined" x-text="$store.office.tabIcon(doc)"></span>
</div>
</template>
</div>
<span class="office-card-title" x-text="$store.office.dashboardTitle(doc)"></span>
<small x-text="$store.office.dashboardMeta(doc)"></small>
</button>
</template>
</div>
</section>
</div>
<div class="office-frame-wrap" x-show="$store.office.session" style="display: none;">
<iframe
data-office-frame
allow="clipboard-read *; clipboard-write *; fullscreen *"
allowfullscreen
></iframe>
</div>
</div>
</div>
</template>
</div>
<style>
.office-panel,
.office-shell {
display: flex;
flex: 1 1 auto;
flex-direction: column;
width: 100%;
height: 100%;
min-width: 0;
min-height: 0;
background: var(--color-background);
}
.office-panel {
container-type: inline-size;
}
.modal-inner.office-modal {
box-sizing: border-box;
container-type: inline-size;
width: min(82vw, 1180px);
height: min(88vh, 900px);
min-width: min(340px, calc(100vw - 16px));
min-height: min(500px, calc(100vh - 16px));
max-width: calc(100vw - 16px);
max-height: calc(100vh - 16px);
resize: both;
border: 1px solid color-mix(in srgb, var(--color-border) 75%, transparent);
border-radius: 7px;
box-shadow: 0 18px 48px rgba(0, 0, 0, 0.32);
background: color-mix(in srgb, var(--color-background) 94%, #000 6%);
}
.modal.modal-floating {
pointer-events: none;
}
.modal.modal-floating .modal-inner {
pointer-events: auto;
}
.modal-inner.office-modal .modal-header {
min-height: 34px;
padding: 0.35rem 0.75rem 0.35rem 1rem;
cursor: move;
user-select: none;
background: color-mix(in srgb, var(--color-background) 92%, #000 8%);
border-bottom: 1px solid color-mix(in srgb, var(--color-border) 70%, transparent);
}
.modal-inner.office-modal .modal-close {
font-size: 1.35rem;
line-height: 1;
}
.modal-inner.office-modal .modal-scroll {
display: flex;
flex-direction: column;
flex: 1 1 auto;
min-height: 0;
overflow: hidden;
padding: 0;
}
.modal-inner.office-modal .modal-bd.office-modal-body {
box-sizing: border-box;
display: flex;
flex-direction: column;
flex: 1 1 auto;
width: 100%;
height: 100%;
min-height: 0;
padding: 0;
}
.modal-inner.office-modal .modal-bd.office-modal-body > x-component,
.modal-inner.office-modal .modal-bd.office-modal-body > div[x-data] {
display: flex;
flex: 1 1 auto;
width: 100%;
height: 100%;
min-height: 0;
}
.modal-inner.office-modal .modal-bd.office-modal-body > x-component > .office-panel {
flex: 1 1 auto;
height: 100%;
min-height: 0;
}
.office-toolbar {
display: flex;
align-items: center;
gap: 6px;
min-height: 44px;
padding: 7px 9px;
border-bottom: 1px solid color-mix(in srgb, var(--color-border) 66%, transparent);
background: color-mix(in srgb, var(--color-background) 92%, #000 8%);
overflow-x: auto;
}
.office-tabs {
--office-tab-height: 34px;
--office-tab-close-size: 27px;
display: flex;
align-items: end;
gap: 4px;
min-height: 39px;
min-width: 0;
padding: 5px 9px 0;
border-bottom: 1px solid color-mix(in srgb, var(--color-border) 58%, transparent);
background: color-mix(in srgb, var(--color-background) 92%, #000 8%);
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: thin;
}
.office-tabs::-webkit-scrollbar {
height: 4px;
}
.office-tabs::-webkit-scrollbar-track {
background: transparent;
}
.office-tabs::-webkit-scrollbar-thumb {
background: color-mix(in srgb, var(--color-border) 76%, transparent);
border-radius: 999px;
}
.office-tab-shell {
flex: 0 1 220px;
position: relative;
display: grid;
grid-template-columns: minmax(0, 1fr) var(--office-tab-close-size);
align-items: center;
gap: 3px;
min-width: 132px;
max-width: 260px;
height: var(--office-tab-height);
padding: 0 6px 0 10px;
border: 1px solid transparent;
border-radius: 7px 7px 0 0;
opacity: 0.72;
transition: border-color 0.16s ease, opacity 0.16s ease, background-color 0.16s ease;
}
.office-tab-shell:hover,
.office-tab-shell:focus-within {
opacity: 0.94;
border-color: color-mix(in srgb, var(--color-border) 78%, transparent);
}
.office-tab-shell.is-active {
z-index: 2;
margin-bottom: -1px;
opacity: 1;
border-color: color-mix(in srgb, var(--color-border) 68%, transparent);
background: color-mix(in srgb, var(--color-panel) 72%, transparent);
}
.office-tab,
.office-tab-close {
appearance: none;
border: 0;
background: transparent;
color: inherit;
font: inherit;
cursor: pointer;
}
.office-tab {
display: inline-flex;
align-items: center;
justify-content: flex-start;
gap: 8px;
min-width: 0;
width: 100%;
height: 100%;
padding: 0;
text-align: left;
}
.office-tab-icon {
flex: 0 0 auto;
color: color-mix(in srgb, var(--color-text) 72%, var(--color-primary) 28%);
font-size: 18px;
line-height: 1;
}
.office-tab-title {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 0.84rem;
font-weight: 650;
}
.office-tab-close {
display: inline-flex;
align-items: center;
justify-content: center;
width: var(--office-tab-close-size);
min-width: var(--office-tab-close-size);
height: var(--office-tab-close-size);
min-height: var(--office-tab-close-size);
padding: 0;
border-radius: 6px;
color: color-mix(in srgb, var(--color-text) 52%, var(--color-primary) 48%);
opacity: 0.74;
}
.office-tab-close:hover,
.office-tab-close.confirming {
opacity: 1;
background: color-mix(in srgb, var(--color-background-hover) 70%, transparent);
color: var(--color-text);
}
.office-tab-close:focus-visible,
.office-tab:focus-visible {
outline: 1px solid color-mix(in srgb, var(--color-primary) 70%, transparent);
outline-offset: 1px;
}
.office-tab-close .material-symbols-outlined {
font-size: 15px;
line-height: 1;
}
.office-toolbar-spacer {
flex: 1 1 auto;
min-width: 8px;
}
.office-button,
.office-icon-button,
.office-health-pill,
.office-create-tile,
.office-document-card {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 7px;
border: 1px solid color-mix(in srgb, var(--color-border) 64%, transparent);
border-radius: 7px;
background: color-mix(in srgb, var(--color-panel) 80%, transparent);
color: var(--color-text);
font: inherit;
cursor: pointer;
}
.office-button {
min-height: 32px;
padding: 5px 9px;
font-size: 0.8rem;
white-space: nowrap;
}
.office-icon-button {
width: 32px;
height: 32px;
min-width: 32px;
padding: 0;
}
.office-health-pill {
min-height: 28px;
padding: 4px 8px;
cursor: default;
font-size: 0.75rem;
text-transform: capitalize;
white-space: nowrap;
color: var(--color-text-muted);
background: color-mix(in srgb, var(--color-panel) 64%, transparent);
}
.office-health-pill.is-healthy {
width: 28px;
min-width: 28px;
padding: 0;
gap: 0;
}
.office-health-dot {
width: 7px;
height: 7px;
border-radius: 999px;
background: color-mix(in srgb, var(--color-text-muted) 70%, transparent);
}
.office-health-pill.is-healthy .office-health-dot {
background: #31c48d;
}
.office-health-pill.is-installing .office-health-dot {
background: #f6ad55;
}
.office-health-pill.is-degraded .office-health-dot,
.office-health-pill.is-failed .office-health-dot {
background: #f05252;
}
.office-button:hover:not(:disabled),
.office-icon-button:hover:not(:disabled),
.office-create-tile:hover,
.office-document-card:hover {
background: color-mix(in srgb, var(--color-background-hover) 70%, transparent);
border-color: color-mix(in srgb, var(--color-primary) 28%, var(--color-border));
}
.office-button:disabled,
.office-icon-button:disabled {
cursor: not-allowed;
opacity: 0.42;
}
.office-button .material-symbols-outlined,
.office-icon-button .material-symbols-outlined {
font-size: 18px;
}
.office-status-line {
display: flex;
align-items: center;
gap: 8px;
min-height: 32px;
padding: 5px 10px;
border-bottom: 1px solid color-mix(in srgb, var(--color-border) 44%, transparent);
font-size: 0.82rem;
color: var(--color-text);
}
.office-body {
position: relative;
display: flex;
flex: 1 1 auto;
min-width: 0;
min-height: 0;
overflow: hidden;
}
.office-start {
display: flex;
flex: 1 1 auto;
min-width: 0;
min-height: 0;
flex-direction: column;
gap: 22px;
padding: 18px;
overflow: auto;
}
.office-bootstrap {
display: flex;
flex: 1 1 auto;
min-width: 0;
min-height: 0;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 16px;
padding: clamp(24px, 7cqi, 56px);
overflow: auto;
text-align: center;
}
.office-setup-mark {
display: inline-flex;
align-items: center;
justify-content: center;
width: 58px;
height: 58px;
border: 1px solid color-mix(in srgb, var(--color-primary) 28%, var(--color-border));
border-radius: 7px;
color: color-mix(in srgb, var(--color-primary) 70%, var(--color-text));
background: color-mix(in srgb, var(--color-panel) 78%, transparent);
}
.office-setup-mark.is-busy {
border-color: color-mix(in srgb, var(--color-primary) 42%, var(--color-border));
}
.office-setup-mark.is-alert {
border-color: color-mix(in srgb, #f05252 48%, var(--color-border));
color: #f05252;
}
.office-setup-mark .material-symbols-outlined {
font-size: 30px;
line-height: 1;
}
.office-setup-copy {
display: flex;
align-items: center;
min-width: 0;
flex-direction: column;
gap: 6px;
max-width: 460px;
line-height: 1.35;
}
.office-setup-copy > span {
color: var(--color-text-muted);
font-size: 0.74rem;
font-weight: 700;
letter-spacing: 0;
text-transform: uppercase;
}
.office-setup-copy > strong {
color: var(--color-text);
font-size: clamp(1.05rem, 4cqi, 1.35rem);
font-weight: 760;
}
.office-setup-copy > p {
margin: 0;
color: var(--color-text-muted);
font-size: 0.9rem;
}
.office-setup-progress {
position: relative;
width: min(260px, 72cqi);
height: 5px;
overflow: hidden;
border-radius: 999px;
background: color-mix(in srgb, var(--color-border) 45%, transparent);
}
.office-setup-progress > span {
position: absolute;
inset: 0 auto 0 0;
width: 42%;
border-radius: inherit;
background: color-mix(in srgb, var(--color-primary) 72%, var(--color-text) 28%);
animation: office-setup-progress 1.45s ease-in-out infinite;
}
.office-setup-progress.is-paused > span {
width: 100%;
opacity: 0.42;
animation: none;
}
.office-bootstrap-actions,
.office-template-grid {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 8px;
}
.office-dashboard-section {
display: flex;
flex-direction: column;
gap: 10px;
width: 100%;
max-width: 1180px;
}
.office-dashboard-heading {
color: var(--color-text-muted);
font-size: 0.76rem;
font-weight: 700;
letter-spacing: 0;
text-transform: uppercase;
}
.office-template-grid {
justify-content: flex-start;
}
.office-card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(min(190px, 100%), 1fr));
gap: 10px;
width: 100%;
}
.office-create-tile {
min-width: 132px;
min-height: 88px;
flex-direction: column;
padding: 12px;
font-weight: 650;
}
.office-create-tile .material-symbols-outlined {
font-size: 28px;
}
.office-document-card {
position: relative;
display: grid;
grid-template-rows: auto auto auto;
align-content: start;
justify-content: stretch;
gap: 8px;
min-width: 0;
min-height: 196px;
padding: 10px;
text-align: left;
overflow: hidden;
}
.office-document-card.is-open {
border-color: color-mix(in srgb, var(--color-primary) 36%, var(--color-border));
}
.office-card-badge {
position: absolute;
top: 8px;
right: 8px;
z-index: 2;
max-width: calc(100% - 16px);
overflow: hidden;
padding: 2px 6px;
border: 1px solid color-mix(in srgb, var(--color-primary) 42%, transparent);
border-radius: 999px;
background: color-mix(in srgb, var(--color-background) 82%, transparent);
color: var(--color-text);
font-size: 0.68rem;
font-weight: 700;
line-height: 1.2;
text-overflow: ellipsis;
white-space: nowrap;
}
.office-card-preview {
position: relative;
display: grid;
align-items: stretch;
width: 100%;
aspect-ratio: 16 / 10;
min-height: 112px;
overflow: hidden;
border: 1px solid color-mix(in srgb, var(--color-border) 58%, transparent);
border-radius: 6px;
background: color-mix(in srgb, var(--color-background) 76%, #fff 4%);
}
.office-page-preview,
.office-sheet-preview,
.office-slide-preview,
.office-preview-fallback {
min-width: 0;
min-height: 0;
}
.office-page-preview {
display: flex;
flex-direction: column;
gap: 5px;
padding: 12px 13px;
background:
linear-gradient(to bottom, transparent 0, transparent 21px, color-mix(in srgb, var(--color-border) 30%, transparent) 22px),
color-mix(in srgb, var(--color-panel) 72%, transparent);
background-size: 100% 22px;
color: var(--color-text);
}
.office-page-preview span,
.office-slide-line span,
.office-slide-line strong,
.office-sheet-row span {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.office-page-preview span {
font-size: 0.7rem;
line-height: 1.25;
}
.office-sheet-preview {
min-width: 0;
padding: 8px;
background: color-mix(in srgb, var(--color-panel) 74%, transparent);
}
.office-sheet-row {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
min-height: 20px;
}
.office-sheet-row + .office-sheet-row {
border-top: 1px solid color-mix(in srgb, var(--color-border) 34%, transparent);
}
.office-sheet-row span {
padding: 4px 5px;
border-right: 1px solid color-mix(in srgb, var(--color-border) 34%, transparent);
color: var(--color-text-muted);
font-size: 0.66rem;
line-height: 1.1;
}
.office-sheet-row span:last-child {
border-right: 0;
}
.office-slide-preview {
display: flex;
flex-direction: column;
justify-content: center;
gap: 10px;
padding: 14px;
background:
linear-gradient(135deg, color-mix(in srgb, var(--color-panel) 80%, transparent), color-mix(in srgb, var(--color-background) 84%, var(--color-primary) 10%));
}
.office-slide-line {
display: flex;
min-width: 0;
flex-direction: column;
gap: 3px;
}
.office-slide-line strong {
color: var(--color-text);
font-size: 0.78rem;
font-weight: 760;
line-height: 1.15;
}
.office-slide-line span {
color: var(--color-text-muted);
font-size: 0.68rem;
line-height: 1.15;
}
.office-preview-fallback {
display: flex;
align-items: center;
justify-content: center;
color: color-mix(in srgb, var(--color-primary) 64%, var(--color-text));
background: color-mix(in srgb, var(--color-panel) 76%, transparent);
}
.office-preview-fallback .material-symbols-outlined {
font-size: 36px;
}
.office-card-title {
display: block;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: var(--color-text);
font-size: 0.86rem;
font-weight: 720;
line-height: 1.2;
}
.office-document-card small {
display: block;
min-width: 0;
overflow: hidden;
color: var(--color-text-muted);
font-size: 0.7rem;
line-height: 1.2;
text-overflow: ellipsis;
white-space: nowrap;
}
.office-frame-wrap {
display: flex;
position: absolute;
inset: 0;
flex: 1 1 auto;
width: 100%;
height: 100%;
min-width: 0;
min-height: 0;
background: #fff;
}
.office-frame-wrap iframe {
flex: 1 1 auto;
width: 100%;
height: 100%;
min-width: 0;
min-height: 0;
border: 0;
background: #fff;
}
.office-panel .spinning {
display: inline-block;
animation: office-spin 0.8s linear infinite;
}
@keyframes office-spin {
to { transform: rotate(360deg); }
}
@keyframes office-setup-progress {
0% { transform: translateX(-110%); }
55% { transform: translateX(85%); }
100% { transform: translateX(250%); }
}
@media (prefers-reduced-motion: reduce) {
.office-panel .spinning,
.office-setup-progress > span {
animation: none;
}
}
@media (max-width: 520px) {
.office-button span:last-child {
display: none;
}
.office-health-pill span:last-child {
display: none;
}
.office-tab-shell {
flex-basis: 152px;
min-width: 116px;
}
.office-create-tile {
min-width: 104px;
}
}
@container (max-width: 560px) {
.office-button span:last-child {
display: none;
}
.office-health-pill span:last-child {
display: none;
}
.office-tab-shell {
flex-basis: 152px;
min-width: 116px;
}
.office-create-tile {
min-width: 104px;
}
}
</style>
</body>
</html>