| 1 |
/* |
| 2 |
* The notch — top-centre, floating, never reserving. |
| 3 |
* |
| 4 |
* The load-bearing rule in this file is that nothing here touches |
| 5 |
* `.os-area`. The desktop has one work-area rectangle now |
| 6 |
* (`src/work-area/index.ts`, carved out of the area by the dock), |
| 7 |
* and it stays useful only while few things carve it; a notch that |
| 8 |
* reserved height would be a second claimant on the top edge for |
| 9 |
* the sake of a pill a window may cover. It floats, and it gets out |
| 10 |
* of the way instead. |
| 11 |
* |
| 12 |
* Getting out of the way is also what the stacking order says: the |
| 13 |
* notch is on the desk, under the window layer, so a window reaching |
| 14 |
* the top edge covers the pill instead of the pill hanging over it. |
| 15 |
*/ |
| 16 |
|
| 17 |
/* |
| 18 |
* Anchored to the SHELL, not the viewport. `.os-shell` starts below |
| 19 |
* the admin bar when the user keeps it, and at the viewport top when |
| 20 |
* they don't, so an absolutely-positioned notch lands correctly in |
| 21 |
* every admin-bar mode without knowing which one is on. Fixed |
| 22 |
* positioning put it under the bar, which paints above the shell. |
| 23 |
*/ |
| 24 |
.os-notch { |
| 25 |
position: absolute; |
| 26 |
top: 0; |
| 27 |
left: 50%; |
| 28 |
transform: translateX( -50% ); |
| 29 |
z-index: var( --os-z-notch, 60 ); |
| 30 |
|
| 31 |
display: flex; |
| 32 |
align-items: center; |
| 33 |
gap: 7px; |
| 34 |
max-width: min( 420px, 60vw ); |
| 35 |
height: 34px; |
| 36 |
padding: 0 14px; |
| 37 |
/* |
| 38 |
* A hairline all the way round except the top, where the pill meets |
| 39 |
* the screen edge and a line would draw a lid on it. Without the |
| 40 |
* border the surface disappeared into the default wallpaper — Void |
| 41 |
* on Void, with only the shadow separating them. |
| 42 |
*/ |
| 43 |
border: 1px solid var( --os-ui-border-strong, rgba( 255, 251, 255, 0.22 ) ); |
| 44 |
border-top: 0; |
| 45 |
/* Square at the top, round below — it reads as hanging from the |
| 46 |
screen edge rather than floating near it. */ |
| 47 |
border-radius: 0 0 15px 15px; |
| 48 |
background: var( --os-ui-surface-raised, rgba( 20, 18, 24, 0.92 ) ); |
| 49 |
box-shadow: 0 2px 12px rgba( 0, 0, 0, 0.4 ); |
| 50 |
color: var( --os-ui-fg, #fffbff ); |
| 51 |
font-size: 13px; |
| 52 |
line-height: 1; |
| 53 |
cursor: pointer; |
| 54 |
/* `width` is not animated: the pill is sized by its content, and |
| 55 |
transitioning an auto width does nothing. The message's own |
| 56 |
max-width is what opens and closes. */ |
| 57 |
transition: background 160ms ease, opacity 160ms ease, |
| 58 |
border-color 160ms ease, top 180ms ease, |
| 59 |
visibility 0s linear 160ms; |
| 60 |
} |
| 61 |
|
| 62 |
.os-notch:hover, |
| 63 |
.os-notch:focus-visible { |
| 64 |
background: var( --os-ui-surface-elevated, rgba( 32, 29, 38, 0.96 ) ); |
| 65 |
border-color: var( --os-ui-accent-dim, rgba( 242, 82, 252, 0.5 ) ); |
| 66 |
} |
| 67 |
|
| 68 |
.os-notch:focus-visible { |
| 69 |
outline: 2px solid var( --os-ui-accent, #f252fc ); |
| 70 |
outline-offset: -2px; |
| 71 |
} |
| 72 |
|
| 73 |
.os-notch__glyph { |
| 74 |
display: flex; |
| 75 |
flex: 0 0 auto; |
| 76 |
width: 18px; |
| 77 |
height: 18px; |
| 78 |
} |
| 79 |
|
| 80 |
.os-notch__glyph svg { |
| 81 |
width: 100%; |
| 82 |
height: 100%; |
| 83 |
fill: currentColor; |
| 84 |
} |
| 85 |
|
| 86 |
/* |
| 87 |
* The resting label. It yields to a message rather than sitting |
| 88 |
* beside one — the notch says one thing at a time, and "Site |
| 89 |
* assistant · Saving…" would be two. |
| 90 |
*/ |
| 91 |
.os-notch__label { |
| 92 |
overflow: hidden; |
| 93 |
max-width: 160px; |
| 94 |
white-space: nowrap; |
| 95 |
text-overflow: ellipsis; |
| 96 |
transition: max-width 200ms ease, opacity 120ms ease; |
| 97 |
} |
| 98 |
|
| 99 |
.os-notch--speaking .os-notch__label { |
| 100 |
max-width: 0; |
| 101 |
opacity: 0; |
| 102 |
} |
| 103 |
|
| 104 |
/* |
| 105 |
* The message is always in the DOM (it is an `aria-live` region and |
| 106 |
* has to be, to be announced reliably); it is its WIDTH that opens. |
| 107 |
* `max-width` rather than `display` so there is something to animate |
| 108 |
* and so the live region is never removed from the accessibility tree. |
| 109 |
*/ |
| 110 |
.os-notch__message { |
| 111 |
overflow: hidden; |
| 112 |
max-width: 0; |
| 113 |
white-space: nowrap; |
| 114 |
text-overflow: ellipsis; |
| 115 |
transition: max-width 200ms ease; |
| 116 |
} |
| 117 |
|
| 118 |
.os-notch--speaking .os-notch__message { |
| 119 |
max-width: 320px; |
| 120 |
} |
| 121 |
|
| 122 |
/* |
| 123 |
* Overview is the shell talking about itself — a zoomed-out view of |
| 124 |
* every window and desktop. A pill hanging over that view is chrome |
| 125 |
* on top of chrome, and it lands squarely on the desktop thumbnails. |
| 126 |
* Mio steps aside for the same reason and in the same way. |
| 127 |
* |
| 128 |
* `visibility` alongside the fade so it stops taking clicks on the way |
| 129 |
* out; a transparent button over a desktop thumbnail still swallows |
| 130 |
* the click that was meant to switch desktops. |
| 131 |
*/ |
| 132 |
.os-shell:has( .os-area--overview ) .os-notch { |
| 133 |
opacity: 0; |
| 134 |
visibility: hidden; |
| 135 |
/* No delay on the way OUT — the pill must stop taking clicks the |
| 136 |
moment it starts fading, not after. The delay in the base rule |
| 137 |
is what holds it visible while it fades back IN. */ |
| 138 |
transition-delay: 0s; |
| 139 |
} |
| 140 |
|
| 141 |
/* Solo mode is one window freed into a native OS window — the shell's |
| 142 |
own chrome has no place in it. */ |
| 143 |
body.os-solo .os-notch { |
| 144 |
display: none; |
| 145 |
} |
| 146 |
|
| 147 |
@media ( prefers-reduced-motion: reduce ) { |
| 148 |
.os-notch, |
| 149 |
.os-notch__label, |
| 150 |
.os-notch__message { |
| 151 |
transition-duration: 1ms; |
| 152 |
} |
| 153 |
} |
| 154 |
|
| 155 |
/* |
| 156 |
* Auto-hide admin bar. |
| 157 |
* |
| 158 |
* The shell starts at the viewport top in this mode (the bar is out of |
| 159 |
* flow), so the notch would be underneath the bar whenever it rolls |
| 160 |
* down. It steps down to sit below it instead, on the bar's own timing. |
| 161 |
*/ |
| 162 |
body.os-active.os-admin-bar-dynamic:has( #wpadminbar:hover ) .os-notch, |
| 163 |
body.os-active.os-admin-bar-dynamic:has( #wpadminbar:focus-within ) .os-notch, |
| 164 |
body.os-active.os-admin-bar-dynamic:has( .os-notch:hover ) .os-notch, |
| 165 |
body.os-active.os-admin-bar-dynamic:has( .os-notch:focus-visible ) .os-notch { |
| 166 |
/* The bar's measured edge (src/admin-bar-height.ts), Core's token |
| 167 |
behind it — a host that made the bar taller moved this step too. */ |
| 168 |
top: var( --os-admin-bar-height, var( --wp-admin--admin-bar--height, 32px ) ); |
| 169 |
} |
| 170 |
|
| 171 |
/* |
| 172 |
* The hover bridge, and it is what makes the step stable. |
| 173 |
* |
| 174 |
* Hovering the notch is one of the things that rolls the bar down, and |
| 175 |
* the notch answers by moving 32px away from the pointer — which ends |
| 176 |
* the hover, retracts the bar, and brings the notch back up under the |
| 177 |
* pointer to start again. This claims the space the notch vacates, so |
| 178 |
* the pointer is still over the element after the step. Hovering a |
| 179 |
* pseudo-element counts as hovering its owner, the same trick the |
| 180 |
* bar's own reveal zone uses. |
| 181 |
* |
| 182 |
* Only in this mode: nothing moves in the other two, and a permanent |
| 183 |
* 32px hit area hanging off the notch would swallow clicks meant for |
| 184 |
* whatever is behind it. |
| 185 |
*/ |
| 186 |
body.os-active.os-admin-bar-dynamic .os-notch::before { |
| 187 |
content: ""; |
| 188 |
position: absolute; |
| 189 |
inset-inline: 0; |
| 190 |
inset-block-end: 100%; |
| 191 |
height: var( --os-admin-bar-height, var( --wp-admin--admin-bar--height, 32px ) ); |
| 192 |
} |
| 193 |
|