Files
Veskslop-t/poc/public/settings-popups.css
T

477 lines
14 KiB
CSS

/* ─────────────────────────────────────────────────────────────────────────
Veskslop — settings, menus, dialogs and toasts.
The shell reads as Nocturne; these surfaces did not. They were built from
flat 1px rules, bare native controls and text glyphs, so opening Settings
or a scrape dialog felt like leaving the app. Nothing here changes
behaviour or markup — it is presentation only.
Load LAST: nocturne.css · tokens-status.css · style.css · media.css ·
style-overrides.css · settings-popups.css
───────────────────────────────────────────────────────────────────────── */
/* ── 1. The scrim ─────────────────────────────────────────────────────────
A flat 55% wash left the page behind perfectly legible, so the eye kept
reading it. Deepen it, blur what is behind, and fade it in. */
.vk-overlay {
background: color-mix(in srgb, var(--color-bg) 72%, transparent);
backdrop-filter: blur(3px) saturate(0.9);
animation: vk-scrim-in 140ms ease-out;
}
@keyframes vk-scrim-in {
from {
opacity: 0;
}
}
/* ── 2. Settings panel ────────────────────────────────────────────────────
A slide-over with a square left edge butting into a blurred page reads as
a torn sheet. Round the leading corners, give it the system's hairline,
and let it arrive from the edge it is attached to. */
.vk-settings-panel {
width: min(720px, 100%);
border-radius: var(--radius-lg) 0 0 var(--radius-lg);
box-shadow: var(--shadow-lg), inset 1px 0 0 var(--color-neutral-800);
animation: vk-panel-in 200ms cubic-bezier(0.2, 0.7, 0.3, 1);
}
@keyframes vk-panel-in {
from {
transform: translateX(18px);
opacity: 0;
}
}
/* Head: sticky, so the title survives a long scroll, and on the system's
fading rule rather than a flat one. */
.vk-settings-head {
position: sticky;
top: 0;
z-index: 2;
padding: 13px 16px;
background: var(--color-surface);
box-shadow: none;
background-image: linear-gradient(
to right,
transparent,
var(--color-neutral-800) 40px,
var(--color-neutral-800) calc(100% - 40px),
transparent
);
background-repeat: no-repeat;
background-position: bottom;
background-size: 100% 1px;
}
.vk-settings-title {
letter-spacing: -0.01em;
}
.vk-settings-body {
gap: 26px;
padding: 18px 16px 28px;
scrollbar-width: thin;
scrollbar-color: var(--color-neutral-800) transparent;
}
.vk-settings-body::-webkit-scrollbar {
width: 10px;
}
.vk-settings-body::-webkit-scrollbar-thumb {
border: 3px solid transparent;
border-radius: 6px;
background: var(--color-neutral-800) padding-box;
}
.vk-settings-body::-webkit-scrollbar-thumb:hover {
background: var(--color-neutral-700) padding-box;
}
/* ── 3. Section heads ─────────────────────────────────────────────────────
Label, breathing room, then the actions pinned right on the same
baseline — previously the buttons crowded the label. */
.vk-section-head {
align-items: center;
gap: 12px;
padding: 0 2px 9px;
}
.vk-settings-actions {
gap: 6px;
}
/* ── 4. Cards and rows ────────────────────────────────────────────────────
The card was a flat fill; rows ran into each other. Give the card the
system edge and separate rows with fading rules, inset from both ends. */
.vk-settings-card {
padding: 4px 12px;
box-shadow: inset 0 0 0 1px var(--color-neutral-800);
}
.vk-status-row {
align-items: center;
gap: 16px;
padding: 9px 0;
box-shadow: none;
background: linear-gradient(
to right,
transparent,
var(--color-neutral-800) 16px,
var(--color-neutral-800) calc(100% - 16px),
transparent
)
no-repeat bottom / 100% 1px;
}
.vk-status-row:last-child,
.vk-settings-card > .vk-status-row:last-of-type {
background: none;
}
.vk-status-row .vk-status-label {
display: flex;
align-items: center;
gap: 6px;
color: var(--color-neutral-400);
}
.vk-status-row .vk-status-value {
display: flex;
align-items: center;
gap: 8px;
justify-content: flex-end;
font-variant-numeric: tabular-nums;
}
/* ── 5. The info affordance ───────────────────────────────────────────────
.vk-info was an unstyled letter "i" sitting in the text, and its help
text only existed as a native browser tooltip — half a second of delay,
no styling, and invisible on touch. Make it a real mark with a themed
tooltip. polish.js moves title → data-tip so the native one stops
double-firing; the title fallback below keeps it working without JS. */
.vk-info {
display: inline-grid;
place-items: center;
width: 14px;
height: 14px;
flex: none;
border-radius: 50%;
box-shadow: inset 0 0 0 1px var(--color-neutral-700);
font: 500 9px/1 var(--font-heading);
color: var(--color-neutral-400);
cursor: help;
position: relative;
transition: color 120ms, box-shadow 120ms;
}
.vk-info:hover,
.vk-info:focus-visible {
color: var(--color-accent-300);
box-shadow: inset 0 0 0 1px var(--color-accent-700);
}
.vk-info::after {
content: attr(data-tip);
position: absolute;
bottom: calc(100% + 8px);
left: 50%;
translate: -50% 0;
z-index: 30;
width: max-content;
max-width: 280px;
padding: 8px 10px;
border-radius: var(--radius-md);
background: var(--color-neutral-900);
box-shadow: var(--shadow-md), inset 0 0 0 1px var(--color-neutral-800);
font: 400 11.5px/1.5 var(--font-body);
letter-spacing: 0;
text-transform: none;
text-align: left;
color: var(--color-neutral-200);
opacity: 0;
pointer-events: none;
transition: opacity 120ms;
}
.vk-info:not([data-tip])::after {
content: attr(title);
}
.vk-info:hover::after,
.vk-info:focus-visible::after {
opacity: 1;
}
/* ── 6. Collapsible groups ────────────────────────────────────────────────
The summary was a bare line of uppercase text; nothing said it was a
control. Give it a row of its own, a hover tint and a caret that turns. */
.vk-settings-summary {
padding: 7px 10px;
border-radius: var(--radius-sm);
background: var(--color-neutral-900);
transition: background 120ms, color 120ms;
}
.vk-settings-summary:hover {
background: var(--color-neutral-800);
color: var(--color-accent-200);
}
.vk-settings-summary .vk-caret {
display: inline-flex;
color: var(--color-neutral-400);
transition: transform 160ms cubic-bezier(0.2, 0.7, 0.3, 1);
}
.vk-settings-group[open] > .vk-settings-summary {
color: var(--color-accent-200);
}
.vk-settings-group-body {
gap: 24px;
padding: 16px 0 4px 10px;
box-shadow: inset 1px 0 0 var(--color-neutral-900);
margin-left: 4px;
}
/* ── 7. Native controls ───────────────────────────────────────────────────
Selects kept the platform arrow and a 1px border; numbers kept their
spinners; ranges kept the platform track. All three read as unstyled. */
.vk-select {
appearance: none;
padding: 5px 26px 5px 9px;
border: none;
border-radius: var(--radius-sm);
box-shadow: inset 0 0 0 1px var(--color-neutral-800);
background-color: var(--color-bg);
background-image: none;
cursor: pointer;
transition: box-shadow 120ms;
}
/* The caret is a mask, not a coloured data-URI, so it takes its colour from
the token rather than from a hex baked into the URL. */
.vk-select-wrap {
position: relative;
display: inline-flex;
}
.vk-select-wrap::after,
.vk-status-value:has(> .vk-select)::after {
content: "";
position: absolute;
right: 9px;
top: 50%;
translate: 0 -50%;
width: 9px;
height: 9px;
pointer-events: none;
background: var(--color-neutral-400);
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256' fill='none' stroke='%23000' stroke-width='26' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M208 96l-80 80-80-80'/%3E%3C/svg%3E")
center / contain no-repeat;
}
.vk-status-value:has(> .vk-select) {
position: relative;
}
.vk-select:hover {
box-shadow: inset 0 0 0 1px var(--color-neutral-700);
}
.vk-select:focus-visible {
outline: 2px solid var(--color-accent);
outline-offset: 2px;
}
.vk-media-input,
input[type="number"].input {
font-variant-numeric: tabular-nums;
text-align: right;
}
.vk-media-input::-webkit-outer-spin-button,
.vk-media-input::-webkit-inner-spin-button {
appearance: none;
margin: 0;
}
input[type="range"] {
appearance: none;
height: 18px;
background: none;
cursor: pointer;
}
input[type="range"]::-webkit-slider-runnable-track {
height: 3px;
border-radius: 2px;
background: var(--color-neutral-800);
}
input[type="range"]::-moz-range-track {
height: 3px;
border-radius: 2px;
background: var(--color-neutral-800);
}
input[type="range"]::-webkit-slider-thumb {
appearance: none;
width: 12px;
height: 12px;
margin-top: -4.5px;
border-radius: 50%;
background: var(--color-accent-200);
box-shadow: 0 0 0 1px var(--color-accent);
}
input[type="range"]::-moz-range-thumb {
width: 12px;
height: 12px;
border: none;
border-radius: 50%;
background: var(--color-accent-200);
box-shadow: 0 0 0 1px var(--color-accent);
}
input[type="range"]:focus-visible {
outline: 2px solid var(--color-accent);
outline-offset: 2px;
border-radius: var(--radius-sm);
}
/* ── 8. Menus ─────────────────────────────────────────────────────────────
The guild picker floated with no edge, and the active item was a tint
with nothing to anchor it. */
.vk-menu {
padding: 5px;
box-shadow: var(--shadow-lg), inset 0 0 0 1px var(--color-neutral-800);
animation: vk-menu-in 120ms ease-out;
scrollbar-width: thin;
scrollbar-color: var(--color-neutral-800) transparent;
}
@keyframes vk-menu-in {
from {
transform: translateY(-4px);
opacity: 0;
}
}
.vk-menu-item {
border-radius: var(--radius-sm);
transition: background 100ms, color 100ms;
}
.vk-menu-item.active {
box-shadow: inset 2px 0 0 var(--color-accent);
}
/* ── 9. Dialogs ───────────────────────────────────────────────────────────
The voice-profile drawer was square-topped and flat-bottomed against the
scrim; the media modals had their own unrelated corner radii. */
.vk-profile-panel {
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg), inset 0 0 0 1px var(--color-neutral-800);
}
.vk-profile-panel .vk-settings-head {
cursor: grab;
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
}
.vk-profile-panel .vk-settings-head:active {
cursor: grabbing;
}
/* A grab handle, so the drag target is discoverable. */
.vk-profile-panel .vk-settings-head::before {
content: "";
position: absolute;
top: 6px;
left: 50%;
translate: -50% 0;
width: 34px;
height: 3px;
border-radius: 2px;
background: var(--color-neutral-800);
}
.vk-profile-panel .vk-settings-head {
position: relative;
padding-top: 15px;
}
.vk-media-modal-box {
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg), inset 0 0 0 1px var(--color-neutral-800);
}
.vk-media-head strong {
font: 500 14px/1.2 var(--font-heading);
letter-spacing: -0.01em;
}
/* ── 10. Toasts ───────────────────────────────────────────────────────────
Icon slot, roles carried by tokens, and an entrance that comes from the
corner they live in. */
.vk-toast {
display: flex;
align-items: flex-start;
gap: 9px;
line-height: 1.5;
box-shadow: var(--shadow-md), inset 0 0 0 1px var(--color-neutral-800);
animation: vk-toast-in 160ms cubic-bezier(0.2, 0.7, 0.3, 1);
}
@keyframes vk-toast-in {
from {
transform: translateY(6px);
opacity: 0;
}
}
.vk-toast .vk-icon {
flex: none;
margin-top: 1px;
}
.vk-toast.error .vk-icon {
color: var(--color-danger-400);
}
.vk-toast.warn .vk-icon {
color: var(--color-warn-400);
}
.vk-toast.ok .vk-icon {
color: var(--color-ok-400);
}
/* ── 11. Motion ───────────────────────────────────────────────────────────*/
@media (prefers-reduced-motion: reduce) {
.vk-overlay,
.vk-settings-panel,
.vk-menu,
.vk-toast {
animation: none;
}
.vk-settings-summary .vk-caret {
transition: none;
}
}