477 lines
14 KiB
CSS
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;
|
|
}
|
|
}
|