Story 3.2 done

This commit is contained in:
2026-05-23 18:23:48 +02:00
parent d175f92806
commit a1e8886fce
66 changed files with 18258 additions and 1650 deletions
+167
View File
@@ -0,0 +1,167 @@
// ConfirmationBar component styles
// Story 3.2: Scene Auto-Apply & ConfirmationBar
//
// Import rule: All selectors scoped under .scrying-pool namespace
// Use --sp-* semantic tokens only, never Foundry tokens directly
.scrying-pool {
&__confirmation-bar {
// Base positioning and layout
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 12px 16px;
border-radius: 4px 4px 0 0;
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.3);
z-index: 100; // Above strip but below modals
// Typography
font-size: 14px;
line-height: 1.4;
// Display and box model
display: flex;
align-items: center;
gap: 12px;
// Transition: opacity only (never height or max-height per AC-5)
transition: opacity 200ms ease-out;
opacity: 1;
// Prevent text selection
user-select: none;
// Variants
&--default {
background-color: var(--sp-surface);
border-top: 1px solid var(--sp-border);
color: var(--sp-text-primary);
.sp-confirmation-bar {
&__message {
color: var(--sp-text-primary);
}
}
}
&--amber {
background-color: var(--sp-surface);
border-top: 1px solid var(--sp-border-warning);
.sp-confirmation-bar {
&__message {
color: var(--sp-text-warning);
}
}
}
}
// Message area
.sp-confirmation-bar {
&__message {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
// Undo button
.sp-confirmation-bar {
&__undo-btn {
// Button reset
background: transparent;
border: none;
padding: 6px 12px;
margin: 0;
cursor: pointer;
// Typography
font-family: inherit;
font-size: inherit;
font-weight: 600;
text-decoration: none;
// Colors
color: var(--sp-accent);
// Border and radius
border-radius: 4px;
// Transition
transition: background-color 200ms ease, color 200ms ease;
// Hover state
&:hover {
background-color: rgba(0, 0, 0, 0.1);
color: var(--sp-accent-emphasis, var(--sp-accent));
}
// Active state
&:active {
background-color: rgba(0, 0, 0, 0.2);
}
// Focus state
&:focus {
outline: 2px solid var(--sp-focus);
outline-offset: 2px;
}
// Disabled state (shouldn't happen but just in case)
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
}
}
}
// Animation: slide up for entry
@keyframes sp-confirmation-bar-slide-up {
from {
opacity: 0;
transform: translateY(100%);
}
to {
opacity: 1;
transform: translateY(0);
}
}
// Animation: slide down for exit
@keyframes sp-confirmation-bar-slide-down {
from {
opacity: 1;
transform: translateY(0);
}
to {
opacity: 0;
transform: translateY(100%);
}
}
// Apply animations (gated under prefers-reduced-motion)
@media (prefers-reduced-motion: no-preference) {
.scrying-pool {
&__confirmation-bar {
animation: sp-confirmation-bar-slide-up 200ms ease-out;
}
// Hide animation class (added when hiding)
&.is-hiding {
animation: sp-confirmation-bar-slide-down 200ms ease-in forwards;
}
}
}
// Reduced motion: no animations
@media (prefers-reduced-motion: reduce) {
.scrying-pool {
&__confirmation-bar {
animation: none;
transition: none;
}
}
}
+17 -7
View File
@@ -93,7 +93,7 @@
&:hover { background: var(--sp-accent, #4a6f9c); color: #fff; border-color: transparent; }
}
// ── Footer (disabled preset actions) ─────────────────────────────────────
// ── Footer (preset actions) ────────────────────────────────────────────
.directors-board__footer {
display: flex;
gap: 8px;
@@ -101,16 +101,26 @@
border-top: 1px solid var(--sp-border);
flex-shrink: 0;
button {
&-btn {
flex: 1;
font-size: 12px;
color: var(--sp-text-muted);
background: transparent;
border: 1px solid var(--sp-border);
background: var(--sp-accent, #4a6f9c);
color: #fff;
border: none;
border-radius: 3px;
padding: 4px 8px;
cursor: not-allowed;
opacity: 0.5;
cursor: pointer;
transition: opacity 0.15s;
&:hover { opacity: 0.85; }
&:active { opacity: 0.7; }
&[disabled] {
cursor: not-allowed;
opacity: 0.5;
background: transparent;
color: var(--sp-text-muted);
border: 1px solid var(--sp-border);
}
}
}
}
+7 -1
View File
@@ -1,3 +1,9 @@
// All selectors MUST be scoped under .scrying-pool.
// Use --sp-* tokens only — no Foundry --color-* / --font-* / --border-* tokens allowed.
// Implemented in story 1.5+.
// Notifications are delivered via Foundry's native ui.notifications toast system — no custom
// DOM insertion is required. This file is reserved for any future custom notification chrome.
.scrying-pool {
// Placeholder for notification-related overrides.
// Native ui.notifications toasts do not require additional scoping.
}
+108 -3
View File
@@ -1,3 +1,108 @@
// All selectors MUST be scoped under .scrying-pool.
// Use --sp-* tokens only — no Foundry --color-* / --font-* / --border-* tokens allowed.
// Implemented in story 1.5+.
/**
* styles/components/_participant-card.less
*
* 80×100px card tile used in the Director's Board grid.
* All selectors scoped under .scrying-pool.
* Uses --sp-* tokens only — no Foundry --color-* / --font-* / --border-* tokens.
*/
.scrying-pool .participant-card {
width: 80px;
height: 100px;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
border: 2px solid var(--sp-border);
border-radius: 4px;
background: var(--sp-surface);
cursor: pointer;
overflow: hidden;
transition:
border-color var(--sp-transition-state),
background-color var(--sp-transition-state);
// ── State ring colour + shape signals (second-signal rule) ──────────────
&.sp-state-active { border-color: var(--sp-state-active-border); border-style: solid; background: var(--sp-state-active-bg); }
&.sp-state-hidden { border-color: var(--sp-state-hidden-border); border-style: dashed; background: var(--sp-state-hidden-bg); }
&.sp-state-self-muted { border-color: var(--sp-state-self-muted-border); border-style: solid; background: var(--sp-state-self-muted-bg); }
&.sp-state-offline { border-color: var(--sp-state-offline-border); border-style: none; background: var(--sp-state-offline-bg); }
&.sp-state-cam-lost { border-color: var(--sp-state-cam-lost-border); border-style: dashed; background: var(--sp-state-cam-lost-bg); }
&.sp-state-reconnecting { border-color: var(--sp-state-reconnecting-border); border-style: solid; background: var(--sp-state-reconnecting-bg); }
&.sp-state-never-connected { border-color: var(--sp-state-never-connected-border); border-style: none; background: var(--sp-state-never-connected-bg); }
&.sp-state-ghost { border-color: var(--sp-state-ghost-border); border-style: dotted; background: var(--sp-state-ghost-bg); }
&.sp-state-pending { border-color: var(--sp-state-pending-border); border-style: solid; background: var(--sp-state-pending-bg); }
// ── Avatar ──────────────────────────────────────────────────────────────
&__avatar {
width: 48px;
height: 48px;
margin: 8px auto 4px;
flex-shrink: 0;
img {
width: 100%;
height: 100%;
border-radius: 50%;
object-fit: cover;
display: block;
}
}
// ── Name (12px, 2-line truncate) ─────────────────────────────────────────
&__name {
font-size: 12px;
line-height: 1.2;
text-align: center;
color: var(--sp-text-primary);
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
padding: 0 4px;
margin: 0;
width: 100%;
word-break: break-word;
}
// ── Toggle overlay (shown on hover / focus-within) ───────────────────────
&__toggle {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
background: var(--sp-badge-bg);
color: var(--sp-badge-text);
border: none;
border-radius: 2px;
cursor: pointer;
opacity: 0;
transition: opacity var(--sp-transition-state);
font-size: 18px;
padding: 0;
&:focus-visible {
opacity: 1;
}
}
&:hover &__toggle,
&:focus-within &__toggle {
opacity: 1;
}
// ── Focus ring (inherits module-wide pattern from tokens/_focus.less) ────
&:focus-visible {
outline: none;
box-shadow: var(--sp-focus-ring), 0 0 0 4px var(--sp-surface);
}
}
// ── Reduced motion ────────────────────────────────────────────────────────
@media (prefers-reduced-motion: reduce) {
.scrying-pool .participant-card {
transition: none;
&__toggle { transition: none; }
}
}
+174 -3
View File
@@ -1,3 +1,174 @@
// All selectors MUST be scoped under .scrying-pool.
// Use --sp-* tokens only — no Foundry --color-* / --font-* / --border-* tokens allowed.
// Implemented in story 1.5+.
// VisibilityBadge — DOCUMENTED EXCEPTION to .scrying-pool scoping.
// The badge is injected into the AV tile DOM via AVTileAdapter — outside any .scrying-pool root.
// Selectors here are top-level (not nested under .scrying-pool).
// Badge-specific tokens are declared on :root so they are reachable from tile-adjacent DOM.
// Source: Architecture §Token System + Story 1.1 AC (VisibilityBadge :root exception).
// Base: disable all motion before the media query (accessibility-first)
.sp-visibility-badge {
transition: none;
animation: none;
}
:root {
--sp-badge-bg: hsl(220, 15%, 10%);
--sp-badge-text: hsl(0, 0%, 85%);
--sp-badge-border: hsl(220, 15%, 25%);
--sp-badge-font-size: 0.6875rem;
--sp-badge-letter-sp: 0.02em;
--sp-chip-bg: hsl(220, 15%, 15%);
--sp-chip-text: hsl(0, 0%, 75%);
}
// ---------------------------------------------------------------------------
// Badge — mounted top-center on the AV tile
// ---------------------------------------------------------------------------
.sp-visibility-badge {
position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%);
z-index: 10;
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 6px;
background: var(--sp-badge-bg);
color: var(--sp-badge-text);
border: 1px solid var(--sp-badge-border);
border-radius: 4px;
cursor: pointer;
white-space: nowrap;
&__label {
font-size: var(--sp-badge-font-size);
letter-spacing: var(--sp-badge-letter-sp);
line-height: 1.2;
}
}
// ---------------------------------------------------------------------------
// FirstEncounterPanel
// ---------------------------------------------------------------------------
.sp-first-encounter-panel {
position: fixed;
z-index: 100;
bottom: 80px;
left: 50%;
transform: translateX(-50%);
max-width: 280px;
background: var(--sp-badge-bg);
color: var(--sp-badge-text);
border: 1px solid var(--sp-badge-border);
border-radius: 6px;
padding: 12px 16px;
overflow: hidden;
max-height: 200px;
&__title {
font-size: 0.75rem;
font-weight: 600;
margin: 0 0 6px;
}
&__body {
font-size: 0.6875rem;
margin: 0 0 10px;
}
&__got-it {
font-size: 0.6875rem;
padding: 4px 10px;
cursor: pointer;
border-radius: 3px;
}
}
// ---------------------------------------------------------------------------
// Chip (collapsed state)
// ---------------------------------------------------------------------------
.sp-visibility-chip {
position: fixed;
z-index: 100;
bottom: 80px;
left: 50%;
transform: translateX(-50%);
display: inline-flex;
align-items: center;
padding: 2px 8px;
background: var(--sp-chip-bg);
color: var(--sp-chip-text);
border: 1px solid var(--sp-badge-border);
border-radius: 12px;
font-size: var(--sp-badge-font-size);
cursor: pointer;
&:focus-visible {
outline: 2px solid var(--sp-state-active, hsl(220, 80%, 60%));
outline-offset: 2px;
}
}
// ---------------------------------------------------------------------------
// VisibilityDetailsPanel (<dialog>)
// ---------------------------------------------------------------------------
dialog.sp-visibility-details-panel {
background: var(--sp-badge-bg);
color: var(--sp-badge-text);
border: 1px solid var(--sp-badge-border);
border-radius: 8px;
padding: 20px 24px;
max-width: 320px;
width: 100%;
&__state {
font-size: 0.875rem;
font-weight: 600;
margin: 0 0 10px;
}
&__reassurance,
&__audio-note {
font-size: 0.75rem;
margin: 0 0 10px;
}
&__stale {
font-size: 0.6875rem;
opacity: 0.6;
margin: 0 0 10px;
font-style: italic;
}
&__close {
display: block;
margin-top: 12px;
padding: 6px 14px;
cursor: pointer;
border-radius: 4px;
font-size: 0.75rem;
}
&::backdrop {
background: hsla(0, 0%, 0%, 0.4);
}
}
// ---------------------------------------------------------------------------
// Motion (gated under preference query)
// ---------------------------------------------------------------------------
@media (prefers-reduced-motion: no-preference) {
.sp-first-encounter-panel {
transition: max-height 300ms ease-out, opacity 300ms ease-out;
&--collapsing {
max-height: 0;
opacity: 0;
}
}
}
+105
View File
@@ -0,0 +1,105 @@
/**
* styles/components/_preset-load-dialog.less
*
* Layout for the Preset Load Dialog.
* All selectors scoped under .scrying-pool.
* Uses --sp-* tokens only — no Foundry --color-* / --font-* / --border-* tokens.
*/
// The ApplicationV2 window root already has .scrying-pool .preset-load-dialog applied
// via DEFAULT_OPTIONS.classes. The content lives inside PARTS.
.scrying-pool.preset-load-dialog {
// ── Content wrapper ────────────────────────────────────────────────────
.preset-load-dialog__content {
display: flex;
flex-direction: column;
height: 100%;
gap: 0;
}
// ── Header ────────────────────────────────────────────────────────────
.preset-load-dialog__header {
padding: 12px 16px;
border-bottom: 1px solid var(--sp-border);
flex-shrink: 0;
}
.preset-load-dialog__title {
margin: 0;
font-size: 14px;
font-weight: bold;
color: var(--sp-text, inherit);
}
// ── Body ──────────────────────────────────────────────────────────────
.preset-load-dialog__body {
padding: 16px;
overflow-y: auto;
flex: 1 1 auto;
}
// ── Empty state ────────────────────────────────────────────────────────
.preset-load-dialog__empty {
text-align: center;
color: var(--sp-text-muted);
font-size: 13px;
padding: 24px 0;
margin: 0;
}
// ── Preset list ────────────────────────────────────────────────────────
.preset-load-dialog__list {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 4px;
}
.preset-load-dialog__item {
margin: 0;
padding: 0;
}
.preset-load-dialog__btn {
width: 100%;
text-align: left;
font-size: 13px;
padding: 8px 12px;
border-radius: 3px;
cursor: pointer;
transition: background-color 0.15s, border-color 0.15s;
// Load button — primary
&--load {
background: var(--sp-accent, #4a6f9c);
color: #fff;
border: none;
&:hover { opacity: 0.85; }
&:active { opacity: 0.7; }
}
// Cancel button — secondary
&--cancel {
background: transparent;
color: var(--sp-text-muted);
border: 1px solid var(--sp-border);
&:hover { color: var(--sp-text, inherit); border-color: currentColor; }
}
}
// ── Footer ────────────────────────────────────────────────────────────
.preset-load-dialog__footer {
display: flex;
justify-content: flex-end;
gap: 8px;
padding: 12px 16px;
border-top: 1px solid var(--sp-border);
flex-shrink: 0;
}
}
+111
View File
@@ -0,0 +1,111 @@
/**
* styles/components/_preset-save-dialog.less
*
* Layout for the Preset Save Dialog.
* All selectors scoped under .scrying-pool.
* Uses --sp-* tokens only — no Foundry --color-* / --font-* / --border-* tokens.
*/
// The ApplicationV2 window root already has .scrying-pool .preset-save-dialog applied
// via DEFAULT_OPTIONS.classes. The content lives inside PARTS.
.scrying-pool.preset-save-dialog {
// ── Form ───────────────────────────────────────────────────────────────
.preset-save-dialog__form {
display: flex;
flex-direction: column;
height: 100%;
gap: 0;
}
// ── Header ────────────────────────────────────────────────────────────
.preset-save-dialog__header {
padding: 12px 16px;
border-bottom: 1px solid var(--sp-border);
flex-shrink: 0;
}
.preset-save-dialog__title {
margin: 0;
font-size: 14px;
font-weight: bold;
color: var(--sp-text, inherit);
}
// ── Body ──────────────────────────────────────────────────────────────
.preset-save-dialog__body {
padding: 16px;
overflow-y: auto;
flex: 1 1 auto;
}
.preset-save-dialog__field {
display: flex;
flex-direction: column;
gap: 4px;
}
.preset-save-dialog__label {
font-size: 12px;
color: var(--sp-text-muted);
cursor: default;
}
.preset-save-dialog__input {
font-size: 14px;
padding: 6px 10px;
border: 1px solid var(--sp-border);
border-radius: 3px;
background: var(--sp-bg, #fff);
color: var(--sp-text, inherit);
outline: none;
transition: border-color 0.15s, box-shadow 0.15s;
&:focus {
border-color: var(--sp-accent, #4a6f9c);
box-shadow: 0 0 0 1px var(--sp-accent, #4a6f9c);
}
&::placeholder {
color: var(--sp-text-muted);
}
}
// ── Footer ────────────────────────────────────────────────────────────
.preset-save-dialog__footer {
display: flex;
justify-content: flex-end;
gap: 8px;
padding: 12px 16px;
border-top: 1px solid var(--sp-border);
flex-shrink: 0;
}
.preset-save-dialog__btn {
font-size: 12px;
padding: 6px 14px;
border-radius: 3px;
cursor: pointer;
transition: opacity 0.15s;
// Save button — primary
&--save {
background: var(--sp-accent, #4a6f9c);
color: #fff;
border: none;
&:hover { opacity: 0.85; }
&:active { opacity: 0.7; }
}
// Cancel button — secondary
&--cancel {
background: transparent;
color: var(--sp-text-muted);
border: 1px solid var(--sp-border);
&:hover { color: var(--sp-text, inherit); border-color: currentColor; }
}
}
}
+314 -1
View File
@@ -1,3 +1,316 @@
// All selectors MUST be scoped under .scrying-pool.
// Use --sp-* tokens only — no Foundry --color-* / --font-* / --border-* tokens allowed.
// Implemented in story 1.5+.
// 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;
}
}
.sp-avatar__img {
width: 32px;
height: 32px;
border-radius: 50%;
object-fit: cover;
flex-shrink: 0;
}
.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;
}
}
}
+254 -3
View File
@@ -1,3 +1,254 @@
// All selectors MUST be scoped under .scrying-pool.
// Use --sp-* tokens only — no Foundry --color-* / --font-* / --border-* tokens allowed.
// Implemented in story 1.5+.
// ScenePresetPanel component styles
// Story 3.2: Scene Auto-Apply & ConfirmationBar
//
// Import rule: All selectors scoped under .scrying-pool or .directors-board namespace
// Use --sp-* semantic tokens only, never Foundry tokens directly
// Panel container
.directors-board__preset-panel {
// Base styles
background-color: var(--sp-surface);
border: 1px solid var(--sp-border);
border-radius: 6px;
padding: 12px;
margin-top: 12px;
// Layout
display: flex;
flex-direction: column;
gap: 8px;
// Typography
font-size: 14px;
line-height: 1.4;
color: var(--sp-text-primary);
}
// Panel header
.directors-board__preset-panel-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 4px;
padding-bottom: 8px;
border-bottom: 1px solid var(--sp-border-subtle);
}
.directors-board__preset-panel-title {
margin: 0;
font-size: 15px;
font-weight: 600;
color: var(--sp-text-primary);
}
// Panel body
.directors-board__preset-panel-body {
display: flex;
flex-direction: column;
gap: 10px;
}
// Panel row
.directors-board__preset-panel-row {
display: flex;
align-items: center;
gap: 8px;
&--hint {
font-size: 12px;
color: var(--sp-text-secondary);
margin-top: 4px;
padding-top: 8px;
border-top: 1px solid var(--sp-border-subtle);
}
}
// Panel label
.directors-board__preset-panel-label {
display: flex;
align-items: center;
gap: 8px;
cursor: default;
user-select: none;
// Ensure proper spacing when label wraps
flex-wrap: wrap;
align-items: flex-start;
}
// Toggle switch
.directors-board__preset-panel-toggle {
// Button reset
background: transparent;
border: none;
padding: 0;
margin: 0;
cursor: pointer;
// Appearance
width: 18px;
height: 18px;
border: 2px solid var(--sp-border);
border-radius: 4px;
background-color: var(--sp-surface);
position: relative;
transition: background-color 150ms ease, border-color 150ms ease;
// Checked state
&:checked {
background-color: var(--sp-accent);
border-color: var(--sp-accent);
}
// After pseudo-element for toggle effect
&:after {
content: '';
position: absolute;
top: 1px;
left: 1px;
width: 12px;
height: 12px;
background-color: var(--sp-surface);
border-radius: 2px;
transition: transform 150ms ease, background-color 150ms ease;
}
&:checked:after {
transform: translateX(100%);
background-color: var(--sp-surface-inverse, white);
}
// Focus state
&:focus {
outline: 2px solid var(--sp-focus);
outline-offset: 2px;
}
// Hover state
&:hover:not(:checked) {
background-color: var(--sp-surface-hover, rgba(0, 0, 0, 0.05));
}
}
// Preset selector
.directors-board__preset-panel-select {
// Button reset
background: transparent;
border: 1px solid var(--sp-border);
padding: 6px 8px;
margin: 0;
cursor: pointer;
// Typography
font-family: inherit;
font-size: 14px;
color: var(--sp-text-primary);
// Border and radius
border-radius: 4px;
// Transition
transition: border-color 150ms ease, box-shadow 150ms ease;
// Hover state
&:hover:not(:disabled) {
border-color: var(--sp-border-hover, var(--sp-accent));
}
// Focus state
&:focus {
outline: none;
border-color: var(--sp-focus);
box-shadow: 0 0 0 2px rgba(var(--sp-focus-rgb, 0, 0, 255), 0.2);
}
// Disabled state
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
}
// Pre-delay slider container
.directors-board__preset-panel-slider {
// Remove default slider styling
-webkit-appearance: none;
appearance: none;
width: 120px;
height: 6px;
background: var(--sp-surface-subtle, rgba(0, 0, 0, 0.1));
border-radius: 3px;
outline: none;
cursor: pointer;
// Webkit slider thumb
&::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 16px;
height: 16px;
background: var(--sp-accent);
border-radius: 50%;
cursor: pointer;
transition: transform 150ms ease;
&:hover {
transform: scale(1.1);
}
}
// Firefox slider thumb
&::-moz-range-thumb {
width: 16px;
height: 16px;
background: var(--sp-accent);
border-radius: 50%;
cursor: pointer;
border: none;
transition: transform 150ms ease;
&:hover {
transform: scale(1.1);
}
}
// Focus state
&:focus {
outline: none;
}
}
// Delay value display
.directors-board__preset-panel-delay-value {
display: inline-block;
min-width: 40px;
text-align: right;
font-family: monospace;
font-size: 12px;
color: var(--sp-text-secondary);
padding: 0 4px;
}
// Panel message (when no scene)
.directors-board__preset-panel-message {
margin: 0;
color: var(--sp-text-secondary);
font-style: italic;
text-align: center;
padding: 8px 0;
}
// Reduced motion: disable transitions
@media (prefers-reduced-motion: reduce) {
.directors-board__preset-panel-toggle,
.directors-board__preset-panel-select,
.directors-board__preset-panel-slider::-webkit-slider-thumb,
.directors-board__preset-panel-slider::-moz-range-thumb {
transition: none;
}
.directors-board__preset-panel-slider::-webkit-slider-thumb:hover,
.directors-board__preset-panel-slider::-moz-range-thumb:hover {
transform: none;
}
}
@@ -0,0 +1,25 @@
// StripOverlayLayer component styles
// Story 1.5: Original creation
// Story 3.2: Used by ConfirmationBar
//
// Import rule: All selectors scoped under .scrying-pool namespace
// Use --sp-* semantic tokens only, never Foundry tokens directly
.scrying-pool {
// Overlay layer container
.sp-strip__overlay-layer {
// Critical: These are set inline in JS per UX-DR6
// position: absolute;
// inset: 0;
// pointer-events: none;
// overflow: visible;
// Ensure proper stacking
z-index: 10;
// Child elements restore pointer-events
> * {
pointer-events: auto;
}
}
}