591 lines
14 KiB
Plaintext
591 lines
14 KiB
Plaintext
// All selectors MUST be scoped under .scrying-pool.
|
||
// Use --sp-* tokens only — no Foundry --color-* / --font-* / --border-* tokens allowed.
|
||
// Implemented in story 1.5.
|
||
|
||
// ============================================================
|
||
// CSS Custom Properties (State Tokens)
|
||
// ============================================================
|
||
:root {
|
||
--sp-state-active: hsl(140, 60%, 55%);
|
||
--sp-state-hidden: hsl(0, 0%, 50%);
|
||
--sp-state-self-muted: hsl(200, 60%, 55%);
|
||
--sp-state-cam-lost: hsl(30, 80%, 55%);
|
||
--sp-state-pending: hsl(50, 90%, 55%);
|
||
--sp-urgency-director: hsl(38, 90%, 55%);
|
||
--sp-state-color: hsl(140, 60%, 55%); // default, overridden per state
|
||
}
|
||
|
||
// ============================================================
|
||
// ScryingPoolStrip Layout
|
||
// ============================================================
|
||
// Outer Foundry Application window (has .scrying-pool-strip via defaultOptions.classes).
|
||
// Only visual appearance — sizing is controlled by JS setPosition().
|
||
// WARNING: do NOT add max-width or overflow here; the outer window also carries this class
|
||
// and would clip the expanded inner content.
|
||
.scrying-pool-strip {
|
||
background: var(--sp-bg, hsl(220, 15%, 12%));
|
||
border-radius: 8px;
|
||
|
||
// Hide Foundry's default window header — replaced by a lightweight in-content button.
|
||
header.window-header { display: none; }
|
||
|
||
// Remove window-content padding so the strip fills the frame edge-to-edge.
|
||
.window-content {
|
||
padding: 0;
|
||
overflow: hidden;
|
||
}
|
||
}
|
||
|
||
// Inner template div (has BOTH .scrying-pool AND .scrying-pool-strip).
|
||
// Controls the expand/collapse behaviour; safe to use max-width + overflow here.
|
||
.scrying-pool.scrying-pool-strip {
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
max-width: 83px;
|
||
overflow: hidden;
|
||
transition: max-width 200ms ease-in-out;
|
||
|
||
&.is-expanded {
|
||
max-width: 240px;
|
||
}
|
||
|
||
// Horizontal and mosaic layouts: width is controlled by JS setPosition
|
||
&.sp-layout-horizontal-sm,
|
||
&.sp-layout-horizontal-md,
|
||
&.sp-layout-mosaic-sm,
|
||
&.sp-layout-mosaic-md {
|
||
max-width: none;
|
||
align-items: flex-start;
|
||
}
|
||
}
|
||
|
||
// ── Horizontal layout ────────────────────────────────────────────────────────
|
||
.scrying-pool.scrying-pool-strip.sp-layout-horizontal-sm,
|
||
.scrying-pool.scrying-pool-strip.sp-layout-horizontal-md {
|
||
.sp-strip__participants {
|
||
flex-direction: row;
|
||
flex-wrap: wrap;
|
||
padding: 4px;
|
||
gap: 4px;
|
||
justify-content: flex-start;
|
||
width: auto;
|
||
}
|
||
}
|
||
|
||
// ── Mosaic layout ────────────────────────────────────────────────────────────
|
||
.scrying-pool.scrying-pool-strip.sp-layout-mosaic-sm,
|
||
.scrying-pool.scrying-pool-strip.sp-layout-mosaic-md {
|
||
.sp-strip__participants {
|
||
display: grid;
|
||
padding: 4px;
|
||
gap: 4px;
|
||
// Keep width: 100% (from base) so auto-fill has a definite inline size
|
||
}
|
||
}
|
||
|
||
.scrying-pool.scrying-pool-strip.sp-layout-mosaic-sm .sp-strip__participants {
|
||
grid-template-columns: repeat(auto-fill, var(--sp-widget-width, 83px));
|
||
}
|
||
|
||
.scrying-pool.scrying-pool-strip.sp-layout-mosaic-md .sp-strip__participants {
|
||
grid-template-columns: repeat(auto-fill, var(--sp-widget-width, 150px));
|
||
}
|
||
|
||
// ── Medium tile size for horizontal / mosaic ─────────────────────────────────
|
||
.scrying-pool.scrying-pool-strip.sp-layout-horizontal-md,
|
||
.scrying-pool.scrying-pool-strip.sp-layout-mosaic-md {
|
||
.sp-participant-avatar {
|
||
width: 150px;
|
||
height: 150px;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
padding: 8px 4px 4px;
|
||
gap: 4px;
|
||
|
||
.sp-avatar__img {
|
||
width: 91px;
|
||
height: 91px;
|
||
border-radius: 6px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.sp-avatar__name {
|
||
display: block;
|
||
font-size: 0.65rem;
|
||
text-align: center;
|
||
width: 100%;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.sp-avatar__corner-badge {
|
||
bottom: 4px;
|
||
right: 4px;
|
||
}
|
||
|
||
// Remove the is-expanded overlay styles that interfere with tile layout
|
||
&::after { display: none; }
|
||
}
|
||
}
|
||
|
||
// ── Drag grip (top bar, replaces window header drag affordance) ────────────────
|
||
.sp-strip__grip {
|
||
width: 100%;
|
||
height: 16px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: grab;
|
||
color: var(--sp-text, hsl(0, 0%, 80%));
|
||
opacity: 0.35;
|
||
font-size: 10px;
|
||
flex-shrink: 0;
|
||
transition: opacity 0.15s;
|
||
user-select: none;
|
||
|
||
&:hover { opacity: 0.75; }
|
||
&:active { cursor: grabbing; opacity: 1; }
|
||
}
|
||
|
||
// ── Lightweight close button (replaces window header) ─────────────────────────
|
||
.sp-strip__close-btn {
|
||
position: absolute;
|
||
top: 4px;
|
||
right: 4px;
|
||
z-index: 10;
|
||
width: 18px;
|
||
height: 18px;
|
||
padding: 0;
|
||
line-height: 18px;
|
||
font-size: 13px;
|
||
font-weight: 400;
|
||
background: transparent;
|
||
color: var(--sp-text, hsl(0, 0%, 80%));
|
||
border: none;
|
||
border-radius: 3px;
|
||
cursor: pointer;
|
||
opacity: 0.45;
|
||
transition: opacity 0.15s, background 0.15s;
|
||
|
||
&:hover {
|
||
opacity: 1;
|
||
background: rgba(255, 255, 255, 0.1);
|
||
}
|
||
&:active { opacity: 0.75; }
|
||
}
|
||
|
||
// ── Toolbar row: toggle + Director's Board on the same line ───────────────────
|
||
.sp-strip__toolbar {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
width: 100%;
|
||
flex-shrink: 0;
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||
}
|
||
|
||
.sp-strip__toggle {
|
||
width: 83px;
|
||
min-width: 83px;
|
||
height: 28px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: none;
|
||
border: none;
|
||
cursor: pointer;
|
||
color: var(--sp-text, hsl(0, 0%, 80%));
|
||
font-size: 11px;
|
||
flex-shrink: 0;
|
||
opacity: 0.6;
|
||
|
||
&:hover { opacity: 1; }
|
||
}
|
||
|
||
// ── Director's Board CTA button (shown when sidebar injection not available) ──
|
||
.sp-strip__directors-board-cta {
|
||
flex: 1;
|
||
min-width: 0;
|
||
height: 28px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 0 8px;
|
||
background: none;
|
||
border: none;
|
||
border-left: 1px solid rgba(255, 255, 255, 0.06);
|
||
cursor: pointer;
|
||
color: var(--sp-text-secondary, #7a8390);
|
||
font-size: 11px;
|
||
text-align: left;
|
||
transition: background 0.15s, color 0.15s;
|
||
flex-shrink: 0;
|
||
|
||
i { font-size: 12px; flex-shrink: 0; }
|
||
span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||
|
||
&:hover {
|
||
background: rgba(255, 255, 255, 0.06);
|
||
color: var(--sp-text-primary, #dde2e8);
|
||
}
|
||
}
|
||
|
||
.sp-strip__participants {
|
||
list-style: none;
|
||
margin: 0;
|
||
padding: 0;
|
||
width: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
}
|
||
|
||
.sp-strip__participant-item {
|
||
margin: 0; // override browser/Foundry default <li> margins
|
||
}
|
||
|
||
.sp-strip__first-tip {
|
||
font-size: 0.75rem;
|
||
color: var(--sp-text-muted, hsl(0, 0%, 60%));
|
||
padding: 4px 8px;
|
||
margin: 0;
|
||
}
|
||
|
||
// ============================================================
|
||
// ParticipantAvatar (44×44px container, 32px rounded image)
|
||
// ============================================================
|
||
.sp-participant-avatar {
|
||
position: relative;
|
||
width: 83px;
|
||
height: 83px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: flex-start;
|
||
background: none;
|
||
border: none;
|
||
cursor: pointer;
|
||
padding: 6px;
|
||
border-radius: 4px;
|
||
gap: 8px;
|
||
overflow: hidden;
|
||
|
||
&:focus-visible {
|
||
outline: 2px solid var(--sp-focus-ring, hsl(200, 80%, 60%));
|
||
outline-offset: 2px;
|
||
}
|
||
|
||
.is-expanded & {
|
||
width: 100%;
|
||
height: 135px; // 16:9 at 240 px strip width
|
||
padding: 0;
|
||
align-items: flex-end;
|
||
background: hsl(220, 15%, 14%);
|
||
|
||
// Gradient scrim so name text is legible over any video
|
||
&::after {
|
||
content: '';
|
||
position: absolute;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
height: 48px;
|
||
background: linear-gradient(transparent, hsla(0, 0%, 0%, 0.72));
|
||
z-index: 2;
|
||
pointer-events: none;
|
||
border-radius: 0 0 4px 4px;
|
||
}
|
||
|
||
.sp-avatar__img {
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
z-index: 1;
|
||
width: 48px;
|
||
height: 48px;
|
||
border-radius: 6px; // card view — rectangular
|
||
}
|
||
|
||
.sp-avatar__name {
|
||
position: absolute;
|
||
bottom: 20px;
|
||
left: 8px;
|
||
right: 8px;
|
||
z-index: 3;
|
||
color: hsl(0, 0%, 95%);
|
||
}
|
||
|
||
.sp-avatar__state-label {
|
||
position: absolute;
|
||
bottom: 6px;
|
||
left: 8px;
|
||
z-index: 3;
|
||
color: hsla(0, 0%, 85%, 0.85);
|
||
}
|
||
|
||
.sp-avatar__corner-badge {
|
||
bottom: 6px;
|
||
right: 6px;
|
||
z-index: 4;
|
||
width: 10px;
|
||
height: 10px;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Video container for WebRTC stream (full AV replacement mode)
|
||
.sp-participant-video {
|
||
position: absolute;
|
||
inset: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
overflow: hidden;
|
||
z-index: 1;
|
||
}
|
||
|
||
.sp-avatar__img {
|
||
width: 60px;
|
||
height: 60px;
|
||
border-radius: 6px;
|
||
object-fit: cover;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
// Video element styling
|
||
.sp-participant-video__element {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
border-radius: 6px;
|
||
background: hsl(220, 15%, 18%);
|
||
|
||
.is-expanded & {
|
||
border-radius: 4px;
|
||
}
|
||
}
|
||
|
||
// Hide avatar image when video stream is active (has video element)
|
||
.sp-participant-video:not(:empty) ~ .sp-avatar__img {
|
||
display: none;
|
||
}
|
||
|
||
.sp-avatar__corner-badge {
|
||
position: absolute;
|
||
bottom: 2px;
|
||
right: 2px;
|
||
width: 12px;
|
||
height: 12px;
|
||
border-radius: 50%;
|
||
background: var(--sp-state-color);
|
||
font-size: 8px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.sp-avatar__name {
|
||
font-size: 0.85rem;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
color: var(--sp-text, hsl(0, 0%, 85%));
|
||
}
|
||
|
||
.sp-avatar__state-label {
|
||
font-size: 0.7rem;
|
||
color: var(--sp-text-muted, hsl(0, 0%, 60%));
|
||
}
|
||
|
||
// ============================================================
|
||
// StateRing variants (applied as class on .sp-participant-avatar)
|
||
// ============================================================
|
||
.sp-participant-avatar.sp-state-active,
|
||
.sp-participant-avatar.sp-state-self-muted {
|
||
--sp-state-color: var(--sp-state-active);
|
||
|
||
.sp-avatar__img {
|
||
box-shadow: 0 0 0 2px var(--sp-state-color);
|
||
}
|
||
}
|
||
|
||
.sp-participant-avatar.sp-state-hidden,
|
||
.sp-participant-avatar.sp-state-cam-lost {
|
||
--sp-state-color: var(--sp-state-hidden);
|
||
|
||
.sp-avatar__img {
|
||
outline: 2px dashed var(--sp-state-color);
|
||
outline-offset: 2px;
|
||
}
|
||
}
|
||
|
||
.sp-participant-avatar.sp-state-pending {
|
||
--sp-state-color: var(--sp-state-pending);
|
||
|
||
.sp-avatar__img {
|
||
box-shadow: 0 0 0 2px var(--sp-state-color);
|
||
}
|
||
}
|
||
|
||
// ============================================================
|
||
// StateRing animations — gated under no-preference (AC-16)
|
||
// ============================================================
|
||
@media (prefers-reduced-motion: no-preference) {
|
||
.sp-participant-avatar.sp-state-pending .sp-avatar__img {
|
||
animation: sp-pulse 2s ease-in-out infinite;
|
||
}
|
||
|
||
@keyframes sp-pulse {
|
||
0%, 100% { opacity: 1; }
|
||
50% { opacity: 0.4; }
|
||
}
|
||
|
||
// Revert flash (200ms amber, then restore)
|
||
.sp-participant-avatar.sp-state-revert .sp-avatar__img {
|
||
animation: sp-revert-flash 200ms ease-out forwards;
|
||
}
|
||
|
||
@keyframes sp-revert-flash {
|
||
0% { box-shadow: 0 0 0 3px var(--sp-urgency-director); }
|
||
100% { box-shadow: 0 0 0 2px var(--sp-state-color); }
|
||
}
|
||
}
|
||
|
||
// ============================================================
|
||
// EmptyStatePanel (AC-11)
|
||
// ============================================================
|
||
.sp-strip__empty-state {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
padding: 16px 8px;
|
||
gap: 8px;
|
||
color: var(--sp-text-muted, hsl(0, 0%, 60%));
|
||
}
|
||
|
||
.sp-empty__icon {
|
||
font-size: 1.5rem;
|
||
display: block;
|
||
}
|
||
|
||
.sp-empty__text {
|
||
font-size: 0.75rem;
|
||
text-align: center;
|
||
}
|
||
|
||
@media (prefers-reduced-motion: no-preference) {
|
||
.sp-empty__icon {
|
||
animation: sp-breathe 3s ease-in-out infinite;
|
||
}
|
||
|
||
@keyframes sp-breathe {
|
||
0%, 100% { opacity: 0.6; transform: scale(1); }
|
||
50% { opacity: 1.0; transform: scale(1.05); }
|
||
}
|
||
}
|
||
|
||
// ============================================================
|
||
// AV Tile overlays (applied to .camera-view[data-user-id="..."])
|
||
// ============================================================
|
||
.sp-lock-overlay {
|
||
position: absolute;
|
||
inset: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: hsla(0, 0%, 0%, 0.45);
|
||
pointer-events: none;
|
||
z-index: 10;
|
||
|
||
&::before {
|
||
content: '\f023'; // fa-lock
|
||
font-family: 'Font Awesome 6 Free';
|
||
font-weight: 900;
|
||
font-size: 1.2rem;
|
||
color: hsl(0, 0%, 85%);
|
||
}
|
||
}
|
||
|
||
.camera-view.sp-state-hidden {
|
||
opacity: 0.55;
|
||
position: relative;
|
||
}
|
||
|
||
.sp-portrait-fallback {
|
||
position: absolute;
|
||
inset: 0;
|
||
background: var(--sp-bg, hsl(220, 15%, 18%)) center/cover no-repeat;
|
||
pointer-events: none;
|
||
}
|
||
|
||
// ============================================================
|
||
// Context menu
|
||
// ============================================================
|
||
.sp-context-menu {
|
||
background: var(--sp-bg, hsl(220, 15%, 15%));
|
||
border: 1px solid hsl(0, 0%, 30%);
|
||
border-radius: 4px;
|
||
padding: 4px 0;
|
||
min-width: 160px;
|
||
z-index: 1000;
|
||
box-shadow: 0 4px 12px hsla(0, 0%, 0%, 0.4);
|
||
|
||
.sp-context-menu__item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
width: 100%;
|
||
padding: 6px 12px;
|
||
background: none;
|
||
border: none;
|
||
cursor: pointer;
|
||
color: var(--sp-text, hsl(0, 0%, 85%));
|
||
font-size: 0.875rem;
|
||
text-align: left;
|
||
|
||
&:hover,
|
||
&:focus-visible {
|
||
background: hsla(200, 60%, 55%, 0.15);
|
||
}
|
||
}
|
||
}
|
||
|
||
// ============================================================
|
||
// ActionPopover (<dialog>)
|
||
// ============================================================
|
||
.sp-action-popover {
|
||
background: var(--sp-bg, hsl(220, 15%, 15%));
|
||
border: 1px solid hsl(0, 0%, 30%);
|
||
border-radius: 6px;
|
||
padding: 12px;
|
||
min-width: 160px;
|
||
box-shadow: 0 4px 16px hsla(0, 0%, 0%, 0.5);
|
||
color: var(--sp-text, hsl(0, 0%, 85%));
|
||
|
||
.sp-action-popover__cta {
|
||
display: block;
|
||
width: 100%;
|
||
padding: 8px 16px;
|
||
background: hsl(200, 60%, 40%);
|
||
border: none;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
color: hsl(0, 0%, 95%);
|
||
font-size: 0.875rem;
|
||
|
||
&:hover:not(:disabled) {
|
||
background: hsl(200, 60%, 50%);
|
||
}
|
||
|
||
&:disabled {
|
||
opacity: 0.45;
|
||
cursor: not-allowed;
|
||
}
|
||
}
|
||
}
|