oxedyne/daimond/www/css/responsive.css
20.8 KiB, 1 run
created by r2519314175:1125, which is this file's identity for as long as the history lasts, whatever it is later renamed to
download · who wrote it · its history
| 1 | /* ============================================================ |
| 2 | Daimond — mobile responsive layer |
| 3 | ------------------------------------------------------------ |
| 4 | Loaded AFTER app.css. On small screens the four desktop |
| 5 | panels become one panel at a time, switched with the bottom |
| 6 | nav (.mnav). The active panel is chosen by body[data-mpanel]. |
| 7 | Everything here is confined to `@media (max-width: 760px)` |
| 8 | so desktop (>760px) is untouched. |
| 9 | ============================================================ */ |
| 10 | |
| 11 | @media (max-width: 760px) { |
| 12 | |
| 13 | /* The page itself never scrolls or drags: it is pinned to the DYNAMIC |
| 14 | viewport (dvh — the space actually visible, excluding the browser's |
| 15 | address bar, which `100vh` wrongly includes so the app spilled below the |
| 16 | fold and you had to drag to reach the bar). Panels scroll internally. */ |
| 17 | html, body { |
| 18 | height: 100vh; /* fallback for engines without dvh */ |
| 19 | height: 100dvh; |
| 20 | overflow: hidden; |
| 21 | overscroll-behavior: none; |
| 22 | max-width: 100%; |
| 23 | } |
| 24 | |
| 25 | #app { |
| 26 | height: 100vh; |
| 27 | height: 100dvh; |
| 28 | /* The foot stays at nought: the bottom bar owns the home indicator's |
| 29 | space, in its own padding, and adding it here as well would lift the |
| 30 | bar clear of the screen edge by twice the inset. */ |
| 31 | padding: calc(8px + var(--safe-t)) calc(8px + var(--safe-r)) 0 calc(8px + var(--safe-l)); |
| 32 | gap: 6px; |
| 33 | overflow: hidden; |
| 34 | } |
| 35 | /* The top bar must never push the layout wider than the screen. */ |
| 36 | .topbar { overflow: hidden; } |
| 37 | .top-meter { min-width: 0; overflow: hidden; } |
| 38 | /* ...and `overflow: hidden` is not a fit, it is a guillotine. Signed in, the |
| 39 | row once ran 22-40px past a phone's width, so the appearance-and-layout |
| 40 | button -- theme, skin, reading size -- was CLIPPED OFF the screen on every |
| 41 | phone tested, and on an iPhone SE the guide "?" went with it. The maker's |
| 42 | badge held that slot then, and stood down here because a signature is |
| 43 | worth less room than the two controls it would push off. |
| 44 | |
| 45 | Nothing stands down now: the badge has moved into the About dialog and the |
| 46 | slot holds a 32px icon button instead of a ~70px lozenge, so the row is |
| 47 | NARROWER than it was even with the badge hidden. The phone therefore keeps |
| 48 | About, which is where the badge's two claims now are -- a signature the |
| 49 | phone could never show at all is worse than one behind a button. */ |
| 50 | |
| 51 | /* The lockup drops to the base logo height for the WHOLE mobile shell, not |
| 52 | only for phones. The wordmark was redrawn at 4.44:1 (it was 2.65:1), so at |
| 53 | the desktop's 36px it is 160px wide rather than 96, and the row -- burger, |
| 54 | lockup, five controls -- ran past its box anywhere below 476px. At 24px the |
| 55 | lockup is 106px and the row fits from 422px up, which covers the whole band |
| 56 | this shell is drawn on above the phone rule below. |
| 57 | |
| 58 | Scoped to `.topbar` on purpose. These rules were written unscoped, and |
| 59 | `.brand-wordmark` is also worn by the About dialog's lockup (it carries the |
| 60 | class for the light/dark swap) -- so an unscoped height here silently beat |
| 61 | `.about-word`, and About drew its signature at 20-24px on every phone |
| 62 | instead of the 46px its own rule asks for. */ |
| 63 | .topbar .brand-wordmark { height: var(--logo-h); } |
| 64 | |
| 65 | /* Desktop-only machinery disappears. */ |
| 66 | .phandle { display: none !important; } |
| 67 | .panel-close { display: none !important; } |
| 68 | /* Except the RAIL's. Every other panel is a destination reached from the |
| 69 | bottom bar and has nothing to be closed back to, but the rail here is the |
| 70 | DRAWER, and the drawer covers 335 of a 390px screen. Its only dismissals |
| 71 | were a tap on the 55px of scrim beside it and the hamburger — which the |
| 72 | drawer itself is drawn on top of. A surface that big needs a way out on |
| 73 | the surface. The click is caught in mobile.js and closes the drawer, not |
| 74 | the panel. */ |
| 75 | .panel.rail .panel-close { display: flex !important; } |
| 76 | /* The chip row is not in the header here -- it IS the footer (see .mnav in |
| 77 | css/mobile.css, and `placeChips` in js/mobile.js, which moves the one row |
| 78 | between the two). It used to be hidden on a phone altogether. */ |
| 79 | |
| 80 | /* One DESTINATION at a time, full width. The dwellable panels — Chat, Email, |
| 81 | Files, Agents — take the whole screen and are chosen from the bottom bar. |
| 82 | The desktop zones collapse (`display: contents` lets their panels lay out |
| 83 | against .main directly) and the inline widths/heights the desktop resizers |
| 84 | set must lose, hence !important. The rail (a drawer) and the stage guests |
| 85 | (a sheet) are positioned by mobile.css and are excluded here. */ |
| 86 | .main { position: relative; } |
| 87 | .pcol, .stage, .dock { display: contents !important; width: auto !important; } |
| 88 | .main .panel:not(.rail) { height: auto !important; } |
| 89 | .main .panel:not(.rail) { display: none !important; width: 100% !important; min-width: 0 !important; flex: 1 !important; } |
| 90 | body[data-mpanel="ai"] .main .ai { display: flex !important; } |
| 91 | body[data-mpanel="agents"] .main .agents { display: flex !important; } |
| 92 | body[data-mpanel="mail"] .main .mail { display: flex !important; } |
| 93 | body[data-mpanel="work"] .main .work { display: flex !important; } |
| 94 | |
| 95 | /* The bottom bar. Its contents are the chip row, and its rules are in |
| 96 | css/mobile.css with the rest of the phone shell; what stays here is the |
| 97 | band itself, because the layout above depends on its height. */ |
| 98 | .mnav { |
| 99 | display: flex; |
| 100 | align-items: stretch; |
| 101 | flex: none; |
| 102 | border-top: 1px solid var(--border); |
| 103 | background: var(--bg-primary); |
| 104 | margin: 0 -8px; |
| 105 | } |
| 106 | |
| 107 | /* ── The header's controls SCROLL ───────────────────────────── |
| 108 | Reported from a phone: "this header does not scroll so I can't see a lot |
| 109 | of buttons." Every other stage panel's head wraps (`.web .chead, .doc |
| 110 | .chead, .msg .chead { flex-wrap: wrap }` in app.css) and the AI panel's |
| 111 | never did, so its row of chips — mode, Concise, Copy, Steps, Collapse, |
| 112 | Fold on a Diamond, and full screen — squeezed the title to an ellipsis and |
| 113 | then simply ran off the end of the panel, unreachable. |
| 114 | |
| 115 | SCROLLED RATHER THAN WRAPPED, deliberately. Wrapping costs a second 36px |
| 116 | row of vertical space on every screen for ever, on the axis a phone has |
| 117 | least of; scrolling costs nothing and is the gesture the user asked for. |
| 118 | The title keeps a floor so it cannot be crushed to nothing in the trade, |
| 119 | and the scrollbar is hidden because a phone has no room for one and the |
| 120 | content is dragged, not clicked. */ |
| 121 | .panel.ai .chead { gap: 6px; } |
| 122 | .panel.ai .ctitle { min-width: 4.5em; } |
| 123 | .panel.ai .chead-right { |
| 124 | flex: 1 1 auto; |
| 125 | min-width: 0; |
| 126 | overflow-x: auto; |
| 127 | overflow-y: hidden; |
| 128 | scrollbar-width: none; |
| 129 | -webkit-overflow-scrolling: touch; |
| 130 | /* Without this the chips shrink to fit instead of overflowing, which is |
| 131 | the state that hid them: a row that fits is a row nothing scrolls. */ |
| 132 | justify-content: flex-start; |
| 133 | } |
| 134 | .panel.ai .chead-right::-webkit-scrollbar { display: none; } |
| 135 | .panel.ai .chead-right > * { flex: none; } |
| 136 | |
| 137 | /* Comfortable chat input row. */ |
| 138 | .chat-output { padding: 12px 14px; } |
| 139 | .chat-msg-user .chat-msg-content, |
| 140 | .chat-msg-assistant .chat-msg-content { max-width: 100%; } |
| 141 | .chat-msg-content pre, |
| 142 | .chat-msg-content table, |
| 143 | .tool-args, |
| 144 | .tool-result { max-width: 100%; overflow-x: auto; } |
| 145 | .chat-input-bar { gap: 8px; padding: 8px 10px; } |
| 146 | /* 16px avoids iOS Safari's auto-zoom on focus. */ |
| 147 | #chat-input { font-size: var(--fs-xl); padding: 11px 13px; } |
| 148 | /* Send and the walk-back sit in one row and are one control on desktop, both |
| 149 | 32px square. Only Send was grown for touch, so the pair read as a large |
| 150 | button beside a small one, bottom-aligned and mismatched. They grow together. */ |
| 151 | #chat-send, #chat-jump, #chat-end { min-width: 46px; width: 46px; height: 46px; font-size: var(--fs-2xl); } |
| 152 | |
| 153 | /* ── The box you type in gets the room ──────────────────────── |
| 154 | Measured on a 390px phone: the bar is 372 wide and the text box got 170 of |
| 155 | it, because Send and the two walk-back chevrons take 46 each plus gaps — |
| 156 | three buttons eating more of the row than the thing the row is for. |
| 157 | |
| 158 | The two CHEVRONS ARE NOT COMPOSITION. They walk the transcript; they have |
| 159 | nothing to do with the message being written, and on the one screen where |
| 160 | space is scarce they were charging full price for it. So on a phone they |
| 161 | leave the bar and float at the foot of the thread, where what they act on |
| 162 | actually is. Absolute, so the DOM and every handler are untouched — this is |
| 163 | a placement, not a rebuild. |
| 164 | |
| 165 | The text box then takes the width they gave up: 170px becomes about 280. |
| 166 | Nothing is lost and nothing new is drawn, which is what "without eating |
| 167 | screen real estate" has to mean. */ |
| 168 | .chat-input-bar { position: relative; } |
| 169 | .chat-input-bar #chat-jump, |
| 170 | .chat-input-bar #chat-end { |
| 171 | position: absolute; |
| 172 | bottom: calc(100% + 10px); |
| 173 | width: 40px; min-width: 40px; height: 40px; |
| 174 | border-radius: 50%; |
| 175 | /* Reading over the thread rather than sitting in a bar, so it needs its |
| 176 | own ground: over a message bubble a transparent button is a floating |
| 177 | glyph nobody can tell is pressable. */ |
| 178 | background: var(--bg-secondary); |
| 179 | border: 1px solid var(--border); |
| 180 | box-shadow: 0 2px 8px rgb(0 0 0 / 0.28); |
| 181 | opacity: 0.94; |
| 182 | z-index: 3; |
| 183 | } |
| 184 | .chat-input-bar #chat-jump { right: 62px; } |
| 185 | .chat-input-bar #chat-end { right: 12px; } |
| 186 | /* WHAT THEY FLOAT OVER, which the first attempt got wrong and a screenshot |
| 187 | caught: they cleared the composer and landed squarely on the attachments |
| 188 | strip, hiding the end of "Mark one in with the paperclip". The strip is not |
| 189 | always there — a Diamond with folders marked in draws rows instead — so |
| 190 | lifting the buttons by a fixed amount would clear it in one state and float |
| 191 | them into the middle of the thread in the other. |
| 192 | Keeping the anchor and giving the strip room on its right is the version |
| 193 | that is correct in both: nothing moves, and no words go under a button. |
| 194 | |
| 195 | The room goes on the BODY and not on the strip, which was the second thing |
| 196 | a screenshot caught: padding the whole strip dragged the group's own `+` |
| 197 | button 104px in from the edge it belongs on. The head row is a title and a |
| 198 | button, neither of which the chevrons ever reach; only the body sits under |
| 199 | them. */ |
| 200 | .chat-attach-strip .ws-body, |
| 201 | .chat-attach-strip .attach-body { padding-right: 100px; } |
| 202 | |
| 203 | /* Give lists a little more row height for touch. */ |
| 204 | .session-box { padding: 10px 11px; } |
| 205 | .files-row { padding: 8px 12px; } |
| 206 | |
| 207 | /* A thumb's floor. The shared touch block below lifts the small controls to |
| 208 | ~34px, which suits a dense tablet dock; on a phone, where these are the |
| 209 | only controls in the bar, they get the full 40px box. Confined to the |
| 210 | phone breakpoint on purpose -- 40px each would burst the Workspace |
| 211 | panel's six-button head in a 260px dock column on a tablet. */ |
| 212 | .top-actions .icon-btn, |
| 213 | .top-actions .update-chip, |
| 214 | #drawer-btn { min-width: 40px; min-height: 40px; } |
| 215 | .railhead .addbtn { width: 40px; height: 40px; } |
| 216 | /* The search box was 29px tall: legible, but nothing like a target. */ |
| 217 | .diamond-search { padding: 9px 10px; } |
| 218 | |
| 219 | /* The rail is the DRAWER here, and every row in it is something a thumb has |
| 220 | to land on: the status rows were 23px tall, the account row 26, the drawer's |
| 221 | own close 21 square. Dense is right on a desktop, where these are read with |
| 222 | a pointer over them; in a drawer they need a finger's worth of height. |
| 223 | (The drawer scrolls, so the extra height costs nothing that matters.) */ |
| 224 | /* `.astat-sync` is here because its chip is pressable in the one state the |
| 225 | user can lift -- "Sync off" opens Credits, and a safe start turns syncing |
| 226 | back on -- so it is a target and needs a finger's worth of height like the |
| 227 | rows around it. Without it the sync row sat 12px shorter than its |
| 228 | neighbours and read as a caption rather than a row. */ |
| 229 | .astat-row.astat-btn, |
| 230 | .astat-row.astat-sync { padding-top: 9px; padding-bottom: 9px; } |
| 231 | .astat-id .user-row { padding: 9px 4px; } |
| 232 | .astat-id #settings-btn { min-width: 40px; min-height: 40px; } |
| 233 | .admin-item { padding-top: 10px; padding-bottom: 10px; } |
| 234 | .tile-label { padding-top: 6px; padding-bottom: 6px; } |
| 235 | /* Steps / collapse, in the chat header: 26px tall is a pointer's target, not |
| 236 | a thumb's. */ |
| 237 | .chead .chip-btn { padding: 8px 10px; } |
| 238 | |
| 239 | /* ── Full screen, on a phone ────────────────────────────────────── |
| 240 | The desktop rules (app.css, `[data-cfull]`) take the top bar, the rail and |
| 241 | the dock; here the bottom bar goes too, because a phone's app switcher is |
| 242 | the last piece of chrome around the page. |
| 243 | |
| 244 | THE FOOT'S SAFE INSET COMES BACK WITH IT. `#app` above sets its bottom |
| 245 | padding to nought and says why: the bar owns the home indicator's space in |
| 246 | its own padding, and adding it twice would lift the bar off the screen |
| 247 | edge. With the bar gone there is nothing holding that space, so the panel |
| 248 | would run under the indicator -- and the crystal page's last row with it. */ |
| 249 | :root[data-cfull] .mnav { display: none !important; } |
| 250 | :root[data-cfull] #app { padding-bottom: var(--safe-b); } |
| 251 | /* The way out is the only control left in the header, and on a phone it is |
| 252 | the one a thumb has to find. */ |
| 253 | :root[data-cfull] .chead .cfull-btn { min-height: 40px; padding: 8px 12px; } |
| 254 | } |
| 255 | |
| 256 | /* Touch tap targets: secondary controls default to ~22px which is under the |
| 257 | 24px WCAG floor; enlarge on small screens, and reveal hover-only close |
| 258 | buttons (there is no hover on touch). */ |
| 259 | @media (max-width: 760px), (pointer: coarse) { |
| 260 | /* Seven controls the existing lifts in this block missed. The worst was the |
| 261 | tag disclosure at 315x16.8 -- the way into the whole filing system, in a |
| 262 | drawer whose entire purpose is to be worked with a thumb. 24x24 is the |
| 263 | WCAG 2.2 AA floor. */ |
| 264 | .tagf-toggle { padding-top: 5px; padding-bottom: 5px; } |
| 265 | .tagf-pool .tag-chip, |
| 266 | .tagf-active .tag-chip, |
| 267 | .session-box-meta .tag-chip { padding-top: 3px; padding-bottom: 3px; } |
| 268 | .files-mode-btn, |
| 269 | .files-mode-chip { min-height: 24px; padding-top: 4px; padding-bottom: 4px; } |
| 270 | .tile-fold { padding-top: 5px; padding-bottom: 5px; } |
| 271 | /* The wordmark keeps its 20px ink and gains a 24px target around it. */ |
| 272 | .brand { display: flex; align-items: center; min-height: 24px; } |
| 273 | /* The chips are 24px here rather than 18, so the same cap would show the |
| 274 | same three and a half rows in half again the height -- and the drawer has |
| 275 | less to spare than the desktop rail, not more. Two and a half rows on a |
| 276 | phone: still cut through a row, so it still says there is more of it, |
| 277 | without the filter taking the space the Diamonds came for. */ |
| 278 | .tagf-pool, .tagf-active { max-height: calc(6em + 16px); } |
| 279 | .addbtn { width: 34px; height: 34px; font-size: var(--fs-xl); } |
| 280 | .icon-btn { padding: 8px 10px; font-size: var(--fs-2xl); } |
| 281 | /* ── The closer's thumb floor ───────────────────────────────────────────── |
| 282 | Every way out of every dialog, popover, drawer and sheet, at 44x44 — the |
| 283 | iOS Human Interface floor, above the 24px WCAG 2.2 AA one, because this is |
| 284 | the control a user reaches for when they are stuck and there is no second |
| 285 | attempt if they miss it. |
| 286 | |
| 287 | The BOX grows; the ink does not. `.ic` stays 16px inside it, so a phone |
| 288 | gets a bigger target rather than a bigger cross. |
| 289 | |
| 290 | This is the user's report answered: on a 390px screen the appearance menu |
| 291 | is 359px wide, which leaves 19px either side to tap and no closer at all. |
| 292 | A margin is not a way out. */ |
| 293 | .ui-close { width: 44px; height: 44px; } |
| 294 | /* The two closers written in static markup, which predate `.ui-close` and |
| 295 | carry it now: the phone sheet's and the admin drawer's. Named as well, |
| 296 | because each also carries a size of its own from the block it was tuned |
| 297 | in and the last rule would otherwise win. */ |
| 298 | .msheet-close, #admin-close, .admin-back { min-width: 44px; min-height: 44px; } |
| 299 | |
| 300 | /* These named classes that do not exist (`.files-row-del`), so on touch the |
| 301 | file buttons stayed hidden and could not be reached at all. The real class |
| 302 | is `.files-del`, and the file buttons hide with `visibility`, not |
| 303 | `opacity`. The tile's closer cross was removed in phase C -- Delete lives |
| 304 | at the foot of the cog dialog now -- so its three rules went with it. */ |
| 305 | .files-row .files-del { visibility: visible; opacity: 1; } |
| 306 | |
| 307 | /* A tablet keeps the desktop bar and its chip row, where the controls were |
| 308 | 23-36px tall. They grow in HEIGHT only: the chip row already has more |
| 309 | chips than it can show, so nothing here may take a pixel of width. */ |
| 310 | .top-actions .icon-btn, |
| 311 | .top-actions .update-chip { min-height: 40px; } |
| 312 | .ptag { padding-top: 8px; padding-bottom: 8px; } |
| 313 | } |
| 314 | |
| 315 | /* ── The smallest screens ──────────────────────────────────────────────────── |
| 316 | 320px (an iPhone SE / iPhone 5) has 304px of usable bar, and the phone layout |
| 317 | above wants 343. Rather than drop another control -- the appearance menu is |
| 318 | the one that was being lost, and it is the way to the theme, the skin and the |
| 319 | reading size -- the bar itself gets smaller: a shorter wordmark, tighter gaps |
| 320 | and the compact 34px touch box. Everything stays reachable. */ |
| 321 | /* A Diamond's labelled rows stack below the phone breakpoint. |
| 322 | The label holds a fixed 116px so the pulldowns line up their left edges — |
| 323 | which is right on a desktop and wrong at 380px, where 116 plus the gap leaves |
| 324 | the select too narrow for its own text and it overflowed the card by 19px. |
| 325 | `verify_raildialogs` measured it at warm-dark-380. Stacked, each control gets |
| 326 | the whole width and the column they were lining up for is moot. |
| 327 | Every name the rows and their labels go by is listed. Miss one and that row |
| 328 | alone keeps its 116px column and overflows the card exactly as before — |
| 329 | which is why the colour rows are named here as well as the model rows. */ |
| 330 | @media (max-width: 760px) { |
| 331 | .tile-dlg-field, |
| 332 | .tile-dlg-model { flex-direction: column; align-items: stretch; gap: 4px; } |
| 333 | .tile-dlg-label, |
| 334 | .tile-dlg-model .tile-model-chip { flex: 0 0 auto; width: auto; text-align: left; } |
| 335 | /* A swatch stretched to the card's width is a colour BAR, not a chip a |
| 336 | thumb presses; it keeps its size and sits at the start of its own row. */ |
| 337 | .tile-dlg-swatch { align-self: flex-start; } |
| 338 | } |
| 339 | |
| 340 | /* THE WORDMARK STANDS DOWN BEFORE A CONTROL DOES, and on a phone it now stands |
| 341 | down to the mark alone. The lockup was redrawn at 4.44:1, so even at the base |
| 342 | 24px it is 106px wide, and the row -- burger, lockup, five controls -- needs |
| 343 | 422px to hold that. Every phone but the two Pro Maxes is narrower, and the |
| 344 | only heights that would have fitted (17px at 390) put the lettering below |
| 345 | legible. Squashing the artwork is not on the table: the proportions are the |
| 346 | author's. |
| 347 | |
| 348 | So the WORD goes and the MARK stays. The mark is the brand -- it is what the |
| 349 | chat header wears at 18px (`#chead-mark`) and what the extension ships alone |
| 350 | -- and at 24px tall it is 17px wide, which the row can hold down to 320px with |
| 351 | room to spare. That is why the height rule that used to drop to 20px below 380 |
| 352 | has gone: with the word off the bar there is nothing left to save. |
| 353 | |
| 354 | Swapped with `content`, not by hiding one image and showing another, so the |
| 355 | two ink variants keep their single light/dark rule and the markup is |
| 356 | untouched. The identity modal does the same thing two files over. */ |
| 357 | @media (max-width: 430px) { |
| 358 | .topbar .brand-wordmark { content: url("../assets/daimond_mark.svg"); } |
| 359 | } |
| 360 | |
| 361 | /* 380, not the 360 this began at. The bar's right-hand row is five controls |
| 362 | once you are signed in -- update, About, link a device, guide, appearance -- |
| 363 | and at the full 40px touch box those five want 216px, which the layout only |
| 364 | affords from 380 up. Between 361 and 379 the bar's `overflow: hidden` was |
| 365 | taking 3-17px off the appearance menu at the end of the row: an iPhone SE |
| 366 | (2nd/3rd gen) and an iPhone 8 are both 375. |
| 367 | |
| 368 | The policy is the one this block was written for and is unchanged by About |
| 369 | arriving: the bar gets smaller rather than shedding a control, because every |
| 370 | one of the five is the only way to the thing it opens. `verify_about.mjs` |
| 371 | measures the row for a clip at 320, 375 and 390 so the next control added |
| 372 | here fails loudly rather than quietly amputating the last one. */ |
| 373 | @media (max-width: 380px) { |
| 374 | .topbar { gap: 8px; } |
| 375 | /* 2px between the buttons rather than 4. The TARGETS keep their 34px, which |
| 376 | is what a thumb lands on; it is only the air between them that goes. */ |
| 377 | .top-actions { gap: 2px; } |
| 378 | .top-actions .icon-btn, |
| 379 | .top-actions .update-chip, |
| 380 | #drawer-btn { min-width: 34px; min-height: 34px; } |
| 381 | /* The unlock lockup, at 54px, is 240px wide -- which is the entire content |
| 382 | width of a 90vw card on a 320px phone, so it ran to both padding edges and |
| 383 | over them. 40px is 177px, and leaves the card its margins back. */ |
| 384 | #identity-modal .login-logo { height: 40px; } |
| 385 | } |