| 1 |
/** |
| 2 |
* OpenStation — Files-on-the-Desktop tile + layer styles. |
| 3 |
* |
| 4 |
* The layer is an absolutely-positioned container inside |
| 5 |
* `#os-area`; tiles inside use absolute positioning |
| 6 |
* keyed off `(x, y)` coordinates persisted server-side. |
| 7 |
* |
| 8 |
* Tiles are intentionally close to the look of `os-icon` |
| 9 |
* (the plugin-shortcut tiles) so the desktop reads as one |
| 10 |
* coherent surface — but they live in their own class namespace |
| 11 |
* (`os-file-tile`) so plugins can theme each rail |
| 12 |
* independently. |
| 13 |
* |
| 14 |
* @since 0.9.0 |
| 15 |
*/ |
| 16 |
|
| 17 |
/* |
| 18 |
* Unified rail (since 0.9.0): plugin shortcuts now ride on the |
| 19 |
* same files layer as folders / posts / etc — they're a |
| 20 |
* `'shortcut'` file type. The legacy `.os-icons` DIV |
| 21 |
* still renders for backwards compatibility (existing tests and |
| 22 |
* non-files-active code paths), but when a files layer is |
| 23 |
* mounted as a sibling we hide it so we don't end up with two |
| 24 |
* rails competing for the same wallpaper. |
| 25 |
*/ |
| 26 |
#os-area:has( > .os-files-layer ) > .os-icons { |
| 27 |
display: none; |
| 28 |
} |
| 29 |
|
| 30 |
/* |
| 31 |
* Drop-target highlight — the `data-files-drop-active` attribute |
| 32 |
* is set on the FilesLayer host (desktop area or folder window |
| 33 |
* body) while a shortcut payload is being dragged over it. |
| 34 |
* |
| 35 |
* A quiet inner glow only. This used to be a dashed, animated |
| 36 |
* marching-ants frame around the whole wallpaper, and on a full |
| 37 |
* desktop that read as a large moving grid behind the tile in hand |
| 38 |
* — loud, and design asked for it to go. The ghost's accept / |
| 39 |
* reject chip and the per-cell drop preview already say where the |
| 40 |
* tile will land; the glow just confirms the surface is listening. |
| 41 |
*/ |
| 42 |
[ data-files-drop-active ] { |
| 43 |
position: relative; |
| 44 |
box-shadow: inset 0 0 32px 4px rgba( 34, 113, 177, 0.12 ); |
| 45 |
transition: box-shadow 0.18s ease-out; |
| 46 |
} |
| 47 |
|
| 48 |
.os-files-layer { |
| 49 |
position: absolute; |
| 50 |
inset: 0; |
| 51 |
z-index: 0; |
| 52 |
/* |
| 53 |
* Layer doesn't intercept pointer events itself — tiles and |
| 54 |
* the wallpaper own that. Phase 4's wallpaper context menu |
| 55 |
* binds clicks on the parent `#os-area`, which is |
| 56 |
* unblocked because of this rule. |
| 57 |
*/ |
| 58 |
pointer-events: none; |
| 59 |
} |
| 60 |
|
| 61 |
/* |
| 62 |
* Live drop-cell preview. A soft outline that hovers at the cell |
| 63 |
* the dropped tile will land in, so the user can predict where the |
| 64 |
* grid snap will place it before releasing. Positioned absolutely |
| 65 |
* inside the files layer; `translate3d` updated from JS on every |
| 66 |
* pointermove while a desktop-file drag hovers the canvas. |
| 67 |
* |
| 68 |
* The tile dimensions are baked into `grid.ts` (88 × 96 visual, |
| 69 |
* 96 × 110 cell pitch). The outline matches the cell pitch — slightly |
| 70 |
* larger than the tile — so the highlight reads as "this slot" rather |
| 71 |
* than "this tile." |
| 72 |
* |
| 73 |
* @since 0.20.0 |
| 74 |
*/ |
| 75 |
.os-files-drop-preview { |
| 76 |
position: absolute; |
| 77 |
top: 0; |
| 78 |
left: 0; |
| 79 |
width: 88px; |
| 80 |
height: 96px; |
| 81 |
pointer-events: none; |
| 82 |
box-sizing: border-box; |
| 83 |
border-radius: 12px; |
| 84 |
background: var( |
| 85 |
--os-drop-preview-bg, |
| 86 |
rgba( 34, 113, 177, 0.08 ) |
| 87 |
); |
| 88 |
border: 2px dashed |
| 89 |
var( |
| 90 |
--os-drop-preview-border, |
| 91 |
rgba( 34, 113, 177, 0.55 ) |
| 92 |
); |
| 93 |
transition: transform 90ms ease-out; |
| 94 |
will-change: transform; |
| 95 |
/* Sit BELOW tiles so a tile being dragged over the cell isn't |
| 96 |
* visually clipped by the dashed outline; the outline still reads |
| 97 |
* because the tile is semi-transparent during drag (`--dragging` |
| 98 |
* sets `opacity: 0.4`). */ |
| 99 |
z-index: 0; |
| 100 |
} |
| 101 |
|
| 102 |
@media ( prefers-reduced-motion: reduce ) { |
| 103 |
.os-files-drop-preview { |
| 104 |
transition: none; |
| 105 |
} |
| 106 |
} |
| 107 |
|
| 108 |
.os-file-tile { |
| 109 |
pointer-events: auto; |
| 110 |
position: absolute; |
| 111 |
display: flex; |
| 112 |
flex-direction: column; |
| 113 |
align-items: center; |
| 114 |
justify-content: flex-start; |
| 115 |
gap: 6px; |
| 116 |
/* |
| 117 |
* `box-sizing` is load-bearing, not housekeeping. Without it the |
| 118 |
* horizontal padding is ADDED to the declared width, so an 88px |
| 119 |
* tile occupies 96px — which was exactly the desktop's old cell |
| 120 |
* pitch, and why icons sat edge to edge with no gap at all while |
| 121 |
* the maths insisted there were 8px between them. |
| 122 |
* |
| 123 |
* The tile is the size the grid says it is. Padding lives inside. |
| 124 |
*/ |
| 125 |
box-sizing: border-box; |
| 126 |
width: var( --os-tile-w, 88px ); |
| 127 |
/* |
| 128 |
* Fixed, not minimum. The tile box IS the selection ring, and a |
| 129 |
* box that grows with its label gives a row of selected icons a |
| 130 |
* ragged top edge — one height per label that happened to wrap |
| 131 |
* to two lines. Every tile occupies its cell; short labels leave |
| 132 |
* the slack inside the ring, where it reads as padding. |
| 133 |
*/ |
| 134 |
height: var( --os-tile-h, 104px ); |
| 135 |
padding: 8px 4px; |
| 136 |
border: 0; |
| 137 |
background: transparent; |
| 138 |
/* Tile foreground driven by `--os-tile-fg` so any |
| 139 |
* light-on-dark surface (folder window, My WordPress preview) |
| 140 |
* can retint tiles by overriding the variable in its scope — |
| 141 |
* no per-surface selectors needed here. */ |
| 142 |
color: var( --os-tile-fg, var( --os-fg, #fff ) ); |
| 143 |
cursor: pointer; |
| 144 |
border-radius: 8px; |
| 145 |
transition: background-color 0.15s ease, transform 0.05s linear; |
| 146 |
-webkit-user-select: none; |
| 147 |
user-select: none; |
| 148 |
touch-action: none; |
| 149 |
/* Soft fade-in on mount — each tile carries its own randomised |
| 150 |
* `--enter-delay` / `--enter-duration` (set inline by the |
| 151 |
* tile builder), so a freshly-painted grid reads as a cascade |
| 152 |
* rather than a synchronised pop-in. Both values land in the |
| 153 |
* decimal-of-a-second range — perceivable, never sluggish. |
| 154 |
* |
| 155 |
* `backwards` keeps the tile invisible during its delay so the |
| 156 |
* cascade is honest; the animation runs once, leaves the tile |
| 157 |
* at its static `opacity: 1`, and never re-fires from later |
| 158 |
* state changes (drag, hover, drop-target). |
| 159 |
*/ |
| 160 |
animation: os-file-tile-enter |
| 161 |
var( --os-file-tile-enter-duration, 0.4s ) |
| 162 |
ease-out |
| 163 |
var( --os-file-tile-enter-delay, 0s ) |
| 164 |
backwards; |
| 165 |
} |
| 166 |
|
| 167 |
@keyframes os-file-tile-enter { |
| 168 |
from { |
| 169 |
opacity: 0; |
| 170 |
} |
| 171 |
to { |
| 172 |
opacity: 1; |
| 173 |
} |
| 174 |
} |
| 175 |
|
| 176 |
@media ( prefers-reduced-motion: reduce ) { |
| 177 |
.os-file-tile { |
| 178 |
animation: none; |
| 179 |
} |
| 180 |
} |
| 181 |
|
| 182 |
.os-file-tile:hover, |
| 183 |
.os-file-tile:focus-visible { |
| 184 |
background: var( |
| 185 |
--os-tile-hover-bg, |
| 186 |
rgba( 255, 255, 255, 0.12 ) |
| 187 |
); |
| 188 |
outline: none; |
| 189 |
} |
| 190 |
|
| 191 |
.os-file-tile:focus-visible { |
| 192 |
/* Inset focus ring — outset rings extended past the tile and |
| 193 |
* visually overlapped the colindant icon when the canvas cell |
| 194 |
* pitch is tight (96 px cell, 88 px tile → only 4 px clearance |
| 195 |
* per side). Inset keeps the ring fully inside the tile. */ |
| 196 |
box-shadow: inset 0 0 0 2px |
| 197 |
var( --os-tile-focus-ring, var( --wp-admin-theme-color, #2271b1 ) ); |
| 198 |
} |
| 199 |
|
| 200 |
/* Status ribbon sizing inside tiles. The `<os-ribbon>` defaults |
| 201 |
* are tuned for cards (~90 px clipping window); 88 px-wide tiles |
| 202 |
* need a tighter footprint or the ribbon dominates the icon. We |
| 203 |
* override the component's CSS custom-property theming surface so |
| 204 |
* the ribbon stays a small accent in the corner. Authors who want |
| 205 |
* different sizing can override per-tile or per-section. */ |
| 206 |
.os-file-tile > os-ribbon { |
| 207 |
--os-ui-ribbon-size: 48px; |
| 208 |
--os-ui-ribbon-banner-width: 72px; |
| 209 |
--os-ui-ribbon-banner-offset: 12px; |
| 210 |
--os-ui-ribbon-banner-pull: -18px; |
| 211 |
--os-ui-ribbon-padding: 1px 0; |
| 212 |
--os-ui-ribbon-font: 700 7px/1.4 var( --os-font, system-ui ); |
| 213 |
--os-ui-ribbon-tracking: 0.05em; |
| 214 |
--os-ui-ribbon-shadow: 0 1px 2px rgba( 0, 0, 0, 0.2 ); |
| 215 |
} |
| 216 |
|
| 217 |
.os-file-tile--dragging { |
| 218 |
/* The DragManager renders a separate ghost element that follows |
| 219 |
* the pointer; the SOURCE tile stays in place but is dimmed so |
| 220 |
* the user has a stable reference for "this is what I'm |
| 221 |
* dragging." |
| 222 |
* |
| 223 |
* @since 0.18.0 |
| 224 |
*/ |
| 225 |
opacity: 0.4; |
| 226 |
cursor: grabbing; |
| 227 |
} |
| 228 |
|
| 229 |
/* Pinned tiles ("My WordPress", Recycle Bin) anchor to a fixed slot |
| 230 |
* and DO NOT wire drag — but they look + behave identically to any |
| 231 |
* other tile until the user attempts a drag (which silently fails). |
| 232 |
* No upfront visual cue: feedback now happens at the moment of the |
| 233 |
* gesture, not before, per @since 0.9.0 design feedback. |
| 234 |
* |
| 235 |
* Default `cursor: pointer` from the base `.os-file-tile` |
| 236 |
* rule covers click-to-open. Touch interactions stay as |
| 237 |
* `touch-action: auto` so the OS scroll gesture works on top of |
| 238 |
* the tile. |
| 239 |
*/ |
| 240 |
.os-file-tile--pinned { |
| 241 |
touch-action: auto; |
| 242 |
} |
| 243 |
|
| 244 |
/* Folder tile is the drop target during a cross-window shortcut |
| 245 |
* drag — the user dragged a post / page / user tile out of My |
| 246 |
* WordPress and is hovering over a folder. Bright outline, soft |
| 247 |
* pulse, and a slight scale so the folder reads unmistakably as |
| 248 |
* "yes, drop here." @since 0.8.0; pulse added 0.20.0. */ |
| 249 |
@keyframes os-drop-target-pulse { |
| 250 |
0% { |
| 251 |
box-shadow: |
| 252 |
0 0 0 0 rgba( 34, 113, 177, 0.5 ), |
| 253 |
inset 0 0 0 2px var( --wp-admin-theme-color, #2271b1 ); |
| 254 |
} |
| 255 |
100% { |
| 256 |
box-shadow: |
| 257 |
0 0 0 8px rgba( 34, 113, 177, 0 ), |
| 258 |
inset 0 0 0 2px var( --wp-admin-theme-color, #2271b1 ); |
| 259 |
} |
| 260 |
} |
| 261 |
|
| 262 |
.os-file-tile--drop-target { |
| 263 |
outline: 2px solid var( --wp-admin-theme-color, #2271b1 ); |
| 264 |
outline-offset: -4px; |
| 265 |
background: rgba( 34, 113, 177, 0.2 ); |
| 266 |
transform: scale( 1.08 ); |
| 267 |
z-index: 5; |
| 268 |
animation: os-drop-target-pulse 1s ease-out infinite; |
| 269 |
border-radius: 12px; |
| 270 |
} |
| 271 |
|
| 272 |
@media ( prefers-reduced-motion: reduce ) { |
| 273 |
.os-file-tile--drop-target { |
| 274 |
animation: none; |
| 275 |
} |
| 276 |
} |
| 277 |
|
| 278 |
/* Drop-hint chip — floats next to the cursor during a drag and |
| 279 |
* shows context-aware text ("Drop here to create shortcut", |
| 280 |
* "Can't drop here", etc.). The chip text is owned by the |
| 281 |
* ghost module (`src/drag/ghost.ts`); these styles control the |
| 282 |
* appearance per accept/reject/neutral mode. |
| 283 |
* |
| 284 |
* @since 0.20.0 |
| 285 |
*/ |
| 286 |
.os-drag-hint { |
| 287 |
position: fixed; |
| 288 |
top: 0; |
| 289 |
left: 0; |
| 290 |
padding: 6px 10px; |
| 291 |
border-radius: 999px; |
| 292 |
font-size: 12px; |
| 293 |
font-weight: 600; |
| 294 |
letter-spacing: 0.02em; |
| 295 |
white-space: nowrap; |
| 296 |
pointer-events: none; |
| 297 |
user-select: none; |
| 298 |
box-shadow: 0 4px 14px rgba( 0, 0, 0, 0.22 ); |
| 299 |
background: rgba( 30, 30, 30, 0.85 ); |
| 300 |
color: var( --os-ui-fg-on-accent, #fff ); |
| 301 |
z-index: 2147483647; |
| 302 |
transition: |
| 303 |
background-color 0.12s ease, |
| 304 |
color 0.12s ease, |
| 305 |
transform 0.06s ease-out; |
| 306 |
} |
| 307 |
|
| 308 |
.os-drag-hint--neutral { |
| 309 |
background: rgba( 30, 30, 30, 0.85 ); |
| 310 |
color: var( --os-ui-fg-on-accent, #fff ); |
| 311 |
} |
| 312 |
|
| 313 |
.os-drag-hint--accept { |
| 314 |
background: #1e8449; |
| 315 |
color: var( --os-ui-fg-on-accent, #fff ); |
| 316 |
box-shadow: |
| 317 |
0 0 0 2px rgba( 30, 132, 73, 0.25 ), |
| 318 |
0 6px 18px rgba( 30, 132, 73, 0.3 ); |
| 319 |
} |
| 320 |
|
| 321 |
.os-drag-hint--accept::before { |
| 322 |
/* Tiny checkmark chevron prefix so "Drop here" reads as an |
| 323 |
* actionable affirmation rather than a passive label. The |
| 324 |
* checkmark is a real character (U+2713) so screen readers |
| 325 |
* skip past it via the `aria-hidden` on the host chip. */ |
| 326 |
content: '✓ '; |
| 327 |
} |
| 328 |
|
| 329 |
.os-drag-hint--reject { |
| 330 |
background: var( --os-ui-danger-hover, #b32d2e ); |
| 331 |
color: var( --os-ui-fg-on-accent, #fff ); |
| 332 |
box-shadow: |
| 333 |
0 0 0 2px rgba( 179, 45, 46, 0.25 ), |
| 334 |
0 6px 18px rgba( 179, 45, 46, 0.3 ); |
| 335 |
} |
| 336 |
|
| 337 |
.os-drag-hint--reject::before { |
| 338 |
content: '⊘ '; |
| 339 |
} |
| 340 |
|
| 341 |
/* |
| 342 |
* No body-level cursor swap during drag — the user prefers the |
| 343 |
* default arrow cursor regardless of state, so the accept/reject |
| 344 |
* feedback lives entirely in the hint chip + outline animation. |
| 345 |
* Don't reintroduce a `cursor: grabbing/copy/no-drop` here. |
| 346 |
*/ |
| 347 |
|
| 348 |
/* Shortcut overlay — small arrow badge in the BOTTOM-RIGHT corner |
| 349 |
* of the icon visual on any tile that's a reference to an external |
| 350 |
* entity (post, attachment, comment, user, term — anything that |
| 351 |
* ISN'T a folder or a plugin shortcut). Mirrors the macOS / Windows |
| 352 |
* convention of an arrow decoration on alias/shortcut icons. |
| 353 |
* |
| 354 |
* The geometry: tile is 88px wide with `padding: 8px 4px`. The icon |
| 355 |
* visual is 48×48 centered horizontally, so the icon's bottom-right |
| 356 |
* corner lands at roughly (68, 56) from the tile's top-left. The |
| 357 |
* 18×18 arrow circle is anchored just inside that corner so it |
| 358 |
* reads as part of the icon, not floating off in space. |
| 359 |
* |
| 360 |
* @since 0.8.0 |
| 361 |
*/ |
| 362 |
/* Only real placements get the badge — i.e. tiles built by |
| 363 |
* `buildTile(placement)` on the desktop / folder windows, which |
| 364 |
* are the only surface where "this is a shortcut" actually means |
| 365 |
* something. Tiles in My WordPress grids (and any future content |
| 366 |
* surface) are the entity, not a shortcut to it, so the arrow |
| 367 |
* would just be noise. `[data-placement-id]` is the canonical |
| 368 |
* placement marker. */ |
| 369 |
.os-file-tile[ data-placement-id ][ data-file-type="post" ]::after, |
| 370 |
.os-file-tile[ data-placement-id ][ data-file-type="attachment" ]::after, |
| 371 |
.os-file-tile[ data-placement-id ][ data-file-type="comment" ]::after, |
| 372 |
.os-file-tile[ data-placement-id ][ data-file-type="user" ]::after, |
| 373 |
.os-file-tile[ data-placement-id ][ data-file-type="term" ]::after { |
| 374 |
content: "↗"; |
| 375 |
position: absolute; |
| 376 |
right: 14px; |
| 377 |
top: 44px; |
| 378 |
width: 18px; |
| 379 |
height: 18px; |
| 380 |
border-radius: 50%; |
| 381 |
background: var( --os-tile-shortcut-bg, rgba( 0, 0, 0, 0.65 ) ); |
| 382 |
color: var( --os-tile-shortcut-fg, #fff ); |
| 383 |
font-size: 11px; |
| 384 |
line-height: 18px; |
| 385 |
text-align: center; |
| 386 |
box-shadow: var( |
| 387 |
--os-tile-shortcut-shadow, |
| 388 |
0 1px 3px rgba( 0, 0, 0, 0.5 ) |
| 389 |
); |
| 390 |
pointer-events: none; |
| 391 |
} |
| 392 |
|
| 393 |
/* |
| 394 |
* Missing files (the underlying entity was deleted but the |
| 395 |
* placement still exists) render the tile dimmed so the user |
| 396 |
* can right-click → Remove without confusing it for an active |
| 397 |
* file. Phase 6's per-placement permission re-check uses the |
| 398 |
* same affordance for "you can't see this" placeholders. |
| 399 |
*/ |
| 400 |
.os-file-tile--missing { |
| 401 |
opacity: 0.5; |
| 402 |
} |
| 403 |
|
| 404 |
/** |
| 405 |
* Access-gated tile — the recipient sees the icon (the owner |
| 406 |
* shared the folder containing it) but lacks `can_read` on the |
| 407 |
* underlying entity. Dim the tile, kill the pointer affordance, |
| 408 |
* and overlay a lock badge so the state reads at a glance. |
| 409 |
*/ |
| 410 |
.os-file-tile--access-gated { |
| 411 |
opacity: 0.65; |
| 412 |
cursor: not-allowed; |
| 413 |
} |
| 414 |
.os-file-tile--access-gated .os-file-tile__visual, |
| 415 |
.os-file-tile--access-gated .os-file-tile__preview { |
| 416 |
filter: grayscale( 0.6 ); |
| 417 |
} |
| 418 |
.os-file-tile--access-gated:hover, |
| 419 |
.os-file-tile--access-gated:focus-visible { |
| 420 |
background: var( --os-ui-hover, rgba( 0, 0, 0, 0.06 ) ); |
| 421 |
} |
| 422 |
.os-file-tile__lock { |
| 423 |
position: absolute; |
| 424 |
top: 4px; |
| 425 |
inset-inline-end: 4px; |
| 426 |
width: 20px; |
| 427 |
height: 20px; |
| 428 |
border-radius: 50%; |
| 429 |
background: var( --os-ui-scrim, rgba( 0, 0, 0, 0.7 ) ); |
| 430 |
color: var( --os-ui-fg-on-accent, #fff ); |
| 431 |
font-size: 14px; |
| 432 |
line-height: 20px; |
| 433 |
text-align: center; |
| 434 |
pointer-events: none; |
| 435 |
box-shadow: 0 1px 3px rgba( 0, 0, 0, 0.5 ); |
| 436 |
} |
| 437 |
|
| 438 |
/** |
| 439 |
* Preview-pane empty state when a recipient selects a tile they |
| 440 |
* can't open. Big lock ring + clear copy + secondary hint. Designed |
| 441 |
* to read at a glance in the otherwise-busy preview column. |
| 442 |
*/ |
| 443 |
.os-files__access-gated { |
| 444 |
display: flex; |
| 445 |
flex-direction: column; |
| 446 |
align-items: center; |
| 447 |
justify-content: center; |
| 448 |
gap: 16px; |
| 449 |
padding: 32px 24px; |
| 450 |
min-height: 240px; |
| 451 |
text-align: center; |
| 452 |
color: var( --os-fg, inherit ); |
| 453 |
} |
| 454 |
.os-files__access-gated-ring { |
| 455 |
display: flex; |
| 456 |
align-items: center; |
| 457 |
justify-content: center; |
| 458 |
width: 96px; |
| 459 |
height: 96px; |
| 460 |
border-radius: 50%; |
| 461 |
background: linear-gradient( |
| 462 |
145deg, |
| 463 |
rgba( 214, 54, 56, 0.22 ), |
| 464 |
rgba( 214, 54, 56, 0.08 ) |
| 465 |
); |
| 466 |
border: 2px solid rgba( 214, 54, 56, 0.45 ); |
| 467 |
box-shadow: |
| 468 |
inset 0 1px 0 rgba( 255, 255, 255, 0.08 ), |
| 469 |
0 8px 24px rgba( 214, 54, 56, 0.18 ); |
| 470 |
} |
| 471 |
.os-files__access-gated-glyph { |
| 472 |
font-size: 44px; |
| 473 |
width: 44px; |
| 474 |
height: 44px; |
| 475 |
color: #ff8080; |
| 476 |
filter: drop-shadow( 0 1px 2px rgba( 0, 0, 0, 0.4 ) ); |
| 477 |
} |
| 478 |
.os-files__access-gated-title { |
| 479 |
margin: 0; |
| 480 |
font-size: 17px; |
| 481 |
font-weight: 600; |
| 482 |
letter-spacing: 0.2px; |
| 483 |
} |
| 484 |
.os-files__access-gated-sub { |
| 485 |
margin: 0; |
| 486 |
font-size: 13px; |
| 487 |
line-height: 1.5; |
| 488 |
max-width: 340px; |
| 489 |
opacity: 0.85; |
| 490 |
} |
| 491 |
.os-files__access-gated-hint { |
| 492 |
margin: 0; |
| 493 |
font-size: 12px; |
| 494 |
line-height: 1.5; |
| 495 |
max-width: 340px; |
| 496 |
opacity: 0.6; |
| 497 |
} |
| 498 |
|
| 499 |
.os-file-tile__visual { |
| 500 |
display: inline-flex; |
| 501 |
align-items: center; |
| 502 |
justify-content: center; |
| 503 |
width: 48px; |
| 504 |
height: 48px; |
| 505 |
font-size: 32px; |
| 506 |
line-height: 1; |
| 507 |
} |
| 508 |
|
| 509 |
.os-file-tile__icon { |
| 510 |
display: inline-flex; |
| 511 |
align-items: center; |
| 512 |
justify-content: center; |
| 513 |
width: 48px; |
| 514 |
height: 48px; |
| 515 |
font-size: 32px; |
| 516 |
line-height: 1; |
| 517 |
} |
| 518 |
|
| 519 |
.os-file-tile__preview { |
| 520 |
width: 48px; |
| 521 |
height: 48px; |
| 522 |
object-fit: cover; |
| 523 |
border-radius: 4px; |
| 524 |
} |
| 525 |
|
| 526 |
/* `<img>` defaults to `draggable=true`, which lets the browser |
| 527 |
* start a native HTML5 image-drag from a pointerdown — that |
| 528 |
* pre-empts the DragManager's pointer-event-driven tile rearrange |
| 529 |
* and the tile silently refuses to move. The renderer also sets |
| 530 |
* `draggable="false"` on every `<img>` it produces; this CSS rule |
| 531 |
* covers anything injected later (plugin filters, decorators) |
| 532 |
* without having to remember the attribute. */ |
| 533 |
.os-file-tile img { |
| 534 |
-webkit-user-drag: none; |
| 535 |
user-select: none; |
| 536 |
} |
| 537 |
|
| 538 |
/* |
| 539 |
* Wallpaper + tile context menus use `<os-context-menu>` and |
| 540 |
* `<os-context-menu-option>` (Phase 4 / 0.9.0). Their host-level |
| 541 |
* styles live inside the component shadow DOM — see |
| 542 |
* `src/ui/components/os-context-menu/os-context-menu.styles.ts`. |
| 543 |
* No project CSS rules needed here; the legacy `.os- |
| 544 |
* wallpaper-menu*` rules were removed when the components landed. |
| 545 |
*/ |
| 546 |
|
| 547 |
/* |
| 548 |
* Folder native window — the body of `os-folder-<id>` |
| 549 |
* contains a `FilesLayer` rendering that folder's contents. |
| 550 |
* We ensure the body has positioning context so absolutely- |
| 551 |
* positioned tiles inside the layer anchor correctly. |
| 552 |
*/ |
| 553 |
.desktop-mode-folder-window { |
| 554 |
position: relative; |
| 555 |
min-height: 200px; |
| 556 |
/* Same window background recipe as My WordPress — defaults to |
| 557 |
* the standard window bg (#fff) and themable per-window. */ |
| 558 |
background: var( |
| 559 |
--os-folder-window-bg, |
| 560 |
var( --os-window-bg, #fff ) |
| 561 |
); |
| 562 |
color: var( --os-fg-on-light, var( --os-ui-fg, #1d2327 ) ); |
| 563 |
display: flex; |
| 564 |
flex-direction: column; |
| 565 |
/* Light-on-dark context: the folder window opens inside a |
| 566 |
* standard window chrome whose body is white-ish. Retint the |
| 567 |
* tile color tokens here so dashicons + labels read against |
| 568 |
* a light background. Plugins / themes can override these |
| 569 |
* same variables at any scope to retint further. */ |
| 570 |
--os-tile-fg: var( --os-fg-on-light, var( --os-ui-fg, #1d2327 ) ); |
| 571 |
/* Chains through the palette for the same reason as |
| 572 |
* `--os-tile-fg` beside it: a bare literal here outranks the |
| 573 |
* palette's own value and pins every tile's secondary line to |
| 574 |
* near-black, which disappears the moment the surface is dark. |
| 575 |
* The literal stays as the pre-brand floor. */ |
| 576 |
--os-tile-fg-muted: var( |
| 577 |
--os-folder-window-fg-muted, |
| 578 |
var( --os-ui-fg-muted, rgba( 0, 0, 0, 0.55 ) ) |
| 579 |
); |
| 580 |
--os-tile-hover-bg: rgba( 0, 0, 0, 0.06 ); |
| 581 |
/* Light-context label rendering — share the same recipe with |
| 582 |
* every other light-bg tile surface (My WordPress, future |
| 583 |
* windows). One set of tokens, no per-surface duplication. */ |
| 584 |
--os-tile-label-shadow: none; |
| 585 |
--os-tile-label-weight: 500; |
| 586 |
--os-tile-label-smoothing: antialiased; |
| 587 |
--os-tile-label-color: var( --os-fg-on-light, var( --os-ui-fg, #1d2327 ) ); |
| 588 |
} |
| 589 |
|
| 590 |
/* Folder window's bottom status bar — paint with the same light- |
| 591 |
* context treatment My WordPress uses (transparent bg, soft border, |
| 592 |
* muted dark text). Without this, the wallpaper-targeted defaults |
| 593 |
* (`background: rgba(0,0,0,0.18); color: white`) would show as a |
| 594 |
* dark band against the white window bg. */ |
| 595 |
.desktop-mode-folder-window .os-folder-status-bar { |
| 596 |
border-top: 1px solid var( --os-ui-border, #dcdcde ); |
| 597 |
background: transparent; |
| 598 |
color: var( --os-ui-fg-muted, #50575e ); |
| 599 |
} |
| 600 |
|
| 601 |
.desktop-mode-folder-window |
| 602 |
.os-folder-status-bar |
| 603 |
button.os-folder-status-bar__segment:hover { |
| 604 |
background: var( --os-hover, var( --os-ui-hover, rgba( 0, 0, 0, 0.06 ) ) ); |
| 605 |
} |
| 606 |
|
| 607 |
/* Generic breadcrumb header — any surface that grows a navigation |
| 608 |
* stack (folder window, My WordPress folder, future drill-down |
| 609 |
* windows) renders one of these via `renderBreadcrumbs` from |
| 610 |
* `src/desktop-files/breadcrumbs.ts`. Visual vocabulary: small |
| 611 |
* icon-only Back button on the left, `›`-separated crumb buttons, |
| 612 |
* current segment is bold + non-interactive. |
| 613 |
* |
| 614 |
* @since 0.8.0 |
| 615 |
*/ |
| 616 |
.os-breadcrumbs { |
| 617 |
display: flex; |
| 618 |
align-items: center; |
| 619 |
gap: 8px; |
| 620 |
padding: 6px 10px; |
| 621 |
border-bottom: 1px solid var( --os-ui-border, #dcdcde ); |
| 622 |
background: transparent; |
| 623 |
flex: 0 0 auto; |
| 624 |
} |
| 625 |
|
| 626 |
.os-breadcrumbs__back { |
| 627 |
display: inline-flex; |
| 628 |
align-items: center; |
| 629 |
justify-content: center; |
| 630 |
width: 26px; |
| 631 |
height: 26px; |
| 632 |
padding: 0; |
| 633 |
border: 0; |
| 634 |
background: transparent; |
| 635 |
border-radius: 4px; |
| 636 |
cursor: pointer; |
| 637 |
color: inherit; |
| 638 |
flex: 0 0 auto; |
| 639 |
} |
| 640 |
|
| 641 |
.os-breadcrumbs__back:hover:not(:disabled), |
| 642 |
.os-breadcrumbs__back:focus-visible:not(:disabled) { |
| 643 |
background: var( --os-hover, var( --os-ui-hover, rgba( 0, 0, 0, 0.06 ) ) ); |
| 644 |
outline: none; |
| 645 |
} |
| 646 |
|
| 647 |
.os-breadcrumbs__back:disabled { |
| 648 |
opacity: 0.35; |
| 649 |
cursor: default; |
| 650 |
} |
| 651 |
|
| 652 |
.os-breadcrumbs__back .dashicons { |
| 653 |
font-size: 18px; |
| 654 |
width: 18px; |
| 655 |
height: 18px; |
| 656 |
line-height: 1; |
| 657 |
} |
| 658 |
|
| 659 |
.os-breadcrumbs__crumbs { |
| 660 |
display: flex; |
| 661 |
align-items: center; |
| 662 |
gap: 6px; |
| 663 |
font-size: 13px; |
| 664 |
min-width: 0; |
| 665 |
overflow: hidden; |
| 666 |
text-overflow: ellipsis; |
| 667 |
white-space: nowrap; |
| 668 |
} |
| 669 |
|
| 670 |
.os-breadcrumbs__crumb { |
| 671 |
border: 0; |
| 672 |
background: transparent; |
| 673 |
color: var( --os-link, var( --os-ui-accent, #2271b1 ) ); |
| 674 |
cursor: pointer; |
| 675 |
padding: 2px 4px; |
| 676 |
border-radius: 4px; |
| 677 |
font: inherit; |
| 678 |
} |
| 679 |
|
| 680 |
.os-breadcrumbs__crumb:hover { |
| 681 |
background: var( --os-hover, var( --os-ui-hover, rgba( 0, 0, 0, 0.06 ) ) ); |
| 682 |
} |
| 683 |
|
| 684 |
.os-breadcrumbs__crumb--current { |
| 685 |
color: var( --os-fg-on-light, var( --os-ui-fg, #1d2327 ) ); |
| 686 |
cursor: default; |
| 687 |
font-weight: 600; |
| 688 |
} |
| 689 |
|
| 690 |
.os-breadcrumbs__crumb--current:hover { |
| 691 |
background: transparent; |
| 692 |
} |
| 693 |
|
| 694 |
.os-breadcrumbs__sep { |
| 695 |
color: var( --os-ui-fg-muted, #787c82 ); |
| 696 |
} |
| 697 |
|
| 698 |
.os-folder-window__layer { |
| 699 |
position: relative; |
| 700 |
flex: 1; |
| 701 |
min-height: 0; |
| 702 |
overflow: auto; |
| 703 |
} |
| 704 |
|
| 705 |
/* Two-pane shell — left: tile canvas, right: preview pane that |
| 706 |
* reflects the currently-selected tile. Same model as the My |
| 707 |
* WordPress two-pane view so the UX is unified. |
| 708 |
* |
| 709 |
* @since 0.8.0 */ |
| 710 |
.os-folder-window__split { |
| 711 |
display: grid; |
| 712 |
grid-template-columns: minmax( 240px, 60% ) minmax( 0, 1fr ); |
| 713 |
flex: 1 1 auto; |
| 714 |
min-height: 0; |
| 715 |
} |
| 716 |
|
| 717 |
.os-folder-window__preview { |
| 718 |
overflow-y: auto; |
| 719 |
min-width: 0; |
| 720 |
background: var( --os-window-bg, #fff ); |
| 721 |
border-inline-start: 1px solid var( --os-ui-border, #dcdcde ); |
| 722 |
color: var( --os-fg-on-light, var( --os-ui-fg, #1d2327 ) ); |
| 723 |
} |
| 724 |
|
| 725 |
/* Selected file tile — light-blue accent on the wallpaper, soft |
| 726 |
* accent inside light-context windows. Variable-driven so plugins |
| 727 |
* can retune. */ |
| 728 |
.os-file-tile--selected, |
| 729 |
.os-file-tile--selected:hover { |
| 730 |
background: var( |
| 731 |
--os-tile-selected-bg, |
| 732 |
rgba( 34, 113, 177, 0.18 ) |
| 733 |
); |
| 734 |
box-shadow: inset 0 0 0 1px |
| 735 |
var( --os-tile-focus-ring, var( --wp-admin-theme-color, #2271b1 ) ); |
| 736 |
} |
| 737 |
|
| 738 |
/* Inside a folder window the selected highlight should read on |
| 739 |
* white. */ |
| 740 |
.desktop-mode-folder-window { |
| 741 |
--os-tile-selected-bg: rgba( 34, 113, 177, 0.12 ); |
| 742 |
} |
| 743 |
|
| 744 |
/* Type breakdown under a multi-selection's count in a folder |
| 745 |
* window's preview pane. The count is the headline; this is the |
| 746 |
* detail that tells you which actions the menu will offer. */ |
| 747 |
.os-files-preview__selection-breakdown { |
| 748 |
font-size: 12px; |
| 749 |
opacity: 0.75; |
| 750 |
} |
| 751 |
|
| 752 |
/* While a rubber band is live, nothing in the shell is selectable. |
| 753 |
* |
| 754 |
* The band starts on bare canvas, which — unlike a tile — IS |
| 755 |
* selectable, so the browser begins its own text selection on the |
| 756 |
* same press and keeps extending it as the pointer travels. Inside |
| 757 |
* the canvas that stays invisible; drag out over another window and |
| 758 |
* it starts highlighting that window's text in blue, mid-gesture. |
| 759 |
* |
| 760 |
* The controller also refuses `selectstart` outright while the band |
| 761 |
* is up. This rule is the second half: it covers anything already |
| 762 |
* selectable that the event route misses. */ |
| 763 |
body[ data-os-marquee ] { |
| 764 |
-webkit-user-select: none; |
| 765 |
user-select: none; |
| 766 |
} |
| 767 |
|
| 768 |
/* Marquee (rubber-band) selection box. Painted by the shared |
| 769 |
* selection controller on any canvas that opts into it — the |
| 770 |
* wallpaper, folder windows, every My WordPress list — so this one |
| 771 |
* declaration dresses all of them. |
| 772 |
* |
| 773 |
* `pointer-events: none` is load-bearing: the box tracks under the |
| 774 |
* cursor for the whole gesture, and without it every hit-test |
| 775 |
* (drop targets, the controller's own tile lookup) would land on |
| 776 |
* the box instead of what's beneath it. */ |
| 777 |
.os-selection-marquee { |
| 778 |
position: absolute; |
| 779 |
z-index: 5; |
| 780 |
pointer-events: none; |
| 781 |
border: 1px solid |
| 782 |
var( --os-tile-focus-ring, var( --wp-admin-theme-color, #2271b1 ) ); |
| 783 |
border-radius: 2px; |
| 784 |
/* A WASH, not a fill — the whole point of a rubber band is that |
| 785 |
* you can see what it is about to catch. `--os-ui-accent-dim` is a |
| 786 |
* solid hex (Pulse, one step back), so reaching for it directly |
| 787 |
* paints an opaque rectangle over the icons; `--os-ui-accent-soft` |
| 788 |
* is that same colour already taken down to a 14% wash, which is |
| 789 |
* what every other ambient accent surface in the shell uses. */ |
| 790 |
background: var( |
| 791 |
--os-selection-marquee-bg, |
| 792 |
var( --os-ui-accent-soft, rgba( 34, 113, 177, 0.14 ) ) |
| 793 |
); |
| 794 |
} |
| 795 |
|
| 796 |
.os-folder-status-bar { |
| 797 |
display: flex; |
| 798 |
justify-content: space-between; |
| 799 |
align-items: center; |
| 800 |
gap: 12px; |
| 801 |
padding: 6px 12px; |
| 802 |
border-top: 1px solid rgba( 255, 255, 255, 0.08 ); |
| 803 |
background: var( --os-ui-scrim, rgba( 0, 0, 0, 0.18 ) ); |
| 804 |
color: var( --os-fg-muted, rgba( 255, 255, 255, 0.7 ) ); |
| 805 |
font-size: 12px; |
| 806 |
flex-shrink: 0; |
| 807 |
} |
| 808 |
|
| 809 |
.os-folder-status-bar__cluster { |
| 810 |
display: flex; |
| 811 |
align-items: center; |
| 812 |
gap: 12px; |
| 813 |
} |
| 814 |
|
| 815 |
.os-folder-status-bar__segment { |
| 816 |
display: inline-flex; |
| 817 |
align-items: center; |
| 818 |
gap: 4px; |
| 819 |
padding: 0; |
| 820 |
border: 0; |
| 821 |
background: transparent; |
| 822 |
color: inherit; |
| 823 |
font: inherit; |
| 824 |
} |
| 825 |
|
| 826 |
button.os-folder-status-bar__segment { |
| 827 |
cursor: pointer; |
| 828 |
border-radius: 4px; |
| 829 |
padding: 2px 6px; |
| 830 |
} |
| 831 |
|
| 832 |
button.os-folder-status-bar__segment:hover { |
| 833 |
background: rgba( 255, 255, 255, 0.1 ); |
| 834 |
} |
| 835 |
|
| 836 |
.os-folder-status-bar__icon { |
| 837 |
font-size: 14px; |
| 838 |
} |
| 839 |
|
| 840 |
/* |
| 841 |
* File Associations OS Settings tab (Phase 5). One row per file |
| 842 |
* type: label on the left, a `<os-select>` of compatible |
| 843 |
* openers on the right. Color tokens use the OS-Settings-canonical |
| 844 |
* `--os-ui-fg-muted` so the tab inherits theme colors instead of |
| 845 |
* falling back to white-on-light invisibility. |
| 846 |
*/ |
| 847 |
.os-file-associations__intro { |
| 848 |
display: block; |
| 849 |
margin: 0 0 16px; |
| 850 |
color: var( --os-ui-fg-muted, #50575e ); |
| 851 |
font-size: 13px; |
| 852 |
line-height: 1.5; |
| 853 |
} |
| 854 |
|
| 855 |
.os-file-associations__list { |
| 856 |
display: flex; |
| 857 |
flex-direction: column; |
| 858 |
gap: 8px; |
| 859 |
} |
| 860 |
|
| 861 |
.os-file-associations__row { |
| 862 |
display: flex; |
| 863 |
align-items: center; |
| 864 |
justify-content: space-between; |
| 865 |
gap: 16px; |
| 866 |
padding: 8px 12px; |
| 867 |
background: var( --os-ui-hover, rgba( 0, 0, 0, 0.04 ) ); |
| 868 |
border-radius: 6px; |
| 869 |
} |
| 870 |
|
| 871 |
.os-file-associations__label { |
| 872 |
font-weight: 500; |
| 873 |
flex: 1; |
| 874 |
} |
| 875 |
|
| 876 |
.os-file-associations__select { |
| 877 |
min-width: 220px; |
| 878 |
} |
| 879 |
|
| 880 |
.os-file-associations__none, |
| 881 |
.os-file-associations__empty { |
| 882 |
color: var( --os-ui-fg-muted, #50575e ); |
| 883 |
font-size: 12px; |
| 884 |
} |
| 885 |
|
| 886 |
/* |
| 887 |
* The shell's two built-in modals — "New folder" / "Rename", and the |
| 888 |
* web-link dialog, which reuses this class set for its surface. Both |
| 889 |
* are light-DOM overlays that slot `<os-text-field>` + |
| 890 |
* `<os-button>` for their controls. |
| 891 |
* |
| 892 |
* The surface reads the same `--os-ui-modal-*` family that |
| 893 |
* `<os-modal>` and `<os-confirm-dialog>` do, so the three look like |
| 894 |
* one dialog system and answer to a desktop theme together. Two |
| 895 |
* things it must NOT do, both of which it used to: |
| 896 |
* |
| 897 |
* - Paint from `--os-bg`. That is the WALLPAPER token: the desk |
| 898 |
* default is a gradient and the wallpaper layer overwrites it |
| 899 |
* with whatever artwork is active. A dialog is a surface, not a |
| 900 |
* desk. Same note as `os-modal.styles.ts`. |
| 901 |
* - Style raw form controls. Core's `forms.css` reaches every |
| 902 |
* `<input>` in the parent shell through `input[type="text"]` — |
| 903 |
* (0,1,1), which outranks a single class of ours — so a plain |
| 904 |
* input rendered as a white core-chrome box on this dark |
| 905 |
* surface no matter what we declared. The controls live in |
| 906 |
* shadow DOM now, where that sheet cannot follow. |
| 907 |
*/ |
| 908 |
.os-create-folder-dialog__overlay { |
| 909 |
position: fixed; |
| 910 |
inset: 0; |
| 911 |
background: var( --os-ui-scrim, rgba( 0, 0, 0, 0.45 ) ); |
| 912 |
/* Desktop-theme texture slot: unset resolves to none. Mirrors |
| 913 |
the scrim treatment in os-modal. */ |
| 914 |
background-image: var( --os-ui-scrim-image, none ); |
| 915 |
background-repeat: var( --os-ui-scrim-image-repeat, repeat ); |
| 916 |
background-size: var( --os-ui-scrim-image-size, auto ); |
| 917 |
background-position: var( --os-ui-scrim-image-position, center ); |
| 918 |
backdrop-filter: blur( 2px ); |
| 919 |
display: flex; |
| 920 |
align-items: center; |
| 921 |
justify-content: center; |
| 922 |
z-index: 10000; |
| 923 |
} |
| 924 |
|
| 925 |
.os-create-folder-dialog { |
| 926 |
/* |
| 927 |
* Re-point the shared control tokens for a dark dialog surface, |
| 928 |
* exactly as `<os-modal>` does on its host. The slotted |
| 929 |
* `<os-text-field>` / `<os-button>` inherit these across the |
| 930 |
* shadow boundary, so the field resolves a dark input with light |
| 931 |
* ink instead of the light-admin defaults (`--os-window-bg` is |
| 932 |
* `#fff` in an unthemed shell, which under a light `--os-ui-fg` |
| 933 |
* is the invisible-value trap the modal styles call out). |
| 934 |
* |
| 935 |
* Each reads a palette-owned `--os-ui-modal-*` name first, so a |
| 936 |
* desktop theme can still reach every one of them; the literals |
| 937 |
* are only the floor for a shell whose stylesheet never loaded. |
| 938 |
*/ |
| 939 |
--os-ui-fg: var( --os-ui-modal-text, #f0f0f1 ); |
| 940 |
--os-ui-fg-muted: var( --os-ui-modal-text-muted, #a7aaad ); |
| 941 |
--os-ui-border: var( --os-ui-modal-border, rgba( 255, 255, 255, 0.25 ) ); |
| 942 |
--os-window-bg: var( --os-ui-modal-field-bg, #2c3338 ); |
| 943 |
--os-ui-button-bg-hover: var( |
| 944 |
--os-ui-modal-button-bg-hover, |
| 945 |
rgba( 255, 255, 255, 0.08 ) |
| 946 |
); |
| 947 |
|
| 948 |
width: min( 420px, 92vw ); |
| 949 |
/* Longhand, and a literal fallback: the shorthand would reset the |
| 950 |
texture slot below, and a gradient is invalid as a |
| 951 |
background-color — it would leave the dialog transparent. */ |
| 952 |
background-color: var( --os-ui-modal-bg, #1d2327 ); |
| 953 |
background-image: var( --os-ui-dialog-bg-image, none ); |
| 954 |
background-repeat: var( --os-ui-dialog-bg-image-repeat, repeat ); |
| 955 |
background-size: var( --os-ui-dialog-bg-image-size, auto ); |
| 956 |
background-position: var( --os-ui-dialog-bg-image-position, center ); |
| 957 |
color: var( --os-ui-modal-fg, var( --os-fg, #fff ) ); |
| 958 |
border: 1px solid var( --os-ui-border, rgba( 255, 255, 255, 0.08 ) ); |
| 959 |
border-radius: 10px; |
| 960 |
box-shadow: 0 20px 50px rgba( 0, 0, 0, 0.6 ); |
| 961 |
padding: 20px 22px 18px; |
| 962 |
display: flex; |
| 963 |
flex-direction: column; |
| 964 |
gap: 10px; |
| 965 |
} |
| 966 |
|
| 967 |
.os-create-folder-dialog__title { |
| 968 |
margin: 0 0 4px; |
| 969 |
font-size: 16px; |
| 970 |
font-weight: 600; |
| 971 |
color: var( --os-ui-fg, #f0f0f1 ); |
| 972 |
} |
| 973 |
|
| 974 |
.os-url-dialog__description { |
| 975 |
margin: 0 0 4px; |
| 976 |
font-size: 12px; |
| 977 |
line-height: 1.5; |
| 978 |
color: var( --os-ui-fg-muted, #a7aaad ); |
| 979 |
} |
| 980 |
|
| 981 |
.os-create-folder-dialog__error { |
| 982 |
margin: 0; |
| 983 |
color: var( --os-ui-danger-hover, #ff8a8a ); |
| 984 |
font-size: 12px; |
| 985 |
} |
| 986 |
|
| 987 |
.os-create-folder-dialog__actions { |
| 988 |
display: flex; |
| 989 |
justify-content: flex-end; |
| 990 |
gap: 8px; |
| 991 |
margin-top: 6px; |
| 992 |
} |
| 993 |
|
| 994 |
/* The busy state dims the whole form area rather than the input |
| 995 |
alone — the buttons carry their own disabled treatment from |
| 996 |
os-button, and dimming one control of three read as a glitch. */ |
| 997 |
.os-create-folder-dialog--busy .os-create-folder-dialog__field { |
| 998 |
opacity: 0.7; |
| 999 |
} |
| 1000 |
|
| 1001 |
/* |
| 1002 |
* Single label rule for every tile, regardless of host surface. |
| 1003 |
* Visual properties read from `--os-tile-label-*` |
| 1004 |
* tokens — defined in `variables.css` for the dark wallpaper and |
| 1005 |
* rebound by any light-context scope (folder window, My |
| 1006 |
* WordPress, …) so all surfaces share one source of truth. |
| 1007 |
*/ |
| 1008 |
.os-file-tile__label { |
| 1009 |
font-size: 12px; |
| 1010 |
line-height: 1.2; |
| 1011 |
/* Follows the tile rather than restating its width, so a section |
| 1012 |
* that re-points `--os-tile-w` (image-led sections do) gets a |
| 1013 |
* label that grows with the tile instead of staying narrow. */ |
| 1014 |
max-width: calc( var( --os-tile-w, 88px ) - 4px ); |
| 1015 |
text-align: center; |
| 1016 |
overflow: hidden; |
| 1017 |
text-overflow: ellipsis; |
| 1018 |
display: -webkit-box; |
| 1019 |
-webkit-line-clamp: 2; |
| 1020 |
-webkit-box-orient: vertical; |
| 1021 |
color: var( --os-tile-label-color, var( --os-tile-fg, #fff ) ); |
| 1022 |
font-weight: var( --os-tile-label-weight, 400 ); |
| 1023 |
text-shadow: var( --os-tile-label-shadow, 0 1px 2px rgba( 0, 0, 0, 0.5 ) ); |
| 1024 |
-webkit-font-smoothing: var( --os-tile-label-smoothing, auto ); |
| 1025 |
-moz-osx-font-smoothing: grayscale; |
| 1026 |
} |
| 1027 |
|
| 1028 |
/* |
| 1029 |
* DragManager ghost element (the visual that follows the cursor |
| 1030 |
* during a drag). Cloned from the source by `mountGhost()` and |
| 1031 |
* pinned to the body with `position: fixed` + transform-based |
| 1032 |
* movement. `pointer-events: none` ensures the ghost itself |
| 1033 |
* never obscures the drop targets underneath during hit-testing. |
| 1034 |
* |
| 1035 |
* The accept / reject classes are toggled on each hover transition |
| 1036 |
* so the cursor matches the drop semantics: `copy` over an |
| 1037 |
* accepting target, `no-drop` over rejecting (or no) target. |
| 1038 |
* |
| 1039 |
* @since 0.18.0 |
| 1040 |
*/ |
| 1041 |
.os-drag-ghost { |
| 1042 |
box-shadow: 0 6px 24px rgba( 0, 0, 0, 0.45 ); |
| 1043 |
border-radius: 6px; |
| 1044 |
opacity: 0.95; |
| 1045 |
transition: none; |
| 1046 |
} |
| 1047 |
|
| 1048 |
.os-drag-ghost--accept { |
| 1049 |
cursor: copy; |
| 1050 |
} |
| 1051 |
|
| 1052 |
.os-drag-ghost--reject { |
| 1053 |
cursor: no-drop; |
| 1054 |
} |
| 1055 |
|
| 1056 |
/* Multi-item drag ghost: the grabbed tile, with the rest of the set |
| 1057 |
* implied by two offset cards behind it and stated by a count badge. |
| 1058 |
* The stack is drawn with pseudo-elements rather than real clones — |
| 1059 |
* three cloned tiles cost three subtree copies per lift, and only |
| 1060 |
* the top one is ever legible. */ |
| 1061 |
.os-drag-stack { |
| 1062 |
/* The drag manager overwrites this with `position: fixed` when it |
| 1063 |
* mounts the ghost. It stays declared so the stack still composes |
| 1064 |
* correctly if the helper is used outside a live drag — both |
| 1065 |
* values establish the containing block the cards and the badge |
| 1066 |
* are placed against. */ |
| 1067 |
position: relative; |
| 1068 |
} |
| 1069 |
|
| 1070 |
.os-drag-stack::before, |
| 1071 |
.os-drag-stack::after { |
| 1072 |
content: ''; |
| 1073 |
position: absolute; |
| 1074 |
inset: 0; |
| 1075 |
z-index: -1; |
| 1076 |
border-radius: 6px; |
| 1077 |
background: var( --os-ui-surface, rgba( 30, 30, 40, 0.9 ) ); |
| 1078 |
box-shadow: 0 4px 14px rgba( 0, 0, 0, 0.35 ); |
| 1079 |
} |
| 1080 |
|
| 1081 |
.os-drag-stack::before { |
| 1082 |
transform: translate( 6px, 6px ); |
| 1083 |
opacity: 0.7; |
| 1084 |
} |
| 1085 |
|
| 1086 |
.os-drag-stack::after { |
| 1087 |
transform: translate( 12px, 12px ); |
| 1088 |
opacity: 0.45; |
| 1089 |
} |
| 1090 |
|
| 1091 |
.os-drag-stack__count { |
| 1092 |
position: absolute; |
| 1093 |
inset-block-start: -8px; |
| 1094 |
inset-inline-end: -8px; |
| 1095 |
z-index: 1; |
| 1096 |
min-width: 20px; |
| 1097 |
height: 20px; |
| 1098 |
padding: 0 6px; |
| 1099 |
box-sizing: border-box; |
| 1100 |
display: flex; |
| 1101 |
align-items: center; |
| 1102 |
justify-content: center; |
| 1103 |
border-radius: 10px; |
| 1104 |
background: var( |
| 1105 |
--os-ui-accent, |
| 1106 |
var( --wp-admin-theme-color, #2271b1 ) |
| 1107 |
); |
| 1108 |
color: #fff; |
| 1109 |
font-size: 11px; |
| 1110 |
font-weight: 600; |
| 1111 |
line-height: 1; |
| 1112 |
box-shadow: 0 2px 8px rgba( 0, 0, 0, 0.4 ); |
| 1113 |
} |
| 1114 |
|
| 1115 |
/* ============================================================ * |
| 1116 |
* Upload progress HUD (since 0.31.0) |
| 1117 |
* |
| 1118 |
* Pinned floating panel that surfaces the OS-file-drop manager's |
| 1119 |
* in-flight uploads. One row per file with a `<os-progress-bar>`, |
| 1120 |
* a filename + status, and a Cancel/Dismiss action. The panel |
| 1121 |
* hides itself when there are no rows; it's purely reactive to |
| 1122 |
* the four upload-lifecycle hooks. |
| 1123 |
* ============================================================ */ |
| 1124 |
|
| 1125 |
.os-upload-hud { |
| 1126 |
position: fixed; |
| 1127 |
inset-block-end: 16px; |
| 1128 |
inset-inline-end: 16px; |
| 1129 |
width: 320px; |
| 1130 |
max-width: calc( 100vw - 32px ); |
| 1131 |
background: var( --os-panel-bg, var( --os-ui-surface, #fff ) ); |
| 1132 |
color: var( --os-fg-on-light, var( --os-ui-fg, #1d2327 ) ); |
| 1133 |
border: 1px solid var( --os-ui-border, #dcdcde ); |
| 1134 |
border-radius: 10px; |
| 1135 |
box-shadow: 0 8px 28px rgba( 0, 0, 0, 0.18 ); |
| 1136 |
z-index: 99998; |
| 1137 |
font: inherit; |
| 1138 |
pointer-events: auto; |
| 1139 |
overflow: hidden; |
| 1140 |
} |
| 1141 |
|
| 1142 |
.os-upload-hud[ hidden ] { |
| 1143 |
display: none; |
| 1144 |
} |
| 1145 |
|
| 1146 |
.os-upload-hud__header { |
| 1147 |
display: flex; |
| 1148 |
align-items: center; |
| 1149 |
justify-content: space-between; |
| 1150 |
gap: 8px; |
| 1151 |
padding: 8px 12px; |
| 1152 |
background: var( --os-panel-header-bg, var( --os-ui-hover, rgba( 0, 0, 0, 0.04 ) ) ); |
| 1153 |
border-block-end: 1px solid var( --os-ui-border, #dcdcde ); |
| 1154 |
font-weight: 600; |
| 1155 |
font-size: 12px; |
| 1156 |
} |
| 1157 |
|
| 1158 |
.os-upload-hud__title { |
| 1159 |
min-width: 0; |
| 1160 |
overflow: hidden; |
| 1161 |
text-overflow: ellipsis; |
| 1162 |
white-space: nowrap; |
| 1163 |
} |
| 1164 |
|
| 1165 |
.os-upload-hud__close { |
| 1166 |
border: 0; |
| 1167 |
background: transparent; |
| 1168 |
color: inherit; |
| 1169 |
font-size: 16px; |
| 1170 |
line-height: 1; |
| 1171 |
width: 22px; |
| 1172 |
height: 22px; |
| 1173 |
border-radius: 4px; |
| 1174 |
cursor: pointer; |
| 1175 |
display: inline-flex; |
| 1176 |
align-items: center; |
| 1177 |
justify-content: center; |
| 1178 |
} |
| 1179 |
.os-upload-hud__close:hover, |
| 1180 |
.os-upload-hud__close:focus-visible { |
| 1181 |
background: var( --os-ui-hover, rgba( 0, 0, 0, 0.08 ) ); |
| 1182 |
outline: none; |
| 1183 |
} |
| 1184 |
|
| 1185 |
.os-upload-hud__list { |
| 1186 |
display: flex; |
| 1187 |
flex-direction: column; |
| 1188 |
gap: 10px; |
| 1189 |
padding: 10px 12px; |
| 1190 |
max-height: 50vh; |
| 1191 |
overflow-y: auto; |
| 1192 |
} |
| 1193 |
|
| 1194 |
.os-upload-hud__row { |
| 1195 |
display: grid; |
| 1196 |
grid-template-columns: 1fr auto; |
| 1197 |
grid-template-areas: |
| 1198 |
'meta actions' |
| 1199 |
'bar bar'; |
| 1200 |
gap: 6px 8px; |
| 1201 |
align-items: center; |
| 1202 |
font-size: 12px; |
| 1203 |
} |
| 1204 |
|
| 1205 |
.os-upload-hud__meta { |
| 1206 |
grid-area: meta; |
| 1207 |
min-width: 0; |
| 1208 |
display: flex; |
| 1209 |
flex-direction: column; |
| 1210 |
gap: 2px; |
| 1211 |
} |
| 1212 |
|
| 1213 |
.os-upload-hud__name { |
| 1214 |
overflow: hidden; |
| 1215 |
text-overflow: ellipsis; |
| 1216 |
white-space: nowrap; |
| 1217 |
font-weight: 500; |
| 1218 |
} |
| 1219 |
|
| 1220 |
.os-upload-hud__status { |
| 1221 |
font-size: 11px; |
| 1222 |
opacity: 0.75; |
| 1223 |
font-variant-numeric: tabular-nums; |
| 1224 |
} |
| 1225 |
|
| 1226 |
.os-upload-hud__row os-progress-bar { |
| 1227 |
grid-area: bar; |
| 1228 |
} |
| 1229 |
|
| 1230 |
.os-upload-hud__actions { |
| 1231 |
grid-area: actions; |
| 1232 |
display: inline-flex; |
| 1233 |
gap: 4px; |
| 1234 |
} |
| 1235 |
|