Niveus/niveus/components/window.css
Claude a19e0f205f
Depth, light and translucency without changing the API
Niveus read flat: thin sheens, short shadows, opaque grey chrome and a
painted canvas. This retunes values and component internals only; every
class and token name is unchanged, so existing pages pick it up as is.

- Canvas: a faint accent light falls across the page, so translucent
  layers have something to show through.
- Surfaces: the surface sheen runs the full height (lit top, shaded
  foot); raised and filled sheens, highlights and bevels are stronger;
  the elevation scale is deeper and softer.
- Translucency: cards, dialogs, the sidebar and tab tracks let some of
  the canvas through, with blur so busy grounds never read as texture;
  frosted and glass materials are clearer with more blur.
- Filled buttons give off a soft glow of their own color.
- Light fields are paper instead of grey wells that looked disabled.
- Fixes: navigation bars wrap and lists scroll inside them instead of
  overflowing the page on phones; choice labels sit at control size;
  disabled ghost buttons no longer grow a filled well; the color-swatch
  rule is split so neither engine drops it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019iMUTHrD8K9CSuHqsanje9
2026-10-12 20:49:22 +00:00

192 lines
6.3 KiB
CSS

/* ==========================================================================
Niveus — Window
--------------------------------------------------------------------------
.nv-window a framed application window
.nv-window--inactive a window that does not have focus
.nv-window__titlebar controls · title · trailing actions
.nv-window__controls the three lights, at the start of the bar
.nv-window__control one light: --close, --minimize or --zoom
.nv-window__title the window's name, centered on the bar
.nv-window__actions trailing slot for a few ghost buttons
.nv-window__body the content area
<div class="nv-window">
<div class="nv-window__titlebar">
<div class="nv-window__controls">
<button class="nv-window__control nv-window__control--close" aria-label="Close"></button>
<button class="nv-window__control nv-window__control--minimize" aria-label="Minimize"></button>
<button class="nv-window__control nv-window__control--zoom" aria-label="Zoom"></button>
</div>
<span class="nv-window__title">Settings</span>
</div>
<div class="nv-window__body">…</div>
</div>
A window is a solid object: the content it holds never sits on glass. Its
titlebar is a raised strip lit like a control, and the controls are three
lights at its start — close, minimize, zoom — always in that order and in
those colors, because people find them by position and color before they
read anything. Their glyphs appear only while the pointer is over the
group. An inactive window greys its lights and steps back one elevation,
and the lights regain their color the moment they are hovered.
========================================================================== */
.nv-window {
display: flex;
flex-direction: column;
min-inline-size: 0;
background-color: var(--nv-color-surface);
background-image: var(--nv-sheen-surface);
color: var(--nv-color-text-primary);
border: var(--nv-border-width) solid var(--nv-color-border-subtle);
border-radius: var(--nv-radius-xl);
box-shadow: var(--nv-highlight-top), var(--nv-outline), var(--nv-elevation-4);
overflow: hidden;
transition: var(--nv-transition-surface);
}
.nv-window--inactive {
box-shadow: var(--nv-highlight-top), var(--nv-outline), var(--nv-elevation-3);
}
/* --- Titlebar ---------------------------------------------------------------
Three columns with the outer two sharing the slack, so the title stays on
the window's center line whatever the controls and actions measure. */
.nv-window__titlebar {
display: grid;
grid-template-columns: 1fr auto 1fr;
align-items: center;
gap: var(--nv-space-3);
/* Slim, as titlebars on the desktop are: tall enough for the lights and
a label, not a toolbar's height. */
min-block-size: calc(var(--nv-control-height-sm) + var(--nv-space-1));
padding-inline: var(--nv-space-3);
background-color: var(--nv-color-surface-elevated);
background-image: var(--nv-sheen-raised);
border-block-end: var(--nv-border-width) solid var(--nv-color-border-subtle);
box-shadow: var(--nv-highlight-top);
user-select: none;
}
.nv-window__title {
grid-column: 2;
min-inline-size: 0;
overflow: hidden;
font: var(--nv-font-label);
font-weight: var(--nv-font-weight-semibold);
color: var(--nv-color-text-primary);
text-overflow: ellipsis;
white-space: nowrap;
}
.nv-window--inactive .nv-window__title {
color: var(--nv-color-text-tertiary);
}
.nv-window__actions {
grid-column: 3;
justify-self: end;
display: flex;
align-items: center;
gap: var(--nv-space-1);
}
/* --- Controls ----------------------------------------------------------------- */
.nv-window__controls {
grid-column: 1;
justify-self: start;
display: flex;
align-items: center;
gap: var(--nv-space-2);
}
.nv-window__control {
--_light: var(--nv-color-window-close);
--_c: var(--_light);
position: relative;
flex: none;
inline-size: var(--nv-space-3);
block-size: var(--nv-space-3);
margin: 0;
padding: 0;
background-color: var(--_c);
background-image: var(--nv-sheen-filled);
border: 0;
border-radius: var(--nv-radius-full);
box-shadow: inset 0 0 0 var(--nv-border-width) var(--nv-color-window-control-ring);
cursor: pointer;
appearance: none;
-webkit-tap-highlight-color: transparent;
transition: var(--nv-transition-colors);
}
.nv-window__control--minimize { --_light: var(--nv-color-window-minimize); }
.nv-window__control--zoom { --_light: var(--nv-color-window-zoom); }
/* The glyph is two bars drawn from the light's center: crossed for close, one
for minimize, a plus for zoom. They are always present and fade in, so the
whole group reveals at once instead of one glyph at a time. */
.nv-window__control::before,
.nv-window__control::after {
content: "";
position: absolute;
inset-block-start: 50%;
inset-inline-start: 50%;
inline-size: 55%;
block-size: calc(var(--nv-border-width) * 1.5);
translate: -50% -50%;
background-color: var(--nv-color-window-glyph);
border-radius: var(--nv-radius-full);
opacity: 0;
transition: opacity var(--nv-duration-fast) var(--nv-ease-standard);
}
.nv-window__control--close::before { rotate: 45deg; }
.nv-window__control--close::after { rotate: -45deg; }
.nv-window__control--minimize::after { display: none; }
.nv-window__control--zoom::after { rotate: 90deg; }
.nv-window__controls:hover .nv-window__control::before,
.nv-window__controls:hover .nv-window__control::after,
.nv-window__controls:focus-within .nv-window__control::before,
.nv-window__controls:focus-within .nv-window__control::after {
opacity: 1;
}
.nv-window__control:active {
background-image: none;
box-shadow:
inset 0 0 0 var(--nv-border-width) var(--nv-color-window-control-ring),
var(--nv-elevation-inset-sm);
}
.nv-window__control:focus-visible {
outline: none;
box-shadow:
inset 0 0 0 var(--nv-border-width) var(--nv-color-window-control-ring),
var(--nv-focus-ring);
}
.nv-window--inactive .nv-window__control {
--_c: var(--nv-color-window-inactive);
}
.nv-window--inactive .nv-window__controls:hover .nv-window__control {
--_c: var(--_light);
}
/* --- Body --------------------------------------------------------------------- */
.nv-window__body {
flex: 1;
min-block-size: 0;
overflow: auto;
}