| 1 | /* chrome/chrome.css: the suite's top bar, account menu, app switcher and |
| 2 | sign-in pages. Link it after menu.css (its panels are [estilo-menu]). |
| 3 | Two custom properties are PUBLIC contract, read by apps and never |
| 4 | copied as numbers: the bar's block size and its stacking level. */ |
| 5 | :root { |
| 6 | --estilo-bar-block-size: 3.25rem; /* 52px. PUBLIC */ |
| 7 | --estilo-z-bar: 45; /* PUBLIC: the bar's stacking level, and the ceiling for in-page layers */ |
| 8 | --estilo-trigger-size: 2rem; /* 32px targets, above the 24px floor */ |
| 9 | } |
| 10 | [estilo-bar] { |
| 11 | position: relative; |
| 12 | z-index: var(--estilo-z-bar); |
| 13 | box-sizing: border-box; |
| 14 | display: flex; |
| 15 | flex-wrap: nowrap; |
| 16 | align-items: center; |
| 17 | gap: var(--rst-sp-3); |
| 18 | block-size: var(--estilo-bar-block-size); /* fixed, not min: nothing may grow it */ |
| 19 | padding-inline: var(--rst-sp-4); |
| 20 | background: var(--rst-surface); |
| 21 | color: var(--rst-text); |
| 22 | border-block-end: 1px solid var(--rst-line); |
| 23 | } |
| 24 | [estilo-bar~="sticky"] { position: sticky; inset-block-start: 0; } |
| 25 | [estilo-bar-brand] { |
| 26 | display: inline-flex; |
| 27 | align-items: center; |
| 28 | gap: 0.5rem; |
| 29 | flex: none; |
| 30 | color: var(--rst-text); |
| 31 | font-weight: 650; |
| 32 | text-decoration: none; |
| 33 | white-space: nowrap; |
| 34 | } |
| 35 | [estilo-bar-brand] img { flex: none; inline-size: 1.75rem; block-size: 1.75rem; border-radius: 21%; } |
| 36 | [estilo-bar-context] { |
| 37 | flex: 0 1 auto; |
| 38 | min-inline-size: 0; |
| 39 | overflow: hidden; |
| 40 | text-overflow: ellipsis; |
| 41 | white-space: nowrap; |
| 42 | padding-inline-start: var(--rst-sp-3); |
| 43 | border-inline-start: 1px solid var(--rst-line); |
| 44 | color: var(--rst-text); |
| 45 | font-weight: 550; |
| 46 | } |
| 47 | [estilo-bar-nav] { display: flex; align-self: stretch; gap: var(--rst-sp-4); } |
| 48 | [estilo-bar-nav] a { |
| 49 | display: flex; |
| 50 | align-items: center; |
| 51 | border-block: 2px solid transparent; /* square current marker, in-flow */ |
| 52 | color: var(--rst-text-muted); |
| 53 | font-size: var(--rst-fs-sm); |
| 54 | text-decoration: none; |
| 55 | white-space: nowrap; |
| 56 | } |
| 57 | [estilo-bar-nav] a:hover { color: var(--rst-text); } |
| 58 | [estilo-bar-nav] a[aria-current] { border-block-end-color: var(--rst-accent); color: var(--rst-text); font-weight: 550; } |
| 59 | [estilo-bar-end] { display: flex; align-items: center; gap: var(--rst-sp-2); flex: none; margin-inline-start: auto; } |
| 60 | [estilo-bar-actions] { display: flex; align-items: center; gap: var(--rst-sp-2); } |
| 61 | [estilo-bar-actions] form { display: contents; } /* a POST action lays out as its button */ |
| 62 | [estilo-bar-trigger] { |
| 63 | display: inline-flex; |
| 64 | align-items: center; |
| 65 | justify-content: center; |
| 66 | gap: 0.3rem; |
| 67 | box-sizing: border-box; |
| 68 | min-inline-size: var(--estilo-trigger-size); |
| 69 | block-size: var(--estilo-trigger-size); |
| 70 | padding-inline: 0.4rem; |
| 71 | border-radius: 0; /* in-flow: square */ |
| 72 | color: var(--rst-text-muted); |
| 73 | font-size: var(--rst-fs-sm); |
| 74 | cursor: pointer; |
| 75 | list-style: none; |
| 76 | user-select: none; |
| 77 | } |
| 78 | [estilo-bar-trigger]::-webkit-details-marker { display: none; } |
| 79 | [estilo-bar-trigger]::marker { content: ""; } |
| 80 | [estilo-bar-trigger] svg { flex: none; inline-size: 1.125rem; block-size: 1.125rem; } |
| 81 | [estilo-bar-trigger]:hover, |
| 82 | [open] > [estilo-bar-trigger] { color: var(--rst-text); background: var(--rst-surface-2); } |
| 83 | [estilo-bar-trigger]:focus-visible { outline: 2px solid var(--rst-accent); outline-offset: -2px; } |
| 84 | [estilo-bar-more] { display: none; } |
| 85 | @media (max-width: 799.98px) { /* rastrillo's shell breakpoint */ |
| 86 | [estilo-bar-nav], [estilo-bar-actions] { display: none; } |
| 87 | [estilo-bar-more] { display: block; } |
| 88 | [estilo-bar]:has([estilo-bar-context], [estilo-bar-title]) [estilo-bar-name] { display: none; } /* the icon stays; the title gets the room */ |
| 89 | } |
| 90 | |
| 91 | /* The shell. <div estilo-shell> wraps the skip link, the bar and <main> |
| 92 | on every page and has no styling of its own. estilo-shell="fill" is the |
| 93 | full-height layout for the one kind of page that must fill the viewport |
| 94 | (Sheets' grid): the shell is exactly the viewport, the bar keeps its |
| 95 | 52px, and <main> takes the rest, so the working area is the viewport |
| 96 | less the bar without any app re-deriving that sum. The document never |
| 97 | scrolls there, so the root drops its reserved scrollbar gutter, which |
| 98 | would otherwise leave a dead strip down the inline end. <main> must be |
| 99 | the shell's direct child. */ |
| 100 | [estilo-shell~="fill"] { |
| 101 | display: flex; |
| 102 | flex-direction: column; |
| 103 | block-size: 100dvh; |
| 104 | } |
| 105 | [estilo-shell~="fill"] > [estilo-bar] { flex: none; } |
| 106 | [estilo-shell~="fill"] > main { flex: 1 1 auto; min-block-size: 0; } |
| 107 | :root:has([estilo-shell~="fill"]) { scrollbar-gutter: auto; } |
| 108 | |
| 109 | /* The editable title slot: the open object's title, renamed in place. The |
| 110 | input is a bordered control, so it keeps the theme's radius; its block size is |
| 111 | fixed so an input in the bar can never grow the bar. */ |
| 112 | [estilo-bar-title] { |
| 113 | display: flex; |
| 114 | align-items: center; |
| 115 | gap: var(--rst-sp-2); |
| 116 | flex: 0 1 22rem; |
| 117 | min-inline-size: 0; |
| 118 | margin: 0; |
| 119 | padding-inline-start: var(--rst-sp-3); |
| 120 | border-inline-start: 1px solid var(--rst-line); |
| 121 | } |
| 122 | [estilo-bar-title-input] { |
| 123 | box-sizing: border-box; |
| 124 | block-size: 2rem; |
| 125 | inline-size: 100%; |
| 126 | min-inline-size: 6rem; |
| 127 | padding-inline: 0.4rem; |
| 128 | border: 1px solid transparent; |
| 129 | border-radius: var(--rst-radius-sm); |
| 130 | background: transparent; |
| 131 | color: var(--rst-text); |
| 132 | font: inherit; |
| 133 | font-weight: 550; |
| 134 | text-overflow: ellipsis; |
| 135 | } |
| 136 | [estilo-bar-title-input]:hover { border-color: var(--rst-line); } |
| 137 | [estilo-bar-title-input]:focus { border-color: var(--rst-accent); outline: none; background: var(--rst-surface); } |
| 138 | /* iOS zooms the page into a text field under 16px. tokens.css lifts fields |
| 139 | to 16px on a coarse pointer, but with a bare `input` selector, which |
| 140 | font: inherit above outweighs. Same rule, restated at this weight. */ |
| 141 | @media (pointer: coarse) { |
| 142 | [estilo-bar-title-input] { |
| 143 | font-size: max(1rem, 1em); |
| 144 | } |
| 145 | } |
| 146 | [estilo-bar-title-submit] { position: absolute; clip-path: inset(50%); inline-size: 1px; block-size: 1px; overflow: hidden; white-space: nowrap; } |
| 147 | [estilo-bar-title]:focus-within [estilo-bar-title-submit] { position: static; clip-path: none; inline-size: auto; block-size: 2rem; overflow: visible; flex: none; } |
| 148 | /* Below the shell breakpoint the row has no room for the button beside the |
| 149 | field: it would sit under Menu or the switcher, and a click at its centre |
| 150 | would open them instead (found in the first review, at 390px and 320px). |
| 151 | So the field takes whatever width is left, down to nothing but its own |
| 152 | padding, and on focus the button floats just under it, over the page, |
| 153 | where nothing in the bar competes for it. Enter renames at every width. */ |
| 154 | @media (max-width: 799.98px) { |
| 155 | [estilo-bar-title] { position: relative; } |
| 156 | [estilo-bar-title-input] { min-inline-size: 0; } |
| 157 | [estilo-bar-title]:focus-within [estilo-bar-title-submit] { |
| 158 | position: absolute; |
| 159 | inset-block-start: calc(100% + 0.5rem); |
| 160 | inset-inline-start: var(--rst-sp-3); |
| 161 | border: 1px solid var(--rst-line); |
| 162 | border-radius: var(--estilo-float-radius, 9px); /* a float */ |
| 163 | background: var(--rst-surface); |
| 164 | box-shadow: var(--rst-shadow-pop); |
| 165 | } |
| 166 | } |
| 167 | |
| 168 | /* The app switcher: a float, so its tiles may be rounded. */ |
| 169 | [estilo-switcher] { position: relative; } |
| 170 | [estilo-switcher-panel] { position: absolute; inset-inline-end: 0; inset-block-start: calc(100% + 4px); } |
| 171 | [estilo-switcher-grid] { display: grid; grid-template-columns: repeat(3, 5.5rem); gap: 0.25rem; } |
| 172 | [estilo-switcher-app] { |
| 173 | display: flex; |
| 174 | flex-direction: column; |
| 175 | align-items: center; |
| 176 | gap: 0.35rem; |
| 177 | padding: 0.6rem 0.25rem; |
| 178 | border-radius: var(--rst-radius-sm); /* inside a float: rounded is allowed */ |
| 179 | color: var(--rst-text); |
| 180 | font-size: var(--rst-fs-xs); |
| 181 | text-decoration: none; |
| 182 | } |
| 183 | [estilo-switcher-app]:hover, |
| 184 | [estilo-switcher-app]:focus-visible { background: var(--rst-accent-soft); } |
| 185 | [estilo-switcher-app][aria-current] { background: var(--rst-accent-soft); font-weight: 600; } |
| 186 | [estilo-switcher-app] img { border-radius: 21%; } |
| 187 | |
| 188 | /* The account menu. The account is always the last thing at the inline |
| 189 | end of the bar: the "same corner" rule. */ |
| 190 | [estilo-account] { position: relative; } |
| 191 | [estilo-account-panel] { position: absolute; inset-inline-end: 0; inset-block-start: calc(100% + 4px); min-inline-size: 15rem; } |
| 192 | [estilo-avatar] { |
| 193 | display: inline-flex; |
| 194 | align-items: center; |
| 195 | justify-content: center; |
| 196 | inline-size: 1.75rem; |
| 197 | block-size: 1.75rem; |
| 198 | border-radius: 50%; /* an identity mark, not a highlight */ |
| 199 | background: var(--rst-accent-soft); |
| 200 | color: var(--rst-accent); |
| 201 | font-size: var(--rst-fs-xs); |
| 202 | font-weight: 650; |
| 203 | } |
| 204 | [estilo-menu-identity] { display: grid; gap: 0.1rem; padding: 0.5rem var(--estilo-item-padding-inline, 0.65rem); } |
| 205 | [estilo-menu-identity-label] { color: var(--rst-text-muted); font-size: var(--rst-fs-xs); } |
| 206 | [estilo-menu-identity-name] { font-weight: 600; overflow-wrap: anywhere; } |
| 207 | [estilo-menu-identity-address] { color: var(--rst-text-muted); overflow-wrap: anywhere; } |
| 208 | [estilo-menu-identity-name] + [estilo-menu-identity-address] { font-size: var(--rst-fs-xs); } |
| 209 | [estilo-bar-signin] { flex: none; } |
| 210 | |
| 211 | /* The account at a sidebar rail's foot (Account.Rail), for an app whose |
| 212 | chrome is a rail rather than the bar. The trigger is a row the rail's |
| 213 | width: the avatar, then who is signed in, cut short with an ellipsis. |
| 214 | It stays in the page flow, so its highlight stays square. The panel is |
| 215 | the foot's own width, edge to edge with the trigger: hung from the |
| 216 | inline end of a foot narrower than the bar's 15rem panel it ran off |
| 217 | the window's start edge, and from the inline start it ran past the |
| 218 | rail, whose overflow-y: auto clips it (Fichas' first screenshot). */ |
| 219 | [estilo-account~="rail"] > [estilo-bar-trigger] { |
| 220 | justify-content: flex-start; |
| 221 | gap: var(--rst-sp-2); |
| 222 | inline-size: 100%; |
| 223 | block-size: auto; |
| 224 | min-block-size: 2.5rem; |
| 225 | color: var(--rst-text); |
| 226 | } |
| 227 | [estilo-account-who] { min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } |
| 228 | [estilo-account~="rail"] > [estilo-account-panel] { inset-inline: 0; inline-size: auto; min-inline-size: 0; max-inline-size: none; } |
| 229 | /* A drop-up where the rail is the viewport's height (rastrillo's shell |
| 230 | breakpoint): below the foot there is only the window's edge. Below |
| 231 | 800px the rail is a disclosure at the top of the page, with the page |
| 232 | under it and too little above, so the panel opens downward there. */ |
| 233 | @media (min-width: 800px) { |
| 234 | [estilo-account~="rail"] > [estilo-account-panel] { |
| 235 | inset-block-start: auto; |
| 236 | inset-block-end: calc(100% + 4px); |
| 237 | max-block-size: max(calc(100dvh - 5rem), 8rem); |
| 238 | } |
| 239 | } |
| 240 | |
| 241 | /* Visually hidden, still announced. */ |
| 242 | [estilo-visually-hidden] { position: absolute; clip-path: inset(50%); inline-size: 1px; block-size: 1px; overflow: hidden; white-space: nowrap; } |
| 243 | |
| 244 | /* Sign-in, check-your-email and signed-out pages: the Oficina door. The |
| 245 | suite's sky behind (estilo sky.css places and animates it), one card on |
| 246 | it with the app's mark and name at its head — the same door Home draws |
| 247 | at id.oficina.works, in the app's own colour. The door spans the page |
| 248 | whatever the app's layout gives it, so the sky is never boxed in. */ |
| 249 | :root:has([estilo-door]) { overflow-x: clip; --rst-scrollbar-gutter: auto; scrollbar-gutter: auto; } |
| 250 | [estilo-door] { |
| 251 | box-sizing: border-box; |
| 252 | margin-inline: calc(50% - 50vw); |
| 253 | padding: max(40px, 9vh) var(--rst-sp-4) var(--rst-sp-6); |
| 254 | min-block-size: 680px; |
| 255 | /* The whole sky lies inside the door, fading in and out, so the door's |
| 256 | edges never cut it: under an app's bar there is no page top to hide |
| 257 | an edge behind, as there is on Home. A sky's layers overhang its box |
| 258 | by 12% above and below (sky.css), and fade in over their first 22% |
| 259 | and out from 58% to 92% — so a 560px sky placed 67px down starts |
| 260 | transparent at the door's top and has faded out by 640px. */ |
| 261 | --estilo-sky-top: 67px; |
| 262 | --estilo-sky-height: 560px; |
| 263 | } |
| 264 | [estilo-signin] { |
| 265 | box-sizing: border-box; |
| 266 | max-inline-size: 460px; |
| 267 | margin: 0 auto; |
| 268 | padding: 34px 40px 32px; |
| 269 | background: var(--rst-surface); |
| 270 | border-radius: 20px; |
| 271 | box-shadow: 0 0 0 1px light-dark(transparent, var(--rst-line)), var(--rst-shadow-lift); |
| 272 | } |
| 273 | [estilo-signin] > [estilo-brand] { display: flex; align-items: center; gap: 10px; margin: 0 0 22px; font-size: 1.2rem; font-weight: 650; letter-spacing: -0.01em; } |
| 274 | [estilo-signin] > [estilo-brand] [estilo-brand-icon] { inline-size: 36px; block-size: 36px; } |
| 275 | [estilo-signin] h1 { margin: 0; font-size: 1.75rem; font-weight: 620; letter-spacing: -0.02em; line-height: 1.15; } |
| 276 | [estilo-signin-lede] { margin: 0.5rem 0 var(--rst-sp-4); color: var(--rst-text-muted); } |
| 277 | [estilo-signin] > h1 + :not([estilo-signin-lede]) { margin-block-start: var(--rst-sp-4); } |
| 278 | [estilo-signin-form] { max-inline-size: none; margin-block-start: var(--rst-sp-4); } |
| 279 | [estilo-signin-body] { margin: var(--rst-sp-4) 0; } |
| 280 | @media (max-width: 520px) { |
| 281 | [estilo-door] { padding-inline: 14px; padding-block-start: 28px; } |
| 282 | [estilo-signin] { padding: 26px 22px 24px; } |
| 283 | } |
| 284 | [estilo-signin-foot] { margin-block-start: var(--rst-sp-6); font-size: var(--rst-fs-xs); color: var(--rst-text-muted); } |
| 285 | /* The door's links are drawn here, not left to the app: an app that does |
| 286 | not link base.css (Calendar, Memoria, Calls, Fichas) showed the browser's |
| 287 | blue on the card. As base.css does, a link in the card is the app's |
| 288 | colour at the lowest specificity, so a link drawn as a button keeps the |
| 289 | button's; the foot's is quiet, like its text. */ |
| 290 | :where([estilo-signin]) a { color: var(--rst-accent); text-underline-offset: 0.15em; } |
| 291 | [estilo-signin-foot] a { color: inherit; } |
| 292 | [estilo-signin-foot] a:hover, [estilo-signin-foot] a:focus-visible { color: var(--rst-text); } |
| 293 | |