/* Logs. Two very different sub-screens: a two-column file viewer and a table with fifteen filters. The table classes are the same as in Cache, Allowed and Blocked. */ .wrap { composes: screen from '../../ui/SectionHeader.module.css'; } .acts { display: flex; gap: var(--s-4); flex-wrap: wrap; justify-content: flex-end; align-items: center; } /* ---- View Logs ---- */ /* Master and detail, both at once, and at 390 too. At 1440 it is `250px` for the list and the rest for the viewer. On narrowing it **does not turn into navigation**: they stack and both stay present. And it is a decision, not a convenience: a paginated collection would be worth turning into list→detail, but here there are **three files**, so sending the user to another screen to see one and back to see another costs more than it saves. Each panel answers for itself: the list can fail without the viewer hearing about it, and the other way round. That is why there is no "screen" state, there are two. The breakpoint is 720, the same one `SectionHeader`, `Segmented` and `PanelForm` already use: below that, 250 px of list leave the viewer with less than half the screen and a log line stops fitting anywhere. */ .split { display: grid; grid-template-columns: 250px minmax(0, 1fr); gap: var(--s-10); align-items: start; } @media (max-width: 720px) { .split { grid-template-columns: minmax(0, 1fr); } } /* The window's height, handed down to the reader (see ViewLogs.tsx). The Shell's body is a flex column for a screen marked `data-fills-window`; from there each step takes the rest: the screen, the split under the header, the reader panel (stretched, while the file list keeps its own height), its body, and `Raw`'s `fill` box at the end. */ .viewLogs { composes: wrap; flex: 1 1 auto; } .viewLogs .split { flex: 1 1 auto; grid-template-rows: minmax(0, 1fr); } .reader { align-self: stretch; display: flex; flex-direction: column; } .readerBody { flex: 1 1 auto; display: flex; flex-direction: column; } /* Stacked under the file list: 70 % of the window, see ViewLogs.tsx. */ @media (max-width: 720px) { .viewLogs .split { grid-template-rows: auto; } .readerBody { flex: none; height: 70dvh; } } /* The time of an entry, one step below its message: see LogText.tsx. */ .logStamp { color: var(--faint); } /* A hundred lines of the file, laid out only near the screen: see LogText.tsx. */ .logChunk { display: block; content-visibility: auto; contain-intrinsic-block-size: auto 100lh; } /* A run of zero bytes, laid out zero wide and on one line: see LogText.tsx. `clip` and not `hidden`: Chromium leaves what `hidden` cuts out of a selection, and the run is the file's own text, copied with the rest. */ .logNul { display: inline-block; inline-size: 0; overflow: clip; white-space: pre; vertical-align: top; } .pbList { padding: var(--s-5); } .logfiles { display: flex; flex-direction: column; gap: var(--s-1); } .logfile { composes: row from '../../ui/list.module.css'; justify-content: space-between; } .logfile .name { font-family: var(--font-mono); } .sz { composes: annotationQuiet from '../../ui/text.module.css'; } /* ---- Query Logs ---- */ .fs { composes: panel from '../../ui/Panel.module.css'; margin-bottom: var(--s-9); } /* The box is a panel, so its title is a panel's (`ui/Panel`, `.title`): one step above the field labels under it, not another row of small capitals. */ .fsTitle { composes: title from '../../ui/Panel.module.css'; display: block; width: 100%; padding: var(--s-8) var(--s-10); border-bottom: 1px solid var(--line); } /* The filters are laid out as GROUPS SIDE BY SIDE, not as one row per group. They used to be five full-width rows (Source, Mode, Query, Response, Paging), each with its name in a 110 px column on the left and its fields in a single line on the right. Every row used less than half of its width, and the five stacked made the panel so tall that at 1440x980 the results started at y=842, with one row on screen: the table, which is what the screen is for, was below the fold. Now each group carries its name ABOVE its fields and the groups flow side by side, wrapping as the width runs out: two bands at 1440 instead of five rows (Source, Mode and Paging; then Query and Response), so the table starts at y=629. Nothing is collapsed or hidden at this width: the same fifteen controls, the same labels and options. Below 960 the whole panel folds instead (see `.fsToggle` below). Two things moved: `Live Update` went to the screen's header (QueryLogs.tsx says why), and Paging now follows Mode in the document, which is also the tab order. The grouping is the one decided below (the MODE group keeps its four fields and its edge together). */ .fsIn { display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--s-9) var(--s-10); padding: var(--s-8) var(--s-10) var(--s-10); } .group { display: flex; flex-direction: column; gap: var(--s-4); min-width: 0; max-width: 100%; } /* Every head is as tall as the `While Live Update` pill that appears in Mode's, so the fields of the groups in one band line up and the pill appearing does not move anything. */ .groupLabel { display: flex; align-items: center; gap: var(--s-6) var(--s-8); flex-wrap: wrap; min-height: var(--s-11); font-size: var(--t-1); font-weight: 600; letter-spacing: 0.09em; color: var(--mute); } .groupCtl { display: flex; align-items: flex-end; gap: var(--s-7); flex-wrap: wrap; } .field { display: flex; flex-direction: column; gap: var(--s-3); width: var(--ql-field, auto); } /* On a phone each filter takes the whole line instead of its desktop width: the same column for all fifteen, and no field left half as wide as its neighbour. */ @media (max-width: 560px) { .group, .groupCtl, .field { width: 100%; } } .field label { font-size: var(--t-2); color: var(--mute); font-weight: 550; } .field input, .field select { width: 100%; } /* Folded, below 960 (FOLD_QUERY in QueryLogs.tsx says what and why). The title becomes the toggle and the whole title row is its target, so the button carries the padding the heading had. Closed, the row loses its rule: under it there is either the list of what is set, which brings its own, or the panel's edge. */ .fsIn[hidden] { display: none; } .fsTitleFold { padding: 0; } .fsTitleClosed { border-bottom: 0; } .fsToggle { display: flex; align-items: center; gap: var(--s-5); width: 100%; padding: var(--s-8) var(--s-10); border: 0; background: none; color: inherit; font: inherit; text-align: left; cursor: pointer; } /* The panel clips what overflows it, the focus ring included: drawn inside. */ .fsToggle:focus-visible { outline-offset: calc(-1 * var(--s-1)); } .chevron { composes: chevron from '../../ui/Details.module.css'; color: var(--mute); } [aria-expanded='true'] > .chevron { transform: rotate(90deg); } /* What is set, by the fields' own labels: label quiet, value as typed. */ .fsSet { display: flex; flex-wrap: wrap; gap: var(--s-3) var(--s-9); margin: 0; padding: var(--s-7) var(--s-10) var(--s-8); border-top: 1px solid var(--line); font-size: var(--t-3); color: var(--ink); } .fsSetItem { min-width: 0; overflow-wrap: break-word; } .fsSetLabel { color: var(--mute); } /* The count is `ui/count.module.css`'s: see there for why. */ .count { composes: bar above from '../../ui/count.module.css'; } .mono { composes: mono from '../../ui/text.module.css'; } .nowrap { white-space: nowrap; } /* The Query Logs table: one line per row. Every cell keeps to one line. Domain, Answer and the client address are cut with an ellipsis and carry the whole value in their text and `title` (`Clip` in QueryLogs.tsx); the rest are short by nature. Before, those three broke anywhere, the timestamp took two lines, and a row with a long answer reached 200 px: at 1440x980 one row fitted on the first screen. Answer is the elastic column: `max-width: 0` takes it out of the table's minimum width and `width: 100%` hands it whatever the other columns leave, so a long list of addresses never pushes the row menu out of the panel. Its minimum keeps a readable piece of it where the table no longer fits and scrolls inside its panel. Domain is cut at a fixed width instead, wide enough for 89 of 100 names on a page of the showcase traffic. Written as `table.` to outweigh ui/Table's `.table thead th` and `.table td`, which weigh the same as one class. */ table.queryTable thead th { white-space: normal; padding-left: var(--s-3); padding-right: var(--s-3); } table.queryTable td { white-space: nowrap; padding-left: var(--s-3); padding-right: var(--s-3); } table.queryTable td:first-child { padding-left: var(--s-7); } table.queryTable td.answer { width: 100%; max-width: 0; min-width: 16ch; } .clipDomain, .clipAnswer, .clipClient { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .clipDomain { max-width: 30ch; } .clipClient { max-width: 24ch; } /* Narrower windows: Domain gives up width first, and the timestamp breaks between date and time (`Stamp`), so the table still fits at 1366 and needs less of a scroll inside its panel below that. */ @media (max-width: 1439px) { .clipDomain { max-width: 22ch; } table.queryTable td.answer { min-width: 12ch; } } @media (max-width: 1365px) { table.queryTable td.stamp { white-space: normal; } } /* When the rows stack (`stack` in ui/Table: the table no longer fits its panel, which at 1024 and 768 it does not), each cell is as wide as its value and the widths above stand down: the timestamp on one line, Domain and Answer cut only where the row ends, and each value on its label's line. */ table.queryTable[data-stacked] td:first-child { padding-left: 0; } table.queryTable[data-stacked] td.stamp { white-space: nowrap; } /* Answer: its label and its one cut line side by side, the line taking the rest of the row (a flex item shrinks; an inline box beside a label would not). */ table.queryTable[data-stacked] td.answer { display: flex; align-items: baseline; width: auto; max-width: 100%; min-width: 0; } table.queryTable[data-stacked] .clipAnswer { min-width: 0; } table.queryTable[data-stacked] .clipDomain, table.queryTable[data-stacked] .clipClient { display: inline-block; max-width: 100%; vertical-align: bottom; } table.queryTable[data-stacked] .rtt { display: inline; } /* Upstream's row colour code (logs.js:452-508), with the SAME mapping, drawn as a pill. `rowClass` still decides one category per row from the RCODE and, within it, the response type, exactly as upstream does. What changed is where it is painted. Upstream lays it over the whole row; here it went as a 30 % background over every cell, and on a page of mostly Cached rows the table read as one block of colour, so the exception (a Blocked row) hardly stood out. Now the row is neutral and the category sets ONE custom property, `--ql-tone`, which the pill around the Response Type reads: the same 30 % over the panel the whole row used to carry, so every measurement of `dev/row-colours.mjs` still holds as it is (ΔE00 between pairs as a background, ink at 6.5:1 over the lightest one). The colour is the ROW's, not the cell's: an `NxDomain` row whose type is `Recursive` gets the NX Domain colour on a pill that reads `Recursive`, exactly as the whole row did before. That is why the legend still says "row colours". **A pill and not an edge**: the edge belongs to the amber, and it means "this is yours to change". And the colour is still reinforcement, never the only channel: `RCODE` and `Response Type` are written in their cells, and the legend names the seven conditions. One pair (`Server Failure`/`Cached` at 8.5) is below the collision threshold, see `tokens.css`. The custom property is called `--ql-tone` so that it cannot shadow a token: no name in `tokens.css` starts with `--ql-`. */ .rServerFailure { --ql-tone: var(--ch-fail); } .rBlocked { --ql-tone: var(--ch-block); } .rNxDomain { --ql-tone: var(--ch-nx); } .rRefused { --ql-tone: var(--ch-refuse); } .rAuthoritative { --ql-tone: var(--ch-auth); } .rRecursive { --ql-tone: var(--ch-rec); } .rCached { --ql-tone: var(--ch-cache); } /* A row with no category gets a pill with no fill, so the response type keeps the same place on every row. */ .tone { display: inline-block; padding: var(--s-1) var(--s-5); border-radius: var(--r-1); background: color-mix(in srgb, var(--ql-tone, transparent) 30%, transparent); white-space: nowrap; } .rtt { font-size: var(--t-2); color: var(--faint-on-tint); padding-left: var(--s-5); } /* The legend of the seven, and it is ALWAYS complete. The design rule is explicit: every label that comes from the server carries its legend entry. Here there were seven colours and none: the user saw coloured rows and nothing explaining them. Complete, and not "the ones on this page": a legend that changes with the data shows six colours on one page and four on the next, and stops being a vocabulary to become a summary of what is in front of you. */ .legend { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-5) var(--s-9); padding: var(--s-7) var(--s-9); margin-bottom: var(--s-7); border: 1px solid var(--line); border-radius: var(--r-3); background: var(--pan); } .legendTitle { font-size: var(--t-1); font-weight: 600; letter-spacing: 0.09em; color: var(--mute); } .legendItem { display: inline-flex; align-items: center; gap: var(--s-5); font-size: var(--t-2); } /* Each entry is the pill itself, the same one the Response Type cell carries, followed by the condition that paints it. */ .legendItem .tone { font-weight: 550; color: var(--ink); } .legendWhen { color: var(--faint); } /* Folded (below 960): the seven pills stay, their conditions wait behind the title, which is the toggle. */ .legendFolded .legendWhen { display: none; } .legendToggle { composes: hitArea from '../../ui/interaction.module.css'; display: inline-flex; align-items: center; gap: var(--s-4); padding: 0; border: 0; background: none; font-family: inherit; cursor: pointer; } /* Centred on purpose: it is a bar filter, not a settings row. But the minimum height is the same as `ui/Check.module.css`'s. */ /* `.check` went in the redesign: it was a hand copy of `ui/Check` with eight px of gap instead of nine, and `uniformity.js` counted it as a separate signature of the same checkbox. Query Logs uses the primitive now. */ /* The group a MODE switches off: the edge, once for all four. It is the same master-switch signal as `Settings` with the polarity inverted: there the control is off because its master is OFF and the pill says `Needs …`; here it is off because `Live Update` is ON and it says `While …`. The mechanics do not change because what matters is the same: **the user can undo it themselves**, and that is what the amber means. It composes from `Form.module.css` so that it is THE SAME edge and not another one like it: an inner shadow and not a border, because a border eats two pixels of the box and shifts the content, as measured in `Settings`, where it gave away a third grid signature that existed only on that screen. It goes on the GROUP and not on each field because all four switch off together: four pills saying the same thing are noise, and the `Settings` rule was already one pill per group. */ /* The edge needs room to its left, and it takes it from the gap BEFORE the group (the negative margin cancels the padding): a group that grew when the mode switched on would push its neighbours to another band. */ /* The switch in the header: centred on the buttons beside it. */ .live { display: inline-flex; align-items: center; padding-left: var(--s-5); } .byMode { composes: dependent from '../../ui/Form.module.css'; padding-left: var(--s-8); margin-left: calc(-1 * var(--s-8)); }