oxedyne/daimond/www/css/files.css
29.9 KiB, 1 run
created by r2519314175:1097, 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 | /* ── Daimond workspace file browser (WS-F) — docked panel ── */ |
| 2 | .files-card { |
| 3 | flex: 1; min-height: 0; |
| 4 | display: flex; flex-direction: column; |
| 5 | background: var(--bg-secondary); |
| 6 | border: 1px solid var(--border); |
| 7 | border-radius: 10px; |
| 8 | overflow: hidden; |
| 9 | font-size: var(--fs-sm); |
| 10 | } |
| 11 | |
| 12 | .files-actions { display: flex; gap: 4px; align-items: center; } |
| 13 | |
| 14 | /* Workspace root mode: OPFS sandbox vs an open real folder (FSA). */ |
| 15 | /* The chips total 418px, and the Workspace sheet is 380px wide on a phone, so |
| 16 | the row wraps -- and the second row was then clipped by the foot of the sheet. |
| 17 | It scrolls sideways below the breakpoint instead: a control on a second row |
| 18 | the sheet cuts off is one nobody can reach, where a control reached by a swipe |
| 19 | at least exists. 10px is the dock's one body inset. */ |
| 20 | .files-mode { |
| 21 | display: flex; flex-wrap: wrap; align-items: center; gap: 6px; |
| 22 | padding: 6px 10px; |
| 23 | border-bottom: 1px solid var(--border); |
| 24 | flex: none; |
| 25 | } |
| 26 | @media (max-width: 760px) { |
| 27 | .files-mode { |
| 28 | flex-wrap: nowrap; |
| 29 | overflow-x: auto; |
| 30 | scrollbar-width: none; |
| 31 | } |
| 32 | .files-mode::-webkit-scrollbar { display: none; } |
| 33 | .files-mode > * { flex: 0 0 auto; } |
| 34 | } |
| 35 | .files-mode-chip { |
| 36 | display: inline-flex; align-items: center; gap: 4px; |
| 37 | padding: 2px 8px; border-radius: 999px; |
| 38 | font-size: var(--fs-2xs); font-weight: 600; |
| 39 | border: 1px solid var(--border-2); |
| 40 | } |
| 41 | /* The .ic default of 16px belongs to toolbar buttons; on a --fs-2xs chip the |
| 42 | glyph must sit at cap height. */ |
| 43 | .files-mode-chip .ic { width: 13px; height: 13px; } |
| 44 | /* A chip states where things are. Two of them name the workspace root and are |
| 45 | mutually exclusive; the third names a residency and is additive -- so the |
| 46 | lit one is not "the selected tab", it is simply what is true. */ |
| 47 | .files-mode-chip { color: var(--text-secondary); background: var(--bg-tertiary); } |
| 48 | .files-mode-chip.active { color: var(--accent-text); background: var(--accent-soft); border-color: var(--accent); } |
| 49 | .files-mode-chip.cloud { color: var(--accent-text); background: var(--accent-soft); border-color: var(--accent); } |
| 50 | /* "Nothing in cloud storage yet" -- the chip is quieter because there is nothing |
| 51 | behind it, NOT because it is switched off: it keeps `role="button"`, |
| 52 | `tabindex="0"` and its click, and its title is a sentence the reader is meant |
| 53 | to read. `opacity: 0.55` over the whole chip put those words at 2.22:1 in |
| 54 | Light and Lollypop, 2.26 in Mist, 2.84 in Dark -- under even the audit's |
| 55 | invisible-text alarm, and nowhere near the 4.5 an 11px label asks for. |
| 56 | Opacity cannot be the answer: measured across the eleven palettes it takes |
| 57 | 0.93 before the words clear 4.5, and 0.93 is not a ghost. So the fade moves to |
| 58 | the GLYPH, which carries no words, and the label drops to the app's own quiet |
| 59 | rung instead -- 4.51 (Light) to 5.37 (Dusk), and still a rung below the two |
| 60 | chips beside it. The chip reads as quiet; the sentence stays readable. |
| 61 | |
| 62 | The `:not()` guard is not decoration: `.ghost` is also put on the Machine chip |
| 63 | when no folder can be picked, and that chip can be the LIT one at the same |
| 64 | time. The old rule only set an opacity, so the lit accent survived under it; a |
| 65 | colour would have taken it away. */ |
| 66 | .files-mode-chip.ghost:not(.active):not(.cloud) { color: var(--text-muted); } |
| 67 | .files-mode-chip.ghost .ic { opacity: 0.55; } |
| 68 | .files-mode-chip.act { cursor: pointer; } |
| 69 | .files-mode-chip.act:hover { background: var(--bg-hover); border-color: var(--accent); } |
| 70 | .files-mode-chip.act:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; } |
| 71 | .files-mode-btn { |
| 72 | padding: 2px 8px; border-radius: 6px; |
| 73 | font-size: var(--fs-2xs); cursor: pointer; |
| 74 | color: var(--text-secondary); |
| 75 | background: var(--bg-tertiary); border: 1px solid var(--border-strong); |
| 76 | } |
| 77 | .files-mode-btn:hover { background: var(--bg-hover); } |
| 78 | .files-mode-btn.accent { |
| 79 | color: var(--on-fill, #fff); background: var(--accent); border-color: var(--accent); |
| 80 | } |
| 81 | .files-mode-btn.accent:hover { background: var(--accent-hover); } |
| 82 | .files-mode-msg { |
| 83 | flex-basis: 100%; font-size: var(--fs-2xs); color: var(--text-secondary); |
| 84 | } |
| 85 | .files-mode-msg.err { color: var(--accent-text); } |
| 86 | |
| 87 | /* ── Whose files: the workspace, or one Diamond's ────────────── |
| 88 | A second row under the mode row, and deliberately not part of it: those chips |
| 89 | name a PLACE the files are (the sandbox, a folder on this disk, cloud |
| 90 | storage), these name WHOSE files are being shown. Hidden outright when no |
| 91 | Diamond is open, because a switch with one position is furniture. */ |
| 92 | .files-scope { |
| 93 | display: flex; flex-wrap: wrap; align-items: center; gap: 6px; |
| 94 | padding: 6px 10px; |
| 95 | border-bottom: 1px solid var(--border); |
| 96 | flex: none; |
| 97 | } |
| 98 | .files-scope-chip { |
| 99 | display: inline-flex; align-items: center; gap: 4px; |
| 100 | padding: 2px 8px; border-radius: 999px; |
| 101 | font-size: var(--fs-2xs); font-weight: 600; |
| 102 | color: var(--text-secondary); background: var(--bg-tertiary); |
| 103 | border: 1px solid var(--border-strong); cursor: pointer; |
| 104 | } |
| 105 | .files-scope-chip:hover { background: var(--bg-hover); border-color: var(--accent); } |
| 106 | .files-scope-chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; } |
| 107 | /* The lit one is what the tree below is showing, so it has to read as a state |
| 108 | and not as an offer -- the two trees can differ by a whole workspace. */ |
| 109 | .files-scope-chip.active { |
| 110 | color: var(--accent-text); background: var(--accent-soft); border-color: var(--accent); |
| 111 | } |
| 112 | |
| 113 | /* The word in front of the chips. They pick a TREE and change no permission, |
| 114 | and without a label they read as a setting for how much the daimon may |
| 115 | touch -- which is exactly how they were read. */ |
| 116 | .files-scope-label, .files-reach-label { |
| 117 | font-size: var(--fs-2xs); font-weight: 600; color: var(--text-secondary); |
| 118 | } |
| 119 | |
| 120 | /* ── Where this Diamond's daimon may actually work ───────────── |
| 121 | Under the chips because it answers the question the chips were being asked: |
| 122 | what may be written, and where a search that names no path will look. Drawn |
| 123 | in BOTH trees and built from the same `Files.bounds` that composes the fence, |
| 124 | so it cannot come to disagree with what a turn is confined to. Nothing here |
| 125 | waits for a hover: a fence nobody can see is a fence nobody trusts. */ |
| 126 | .files-reach { |
| 127 | display: flex; flex-wrap: wrap; align-items: center; gap: 6px; |
| 128 | padding: 6px 10px; |
| 129 | border-bottom: 1px solid var(--border); |
| 130 | flex: none; |
| 131 | } |
| 132 | /* A place inside the fence. The accent edge is the app's one mark for "this |
| 133 | grants something", worn by the workspace rail in the chat footer and by a |
| 134 | granted toolchain beside it. */ |
| 135 | .files-reach-chip { |
| 136 | display: inline-flex; align-items: center; |
| 137 | padding: 2px 8px; border-radius: 999px; |
| 138 | font-size: var(--fs-2xs); font-weight: 600; |
| 139 | max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; |
| 140 | color: var(--accent-text); background: var(--accent-soft); |
| 141 | border: 1px solid var(--accent); |
| 142 | } |
| 143 | /* Consulted, not worked in. It reads as a place and not as a grant, so it drops |
| 144 | the accent and keeps the ordinary control edge. */ |
| 145 | .files-reach-chip.ro { |
| 146 | color: var(--text-secondary); background: var(--bg-tertiary); |
| 147 | border-color: var(--border-strong); |
| 148 | } |
| 149 | .files-reach-says { |
| 150 | flex: 1 1 100%; |
| 151 | font-size: var(--fs-3xs-2xs); color: var(--text-secondary); line-height: 1.5; |
| 152 | } |
| 153 | /* The state that most needs saying, and the one a person is most likely to be |
| 154 | in without knowing: the daimon can write nowhere of theirs. */ |
| 155 | .files-reach-says.none { color: var(--accent-text); } |
| 156 | /* One press, from the folder already on screen. A button and not a chip: the |
| 157 | row beside it reports, and this changes something. */ |
| 158 | .files-mark-here { |
| 159 | display: inline-flex; align-items: center; |
| 160 | padding: 2px 9px; border-radius: 999px; |
| 161 | font-size: var(--fs-2xs); font-weight: 600; |
| 162 | color: var(--text-secondary); background: var(--bg-tertiary); |
| 163 | border: 1px dashed var(--border-strong); cursor: pointer; |
| 164 | } |
| 165 | .files-mark-here:hover { color: var(--accent-text); border-color: var(--accent); } |
| 166 | .files-mark-here:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; } |
| 167 | |
| 168 | /* ── What a Diamond may reach that is not a folder ───────────── |
| 169 | A toolchain grant. It sits under the scope row because it answers the same |
| 170 | question the row above and the tree below answer -- what this Diamond may |
| 171 | open -- and it is the one answer that is not a path in the workspace: a |
| 172 | compiler lives on the machine, outside anything the user granted as a folder. |
| 173 | Off unless the user says so, and a daimon never chooses one. */ |
| 174 | .files-kits { |
| 175 | display: flex; flex-wrap: wrap; align-items: center; gap: 6px; |
| 176 | padding: 6px 10px; |
| 177 | border-bottom: 1px solid var(--border); |
| 178 | flex: none; |
| 179 | } |
| 180 | .files-kits-label { |
| 181 | font-size: var(--fs-2xs); font-weight: 600; |
| 182 | color: var(--text-secondary); |
| 183 | } |
| 184 | .files-kit-chip { |
| 185 | display: inline-flex; align-items: center; |
| 186 | padding: 2px 8px; border-radius: 999px; |
| 187 | font-size: var(--fs-2xs); font-weight: 600; |
| 188 | color: var(--text-secondary); background: var(--bg-tertiary); |
| 189 | border: 1px solid var(--border-strong); cursor: pointer; |
| 190 | } |
| 191 | .files-kit-chip:hover { background: var(--bg-hover); border-color: var(--accent); } |
| 192 | .files-kit-chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; } |
| 193 | /* A granted toolchain is a PERMISSION in force, so it reads as a state. */ |
| 194 | .files-kit-chip.active { |
| 195 | color: var(--accent-text); background: var(--accent-soft); border-color: var(--accent); |
| 196 | } |
| 197 | /* Said out loud rather than left to be inferred from four unlit chips: "none" |
| 198 | is the ordinary state and the safe one, and a person should be able to see |
| 199 | that it is deliberate. */ |
| 200 | .files-kits-none { |
| 201 | flex: 1 1 100%; |
| 202 | font-size: var(--fs-3xs-2xs); color: var(--text-secondary); |
| 203 | } |
| 204 | |
| 205 | /* The step out of this folder, at the top of the listing where every file |
| 206 | manager has put it. Dimmer than a real entry, because it is navigation and |
| 207 | not a thing in the folder, and it carries none of the controls a real row |
| 208 | does. It replaced a button in the panel's header on 2026-08-24. */ |
| 209 | .files-row.files-up .files-name { color: var(--text-secondary); } |
| 210 | |
| 211 | /* A row for something attached from elsewhere in the workspace. It is a |
| 212 | pointer, not a copy: the file lives where it always did, and every other |
| 213 | Diamond holding it goes on holding it. */ |
| 214 | .files-row.attached .files-name { font-style: italic; } |
| 215 | .files-badge { |
| 216 | flex: none; font-size: var(--fs-3xs-2xs); |
| 217 | color: var(--text-secondary); |
| 218 | border: 1px solid var(--border-2); border-radius: 999px; |
| 219 | padding: 0 5px; line-height: 1.6; |
| 220 | } |
| 221 | .files-badge.files-elsewhere { border: none; padding: 0 2px; font-size: var(--fs-2xs); } |
| 222 | /* Read-only says what a daimon may do with it, so it stays visible: a hover-only |
| 223 | badge is a permission nobody reads. */ |
| 224 | .files-badge.files-ro { white-space: nowrap; } |
| 225 | /* ◈ on a row: put this folder in the open Diamond's workspace, or take it out. |
| 226 | Like the pin, the LIT state is standing information and must not hide until |
| 227 | the pointer finds it. */ |
| 228 | .files-res.files-hold.on { visibility: visible; color: var(--accent-text); } |
| 229 | /* The hint under an empty Diamond workspace, which sits below real rows rather |
| 230 | than replacing them. */ |
| 231 | .files-dws-hint { text-align: left; font-size: var(--fs-2xs); line-height: 1.5; } |
| 232 | |
| 233 | /* A folder row with a Diamond in focus: the paperclip is the control that puts |
| 234 | that folder inside the write fence, so it does not wait for a hover. The same |
| 235 | rule `.files-hold.on` already follows, for the same reason. */ |
| 236 | .attach-btn.mark { visibility: visible; } |
| 237 | |
| 238 | /* The paperclip: "Attach to current focus" (ATTACH_CONTRACT.md §4), on a |
| 239 | folder row, a file row, and the Doc header. Its own class rather than a |
| 240 | `.files-res` modifier for the reason `.files-pin` already has one -- '📎' is |
| 241 | an emoji, drawn at full size in a colour font, so the size bump `.files-res` |
| 242 | gives its thin dingbats would only inflate it. Lit and away are standing |
| 243 | information, exactly as the pin's `.on` is, and must not hide until the |
| 244 | pointer finds them. */ |
| 245 | .attach-btn { |
| 246 | background: transparent; border: none; color: var(--text-secondary); |
| 247 | cursor: pointer; padding: 0 3px; visibility: hidden; font-size: var(--fs-sm); |
| 248 | } |
| 249 | .files-row:hover .attach-btn { visibility: visible; } |
| 250 | .attach-btn:hover { color: var(--accent-text); } |
| 251 | .attach-btn.on { visibility: visible; color: var(--accent-text); } |
| 252 | .attach-btn.away { visibility: visible; color: var(--text-muted); } |
| 253 | |
| 254 | /* Touch. The scope chips take the same 24px floor as the mode row's, and the ◈ |
| 255 | comes out of hiding: there is no hover on a phone, so a control that waits for |
| 256 | one cannot be reached at all. */ |
| 257 | @media (max-width: 760px), (pointer: coarse) { |
| 258 | .files-scope-chip { min-height: 24px; padding-top: 4px; padding-bottom: 4px; } |
| 259 | .files-mark-here { min-height: 24px; padding-top: 4px; padding-bottom: 4px; } |
| 260 | .files-row .files-hold { visibility: visible; padding: 4px 6px; } |
| 261 | .files-row .attach-btn { visibility: visible; padding: 4px 6px; } |
| 262 | } |
| 263 | |
| 264 | .files-path { |
| 265 | padding: 6px 10px; color: var(--text-secondary); |
| 266 | font-family: var(--font-mono); font-size: var(--fs-2xs); |
| 267 | border-bottom: 1px solid var(--border); |
| 268 | flex: none; |
| 269 | display: flex; flex-wrap: wrap; align-items: center; |
| 270 | } |
| 271 | |
| 272 | /* One folder in the path line, or the whole trail's root. A plain button reset |
| 273 | rather than a link -- there is nowhere to navigate TO, only a tree to relist -- |
| 274 | so it takes the surrounding text colour and only the current, disabled one |
| 275 | (the folder already on screen) reads differently. */ |
| 276 | .path-crumb { |
| 277 | background: none; border: none; padding: 0; margin: 0; |
| 278 | font: inherit; color: inherit; cursor: pointer; |
| 279 | border-radius: 3px; |
| 280 | } |
| 281 | .path-crumb:hover:not(:disabled) { color: var(--text-primary); text-decoration: underline; } |
| 282 | .path-crumb:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; } |
| 283 | .path-crumb:disabled { color: var(--text-primary); cursor: default; font-weight: 600; } |
| 284 | .path-sep { margin: 0 3px; opacity: 0.55; } |
| 285 | |
| 286 | /* Residency controls on a row: fetch, pin, free up space. Like .files-del they |
| 287 | stay out of the way until the row is under the pointer -- except a pin, which |
| 288 | is a standing state and must be visible without hunting for it. |
| 289 | The pin is its OWN class rather than a `.files-res` modifier, because the two |
| 290 | want different type sizes and a shared class can only carry one. Everything |
| 291 | else about them is identical, so the shape is stated once for both. */ |
| 292 | .files-res, .files-pin { |
| 293 | background: transparent; border: none; color: var(--text-secondary); |
| 294 | cursor: pointer; padding: 0 3px; visibility: hidden; |
| 295 | } |
| 296 | /* '⤓', '⤒' and '◈' are dingbats drawn well inside their em box -- the same |
| 297 | reason `.files-ren` needs a size of its own. At the 13px they had, they |
| 298 | carried visibly less ink than the 15px '×' on the same row and read as specks |
| 299 | rather than as controls. A step ABOVE the cross, not level with it, because |
| 300 | the cross fills its box and these do not. */ |
| 301 | .files-res { font-size: var(--fs-xl); } |
| 302 | /* '📌' is an emoji: the platform draws it at full size in a colour font, so it |
| 303 | was never the undersized one and the bump above would only have inflated it. */ |
| 304 | .files-pin { font-size: var(--fs-sm); } |
| 305 | .files-row:hover .files-res, |
| 306 | .files-row:hover .files-pin { visibility: visible; } |
| 307 | .files-res:hover, .files-pin:hover { color: var(--accent-text); } |
| 308 | .files-pin.on { visibility: visible; opacity: 0.9; } |
| 309 | /* A cloud row is the user's file, safe, and simply not here at the moment -- |
| 310 | so it is quieter than a held file, never struck through or greyed to junk. */ |
| 311 | .files-row.cloud .files-name { color: var(--text-secondary); } |
| 312 | .files-row.cloud .files-size { font-style: italic; } |
| 313 | |
| 314 | .files-tree { flex: 1; overflow-y: auto; padding: 4px 0; } |
| 315 | .files-empty { padding: 16px; text-align: center; color: var(--text-secondary); } |
| 316 | |
| 317 | .files-row { |
| 318 | display: flex; align-items: center; gap: 8px; |
| 319 | padding: 5px 10px; cursor: pointer; |
| 320 | } |
| 321 | .files-row:hover { background: var(--bg-hover); } |
| 322 | .files-name { flex: 1; color: var(--text-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } |
| 323 | /* A folder is named in the accent, and the accent has TWO tokens because it does |
| 324 | two jobs. `--accent` is the FILL and the EDGE -- a button's ground, a border, |
| 325 | a focus ring -- and SC 1.4.11 asks 3.0 of it. `--accent-text` is the accent as |
| 326 | INK, which SC 1.4.3 asks 4.5 of, and every palette carries it a rung further |
| 327 | from its own surfaces for exactly that reason. |
| 328 | This rule, and the three above it, reached for `--accent`. On the four dark |
| 329 | palettes that is a mid-toned accent on a near-black panel: Midnight measured |
| 330 | 4.19:1 for a folder name on `--bg-secondary` and Amber 4.35, both under the |
| 331 | floor for 13px. Nothing about the token's VALUE was wrong -- 4.19 is a |
| 332 | perfectly good ring -- so lifting `--accent` would have paled every accent |
| 333 | fill in the app to fix four lines of text in one panel. The wrong token was |
| 334 | named, and naming the right one moves nothing outside these four rules. |
| 335 | Against all four surfaces of its own palette -- primary, secondary, tertiary |
| 336 | and hover -- `--accent-text` reads 6.04-10.56 across the dark band and |
| 337 | 4.98-9.38 across the light. */ |
| 338 | .files-row.dir .files-name { color: var(--accent-text); } |
| 339 | .files-size { color: var(--text-secondary); font-size: var(--fs-2xs); font-family: var(--font-mono); } |
| 340 | .files-del { |
| 341 | background: transparent; border: none; color: var(--text-secondary); |
| 342 | cursor: pointer; font-size: var(--fs-lg); padding: 0 4px; visibility: hidden; |
| 343 | } |
| 344 | .files-row:hover .files-del { visibility: visible; } |
| 345 | .files-del:hover { color: var(--danger); } |
| 346 | /* A control that waits for a hover is a control the keyboard cannot see: tabbing |
| 347 | to the cross moved the focus to something not on the screen, so the row's |
| 348 | controls come out when anything inside the row takes focus, exactly as the |
| 349 | touch rule above brings the ◈ out where there is no pointer at all. |
| 350 | Appearing is not enough on its own -- `verify_a11y_keyboard` compares the |
| 351 | focused element's own ink, and rightly, since a control that merely appears |
| 352 | has not said WHICH of the three now holds the focus. So each draws the ring. |
| 353 | `:focus`, not `:focus-visible`: these are reached by Tab, but the check |
| 354 | focuses them from script, and a ring that depends on the browser's guess at |
| 355 | the user's modality is a ring that is sometimes not there. */ |
| 356 | .files-row:focus-within .files-del, |
| 357 | .files-row:focus-within .files-res, |
| 358 | .files-row:focus-within .files-pin { visibility: visible; } |
| 359 | .files-del:focus, |
| 360 | .files-res:focus, |
| 361 | .files-pin:focus { outline: 2px solid var(--accent); outline-offset: 1px; } |
| 362 | /* The row itself is now a control (`sysRowAsButton`), so it says so as well. |
| 363 | Inset, because a row is flush with the panel and an outset ring would be |
| 364 | clipped by the tree's own scroller -- which the desktop sweep already reports |
| 365 | for the Diamond rail. |
| 366 | `:focus-visible` here, where the three buttons above keep `:focus`: a row is |
| 367 | opened by pointer far more often than by Tab, so a bare `:focus` left a ring |
| 368 | standing on the row of every file anyone clicked. Nothing is lost to the |
| 369 | keyboard -- `verify_a11y_keyboard` presses Tab before it focuses anything, so |
| 370 | modality is established and a script focus matches this too. */ |
| 371 | .files-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; } |
| 372 | /* The rename pencil borrows `.files-del` for its shape and needs a size of its |
| 373 | own: '✎' is drawn well inside its em box where '×' fills it, so at the same |
| 374 | nominal 15px the pencil carried about half the ink of the cross beside it and |
| 375 | looked like a fleck. It is not a delete either, so it keeps the neutral |
| 376 | hover. */ |
| 377 | .files-ren { font-size: var(--fs-2xl); } |
| 378 | .files-ren:hover { color: var(--text-primary); } |
| 379 | |
| 380 | /* The file VIEW now lives in the Doc panel (see app.css, .doc-view): a document |
| 381 | you have opened is something you are attending to, and belongs on the stage. |
| 382 | The inner .files-view-* rules below still apply -- they style the view itself, |
| 383 | wherever it is rendered. */ |
| 384 | .files-view-head { |
| 385 | display: flex; align-items: center; justify-content: space-between; gap: 8px; |
| 386 | padding: 7px 10px; border-bottom: 1px solid var(--border); flex: none; flex-wrap: wrap; |
| 387 | } |
| 388 | .files-view-name { |
| 389 | font-family: var(--font-mono); font-size: var(--fs-xs); |
| 390 | color: var(--text-primary); word-break: break-all; |
| 391 | } |
| 392 | /* A fixed HEIGHT, not padding alone: ◈ below was set in a larger font than its |
| 393 | neighbours for legibility, and with only top/bottom padding to hold it that |
| 394 | grew the button by the same fraction as the glyph, so the row read as four |
| 395 | buttons of three different heights. Centring a variable-size label inside a |
| 396 | box of one fixed height is what keeps the row level regardless of which |
| 397 | button's font changes next. */ |
| 398 | .files-btn { |
| 399 | background: transparent; border: 1px solid var(--border-strong); |
| 400 | color: var(--text-secondary); border-radius: 6px; |
| 401 | height: 24px; padding: 0 8px; cursor: pointer; font-size: var(--fs-xs); |
| 402 | display: inline-flex; align-items: center; line-height: 1; |
| 403 | } |
| 404 | /* The paperclip that attaches the open file to the focus on screen. Always |
| 405 | visible here -- `.attach-btn`'s hover-hide is for a row in a list, and this |
| 406 | is a header button among header buttons, none of which hide. Lit when it is |
| 407 | attached, so the button reports the state rather than only offering the |
| 408 | action -- it is the only place in the Files panel that says whether this |
| 409 | file is part of a pursuit. Drawn with an INSET ring: the strip it sits in |
| 410 | scrolls, and a ring outside the border box is one an overflow rule is |
| 411 | entitled to slice off. */ |
| 412 | .files-btn[data-act="attach"] { visibility: visible; font-size: var(--fs-sm); padding: 0 7px; } |
| 413 | .files-btn[data-act="attach"].on { |
| 414 | color: var(--accent-text); |
| 415 | border-color: var(--accent); |
| 416 | box-shadow: inset 0 0 0 1px var(--accent); |
| 417 | } |
| 418 | .files-btn[data-act="attach"].away { color: var(--text-muted); } |
| 419 | .files-btn:hover { color: var(--text-primary); border-color: var(--accent); } |
| 420 | .files-btn.active { color: var(--accent); border-color: var(--accent); } |
| 421 | .files-btn:disabled { opacity: 0.6; cursor: default; } |
| 422 | |
| 423 | /* Typst compile status line and inline PDF pane. */ |
| 424 | .files-view-msg { |
| 425 | flex: none; padding: 7px 10px; border-bottom: 1px solid var(--border); |
| 426 | font-family: var(--font-mono); font-size: var(--fs-xs); |
| 427 | color: var(--text-secondary); word-break: break-word; white-space: pre-wrap; |
| 428 | } |
| 429 | .files-view-msg.err { color: var(--danger, #e5484d); } |
| 430 | |
| 431 | /* The box the file VIEWER draws into, and it had no rule at all. |
| 432 | Everything inside it is written to stretch -- `.fileview`, `.fv-body` and each |
| 433 | tier are all `flex: 1` -- but a flex item with no rule of its own is |
| 434 | `flex: 0 1 auto`, so the whole chain collapsed to the content's own height. |
| 435 | For a PDF that is an <embed>'s default 150px, which is why a document opened |
| 436 | in a full-height panel sat in a letterbox with empty panel under it. */ |
| 437 | .files-view-viewer { |
| 438 | flex: 1; min-height: 0; |
| 439 | display: flex; flex-direction: column; |
| 440 | overflow: hidden; |
| 441 | } |
| 442 | |
| 443 | .files-view-body { |
| 444 | flex: 1; margin: 0; padding: 12px 14px; overflow: auto; |
| 445 | white-space: pre-wrap; word-break: break-word; |
| 446 | font-family: var(--font-mono); font-size: var(--fs-sm); |
| 447 | color: var(--text-primary); background: var(--bg-primary); |
| 448 | } |
| 449 | |
| 450 | /* Inline editor: swaps in for the <pre> while editing a file. |
| 451 | Wrapped, like the viewer beside it. `pre` meant every prose line ran off the |
| 452 | right edge of a ~400px panel and had to be scrolled to be read, one line at a |
| 453 | time; you cannot edit what you cannot see. Only the EDITOR wraps -- a |
| 454 | read-only <pre> presentation keeps whatever measure its content asks for. */ |
| 455 | .files-edit { |
| 456 | flex: 1; margin: 0; padding: 12px 14px; border: none; resize: none; outline: none; |
| 457 | white-space: pre-wrap; word-break: break-word; overflow: auto; |
| 458 | font-family: var(--font-mono); font-size: var(--fs-sm); |
| 459 | color: var(--text-primary); background: var(--bg-primary); |
| 460 | } |
| 461 | .files-edit:focus { box-shadow: inset 0 0 0 1px var(--accent); } |
| 462 | |
| 463 | /* File viewer line numbers. |
| 464 | A hanging indent, so a line that wraps continues past the gutter instead of |
| 465 | returning to the margin and reading as a new line. Invisible in a narrow |
| 466 | panel where little wrapped; obvious now the document has the stage. The two |
| 467 | measures must agree: the gutter is the .ln width plus its margin. */ |
| 468 | .files-view-body.with-lineno { padding-left: 14px; text-indent: 0; } |
| 469 | /* The hanging indent lives on each line, not on the block: with pre-wrap the |
| 470 | whole file is one block, so an indent set there applies to its first line |
| 471 | only. The two measures must agree -- the gutter is the .ln width plus its |
| 472 | margin. */ |
| 473 | .files-view-body .lnrow { display: block; padding-left: 4em; text-indent: -4em; } |
| 474 | /* `--text-muted` at full strength, and NOT `--text-secondary` behind an |
| 475 | `opacity: 0.55`. The alpha composited the gutter towards the page until it |
| 476 | sat at 2.28:1 on Light -- half the 4.5:1 floor, on 13px digits that are the |
| 477 | only way to say which line anything is on. Muted is the token for exactly |
| 478 | this: the quietest text in the app, and quiet is not the same as faint. */ |
| 479 | .files-view-body .ln { |
| 480 | display: inline-block; width: 3.2em; margin-right: 0.8em; |
| 481 | text-align: right; color: var(--text-muted); |
| 482 | user-select: none; -webkit-user-select: none; |
| 483 | } |
| 484 | |
| 485 | /* ── Cloud storage view (the stage, not a popup) ────────────── |
| 486 | What is stored, what it is costing, and what is not on this device. With |
| 487 | accounts that are keys and have no address, there is nowhere to send a |
| 488 | statement, so this is the only honest place to say it. */ |
| 489 | .cloud-view { white-space: normal; font-family: inherit; } |
| 490 | .cloud-intro { margin: 0 0 12px; color: var(--text-primary); line-height: 1.5; } |
| 491 | .cloud-cap { margin: 6px 0 16px; color: var(--text-secondary); font-size: var(--fs-xs); line-height: 1.5; } |
| 492 | |
| 493 | .cloud-bar { |
| 494 | height: 8px; border-radius: 999px; overflow: hidden; |
| 495 | background: var(--bg-tertiary); border: 1px solid var(--border-2); |
| 496 | } |
| 497 | .cloud-bar span { display: block; height: 100%; background: var(--accent); } |
| 498 | .cloud-bar span.hot { background: var(--danger, #e5484d); } |
| 499 | |
| 500 | .cloud-row { |
| 501 | display: flex; align-items: center; gap: 8px; |
| 502 | padding: 5px 0; border-bottom: 1px solid var(--border); |
| 503 | font-size: var(--fs-sm); |
| 504 | } |
| 505 | .cloud-path { |
| 506 | flex: 1; font-family: var(--font-mono); font-size: var(--fs-xs); |
| 507 | color: var(--text-primary); word-break: break-all; |
| 508 | } |
| 509 | .cloud-row.away .cloud-path { color: var(--text-secondary); } |
| 510 | .cloud-size { color: var(--text-secondary); font-family: var(--font-mono); font-size: var(--fs-2xs); } |
| 511 | .cloud-pin { font-size: var(--fs-2xs); } |
| 512 | |
| 513 | /* ── The rest of the folder, at the foot of the tree ───────────── |
| 514 | One shut row instead of a filter: the tree used to drop every dotfile |
| 515 | silently, which is a file its owner cannot see he has. The clutter it was |
| 516 | avoiding is answered by DESIGN — his own files stay at the top level, and |
| 517 | what he did not put there himself sits under one row with a count on it, so |
| 518 | he knows before pressing whether it costs a line or a screenful. |
| 519 | It borrows the System section's disclosure triangle deliberately: both rows |
| 520 | say the same thing about the same kind of contents, and two pictures for one |
| 521 | idea is how a panel stops being read. */ |
| 522 | .files-rest { border-top: 1px solid var(--border); margin-top: 8px; padding-top: 4px; } |
| 523 | .files-rest-head { |
| 524 | display: flex; align-items: baseline; gap: 8px; width: 100%; text-align: left; |
| 525 | background: none; border: 0; cursor: pointer; padding: 6px 12px; |
| 526 | font-size: var(--fs-2xs); text-transform: uppercase; letter-spacing: 0.04em; |
| 527 | color: var(--text-muted); |
| 528 | } |
| 529 | .files-rest-head::before { content: '\25B8'; display: inline-block; } |
| 530 | .files-rest-head[aria-expanded="true"]::before { content: '\25BE'; } |
| 531 | .files-rest-label { flex: 1; } |
| 532 | /* The count is a number about size, so it is set like every other one in this |
| 533 | panel — mono, quiet, and not shouted by the row's own uppercase. */ |
| 534 | .files-rest-count { |
| 535 | font-family: var(--font-mono); font-size: var(--fs-3xs); |
| 536 | text-transform: none; letter-spacing: 0; |
| 537 | } |
| 538 | .files-rest-head:hover { color: var(--text-primary); } |
| 539 | .files-rest-head:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; } |
| 540 | /* Indented, so an entry inside the row is plainly the row's and not the tree's |
| 541 | — the only difference between the two; the controls on it are the same ones. */ |
| 542 | .files-rest-body .files-row { padding-left: 26px; } |
| 543 | |
| 544 | /* ── The System section: Daimond's own store ───────────────────── |
| 545 | Its own section under the tree, not part of it. The two resolve against |
| 546 | different roots — the tree against whatever workspace is active, this always |
| 547 | against the browser's own store — and a single list quietly spanning both is |
| 548 | the confusion dev/ROOT_SEPARATION.md exists to prevent. Collapsed by default: |
| 549 | it answers a question ("where is DAIMOND.md?") rather than being where the |
| 550 | work is. */ |
| 551 | .sys-sec { border-top: 1px solid var(--border); margin-top: 8px; padding-top: 4px; } |
| 552 | .sys-head { |
| 553 | display: block; width: 100%; text-align: left; background: none; border: 0; |
| 554 | cursor: pointer; padding: 6px 12px; font-size: var(--fs-2xs); |
| 555 | text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-muted); |
| 556 | } |
| 557 | /* A disclosure triangle the section draws itself, from `aria-expanded` -- the |
| 558 | one place the state is written, so the picture cannot disagree with what a |
| 559 | screen reader is told. */ |
| 560 | .sys-head::before { content: '\25B8 '; display: inline-block; } |
| 561 | .sys-head[aria-expanded="true"]::before { content: '\25BE '; } |
| 562 | .sys-head:hover { color: var(--text-primary); } |
| 563 | .sys-head:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; } |
| 564 | .sys-note { padding: 0 12px 6px; font-size: var(--fs-2xs); color: var(--text-muted); line-height: 1.5; } |
| 565 | .sys-path { |
| 566 | padding: 0 12px 4px; font-family: var(--font-mono); font-size: var(--fs-3xs); |
| 567 | color: var(--text-muted); |
| 568 | display: flex; flex-wrap: wrap; align-items: center; |
| 569 | } |
| 570 | /* No × and no rename: this section is for reading and editing, never for |
| 571 | destroying. A row here is a Diamond's whole directory as often as not. */ |
| 572 | .sys-row .files-res, .sys-row .files-pin, .sys-row .files-del { display: none; } |
| 573 | /* A row that can take focus has to SHOW it. These became focusable the hour they |
| 574 | were written -- they are `role="button"` -- and a focus ring is the other half |
| 575 | of that: a keyboard user who cannot see where they are is not being offered a |
| 576 | keyboard route, they are being offered a guess. */ |
| 577 | .sys-row:focus { |
| 578 | outline: 2px solid var(--accent); outline-offset: -2px; |
| 579 | background: var(--bg-secondary); |
| 580 | } |