rspace-online/website/public/shell.css

786 lines
18 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* ── rStack Shell Layout ── */
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
min-height: 100vh;
background: var(--rs-bg-page);
color: var(--rs-text-primary);
padding-bottom: env(safe-area-inset-bottom);
}
/* ── PWA install / update banners ── */
.rspace-banner {
position: fixed;
top: 0; left: 0; right: 0;
z-index: 10000; /* above header (9999) */
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
padding: 8px 16px;
font-size: 0.8125rem;
animation: rspace-banner-in 0.3s ease-out;
}
@keyframes rspace-banner-in {
from { transform: translateY(-100%); }
to { transform: translateY(0); }
}
.rspace-banner--install {
background: linear-gradient(135deg, #14b8a6, #0ea5e9);
color: #fff;
}
.rspace-banner--update {
background: linear-gradient(135deg, #f59e0b, #ef4444);
color: #fff;
}
.rspace-banner__text {
flex: 1;
text-align: center;
font-weight: 500;
}
.rspace-banner__action {
padding: 4px 14px;
border-radius: 6px;
border: 1px solid rgba(255,255,255,0.4);
background: rgba(255,255,255,0.2);
color: #fff;
font-size: 0.8125rem;
font-weight: 600;
cursor: pointer;
white-space: nowrap;
transition: background 0.15s;
}
.rspace-banner__action:hover {
background: rgba(255,255,255,0.35);
}
.rspace-banner__close {
background: none;
border: none;
color: rgba(255,255,255,0.7);
font-size: 1.25rem;
cursor: pointer;
padding: 0 4px;
line-height: 1;
}
.rspace-banner__close:hover {
color: #fff;
}
/* Push header + content down when a banner is visible */
body.rspace-banner-visible .rstack-header {
top: 36px;
}
body.rspace-banner-visible .rstack-tab-row {
top: 89px; /* 36px banner + 56px header - 3px overlap */
}
body.rspace-banner-visible #app {
padding-top: 128px; /* 36px banner + 92px normal */
}
@media (max-width: 640px) {
.rspace-banner {
font-size: 0.75rem;
gap: 8px;
padding: 6px 12px;
}
/* On mobile, header is sticky not fixed, so no offsets needed —
the banner sits in flow above the header */
body.rspace-banner-visible .rstack-header { top: 0; }
body.rspace-banner-visible .rstack-tab-row { top: 0; }
body.rspace-banner-visible #app { padding-top: 0; }
.rspace-banner { position: sticky; }
}
/* ── Header bar ── */
.rstack-header {
position: fixed;
top: 0; left: 0; right: 0;
height: 56px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 max(16px, env(safe-area-inset-right)) 0 max(16px, env(safe-area-inset-left));
padding-top: env(safe-area-inset-top);
z-index: 9999;
border-bottom: 1px solid var(--rs-glass-border);
color: var(--rs-text-primary);
}
/* backdrop-filter on pseudo-element so the header doesn't create a
containing block that traps position:fixed children (e.g. app-switcher sidebar) */
.rstack-header::before {
content: '';
position: absolute;
inset: 0;
z-index: -1;
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
background: var(--rs-glass-bg);
}
.rstack-header__left {
display: flex;
align-items: center;
gap: 4px;
position: relative;
z-index: 2;
}
.rstack-header__center {
flex: 1;
display: flex;
justify-content: center;
padding: 0 16px;
min-width: 0;
overflow: visible;
}
.rstack-header__right {
display: flex;
align-items: center;
gap: 12px;
position: relative;
z-index: 2;
}
.rstack-header__logo {
width: 28px;
height: 28px;
border-radius: 6px;
flex-shrink: 0;
}
.rstack-header__brand {
display: flex;
align-items: center;
gap: 10px;
text-decoration: none;
font-size: 1.25rem;
font-weight: 700;
color: inherit;
}
.rstack-header__brand-gradient {
background: var(--rs-gradient-brand);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
/* ── Dropdown wrapper (positions panel relative to button) ── */
.rstack-header__dropdown-wrap {
position: relative;
display: flex;
align-items: center;
}
/* ── Header icon buttons (settings gear, history clock) ── */
.rstack-header__settings-btn,
.rstack-header__history-btn {
display: flex;
align-items: center;
justify-content: center;
padding: 6px;
border: none;
border-radius: 6px;
background: none;
color: var(--rs-text-secondary);
cursor: pointer;
transition: color 0.15s, background 0.15s;
pointer-events: auto;
position: relative;
}
.rstack-header__settings-btn:hover,
.rstack-header__history-btn:hover {
color: var(--rs-text-primary);
background: var(--rs-btn-secondary-bg, rgba(255,255,255,0.08));
}
.rstack-header__history-btn.active {
color: #fff;
background: var(--rs-accent, #14b8a6);
}
/* ── Tab row (below header) ── */
.rstack-tab-row {
position: fixed;
top: 53px; /* overlap header bottom border to close gap */
left: 0;
right: 0;
z-index: 9998;
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-bottom: 1px solid var(--rs-border-subtle);
background: var(--rs-glass-bg);
display: flex;
align-items: center;
padding: 0; /* no horizontal padding — span full width */
}
/* ── Main content area ── */
#app {
padding-top: 89px; /* Below fixed header (56px) + tab row (36px) - 3px overlap */
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
/* .rapp-content fills below subnav/tabbar — scrollable for landing pages, app views fill exactly */
.rapp-content {
flex: 1;
min-height: 0;
overflow: auto;
position: relative;
display: flex;
flex-direction: column;
}
/* When a single child (app view component), fill the viewport */
.rapp-content > :only-child {
flex: 1;
min-height: 0;
}
/* Tab panes: active pane fills, hidden ones don't participate in flex */
.rapp-content > .rspace-tab-pane--active {
flex: 1;
min-height: 0;
}
/* ── Standalone mode (no app/space switcher) ── */
.rstack-header--standalone .rstack-header__left {
gap: 0;
}
/* ── Shared in-app navigation bar (used by module components) ── */
.rapp-nav {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 16px;
padding: 0;
min-height: 36px;
}
.rapp-nav__back {
padding: 4px 10px;
border-radius: 6px;
border: 1px solid var(--rs-border);
background: transparent;
color: var(--rs-text-secondary);
cursor: pointer;
font-size: 13px;
text-decoration: none;
transition: color 0.15s, border-color 0.15s;
}
.rapp-nav__back:hover {
color: var(--rs-text-primary);
border-color: var(--rs-border-strong);
}
.rapp-nav__title {
font-size: 15px;
font-weight: 600;
color: var(--rs-text-primary);
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.rapp-nav__actions {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
}
.rapp-nav__btn {
padding: 6px 14px;
border-radius: 6px;
border: none;
background: var(--rs-primary);
color: #fff;
font-weight: 600;
cursor: pointer;
font-size: 13px;
transition: background 0.15s;
}
.rapp-nav__btn:hover {
background: var(--rs-primary-hover);
}
.rapp-nav__btn--secondary {
background: transparent;
border: 1px solid var(--rs-border);
color: var(--rs-text-secondary);
}
.rapp-nav__btn--secondary:hover {
border-color: var(--rs-border-strong);
color: var(--rs-text-primary);
}
.rapp-nav__badge {
font-size: 11px;
font-weight: 600;
color: var(--rs-warning);
background: rgba(251,191,36,0.15);
border: 1px solid rgba(251,191,36,0.3);
border-radius: 4px;
padding: 2px 8px;
}
/* ── Iframe embed for external apps ── */
.rspace-iframe-wrap {
position: fixed;
top: 92px; /* header 56px + tab row 36px */
left: 0;
right: 0;
bottom: 0;
z-index: 1;
}
.rspace-iframe {
width: 100%;
height: 100%;
border: none;
background: var(--rs-bg-page);
}
.rspace-iframe-loading {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 12px;
background: var(--rs-bg-page);
color: var(--rs-text-secondary);
font-size: 0.9rem;
z-index: 2;
}
.rspace-iframe-spinner {
width: 32px;
height: 32px;
border: 3px solid var(--rs-spinner-track);
border-top-color: var(--rs-spinner-head);
border-radius: 50%;
animation: rspace-spin 0.8s linear infinite;
}
@keyframes rspace-spin {
to { transform: rotate(360deg); }
}
.rspace-iframe-newtab {
position: absolute;
bottom: 12px;
right: 16px;
font-size: 0.72rem;
color: var(--rs-text-muted);
text-decoration: none;
padding: 4px 10px;
border-radius: 4px;
background: var(--rs-glass-bg);
border: 1px solid var(--rs-glass-border);
z-index: 3;
transition: color 0.15s, border-color 0.15s;
}
.rspace-iframe-newtab:hover {
color: var(--rs-text-primary);
border-color: var(--rs-border-strong);
}
/* "Open Full App" button used in module demo views */
.rapp-nav__btn--app-toggle {
background: var(--rs-gradient-primary);
color: #fff;
font-size: 0.78rem;
padding: 5px 14px;
border-radius: 6px;
text-decoration: none;
font-weight: 600;
border: none;
cursor: pointer;
transition: opacity 0.15s;
}
.rapp-nav__btn--app-toggle:hover {
opacity: 0.88;
}
/* ── Sidebar push offsets ── */
rstack-user-dashboard,
.rspace-iframe-wrap,
#toolbar {
transition: margin-left 0.25s ease, left 0.25s ease;
}
body.rstack-sidebar-open .rstack-tab-row {
left: 280px;
}
body.rstack-sidebar-open #app {
margin-left: 280px;
}
body.rstack-sidebar-open rstack-user-dashboard {
left: 280px;
}
body.rstack-sidebar-open .rspace-iframe-wrap {
left: 280px;
}
body.rstack-sidebar-open #toolbar {
left: 292px; /* 280px sidebar + 12px original margin */
}
/* ── Mobile adjustments ── */
@media (max-width: 640px) {
.rapp-info-btn { display: none; }
/* Switch header + tab row from fixed to sticky on mobile.
This avoids needing a magic padding-top on #app and lets
the header wrap naturally to two rows. */
.rstack-header {
position: sticky;
z-index: 9999;
padding: calc(6px + env(safe-area-inset-top)) max(12px, env(safe-area-inset-right)) 6px max(12px, env(safe-area-inset-left));
height: auto;
flex-wrap: wrap;
gap: 0;
}
.rstack-header__left {
flex: 1 1 0;
min-width: 0;
gap: 2px;
}
.rstack-header__center {
order: 3;
flex-basis: 100%;
padding: 4px 0 2px;
display: flex;
}
.rstack-header__right {
flex: 0 0 auto;
gap: 6px;
margin-left: auto;
}
.rstack-header__brand {
font-size: 1rem;
gap: 6px;
}
.rstack-header__logo {
width: 24px;
height: 24px;
}
.rstack-tab-row {
position: sticky;
top: 0;
}
/* Mobile: body becomes flex column so #app fills below sticky header/tabs */
html, body {
height: 100dvh;
overflow: hidden;
overscroll-behavior: none;
}
body {
display: flex;
flex-direction: column;
}
.rstack-header {
flex-shrink: 0;
}
.rstack-tab-row {
flex-shrink: 0;
}
#app {
padding-top: 0;
height: auto;
flex: 1;
min-height: 0;
}
.rspace-iframe-wrap {
position: relative;
top: 0;
width: 100%;
flex: 1;
min-height: 400px;
}
.rapp-nav {
flex-wrap: wrap;
gap: 6px;
}
/* Hide settings/history on mobile — accessible via identity dropdown */
.rstack-header__settings-btn,
.rstack-header__history-btn { display: none; }
/* Sidebar overlays on mobile — no push offsets */
body.rstack-sidebar-open .rstack-tab-row { left: 0; }
body.rstack-sidebar-open #app { margin-left: 0; }
body.rstack-sidebar-open rstack-user-dashboard { left: 0; }
body.rstack-sidebar-open .rspace-iframe-wrap { left: 0; }
body.rstack-sidebar-open #toolbar { left: 12px; }
.rapp-nav__btn, .rapp-nav__back { min-height: 36px; }
}
@media (max-width: 480px) {
#app {
padding-left: 8px;
padding-right: 8px;
}
}
/* ── Tab pane caching (show/hide) ── */
.rspace-tab-pane {
display: none;
}
/* Active panes must be flex columns so subnav/tabbar/rapp-content
flex properties work after TabCache.init() wraps them into a pane */
.rspace-tab-pane--active {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
}
.rspace-tab-loading {
display: flex;
align-items: center;
justify-content: center;
flex: 1;
min-height: 0;
}
.rspace-tab-spinner {
width: 32px;
height: 32px;
border: 3px solid var(--rs-spinner-track);
border-top-color: var(--rs-spinner-head);
border-radius: 50%;
animation: rspace-spin 0.8s linear infinite;
}
/* ── Touch-device utilities ── */
@media (pointer: coarse) {
.hover-reveal { opacity: 0.5 !important; }
.hover-reveal:active { opacity: 1 !important; }
}
/* ── Header minimize toggle (lives in .rapp-subnav) ── */
.rapp-minimize-btn {
display: flex; align-items: center; justify-content: center;
width: 24px; height: 24px; padding: 0;
margin-left: auto; /* push to right edge of subnav */
flex-shrink: 0;
background: none; border: 1px solid transparent; border-radius: 6px;
color: var(--rs-text-muted); cursor: pointer;
transition: color 0.15s, background 0.15s, border-color 0.15s;
}
.rapp-minimize-btn:hover {
color: var(--rs-text-primary); background: var(--rs-bg-hover); border-color: var(--rs-border);
}
.rapp-minimize-btn svg {
transition: transform 0.25s ease;
}
body.rspace-headers-minimized .rapp-minimize-btn svg {
transform: rotate(180deg);
}
/* Minimized state: hide all 3 header bars, floating restore button */
body.rspace-headers-minimized .rstack-header {
transform: translateY(-100%);
pointer-events: none;
}
body.rspace-headers-minimized .rstack-tab-row {
transform: translateY(-100%);
pointer-events: none;
}
body.rspace-headers-minimized .rapp-subnav {
transform: translateY(-100%);
pointer-events: none;
opacity: 0;
}
body.rspace-headers-minimized #app {
padding-top: 0;
}
body.rspace-headers-minimized #app.canvas-layout {
padding-top: 0;
}
/* Floating restore button when all headers minimized */
body.rspace-headers-minimized .rapp-minimize-btn {
position: fixed;
top: 6px;
right: 12px;
z-index: 10000;
pointer-events: auto;
width: 28px;
height: 28px;
background: var(--rs-glass-bg);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid var(--rs-border);
border-radius: 8px;
opacity: 0.6;
transition: opacity 0.15s;
}
body.rspace-headers-minimized .rapp-minimize-btn:hover {
opacity: 1;
}
/* Smooth transitions for header minimize/restore */
.rstack-header {
transition: transform 0.25s ease, top 0.3s ease-out;
}
.rstack-tab-row {
transition: margin-left 0.25s ease, left 0.25s ease, transform 0.25s ease, top 0.3s ease-out;
}
.rapp-subnav {
transition: transform 0.25s ease, opacity 0.25s ease;
}
#app {
transition: margin-left 0.25s ease, left 0.25s ease, padding-top 0.3s ease-out;
}
/* Mobile: minimized state adjustments (sticky, not fixed) */
@media (max-width: 640px) {
body.rspace-headers-minimized .rstack-header {
max-height: 0;
overflow: hidden;
padding: 0;
border: 0;
}
body.rspace-headers-minimized .rstack-tab-row {
max-height: 0;
overflow: hidden;
transform: none;
}
body.rspace-headers-minimized .rapp-subnav {
max-height: 0;
overflow: hidden;
transform: none;
opacity: 0;
}
}
/* ── Flex gap fallback for Safari <14.1 ── */
@supports not (gap: 1px) {
.rstack-header__left > * + * { margin-left: 4px; }
.rstack-header__right > * + * { margin-left: 12px; }
.rstack-header__brand { gap: 0; }
.rstack-header__brand > * + * { margin-left: 10px; }
.rspace-banner > * + * { margin-left: 12px; }
.rapp-nav > * + * { margin-left: 8px; }
.rapp-nav__actions > * + * { margin-left: 8px; }
}
/* ── Mobile / touch baseline (applies to all modules) ──
iOS Safari auto-zooms on focus for <input>/<select>/<textarea> if
computed font-size < 16px. Force ≥16px on pointer:coarse devices.
Also enforce a 44×44 minimum hit area per Apple HIG on touch screens. */
@media (pointer: coarse) {
input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]),
select,
textarea {
font-size: max(16px, 1rem);
}
button,
[role="button"],
a.rapp-nav__btn,
a.rapp-nav__back,
.btn,
.rapp-nav__btn,
.rapp-nav__back,
summary {
min-height: 44px;
min-width: 44px;
}
/* Inline/icon-only buttons inside dense toolbars may opt out with
class="btn--dense" — still targetable but won't bloat toolbars. */
.btn--dense,
[role="button"].btn--dense {
min-height: 36px;
min-width: 36px;
}
/* Kill 300ms tap-delay and blue highlight across the board */
a, button, [role="button"], input[type="submit"], input[type="button"],
.rapp-nav__btn, .rapp-nav__back, summary, label {
touch-action: manipulation;
-webkit-tap-highlight-color: transparent;
}
}
/* ── Utility classes for modules ── */
/* Horizontal scroller with visible thumb on touch (for rsheets, data tables) */
.rapp-hscroll {
overflow-x: auto;
overflow-y: hidden;
-webkit-overflow-scrolling: touch;
scrollbar-width: thin;
}
.rapp-hscroll::-webkit-scrollbar {
height: 8px;
}
.rapp-hscroll::-webkit-scrollbar-thumb {
background: var(--rs-border-strong, rgba(128,128,128,0.5));
border-radius: 4px;
}
/* Bottom-sheet drawer helper (for rmaps, rnotes panel collapse) */
.rapp-drawer {
position: fixed;
left: 0; right: 0;
background: var(--rs-bg-page);
border-top: 1px solid var(--rs-border);
box-shadow: 0 -4px 16px rgba(0,0,0,0.18);
transition: transform 0.25s ease;
z-index: 1000;
padding-bottom: env(safe-area-inset-bottom);
}
.rapp-drawer--bottom {
bottom: 0;
max-height: 70dvh;
overflow-y: auto;
border-radius: 16px 16px 0 0;
}
.rapp-drawer--collapsed {
transform: translateY(calc(100% - 40px));
}
.rapp-drawer__handle {
display: flex;
justify-content: center;
padding: 8px 0 4px;
cursor: grab;
touch-action: none;
}
.rapp-drawer__handle::before {
content: "";
width: 40px;
height: 4px;
border-radius: 2px;
background: var(--rs-border-strong, rgba(128,128,128,0.6));
}