/* Zen-style vertical tabs: a sidebar column with the terminal inset to its right. Every colour here is a name defined in `palette-dark.css` or `palette-light.css`; `appearance.zig` prepends one of them and loads the pair as a single provider. Nothing below may hardcode a colour — a literal hex would be a rule that looks right in one scheme and wrong in the other, and it would look right in whichever one you happened to be testing in. `alpha()` is used for every wash rather than a second named colour, so a state's tint is always derived from the state's own colour and the two can't drift apart. */ .playpen-window { background-color: @pp_bg; } /* The sidebar is a card, not a column: rounded on every corner and inset from the window edge, so it sits on the backdrop the same way a pane does. It used to run edge to edge and meet the content at a hairline, which left the top-left corner reading as three unrelated things sharing one point — the window edge, the header buttons and the first tab row all at once. */ .playpen-sidebar { background-color: @pp_sidebar; border: 1px solid @pp_border; border-radius: 12px; margin: 6px 0 6px 6px; } /* Room above the buttons so they clear the card's rounded top edge rather than sitting in it. The rule underneath is stated explicitly rather than left to the one Adwaita draws for every header bar: that one is a shadow in the desktop theme's colour, which on this palette read as a stray line rather than as the pair it now makes with the footer's. */ .playpen-sidebar headerbar { background: none; box-shadow: none; min-height: 44px; padding: 6px 8px 2px 8px; border-bottom: 1px solid @pp_border; } /* The two header actions. Styled explicitly rather than left to Adwaita, whose default button is a grey that belongs to a different window than this one. Scoped by class instead of by `headerbar button` so the window controls beside them keep their own appearance — those belong to the desktop, not to the app, and making them match would be the wrong kind of consistent. Two selectors because a MenuButton wraps its button in a `menubutton` node, so the class lands one level above the thing that draws. */ button.playpen-header-button, .playpen-header-button > button { min-width: 30px; min-height: 30px; padding: 0; margin: 0 1px; border-radius: 8px; color: @pp_text_dim; background-color: @pp_surface_raised; background-image: none; border: 1px solid @pp_border; box-shadow: none; } button.playpen-header-button:hover, .playpen-header-button > button:hover { background-color: @pp_row_hover; background-image: none; color: @pp_text; } /* The strip below the tab list, holding the settings button. Separated by a rule so the button doesn't read as a row of the list it sits under. */ .playpen-sidebar-footer { padding: 6px 8px; border-top: 1px solid @pp_border; } .playpen-settings-button { min-width: 26px; min-height: 26px; padding: 0; color: @pp_text_faint; } .playpen-settings-button:hover { color: @pp_text; } .playpen-list { background: none; padding: 4px 8px; } .playpen-list > row { border-radius: 8px; margin: 2px 0; padding: 7px 8px; color: @pp_text_dim; transition: background-color 120ms ease; } .playpen-list > row:hover { background-color: @pp_row_hover; } .playpen-list > row:selected { background-color: @pp_row_selected; color: @pp_text; } /* The kind icon picks up the accent on the current row. The status dot is excluded: it carries a colour that *is* the information, and this rule is specific enough to beat the state classes, which used to leave the selected row's dot accent-coloured whatever it was trying to say. */ .playpen-list > row:selected image:not(.playpen-status-dot) { color: @pp_accent_strong; } /* An emoji standing in for that icon. Sized to the width the icon occupies rather than left to the glyph, so a sidebar mixing emoji rows with icon rows still starts every label in the same column — emoji vary in advance width and without this the labels beside them would step in and out by a pixel or two per row. The font size is a little under the icon's 16px because a colour glyph fills its box where a symbolic icon leaves air around itself. */ .playpen-tab-emoji { min-width: 16px; font-size: 13px; } /* State on the row itself, not just on its dot. A bar down the leading edge and a wash behind the whole row, both in the state's colour. The dot says what a row is doing once you are reading it; these are what let you sweep a sidebar of a dozen tabs and land on the one that wants you without reading any of them. The bar is an inset shadow rather than a real border because a border takes part in layout: 3px of it would shove every label sideways as a tab changed state, and a sidebar that twitches is worse than one that is hard to read. These come after the :selected rules deliberately. Equal specificity means source order decides, and a state outranks "this is the tab you are on". */ .playpen-list > row.playpen-attn-busy { box-shadow: inset 3px 0 0 @pp_accent_strong; } /* No wash for working. It is the most common state by far, and a sidebar where half the rows are tinted all afternoon teaches you to stop looking. */ .playpen-list > row.playpen-attn-done { box-shadow: inset 3px 0 0 @pp_ok; background-color: alpha(@pp_ok, 0.14); } .playpen-list > row.playpen-attn-input { box-shadow: inset 3px 0 0 @pp_warn; background-color: alpha(@pp_warn, 0.14); } .playpen-list > row.playpen-attn-failed { box-shadow: inset 3px 0 0 @pp_err; background-color: alpha(@pp_err, 0.14); } /* The current row keeps its own background: the wash and the selection would otherwise blend into a colour that reads as neither. The bar survives, which is the part that carries the state. */ .playpen-list > row:selected.playpen-attn-done, .playpen-list > row:selected.playpen-attn-input, .playpen-list > row:selected.playpen-attn-failed { background-color: @pp_row_selected; } /* Keep the close button unobtrusive until the row is hovered or current. */ .playpen-close { opacity: 0; min-width: 20px; min-height: 20px; padding: 0; } .playpen-list > row:hover .playpen-close, .playpen-list > row:selected .playpen-close { opacity: 0.65; } .playpen-close:hover { opacity: 1; } .playpen-content { background-color: @pp_bg; } /* A view is the container for one tab's terminals. Even padding now that the sidebar is inset too — the left edge used to be flush against the sidebar's border, so the gutter around the content was open on three sides and closed on the fourth. */ .playpen-view { padding: 6px; } /* Paned itself draws nothing; the panes inside it carry the styling. */ .playpen-view paned { background: none; } /* Each terminal sits in its own rounded frame so multiple panes in a view read as distinct surfaces. */ .playpen-pane { background-color: @pp_surface; border-radius: 10px; border: 1px solid @pp_border; } .playpen-pane.active { border-color: @pp_accent_muted; } .playpen-pane-header { padding: 2px 4px 2px 10px; background-color: @pp_surface_raised; border-bottom: 1px solid @pp_border; } .playpen-pane.active .playpen-pane-header { background-color: @pp_surface_raised_active; } .playpen-pane-title { font-size: 0.82em; color: @pp_text_faint; } .playpen-pane.active .playpen-pane-title { color: @pp_text; } .playpen-pane-button { min-width: 22px; min-height: 22px; padding: 0; opacity: 0; } .playpen-pane:hover .playpen-pane-button, .playpen-pane.active .playpen-pane-button { opacity: 0.55; } .playpen-pane-button:hover { opacity: 1; } .playpen-pane-icon { color: @pp_text_faint; -gtk-icon-size: 14px; } .playpen-pane.active .playpen-pane-icon { color: @pp_accent_strong; } /* Status dots. One shared appearance for the sidebar row and the pane header, coloured entirely by which state class is on the icon. Symbolic icons take their colour from `color`, so nothing here needs a second asset. These are read at a glance from across a screen full of tabs, so the colours are picked to survive that: amber and red carry the two states that actually want you, and they are the only warm colours anywhere in the window. */ .playpen-status-dot { -gtk-icon-size: 11px; opacity: 0; } .playpen-status-busy, .playpen-status-done, .playpen-status-input, .playpen-status-failed { opacity: 1; } /* Working: the same accent as an active pane's border, so "busy" reads as ordinary activity rather than something gone wrong. */ .playpen-status-busy { color: @pp_accent_strong; animation: playpen-pulse 1.6s ease-in-out infinite; } /* Finished while you were elsewhere. Green rather than the accent so it is distinct from still-working at a glance, which is the one distinction this whole indicator exists to make. */ .playpen-status-done { color: @pp_ok; } /* Blocked on you. Amber, and not animated — a pulsing dot reads as progress, and this is the opposite of progress. */ .playpen-status-input { color: @pp_warn; } .playpen-status-failed { color: @pp_err; } @keyframes playpen-pulse { 0% { opacity: 0.35; } 50% { opacity: 1; } 100% { opacity: 0.35; } } /* Renaming a tab: a single entry in a popover hanging off the row. */ .playpen-rename { padding: 8px; min-width: 220px; } /* The row's right-click menu. Narrower than the rename popover and with tighter padding: it holds two words per line, and a menu sized like a form reads as though something failed to fill it. */ .playpen-row-menu { padding: 4px; min-width: 150px; } .playpen-row-menu > button { padding: 5px 8px; border-radius: 6px; } /* A web pane's navigation bar, below the pane header. Kept visually quieter than the header so the two rows don't compete. */ .playpen-nav { padding: 4px 6px; background-color: @pp_surface; border-bottom: 1px solid @pp_border; } .playpen-nav-button { min-width: 24px; min-height: 24px; padding: 0; color: @pp_text_dim; } .playpen-nav-button:disabled { opacity: 0.3; } .playpen-nav-entry { min-height: 24px; margin-left: 4px; padding: 2px 8px; border-radius: 6px; font-size: 0.85em; background-color: @pp_bg; color: @pp_text; border: 1px solid @pp_border; box-shadow: none; } .playpen-nav-entry:focus-within { border-color: @pp_accent; } /* Find-in-page, in the same clothes as the nav bar above it: it is the same kind of furniture, just one that comes and goes. */ .playpen-find { padding: 4px 6px; background-color: @pp_surface; border-bottom: 1px solid @pp_border; } /* A search with no results says so in the box's border rather than only in the count beside it, which is easy to miss mid-typing. */ .playpen-find-entry.error { border-color: @pp_err; } .playpen-find-status { margin: 0 4px; font-size: 0.8em; color: @pp_text_dim; } /* The pane currently filling its tab. The other panes are still open and still running, just not drawn, so this is marked quietly — a lit border rather than anything alarming. The header's toggle icon carries the rest of the message. */ .playpen-pane.zoomed { border-color: @pp_accent; } .playpen-pane.zoomed .playpen-pane-button { opacity: 0.55; } /* State on a pane's own frame, so a tab full of splits says which pane is asking without being read pane by pane. Only the three states that are news get a border. Working deliberately does not: it is the resting state of every pane you have set going, and colouring the frame for it would leave the whole view repainting itself all afternoon while saying nothing you did not already know. Its pulsing dot is enough. After `.active` and `.zoomed` in source order, so a state outranks both — they say where you are, which you know, and this says what happened, which you don't. `.dragging` still wins over all of it, further down: while you are moving a pane, feedback about the move is the only thing that matters. */ .playpen-pane.playpen-attn-done { border-color: @pp_ok; } .playpen-pane.playpen-attn-input { border-color: @pp_warn; } .playpen-pane.playpen-attn-failed { border-color: @pp_err; } /* The header carries a wash of the same colour. The border alone is a hairline around a large shape; the header is a solid band right next to the dot and the title, which is what makes a pane readable at a glance in a four-way split. */ .playpen-pane.playpen-attn-done .playpen-pane-header { background-color: alpha(@pp_ok, 0.13); } .playpen-pane.playpen-attn-input .playpen-pane-header { background-color: alpha(@pp_warn, 0.13); } .playpen-pane.playpen-attn-failed .playpen-pane-header { background-color: alpha(@pp_err, 0.13); } /* The pane being dragged. There is no separate drop indicator: the layout rearranges live during the drag, so the view itself is the preview. */ .playpen-pane.dragging { opacity: 0.65; border-color: @pp_accent_strong; } /* Saved-layout menu, hanging off the sidebar header. */ .playpen-layout-menu { min-width: 260px; } .playpen-layout-menu button { padding: 4px 8px; border-radius: 6px; } .playpen-layout-empty { padding: 8px; color: @pp_text_faint; font-size: 0.9em; } /* Layout dialogs: opening one, and saving the current tab as one. */ .playpen-dialog { background-color: @pp_surface; } .playpen-dialog-content { padding: 16px; } .playpen-dialog-actions { padding: 12px 16px; border-top: 1px solid @pp_border; } .playpen-dialog-heading { margin-top: 8px; font-weight: bold; color: @pp_text; } .playpen-dialog-label { color: @pp_text_dim; } .playpen-dialog-sublabel { font-size: 0.88em; color: @pp_text_faint; } .playpen-dialog-hint { font-size: 0.85em; color: @pp_text_faint; } .playpen-dialog-error { color: @pp_err; } /* Each captured pane in the save dialog, so the sections read apart. */ .playpen-dialog-pane { padding: 8px; border: 1px solid @pp_border; border-radius: 8px; } /* ------------------------------------------------------------------------- Settings One section per group of preferences, each a titled card on the dialog's surface. There is a single group today; the card is what stops the second one from needing a redesign. */ .playpen-settings-group { padding: 12px 14px; border: 1px solid @pp_border; border-radius: 10px; background-color: @pp_surface_raised; } .playpen-settings-title { font-weight: bold; color: @pp_text; } /* The scheme picker: three linked buttons rather than a dropdown, so the whole choice is visible without opening anything, and so the one you are on is readable at a glance rather than by reading a word. */ /* `background-image: none` on every rule below is load-bearing. Adwaita paints button states with a gradient *over* the background colour, so setting the colour alone leaves a washed-out version of it — most visibly on the checked button, where the accent came out as a pale grey-blue. `:backdrop` is spelled out for the same reason: Adwaita dims unfocused windows, and the selected scheme is exactly the thing that has to stay readable while you are looking at the window behind this one to see what it did. */ .playpen-settings-choice > button { padding: 5px 14px; color: @pp_text_dim; background-color: @pp_surface; background-image: none; border: 1px solid @pp_border; box-shadow: none; } .playpen-settings-choice > button:hover { background-color: @pp_row_hover; background-image: none; color: @pp_text; } .playpen-settings-choice > button:checked, .playpen-settings-choice > button:checked:backdrop { background-color: @pp_accent; background-image: none; border-color: @pp_accent; /* Against a filled accent in either scheme — the light palette's accent is dark enough that white text is the readable choice there too. */ color: #ffffff; } .playpen-settings-choice > button:checked:hover { background-color: @pp_accent_strong; background-image: none; border-color: @pp_accent_strong; } /* ------------------------------------------------------------------------- The startup list One card per tab the app opens itself with. The border is doing real work: a row's parameter fields sit *under* its header line, and without something drawing the boundary they read as belonging to the row below them instead. */ .playpen-startup-row { padding: 8px; border: 1px solid @pp_border; border-radius: 8px; } /* Indented under the header line, which is the other half of the same grouping: these fields are a property of the layout picked above them. */ .playpen-startup-params { margin-left: 10px; } /* ------------------------------------------------------------------------- The emoji picker A tab's chosen glyph, then a search box over a grid of every glyph on offer. The grid is a GtkFlowBox, so each cell is a `flowboxchild` wrapping a label and the hover and selection states have to be drawn here — Adwaita styles a flow box for lists of cards, which at this size leaves the cells looking like nothing in particular. */ /* The current choice, big enough to check from across the dialog. Boxed to the same square whether it holds a glyph or the word "None", so switching between them doesn't shift the text beside it. */ .playpen-emoji-preview { min-width: 44px; min-height: 44px; font-size: 26px; border: 1px solid @pp_border; border-radius: 10px; background-color: @pp_surface; } .playpen-emoji-preview-none { font-size: 0.85em; color: @pp_text_faint; } /* Sunk into the dialog rather than raised off it: the grid is a well you look into, and the section card it sits in is already the raised surface. */ .playpen-emoji-scroller { border: 1px solid @pp_border; border-radius: 10px; background-color: @pp_surface; } .playpen-emoji-grid { padding: 6px; background: none; } .playpen-emoji-cell { font-size: 19px; } .playpen-emoji-grid > flowboxchild { padding: 0; border-radius: 8px; background: none; transition: background-color 100ms ease; } .playpen-emoji-grid > flowboxchild:hover { background-color: @pp_row_hover; } /* The glyph in force. `background-image: none` for the same reason as the theme buttons above: Adwaita paints selection with a gradient over the colour, and without this the accent comes out washed. */ .playpen-emoji-grid > flowboxchild:selected, .playpen-emoji-grid > flowboxchild:selected:backdrop { background-color: @pp_accent_muted; background-image: none; box-shadow: inset 0 0 0 1px @pp_accent; } /* Divider between panes. Wide enough to grab without hunting for it. */ .playpen-view paned > separator { background-color: @pp_bg; min-width: 6px; min-height: 6px; } .playpen-view paned > separator:hover { background-color: @pp_accent_muted; }