// 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 // ============================================================ .scrying-pool-strip { position: relative; display: flex; flex-direction: column; align-items: center; max-width: 44px; overflow: hidden; transition: max-width 200ms ease-in-out; background: var(--sp-bg, hsl(220, 15%, 12%)); border-radius: 8px; &.is-expanded { max-width: 240px; } } .sp-strip__toggle { width: 44px; height: 44px; display: flex; align-items: center; justify-content: center; background: none; border: none; cursor: pointer; color: var(--sp-text, hsl(0, 0%, 80%)); flex-shrink: 0; } .sp-strip__participants { list-style: none; margin: 0; padding: 0; width: 100%; display: flex; flex-direction: column; gap: 4px; } .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: 44px; height: 44px; 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%; padding: 6px 8px; } } // 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: 32px; height: 32px; border-radius: 50%; object-fit: cover; flex-shrink: 0; } // Video element styling .sp-participant-video__element { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; 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 () // ============================================================ .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; } } }