/** * OpenStation — Files-on-the-Desktop tile + layer styles. * * The layer is an absolutely-positioned container inside * `#os-area`; tiles inside use absolute positioning * keyed off `(x, y)` coordinates persisted server-side. * * Tiles are intentionally close to the look of `os-icon` * (the plugin-shortcut tiles) so the desktop reads as one * coherent surface — but they live in their own class namespace * (`os-file-tile`) so plugins can theme each rail * independently. * * @since 0.9.0 */ /* * Unified rail (since 0.9.0): plugin shortcuts now ride on the * same files layer as folders / posts / etc — they're a * `'shortcut'` file type. The legacy `.os-icons` DIV * still renders for backwards compatibility (existing tests and * non-files-active code paths), but when a files layer is * mounted as a sibling we hide it so we don't end up with two * rails competing for the same wallpaper. */ #os-area:has( > .os-files-layer ) > .os-icons { display: none; } /* * Drop-target highlight — the `data-files-drop-active` attribute * is set on the FilesLayer host (desktop area or folder window * body) while a shortcut payload is being dragged over it. * * Marching-ants animation makes the affordance unmissable even * against the busiest wallpaper, and the soft inner glow draws * the eye when the user is "shopping" for a drop target. Both * effects respect prefers-reduced-motion below. * * @since 0.18.0; visuals strengthened in 0.20.0. */ @keyframes os-drop-marching-ants { to { background-position: 24px 0, -24px 0, 0 24px, 0 -24px; } } [ data-files-drop-active ] { position: relative; outline: 3px dashed var( --wp-admin-theme-color, #2271b1 ); outline-offset: -8px; background-image: linear-gradient( 90deg, rgba( 34, 113, 177, 0.45 ) 50%, transparent 0 ), linear-gradient( 90deg, rgba( 34, 113, 177, 0.45 ) 50%, transparent 0 ), linear-gradient( 0deg, rgba( 34, 113, 177, 0.45 ) 50%, transparent 0 ), linear-gradient( 0deg, rgba( 34, 113, 177, 0.45 ) 50%, transparent 0 ); background-repeat: repeat-x, repeat-x, repeat-y, repeat-y; background-size: 12px 3px, 12px 3px, 3px 12px, 3px 12px; background-position: 0 6px, 0 calc( 100% - 6px ), 6px 0, calc( 100% - 6px ) 0; box-shadow: inset 0 0 32px 4px rgba( 34, 113, 177, 0.12 ); animation: os-drop-marching-ants 0.6s linear infinite; transition: box-shadow 0.18s ease-out; } @media ( prefers-reduced-motion: reduce ) { [ data-files-drop-active ] { animation: none; } } .os-files-layer { position: absolute; inset: 0; z-index: 0; /* * Layer doesn't intercept pointer events itself — tiles and * the wallpaper own that. Phase 4's wallpaper context menu * binds clicks on the parent `#os-area`, which is * unblocked because of this rule. */ pointer-events: none; } /* * Live drop-cell preview. A soft outline that hovers at the cell * the dropped tile will land in, so the user can predict where the * grid snap will place it before releasing. Positioned absolutely * inside the files layer; `translate3d` updated from JS on every * pointermove while a desktop-file drag hovers the canvas. * * The tile dimensions are baked into `grid.ts` (88 × 96 visual, * 96 × 110 cell pitch). The outline matches the cell pitch — slightly * larger than the tile — so the highlight reads as "this slot" rather * than "this tile." * * @since 0.20.0 */ .os-files-drop-preview { position: absolute; top: 0; left: 0; width: 88px; height: 96px; pointer-events: none; box-sizing: border-box; border-radius: 12px; background: var( --os-drop-preview-bg, rgba( 34, 113, 177, 0.08 ) ); border: 2px dashed var( --os-drop-preview-border, rgba( 34, 113, 177, 0.55 ) ); transition: transform 90ms ease-out; will-change: transform; /* Sit BELOW tiles so a tile being dragged over the cell isn't * visually clipped by the dashed outline; the outline still reads * because the tile is semi-transparent during drag (`--dragging` * sets `opacity: 0.4`). */ z-index: 0; } @media ( prefers-reduced-motion: reduce ) { .os-files-drop-preview { transition: none; } } .os-file-tile { pointer-events: auto; position: absolute; display: flex; flex-direction: column; align-items: center; justify-content: flex-start; gap: 6px; /* * `box-sizing` is load-bearing, not housekeeping. Without it the * horizontal padding is ADDED to the declared width, so an 88px * tile occupies 96px — which was exactly the desktop's old cell * pitch, and why icons sat edge to edge with no gap at all while * the maths insisted there were 8px between them. * * The tile is the size the grid says it is. Padding lives inside. */ box-sizing: border-box; width: var( --os-tile-w, 88px ); /* * Fixed, not minimum. The tile box IS the selection ring, and a * box that grows with its label gives a row of selected icons a * ragged top edge — one height per label that happened to wrap * to two lines. Every tile occupies its cell; short labels leave * the slack inside the ring, where it reads as padding. */ height: var( --os-tile-h, 104px ); padding: 8px 4px; border: 0; background: transparent; /* Tile foreground driven by `--os-tile-fg` so any * light-on-dark surface (folder window, My WordPress preview) * can retint tiles by overriding the variable in its scope — * no per-surface selectors needed here. */ color: var( --os-tile-fg, var( --os-fg, #fff ) ); cursor: pointer; border-radius: 8px; transition: background-color 0.15s ease, transform 0.05s linear; -webkit-user-select: none; user-select: none; touch-action: none; /* Soft fade-in on mount — each tile carries its own randomised * `--enter-delay` / `--enter-duration` (set inline by the * tile builder), so a freshly-painted grid reads as a cascade * rather than a synchronised pop-in. Both values land in the * decimal-of-a-second range — perceivable, never sluggish. * * `backwards` keeps the tile invisible during its delay so the * cascade is honest; the animation runs once, leaves the tile * at its static `opacity: 1`, and never re-fires from later * state changes (drag, hover, drop-target). */ animation: os-file-tile-enter var( --os-file-tile-enter-duration, 0.4s ) ease-out var( --os-file-tile-enter-delay, 0s ) backwards; } @keyframes os-file-tile-enter { from { opacity: 0; } to { opacity: 1; } } @media ( prefers-reduced-motion: reduce ) { .os-file-tile { animation: none; } } .os-file-tile:hover, .os-file-tile:focus-visible { background: var( --os-tile-hover-bg, rgba( 255, 255, 255, 0.12 ) ); outline: none; } .os-file-tile:focus-visible { /* Inset focus ring — outset rings extended past the tile and * visually overlapped the colindant icon when the canvas cell * pitch is tight (96 px cell, 88 px tile → only 4 px clearance * per side). Inset keeps the ring fully inside the tile. */ box-shadow: inset 0 0 0 2px var( --os-tile-focus-ring, var( --wp-admin-theme-color, #2271b1 ) ); } /* Status ribbon sizing inside tiles. The `` defaults * are tuned for cards (~90 px clipping window); 88 px-wide tiles * need a tighter footprint or the ribbon dominates the icon. We * override the component's CSS custom-property theming surface so * the ribbon stays a small accent in the corner. Authors who want * different sizing can override per-tile or per-section. */ .os-file-tile > os-ribbon { --os-ui-ribbon-size: 48px; --os-ui-ribbon-banner-width: 72px; --os-ui-ribbon-banner-offset: 12px; --os-ui-ribbon-banner-pull: -18px; --os-ui-ribbon-padding: 1px 0; --os-ui-ribbon-font: 700 7px/1.4 var( --os-font, system-ui ); --os-ui-ribbon-tracking: 0.05em; --os-ui-ribbon-shadow: 0 1px 2px rgba( 0, 0, 0, 0.2 ); } .os-file-tile--dragging { /* The DragManager renders a separate ghost element that follows * the pointer; the SOURCE tile stays in place but is dimmed so * the user has a stable reference for "this is what I'm * dragging." * * @since 0.18.0 */ opacity: 0.4; cursor: grabbing; } /* Pinned tiles ("My WordPress", Recycle Bin) anchor to a fixed slot * and DO NOT wire drag — but they look + behave identically to any * other tile until the user attempts a drag (which silently fails). * No upfront visual cue: feedback now happens at the moment of the * gesture, not before, per @since 0.9.0 design feedback. * * Default `cursor: pointer` from the base `.os-file-tile` * rule covers click-to-open. Touch interactions stay as * `touch-action: auto` so the OS scroll gesture works on top of * the tile. */ .os-file-tile--pinned { touch-action: auto; } /* Folder tile is the drop target during a cross-window shortcut * drag — the user dragged a post / page / user tile out of My * WordPress and is hovering over a folder. Bright outline, soft * pulse, and a slight scale so the folder reads unmistakably as * "yes, drop here." @since 0.8.0; pulse added 0.20.0. */ @keyframes os-drop-target-pulse { 0% { box-shadow: 0 0 0 0 rgba( 34, 113, 177, 0.5 ), inset 0 0 0 2px var( --wp-admin-theme-color, #2271b1 ); } 100% { box-shadow: 0 0 0 8px rgba( 34, 113, 177, 0 ), inset 0 0 0 2px var( --wp-admin-theme-color, #2271b1 ); } } .os-file-tile--drop-target { outline: 2px solid var( --wp-admin-theme-color, #2271b1 ); outline-offset: -4px; background: rgba( 34, 113, 177, 0.2 ); transform: scale( 1.08 ); z-index: 5; animation: os-drop-target-pulse 1s ease-out infinite; border-radius: 12px; } @media ( prefers-reduced-motion: reduce ) { .os-file-tile--drop-target { animation: none; } } /* Drop-hint chip — floats next to the cursor during a drag and * shows context-aware text ("Drop here to create shortcut", * "Can't drop here", etc.). The chip text is owned by the * ghost module (`src/drag/ghost.ts`); these styles control the * appearance per accept/reject/neutral mode. * * @since 0.20.0 */ .os-drag-hint { position: fixed; top: 0; left: 0; padding: 6px 10px; border-radius: 999px; font-size: 12px; font-weight: 600; letter-spacing: 0.02em; white-space: nowrap; pointer-events: none; user-select: none; box-shadow: 0 4px 14px rgba( 0, 0, 0, 0.22 ); background: rgba( 30, 30, 30, 0.85 ); color: var( --os-ui-fg-on-accent, #fff ); z-index: 2147483647; transition: background-color 0.12s ease, color 0.12s ease, transform 0.06s ease-out; } .os-drag-hint--neutral { background: rgba( 30, 30, 30, 0.85 ); color: var( --os-ui-fg-on-accent, #fff ); } .os-drag-hint--accept { background: #1e8449; color: var( --os-ui-fg-on-accent, #fff ); box-shadow: 0 0 0 2px rgba( 30, 132, 73, 0.25 ), 0 6px 18px rgba( 30, 132, 73, 0.3 ); } .os-drag-hint--accept::before { /* Tiny checkmark chevron prefix so "Drop here" reads as an * actionable affirmation rather than a passive label. The * checkmark is a real character (U+2713) so screen readers * skip past it via the `aria-hidden` on the host chip. */ content: '✓ '; } .os-drag-hint--reject { background: var( --os-ui-danger-hover, #b32d2e ); color: var( --os-ui-fg-on-accent, #fff ); box-shadow: 0 0 0 2px rgba( 179, 45, 46, 0.25 ), 0 6px 18px rgba( 179, 45, 46, 0.3 ); } .os-drag-hint--reject::before { content: '⊘ '; } /* * No body-level cursor swap during drag — the user prefers the * default arrow cursor regardless of state, so the accept/reject * feedback lives entirely in the hint chip + outline animation. * Don't reintroduce a `cursor: grabbing/copy/no-drop` here. */ /* Shortcut overlay — small arrow badge in the BOTTOM-RIGHT corner * of the icon visual on any tile that's a reference to an external * entity (post, attachment, comment, user, term — anything that * ISN'T a folder or a plugin shortcut). Mirrors the macOS / Windows * convention of an arrow decoration on alias/shortcut icons. * * The geometry: tile is 88px wide with `padding: 8px 4px`. The icon * visual is 48×48 centered horizontally, so the icon's bottom-right * corner lands at roughly (68, 56) from the tile's top-left. The * 18×18 arrow circle is anchored just inside that corner so it * reads as part of the icon, not floating off in space. * * @since 0.8.0 */ /* Only real placements get the badge — i.e. tiles built by * `buildTile(placement)` on the desktop / folder windows, which * are the only surface where "this is a shortcut" actually means * something. Tiles in My WordPress grids (and any future content * surface) are the entity, not a shortcut to it, so the arrow * would just be noise. `[data-placement-id]` is the canonical * placement marker. */ .os-file-tile[ data-placement-id ][ data-file-type="post" ]::after, .os-file-tile[ data-placement-id ][ data-file-type="attachment" ]::after, .os-file-tile[ data-placement-id ][ data-file-type="comment" ]::after, .os-file-tile[ data-placement-id ][ data-file-type="user" ]::after, .os-file-tile[ data-placement-id ][ data-file-type="term" ]::after { content: "↗"; position: absolute; right: 14px; top: 44px; width: 18px; height: 18px; border-radius: 50%; background: var( --os-tile-shortcut-bg, rgba( 0, 0, 0, 0.65 ) ); color: var( --os-tile-shortcut-fg, #fff ); font-size: 11px; line-height: 18px; text-align: center; box-shadow: var( --os-tile-shortcut-shadow, 0 1px 3px rgba( 0, 0, 0, 0.5 ) ); pointer-events: none; } /* * Missing files (the underlying entity was deleted but the * placement still exists) render the tile dimmed so the user * can right-click → Remove without confusing it for an active * file. Phase 6's per-placement permission re-check uses the * same affordance for "you can't see this" placeholders. */ .os-file-tile--missing { opacity: 0.5; } /** * Access-gated tile — the recipient sees the icon (the owner * shared the folder containing it) but lacks `can_read` on the * underlying entity. Dim the tile, kill the pointer affordance, * and overlay a lock badge so the state reads at a glance. */ .os-file-tile--access-gated { opacity: 0.65; cursor: not-allowed; } .os-file-tile--access-gated .os-file-tile__visual, .os-file-tile--access-gated .os-file-tile__preview { filter: grayscale( 0.6 ); } .os-file-tile--access-gated:hover, .os-file-tile--access-gated:focus-visible { background: var( --os-ui-hover, rgba( 0, 0, 0, 0.06 ) ); } .os-file-tile__lock { position: absolute; top: 4px; inset-inline-end: 4px; width: 20px; height: 20px; border-radius: 50%; background: var( --os-ui-scrim, rgba( 0, 0, 0, 0.7 ) ); color: var( --os-ui-fg-on-accent, #fff ); font-size: 14px; line-height: 20px; text-align: center; pointer-events: none; box-shadow: 0 1px 3px rgba( 0, 0, 0, 0.5 ); } /** * Preview-pane empty state when a recipient selects a tile they * can't open. Big lock ring + clear copy + secondary hint. Designed * to read at a glance in the otherwise-busy preview column. */ .os-files__access-gated { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 16px; padding: 32px 24px; min-height: 240px; text-align: center; color: var( --os-fg, inherit ); } .os-files__access-gated-ring { display: flex; align-items: center; justify-content: center; width: 96px; height: 96px; border-radius: 50%; background: linear-gradient( 145deg, rgba( 214, 54, 56, 0.22 ), rgba( 214, 54, 56, 0.08 ) ); border: 2px solid rgba( 214, 54, 56, 0.45 ); box-shadow: inset 0 1px 0 rgba( 255, 255, 255, 0.08 ), 0 8px 24px rgba( 214, 54, 56, 0.18 ); } .os-files__access-gated-glyph { font-size: 44px; width: 44px; height: 44px; color: #ff8080; filter: drop-shadow( 0 1px 2px rgba( 0, 0, 0, 0.4 ) ); } .os-files__access-gated-title { margin: 0; font-size: 17px; font-weight: 600; letter-spacing: 0.2px; } .os-files__access-gated-sub { margin: 0; font-size: 13px; line-height: 1.5; max-width: 340px; opacity: 0.85; } .os-files__access-gated-hint { margin: 0; font-size: 12px; line-height: 1.5; max-width: 340px; opacity: 0.6; } .os-file-tile__visual { display: inline-flex; align-items: center; justify-content: center; width: 48px; height: 48px; font-size: 32px; line-height: 1; } .os-file-tile__icon { display: inline-flex; align-items: center; justify-content: center; width: 48px; height: 48px; font-size: 32px; line-height: 1; } .os-file-tile__preview { width: 48px; height: 48px; object-fit: cover; border-radius: 4px; } /* `` defaults to `draggable=true`, which lets the browser * start a native HTML5 image-drag from a pointerdown — that * pre-empts the DragManager's pointer-event-driven tile rearrange * and the tile silently refuses to move. The renderer also sets * `draggable="false"` on every `` it produces; this CSS rule * covers anything injected later (plugin filters, decorators) * without having to remember the attribute. */ .os-file-tile img { -webkit-user-drag: none; user-select: none; } /* * Wallpaper + tile context menus use `` and * `` (Phase 4 / 0.9.0). Their host-level * styles live inside the component shadow DOM — see * `src/ui/components/os-context-menu/os-context-menu.styles.ts`. * No project CSS rules needed here; the legacy `.os- * wallpaper-menu*` rules were removed when the components landed. */ /* * Folder native window — the body of `os-folder-` * contains a `FilesLayer` rendering that folder's contents. * We ensure the body has positioning context so absolutely- * positioned tiles inside the layer anchor correctly. */ .desktop-mode-folder-window { position: relative; min-height: 200px; /* Same window background recipe as My WordPress — defaults to * the standard window bg (#fff) and themable per-window. */ background: var( --os-folder-window-bg, var( --os-window-bg, #fff ) ); color: var( --os-fg-on-light, var( --os-ui-fg, #1d2327 ) ); display: flex; flex-direction: column; /* Light-on-dark context: the folder window opens inside a * standard window chrome whose body is white-ish. Retint the * tile color tokens here so dashicons + labels read against * a light background. Plugins / themes can override these * same variables at any scope to retint further. */ --os-tile-fg: var( --os-fg-on-light, var( --os-ui-fg, #1d2327 ) ); /* Chains through the palette for the same reason as * `--os-tile-fg` beside it: a bare literal here outranks the * palette's own value and pins every tile's secondary line to * near-black, which disappears the moment the surface is dark. * The literal stays as the pre-brand floor. */ --os-tile-fg-muted: var( --os-folder-window-fg-muted, var( --os-ui-fg-muted, rgba( 0, 0, 0, 0.55 ) ) ); --os-tile-hover-bg: rgba( 0, 0, 0, 0.06 ); /* Light-context label rendering — share the same recipe with * every other light-bg tile surface (My WordPress, future * windows). One set of tokens, no per-surface duplication. */ --os-tile-label-shadow: none; --os-tile-label-weight: 500; --os-tile-label-smoothing: antialiased; --os-tile-label-color: var( --os-fg-on-light, var( --os-ui-fg, #1d2327 ) ); } /* Folder window's bottom status bar — paint with the same light- * context treatment My WordPress uses (transparent bg, soft border, * muted dark text). Without this, the wallpaper-targeted defaults * (`background: rgba(0,0,0,0.18); color: white`) would show as a * dark band against the white window bg. */ .desktop-mode-folder-window .os-folder-status-bar { border-top: 1px solid var( --os-ui-border, #dcdcde ); background: transparent; color: var( --os-ui-fg-muted, #50575e ); } .desktop-mode-folder-window .os-folder-status-bar button.os-folder-status-bar__segment:hover { background: var( --os-hover, var( --os-ui-hover, rgba( 0, 0, 0, 0.06 ) ) ); } /* Generic breadcrumb header — any surface that grows a navigation * stack (folder window, My WordPress folder, future drill-down * windows) renders one of these via `renderBreadcrumbs` from * `src/desktop-files/breadcrumbs.ts`. Visual vocabulary: small * icon-only Back button on the left, `›`-separated crumb buttons, * current segment is bold + non-interactive. * * @since 0.8.0 */ .os-breadcrumbs { display: flex; align-items: center; gap: 8px; padding: 6px 10px; border-bottom: 1px solid var( --os-ui-border, #dcdcde ); background: transparent; flex: 0 0 auto; } .os-breadcrumbs__back { display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; padding: 0; border: 0; background: transparent; border-radius: 4px; cursor: pointer; color: inherit; flex: 0 0 auto; } .os-breadcrumbs__back:hover:not(:disabled), .os-breadcrumbs__back:focus-visible:not(:disabled) { background: var( --os-hover, var( --os-ui-hover, rgba( 0, 0, 0, 0.06 ) ) ); outline: none; } .os-breadcrumbs__back:disabled { opacity: 0.35; cursor: default; } .os-breadcrumbs__back .dashicons { font-size: 18px; width: 18px; height: 18px; line-height: 1; } .os-breadcrumbs__crumbs { display: flex; align-items: center; gap: 6px; font-size: 13px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .os-breadcrumbs__crumb { border: 0; background: transparent; color: var( --os-link, var( --os-ui-accent, #2271b1 ) ); cursor: pointer; padding: 2px 4px; border-radius: 4px; font: inherit; } .os-breadcrumbs__crumb:hover { background: var( --os-hover, var( --os-ui-hover, rgba( 0, 0, 0, 0.06 ) ) ); } .os-breadcrumbs__crumb--current { color: var( --os-fg-on-light, var( --os-ui-fg, #1d2327 ) ); cursor: default; font-weight: 600; } .os-breadcrumbs__crumb--current:hover { background: transparent; } .os-breadcrumbs__sep { color: var( --os-ui-fg-muted, #787c82 ); } .os-folder-window__layer { position: relative; flex: 1; min-height: 0; overflow: auto; } /* Two-pane shell — left: tile canvas, right: preview pane that * reflects the currently-selected tile. Same model as the My * WordPress two-pane view so the UX is unified. * * @since 0.8.0 */ .os-folder-window__split { display: grid; grid-template-columns: minmax( 240px, 60% ) minmax( 0, 1fr ); flex: 1 1 auto; min-height: 0; } .os-folder-window__preview { overflow-y: auto; min-width: 0; background: var( --os-window-bg, #fff ); border-inline-start: 1px solid var( --os-ui-border, #dcdcde ); color: var( --os-fg-on-light, var( --os-ui-fg, #1d2327 ) ); } /* Selected file tile — light-blue accent on the wallpaper, soft * accent inside light-context windows. Variable-driven so plugins * can retune. */ .os-file-tile--selected, .os-file-tile--selected:hover { background: var( --os-tile-selected-bg, rgba( 34, 113, 177, 0.18 ) ); box-shadow: inset 0 0 0 1px var( --os-tile-focus-ring, var( --wp-admin-theme-color, #2271b1 ) ); } /* Inside a folder window the selected highlight should read on * white. */ .desktop-mode-folder-window { --os-tile-selected-bg: rgba( 34, 113, 177, 0.12 ); } /* Type breakdown under a multi-selection's count in a folder * window's preview pane. The count is the headline; this is the * detail that tells you which actions the menu will offer. */ .os-files-preview__selection-breakdown { font-size: 12px; opacity: 0.75; } /* While a rubber band is live, nothing in the shell is selectable. * * The band starts on bare canvas, which — unlike a tile — IS * selectable, so the browser begins its own text selection on the * same press and keeps extending it as the pointer travels. Inside * the canvas that stays invisible; drag out over another window and * it starts highlighting that window's text in blue, mid-gesture. * * The controller also refuses `selectstart` outright while the band * is up. This rule is the second half: it covers anything already * selectable that the event route misses. */ body[ data-os-marquee ] { -webkit-user-select: none; user-select: none; } /* Marquee (rubber-band) selection box. Painted by the shared * selection controller on any canvas that opts into it — the * wallpaper, folder windows, every My WordPress list — so this one * declaration dresses all of them. * * `pointer-events: none` is load-bearing: the box tracks under the * cursor for the whole gesture, and without it every hit-test * (drop targets, the controller's own tile lookup) would land on * the box instead of what's beneath it. */ .os-selection-marquee { position: absolute; z-index: 5; pointer-events: none; border: 1px solid var( --os-tile-focus-ring, var( --wp-admin-theme-color, #2271b1 ) ); border-radius: 2px; /* A WASH, not a fill — the whole point of a rubber band is that * you can see what it is about to catch. `--os-ui-accent-dim` is a * solid hex (Pulse, one step back), so reaching for it directly * paints an opaque rectangle over the icons; `--os-ui-accent-soft` * is that same colour already taken down to a 14% wash, which is * what every other ambient accent surface in the shell uses. */ background: var( --os-selection-marquee-bg, var( --os-ui-accent-soft, rgba( 34, 113, 177, 0.14 ) ) ); } .os-folder-status-bar { display: flex; justify-content: space-between; align-items: center; gap: 12px; padding: 6px 12px; border-top: 1px solid rgba( 255, 255, 255, 0.08 ); background: var( --os-ui-scrim, rgba( 0, 0, 0, 0.18 ) ); color: var( --os-fg-muted, rgba( 255, 255, 255, 0.7 ) ); font-size: 12px; flex-shrink: 0; } .os-folder-status-bar__cluster { display: flex; align-items: center; gap: 12px; } .os-folder-status-bar__segment { display: inline-flex; align-items: center; gap: 4px; padding: 0; border: 0; background: transparent; color: inherit; font: inherit; } button.os-folder-status-bar__segment { cursor: pointer; border-radius: 4px; padding: 2px 6px; } button.os-folder-status-bar__segment:hover { background: rgba( 255, 255, 255, 0.1 ); } .os-folder-status-bar__icon { font-size: 14px; } /* * File Associations OS Settings tab (Phase 5). One row per file * type: label on the left, a `` of compatible * openers on the right. Color tokens use the OS-Settings-canonical * `--os-ui-fg-muted` so the tab inherits theme colors instead of * falling back to white-on-light invisibility. */ .os-file-associations__intro { display: block; margin: 0 0 16px; color: var( --os-ui-fg-muted, #50575e ); font-size: 13px; line-height: 1.5; } .os-file-associations__list { display: flex; flex-direction: column; gap: 8px; } .os-file-associations__row { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 8px 12px; background: var( --os-ui-hover, rgba( 0, 0, 0, 0.04 ) ); border-radius: 6px; } .os-file-associations__label { font-weight: 500; flex: 1; } .os-file-associations__select { min-width: 220px; } .os-file-associations__none, .os-file-associations__empty { color: var( --os-ui-fg-muted, #50575e ); font-size: 12px; } /* * The shell's two built-in modals — "New folder" / "Rename", and the * web-link dialog, which reuses this class set for its surface. Both * are light-DOM overlays that slot `` + * `` for their controls. * * The surface reads the same `--os-ui-modal-*` family that * `` and `` do, so the three look like * one dialog system and answer to a desktop theme together. Two * things it must NOT do, both of which it used to: * * - Paint from `--os-bg`. That is the WALLPAPER token: the desk * default is a gradient and the wallpaper layer overwrites it * with whatever artwork is active. A dialog is a surface, not a * desk. Same note as `os-modal.styles.ts`. * - Style raw form controls. Core's `forms.css` reaches every * `` in the parent shell through `input[type="text"]` — * (0,1,1), which outranks a single class of ours — so a plain * input rendered as a white core-chrome box on this dark * surface no matter what we declared. The controls live in * shadow DOM now, where that sheet cannot follow. */ .os-create-folder-dialog__overlay { position: fixed; inset: 0; background: var( --os-ui-scrim, rgba( 0, 0, 0, 0.45 ) ); /* Desktop-theme texture slot: unset resolves to none. Mirrors the scrim treatment in os-modal. */ background-image: var( --os-ui-scrim-image, none ); background-repeat: var( --os-ui-scrim-image-repeat, repeat ); background-size: var( --os-ui-scrim-image-size, auto ); background-position: var( --os-ui-scrim-image-position, center ); backdrop-filter: blur( 2px ); display: flex; align-items: center; justify-content: center; z-index: 10000; } .os-create-folder-dialog { /* * Re-point the shared control tokens for a dark dialog surface, * exactly as `` does on its host. The slotted * `` / `` inherit these across the * shadow boundary, so the field resolves a dark input with light * ink instead of the light-admin defaults (`--os-window-bg` is * `#fff` in an unthemed shell, which under a light `--os-ui-fg` * is the invisible-value trap the modal styles call out). * * Each reads a palette-owned `--os-ui-modal-*` name first, so a * desktop theme can still reach every one of them; the literals * are only the floor for a shell whose stylesheet never loaded. */ --os-ui-fg: var( --os-ui-modal-text, #f0f0f1 ); --os-ui-fg-muted: var( --os-ui-modal-text-muted, #a7aaad ); --os-ui-border: var( --os-ui-modal-border, rgba( 255, 255, 255, 0.25 ) ); --os-window-bg: var( --os-ui-modal-field-bg, #2c3338 ); --os-ui-button-bg-hover: var( --os-ui-modal-button-bg-hover, rgba( 255, 255, 255, 0.08 ) ); width: min( 420px, 92vw ); /* Longhand, and a literal fallback: the shorthand would reset the texture slot below, and a gradient is invalid as a background-color — it would leave the dialog transparent. */ background-color: var( --os-ui-modal-bg, #1d2327 ); background-image: var( --os-ui-dialog-bg-image, none ); background-repeat: var( --os-ui-dialog-bg-image-repeat, repeat ); background-size: var( --os-ui-dialog-bg-image-size, auto ); background-position: var( --os-ui-dialog-bg-image-position, center ); color: var( --os-ui-modal-fg, var( --os-fg, #fff ) ); border: 1px solid var( --os-ui-border, rgba( 255, 255, 255, 0.08 ) ); border-radius: 10px; box-shadow: 0 20px 50px rgba( 0, 0, 0, 0.6 ); padding: 20px 22px 18px; display: flex; flex-direction: column; gap: 10px; } .os-create-folder-dialog__title { margin: 0 0 4px; font-size: 16px; font-weight: 600; color: var( --os-ui-fg, #f0f0f1 ); } .os-url-dialog__description { margin: 0 0 4px; font-size: 12px; line-height: 1.5; color: var( --os-ui-fg-muted, #a7aaad ); } .os-create-folder-dialog__error { margin: 0; color: var( --os-ui-danger-hover, #ff8a8a ); font-size: 12px; } .os-create-folder-dialog__actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 6px; } /* The busy state dims the whole form area rather than the input alone — the buttons carry their own disabled treatment from os-button, and dimming one control of three read as a glitch. */ .os-create-folder-dialog--busy .os-create-folder-dialog__field { opacity: 0.7; } /* * Single label rule for every tile, regardless of host surface. * Visual properties read from `--os-tile-label-*` * tokens — defined in `variables.css` for the dark wallpaper and * rebound by any light-context scope (folder window, My * WordPress, …) so all surfaces share one source of truth. */ .os-file-tile__label { font-size: 12px; line-height: 1.2; /* Follows the tile rather than restating its width, so a section * that re-points `--os-tile-w` (image-led sections do) gets a * label that grows with the tile instead of staying narrow. */ max-width: calc( var( --os-tile-w, 88px ) - 4px ); text-align: center; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; color: var( --os-tile-label-color, var( --os-tile-fg, #fff ) ); font-weight: var( --os-tile-label-weight, 400 ); text-shadow: var( --os-tile-label-shadow, 0 1px 2px rgba( 0, 0, 0, 0.5 ) ); -webkit-font-smoothing: var( --os-tile-label-smoothing, auto ); -moz-osx-font-smoothing: grayscale; } /* * DragManager ghost element (the visual that follows the cursor * during a drag). Cloned from the source by `mountGhost()` and * pinned to the body with `position: fixed` + transform-based * movement. `pointer-events: none` ensures the ghost itself * never obscures the drop targets underneath during hit-testing. * * The accept / reject classes are toggled on each hover transition * so the cursor matches the drop semantics: `copy` over an * accepting target, `no-drop` over rejecting (or no) target. * * @since 0.18.0 */ .os-drag-ghost { box-shadow: 0 6px 24px rgba( 0, 0, 0, 0.45 ); border-radius: 6px; opacity: 0.95; transition: none; } .os-drag-ghost--accept { cursor: copy; } .os-drag-ghost--reject { cursor: no-drop; } /* Multi-item drag ghost: the grabbed tile, with the rest of the set * implied by two offset cards behind it and stated by a count badge. * The stack is drawn with pseudo-elements rather than real clones — * three cloned tiles cost three subtree copies per lift, and only * the top one is ever legible. */ .os-drag-stack { /* The drag manager overwrites this with `position: fixed` when it * mounts the ghost. It stays declared so the stack still composes * correctly if the helper is used outside a live drag — both * values establish the containing block the cards and the badge * are placed against. */ position: relative; } .os-drag-stack::before, .os-drag-stack::after { content: ''; position: absolute; inset: 0; z-index: -1; border-radius: 6px; background: var( --os-ui-surface, rgba( 30, 30, 40, 0.9 ) ); box-shadow: 0 4px 14px rgba( 0, 0, 0, 0.35 ); } .os-drag-stack::before { transform: translate( 6px, 6px ); opacity: 0.7; } .os-drag-stack::after { transform: translate( 12px, 12px ); opacity: 0.45; } .os-drag-stack__count { position: absolute; inset-block-start: -8px; inset-inline-end: -8px; z-index: 1; min-width: 20px; height: 20px; padding: 0 6px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border-radius: 10px; background: var( --os-ui-accent, var( --wp-admin-theme-color, #2271b1 ) ); color: #fff; font-size: 11px; font-weight: 600; line-height: 1; box-shadow: 0 2px 8px rgba( 0, 0, 0, 0.4 ); } /* ============================================================ * * Upload progress HUD (since 0.31.0) * * Pinned floating panel that surfaces the OS-file-drop manager's * in-flight uploads. One row per file with a ``, * a filename + status, and a Cancel/Dismiss action. The panel * hides itself when there are no rows; it's purely reactive to * the four upload-lifecycle hooks. * ============================================================ */ .os-upload-hud { position: fixed; inset-block-end: 16px; inset-inline-end: 16px; width: 320px; max-width: calc( 100vw - 32px ); background: var( --os-panel-bg, var( --os-ui-surface, #fff ) ); color: var( --os-fg-on-light, var( --os-ui-fg, #1d2327 ) ); border: 1px solid var( --os-ui-border, #dcdcde ); border-radius: 10px; box-shadow: 0 8px 28px rgba( 0, 0, 0, 0.18 ); z-index: 99998; font: inherit; pointer-events: auto; overflow: hidden; } .os-upload-hud[ hidden ] { display: none; } .os-upload-hud__header { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 8px 12px; background: var( --os-panel-header-bg, var( --os-ui-hover, rgba( 0, 0, 0, 0.04 ) ) ); border-block-end: 1px solid var( --os-ui-border, #dcdcde ); font-weight: 600; font-size: 12px; } .os-upload-hud__title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .os-upload-hud__close { border: 0; background: transparent; color: inherit; font-size: 16px; line-height: 1; width: 22px; height: 22px; border-radius: 4px; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; } .os-upload-hud__close:hover, .os-upload-hud__close:focus-visible { background: var( --os-ui-hover, rgba( 0, 0, 0, 0.08 ) ); outline: none; } .os-upload-hud__list { display: flex; flex-direction: column; gap: 10px; padding: 10px 12px; max-height: 50vh; overflow-y: auto; } .os-upload-hud__row { display: grid; grid-template-columns: 1fr auto; grid-template-areas: 'meta actions' 'bar bar'; gap: 6px 8px; align-items: center; font-size: 12px; } .os-upload-hud__meta { grid-area: meta; min-width: 0; display: flex; flex-direction: column; gap: 2px; } .os-upload-hud__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; } .os-upload-hud__status { font-size: 11px; opacity: 0.75; font-variant-numeric: tabular-nums; } .os-upload-hud__row os-progress-bar { grid-area: bar; } .os-upload-hud__actions { grid-area: actions; display: inline-flex; gap: 4px; }