/* --------------------------------------------------------------- Native app shell. In a window of its own there's no page to sit on, so the card chrome, the outer margin and the grey backdrop all go: the app is the window. The read-only entry points go too — this build opens folders read-write and nothing else. --------------------------------------------------------------- */ html { -webkit-text-size-adjust: 100%; height: 100%; } body { margin: 0; padding: 0; height: 100%; background: var(--color-bg); color: var(--color-text); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; font-size: 15px; line-height: 1.5; /* The window itself is the scroll container; nothing here should ever produce a horizontal one. */ overflow-x: hidden; } .page { max-width: none; margin: 0; padding: 0; min-height: 100%; } .bwfa-scope.bwfa-app { background: var(--color-bg); border: 0; border-radius: 0; box-shadow: none; /* The plugin caps itself at --container-lg (960px) and centres inside that, which is right for a page in a theme and wrong for a window: widen the window past 960 and the extra space just sat there. In an app the window IS the container, so the cap goes and the padding below becomes the only gutter. */ max-width: none; width: 100%; margin-inline: 0; padding: 18px 20px 28px; min-height: 100%; box-sizing: border-box; } /* Read-only selection is gone, and so is the in-app title: the window already says what this is, and the privacy note was written for a web page. The markup stays put — the analyser wires listeners to these nodes at startup and the bridge still hands files back through the inputs inside them — it just isn't shown. */ .bwfa-scope .bwfa-header, .bwfa-scope .bwfa-dropzone, .bwfa-scope .bwfa-fs-warning, .bwfa-scope .bwfa-edit-divider { display: none !important; } /* Which promotes the editor to the main event. */ .bwfa-scope .bwfa-edit-entry { margin: 0 0 18px; padding: 18px 20px; border: 1px dashed var(--color-border); border-radius: 10px; background: var(--color-surface); text-align: center; transition: border-color .12s ease, background-color .12s ease; } .bwfa-scope .bwfa-edit-entry.is-dragover { border-color: var(--color-info); border-style: solid; background: var(--color-info-bg); } .bwfa-scope .bwfa-edit-entry .btn { font-size: 15px; padding: 9px 20px; } .bwfa-scope .bwfa-edit-entry .bwfa-dropzone-subnote { max-width: 62ch; margin: 12px auto 0; font-size: 12.5px; color: var(--color-text-muted); } /* Header sits tighter now that it's a title bar rather than a page heading. */ .bwfa-scope .bwfa-header { margin-bottom: 14px; } .bwfa-scope .bwfa-title { font-size: 19px; margin: 0 0 4px; } /* Let the table use the full width of the window. */ .bwfa-scope .table-responsive { width: 100%; overflow-x: auto; } .bwfa-scope .bwfa-table { width: 100%; } /* Keep both action columns tight; the rest of the width belongs to the metadata. */ .bwfa-scope .bwfa-table thead th:nth-child(1), .bwfa-scope .bwfa-table thead th:nth-child(2) { width: 1%; white-space: nowrap; } /* ---- Sticky table header ------------------------------------------ Two things in the framework break `position: sticky` on a table header in WebKit specifically, and both were in play: - `border-collapse: collapse`. Collapsed borders belong to the table grid rather than to the cells, and Safari has never painted a sticky header correctly through one — rows bleed over the header and its underline detaches. `separate` with zero spacing looks identical and behaves. - `-webkit-overflow-scrolling: touch` on the scroll container. It hands scrolling to a separate compositing layer, which is a long-standing way to get stale pixels and sticky elements that scroll away. Momentum scrolling is the platform's job on macOS anyway. ------------------------------------------------------------------ */ .bwfa-scope .table-responsive { -webkit-overflow-scrolling: auto; } .bwfa-scope .bwfa-table { border-collapse: separate; border-spacing: 0; } /* Collapse was what drew the row lines; with separate borders each cell draws its own, which comes to the same thing visually. */ .bwfa-scope .bwfa-table th, .bwfa-scope .bwfa-table td { border-bottom: 1px solid var(--color-border); } /* * Both selectors, deliberately. The plugin gives every *sortable* header * `position: relative` (for the sort arrow), and * `.bwfa-table th[data-bwfa-sort]` outranks `.bwfa-table thead th` — so * naming only the second left fourteen of seventeen headers relative * rather than sticky. The three that held were the ones that aren't * sortable: the two action columns, Description and Note. Hence a header * that was half stuck and half scrolling, with rows painting through the * gap. * * Sticky is itself a positioned value, so the absolutely-positioned sort * arrow still anchors to its own cell. */ .bwfa-scope .bwfa-table thead th, .bwfa-scope .bwfa-table thead th[data-bwfa-sort] { position: sticky; top: 0; /* Above the rows, which are unpositioned, and above the row backgrounds that were painting over it. */ z-index: 3; background: var(--color-bg); background-clip: padding-box; } /* Nothing in the body may promote itself above the header. */ .bwfa-scope .bwfa-table tbody td { position: static; z-index: auto; } @media (max-width: 700px) { .bwfa-scope.bwfa-app { padding: 12px 12px 20px; } } /* ---- Toolbar ------------------------------------------------------ The window's minWidth is set so this row always fits; nowrap makes that a hard guarantee rather than a hope, and keeps the search field as the only thing that gives. ------------------------------------------------------------------ */ .bwfa-scope .bwfa-toolbar { flex-wrap: nowrap; align-items: center; gap: 12px; } .bwfa-scope .bwfa-search-group { flex: 1 1 auto; min-width: 180px; margin-bottom: 0; } .bwfa-scope .bwfa-export-actions { flex: none; flex-wrap: nowrap; } .bwfa-scope .bwfa-export-actions .btn, .bwfa-scope .bwfa-toolbar .btn { white-space: nowrap; } /* ---- Selection ---------------------------------------------------- This is an app, not a document: dragging a selection across a table of takes only ever looks like a mistake. Fields you type into keep selection, since editing without it is miserable. ------------------------------------------------------------------ */ .bwfa-scope, body { -webkit-user-select: none; user-select: none; } .bwfa-scope input, .bwfa-scope textarea, .bwfa-scope select, .bwfa-scope [contenteditable="true"] { -webkit-user-select: text; user-select: text; } /* ---- The folder bar, once a folder is open ------------------------- Before: a dashed panel inviting you to open something. After: one line saying which folder you're in, with the button to change it. The invitation has served its purpose and the table needs the room. ------------------------------------------------------------------ */ .bwfa-scope .bwfa-edit-entry.is-folder-open { display: flex; align-items: center; gap: 12px; margin: 0 0 14px; padding: 0; border: 0; background: none; text-align: left; } .bwfa-scope .bwfa-edit-entry.is-folder-open .bwfa-dropzone-subnote { display: none; } .bwfa-scope .bwfa-edit-entry.is-folder-open .btn { font-size: var(--fs-sm); padding: 6px 14px; } .bwfa-scope .bwfa-current-folder { font-size: 15px; font-weight: var(--fw-medium); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .bwfa-scope .bwfa-current-folder::before { content: ""; display: inline-block; width: 13px; height: 11px; margin-right: 8px; vertical-align: -1px; background-color: var(--color-text-muted); -webkit-mask-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 12'%3E%3Cpath d='M1 2.5A1.5 1.5 0 0 1 2.5 1h2.2c.4 0 .8.2 1 .5l.7.9h5.1A1.5 1.5 0 0 1 13 3.9v5.6A1.5 1.5 0 0 1 11.5 11h-9A1.5 1.5 0 0 1 1 9.5v-7Z'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 12'%3E%3Cpath d='M1 2.5A1.5 1.5 0 0 1 2.5 1h2.2c.4 0 .8.2 1 .5l.7.9h5.1A1.5 1.5 0 0 1 13 3.9v5.6A1.5 1.5 0 0 1 11.5 11h-9A1.5 1.5 0 0 1 1 9.5v-7Z'/%3E%3C/svg%3E"); -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; } /* ---- Window layout: the table is the only thing that scrolls ------- The player has to stay put while a long day scrolls past it. Pinning it with position:fixed and reserving page padding didn't hold up — the plugin sets .table-responsive to overflow-y:hidden, so the table never scrolls itself and the window does, which puts the last rows under a fixed footer no matter how much padding is reserved. So the layout is explicit instead: the app is a column exactly as tall as the window, the table region is the one scrolling box, and the player is an ordinary block ordered after it. There is no clearance to get wrong, because the scrolling box ends where the player begins. The sticky table header now sticks to the top of that box, which is also what you want. ------------------------------------------------------------------ */ html, body { height: 100%; overflow: hidden; } .page { height: 100%; min-height: 0; } .bwfa-scope.bwfa-app { display: flex; flex-direction: column; height: 100%; min-height: 0; padding-bottom: 14px; } .bwfa-scope .bwfa-results { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; margin-top: 0; } .bwfa-scope .bwfa-results > .table-responsive { flex: 1 1 auto; min-height: 0; overflow-x: auto; overflow-y: auto; } /* Ordered last regardless of where it sits in the markup. */ .bwfa-scope .bwfa-player { order: 99; flex: none; margin: 12px 0 0; padding: 10px 14px 12px; gap: 8px; } .bwfa-scope .bwfa-player-waveform { height: 34px; } /* The plugin spaced the status line by giving the results panel a top margin; making that panel a flex child meant zeroing it, which left "Done — 35 files analysed" sitting on top of the search field. The status carries its own spacing now, and keeps its min-height so the layout doesn't jump when the line appears. */ .bwfa-scope .bwfa-status { margin: 6px 0 14px; } /* ---- Launch state -------------------------------------------------- With nothing open there's nothing to report, so the status line and the "No files analysed yet" placeholder are noise. What's left is one panel, centred, which is the only thing there is to do. ------------------------------------------------------------------ */ .bwfa-scope.bwfa-app:not(.has-folder) { justify-content: center; } .bwfa-scope.bwfa-app:not(.has-folder) .bwfa-status, .bwfa-scope.bwfa-app:not(.has-folder) .bwfa-empty { display: none; } .bwfa-scope.bwfa-app:not(.has-folder) .bwfa-edit-entry { max-width: 560px; margin: 0 auto; padding: 32px 28px; } /* The app's own icon, inlined at build time from the .icns source set so the frontend stays a single self-contained file. Launch screen only — once you're working, the window's title bar and Dock icon are enough. */ .bwfa-scope .bwfa-launch-logo { display: none; } .bwfa-scope.bwfa-app:not(.has-folder) .bwfa-launch-logo { display: block; width: 88px; height: 88px; margin: 0 auto 22px; background-image: url("@@LAUNCH_LOGO@@"); background-size: contain; background-repeat: no-repeat; background-position: center; } /* Every folder this build opens is opened read-write, so a banner saying so on every screen is telling you something you can't change and already know. The warning that matters is on the launch panel, where you choose the folder in the first place. */ .bwfa-scope .bwfa-editing-note { display: none !important; } /* ---- Bulk edit and export live in modals --------------------------- Both panels used to open as siblings of the table inside a column exactly as tall as the window. The table region is the flexible one (`min-height: 0`, so it may shrink to nothing), so a panel at its natural height squeezed the table out of existence — and since the window itself doesn't scroll, that left nothing scrollable anywhere. Capping the panel and flooring the table held, but only by dividing a space neither of them wanted to share. As modals they're out of that fight: the dialog is positioned against the viewport and scrolls itself, and the table keeps the whole column. The panel inside gives up its own card — the dialog is the card now. ------------------------------------------------------------------ */ .bwfa-scope .bwfa-sheet-dialog { position: relative; /* Width and height come from --modal-width with every other modal's. Setting them here is how the sheets ended up a different size from the rest, twice. */ } .bwfa-scope .bwfa-sheet-wide { max-width: 1120px; } /* Padding comes from --modal-pad with every other modal's. This file is loaded after overrides.css, so anything set here silently beats the shared rules — which is how Bulk Edit ended up with this panel's 24px on top of the shared header's 32px while Sound Report had only the 32. Three rounds of "why is that one different" traced back to here. */ /* The close button is a traffic light now, styled once for every modal in overrides.css. This rule used to pin it top right with padding of its own, which quietly made the sheets' button a different size and place from everything else's — it loaded after the shared rule and won. */ .bwfa-scope .bwfa-sheet-close { z-index: 2; } /* Room for the close button, so a long heading can't run under it. */ /* Header padding comes from --modal-pad with every other modal's. */ .bwfa-scope .bwfa-results > .table-responsive { min-height: 20vh; } /* ---- Reset --------------------------------------------------------- Sits left of the folder button and undoes the session: closes the folder, empties the table, forgets what to reopen. Only meaningful once something is open, so that's the only time it's there. ------------------------------------------------------------------ */ .bwfa-scope .bwfa-reset { display: none; } .bwfa-scope .bwfa-edit-entry.is-folder-open .bwfa-reset { display: inline-flex; align-items: center; justify-content: center; flex: none; width: 30px; height: 30px; padding: 0; font-size: 0; border-radius: 50%; } .bwfa-scope .bwfa-reset::before { content: ""; display: block; width: 13px; height: 13px; background-color: currentColor; -webkit-mask-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 2.5 14.2 10.2H1.8z' fill='black'/%3E%3Crect x='1.8' y='11.9' width='12.4' height='2.1' rx='.7' fill='black'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 2.5 14.2 10.2H1.8z' fill='black'/%3E%3Crect x='1.8' y='11.9' width='12.4' height='2.1' rx='.7' fill='black'/%3E%3C/svg%3E"); -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; -webkit-mask-size: contain; mask-size: contain; } /* ---- Export panel -------------------------------------------------- Wears the bulk-edit panel's classes, so the card, the 38px control height, the label treatment and the 40vh cap all come for free. Only the parts it doesn't share are here: the destination row, which is a field with a button welded to its end, and the report. ------------------------------------------------------------------ */ /* Its own row class rather than the bulk-edit one: the app enumerates .bwfa-bulk-edit-row to work out which fields to write, so sharing the name would hand it five controls that have nothing to do with editing. The look is shared here instead, which is the part worth sharing. */ .bwfa-scope .bwfa-export-fields { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 16px 20px; margin-bottom: var(--space-4); max-width: 1080px; align-items: start; } .bwfa-scope .bwfa-export-row { display: flex; flex-direction: column; gap: 6px; min-width: 0; } .bwfa-scope .bwfa-export-row > .form-label { min-width: 0; margin: 0; color: var(--color-text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .bwfa-scope .bwfa-export-row > .form-control, .bwfa-scope .bwfa-export-row > .form-select { width: 100%; height: 38px; min-height: 38px; padding: 0 10px; line-height: 1.2; box-sizing: border-box; font-size: var(--fs-sm); } .bwfa-scope .bwfa-export-row > .form-select { -webkit-appearance: none; appearance: none; padding-right: 30px; background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%2371717a' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 11px center; background-size: 11px; } .bwfa-scope .bwfa-export-row input[type="number"] { -moz-appearance: textfield; } .bwfa-scope .bwfa-export-row input[type="number"]::-webkit-outer-spin-button, .bwfa-scope .bwfa-export-row input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } /* Nothing to type into means nothing to look at: a disabled target reads as inactive rather than as a field you're failing to fill in. */ .bwfa-scope .bwfa-export-row > .form-control:disabled { background-color: var(--color-surface-alt, #f4f4f5); color: var(--color-text-muted); } /* The path is the long one, so it gets the width when there is any. */ .bwfa-scope .bwfa-export-dest { grid-column: span 2; min-width: 0; } .bwfa-scope .bwfa-export-dest-row { display: flex; gap: 8px; align-items: stretch; min-width: 0; } .bwfa-scope .bwfa-export-dest-row > .form-control { flex: 1 1 auto; min-width: 0; height: 38px; padding: 0 10px; font-size: var(--fs-sm); box-sizing: border-box; direction: ltr; /* A long path should show its end — the folder — not its beginning. */ text-overflow: ellipsis; } .bwfa-scope .bwfa-export-dest-row > .btn { flex: none; height: 38px; padding: 0 14px; white-space: nowrap; } /* ---- The report modal ---------------------------------------------- A finished export is not a form, so it stops looking like one. The dialog is the only one that doesn't scroll as a whole: the heading, the counts and the buttons stay put and the list moves under them, which is the difference between reading a hundred rows and hunting for the button that dismisses them. ------------------------------------------------------------------ */ .bwfa-scope .bwfa-sheet-report { display: flex; flex-direction: column; overflow: hidden; /* Width comes from --modal-width with every other modal's. This used to set its own, which is exactly how six dialogs ended up six different sizes. */ } .bwfa-scope .bwfa-sheet-report .bwfa-result-panel { display: flex; flex-direction: column; min-height: 0; flex: 1 1 auto; } .bwfa-scope .bwfa-result-panel .bwfa-bulk-edit-header { margin-bottom: var(--space-4); } .bwfa-scope .bwfa-result-panel .bwfa-bulk-edit-header p { margin: 4px 0 0; font-size: var(--fs-sm); color: var(--color-text-muted); word-break: break-all; } .bwfa-scope .bwfa-result-tally { display: flex; gap: var(--space-5); flex: none; padding-bottom: var(--space-4); border-bottom: 1px solid var(--color-border); } .bwfa-scope .bwfa-result-stat { display: flex; flex-direction: column; gap: 1px; line-height: 1.15; } .bwfa-scope .bwfa-result-stat strong { font-size: 22px; font-weight: var(--fw-semibold); font-variant-numeric: tabular-nums; color: var(--color-text); } .bwfa-scope .bwfa-result-stat span { font-size: var(--fs-xs, 12px); color: var(--color-text-muted); } /* A count of nothing still belongs on screen — "0 failed" is the whole reassurance — but it shouldn't compete with the counts that matter. */ .bwfa-scope .bwfa-result-stat.is-zero strong, .bwfa-scope .bwfa-result-stat.is-zero span { color: var(--gray-400, #b0b0b8); } .bwfa-scope .bwfa-result-stat.is-bad strong { color: var(--color-danger, #b42318); } .bwfa-scope .bwfa-result-note { flex: none; margin: var(--space-3) 0 0; font-size: var(--fs-sm); color: var(--color-text-muted); } .bwfa-scope .bwfa-result-panel .bwfa-bulk-edit-actions { flex: none; margin-top: var(--space-4); padding-top: var(--space-4); border-top: 1px solid var(--color-border); } .bwfa-scope .bwfa-export-report { margin-top: var(--space-3); font-size: var(--fs-sm); flex: 1 1 auto; min-height: 0; overflow-y: auto; /* Room for the scrollbar so the last column doesn't sit under it. */ padding-right: 4px; } .bwfa-scope .bwfa-export-report ul { margin: 0; padding: 0; list-style: none; } .bwfa-scope .bwfa-export-report li { display: flex; gap: 16px; padding: 7px 2px; color: var(--color-text-muted); border-bottom: 1px solid var(--color-border-subtle, var(--gray-100, #f1f1f4)); } .bwfa-scope .bwfa-export-report li:last-child { border-bottom: 0; } .bwfa-scope .bwfa-export-report li.is-problem { color: var(--color-danger, #b42318); } .bwfa-scope .bwfa-export-report-name { flex: none; width: 38%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--color-text); font-variant-numeric: tabular-nums; } .bwfa-scope .bwfa-export-report-detail { flex: 1 1 auto; min-width: 0; } /* ---- Track picker in the export panel ------------------------------ The same chips as the player's channel strip, because they answer the same question about the same file — there they mute, here they choose what gets written. Only offered for a single file: across a folder of takes, "track 3" isn't the same thing twice. ------------------------------------------------------------------ */ .bwfa-scope .bwfa-export-tracks { grid-column: 1 / -1; } .bwfa-scope .bwfa-export-tracks .bwfa-channel-chips { display: flex; flex-wrap: wrap; gap: 6px; padding: 4px 0; } .bwfa-scope .bwfa-export-tracks .chip { cursor: pointer; user-select: none; } /* Off reads as off: struck through and faded, the way a muted channel already reads in the player. */ .bwfa-scope .bwfa-export-tracks .chip.is-off { opacity: 0.45; text-decoration: line-through; } /* ---- The audio escape hatch ---------------------------------------- Only in the app, where reloading the window costs a folder re-scan and nothing else: the last folder reopens by itself. ------------------------------------------------------------------ */ .bwfa-scope .bwfa-settings-action { display: block; margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--color-border); } /* ---- What a combine would make ------------------------------------ Shown before anything is written, because the answer to "can this be done" is already in the metadata: rates, timecodes, lengths. A problem here disables Export rather than interrupting with a dialog. ------------------------------------------------------------------ */ .bwfa-scope .bwfa-export-summary-line { margin: 4px 0 14px; padding: 10px 12px; border: 1px solid var(--color-border); border-radius: var(--radius-sm, 6px); background-color: var(--color-surface); font-size: var(--fs-sm); color: var(--color-text); } .bwfa-scope .bwfa-export-summary-line.is-blocked { border-color: var(--color-danger); background-color: var(--color-danger-bg); color: var(--color-danger); } .bwfa-scope .bwfa-export-summary-line small { display: block; margin-top: 4px; color: var(--color-text-muted); } .bwfa-scope .bwfa-export-summary-line.is-blocked small { color: inherit; } /* ---- Naming the copies --------------------------------------------- The pattern row and the preview both span, because a filename is a long thing and reading old against new is the entire point of showing it at all. ------------------------------------------------------------------ */ .bwfa-scope .bwfa-export-pattern { grid-column: 1 / -1; min-width: 0; } .bwfa-scope .bwfa-export-tokens { margin: 6px 0 0; font-size: var(--fs-xs, 12px); color: var(--color-text-muted); font-variant-numeric: tabular-nums; word-spacing: 4px; } .bwfa-scope .bwfa-export-names { margin: 4px 0 12px; padding: 10px 12px; border: 1px solid var(--color-border); border-radius: var(--radius-sm, 6px); background-color: var(--color-surface); font-size: var(--fs-sm); color: var(--color-text-muted); } .bwfa-scope .bwfa-export-names.is-blocked { border-color: var(--color-danger); background-color: var(--color-danger-bg); color: var(--color-danger); } .bwfa-scope .bwfa-export-name-row { display: flex; align-items: baseline; gap: 10px; padding: 2px 0; min-width: 0; } /* The arrow is drawn rather than typed, so a name containing one can't be mistaken for the thing pointing at it. */ .bwfa-scope .bwfa-export-name-row > span::after { content: "\2192"; margin-left: 10px; color: var(--color-text-muted); } .bwfa-scope .bwfa-export-name-row > span { flex: none; max-width: 46%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .bwfa-scope .bwfa-export-name-row > strong { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: var(--fw-medium, 500); color: var(--color-text); } .bwfa-scope .bwfa-export-names small { display: block; margin-top: 4px; }