/* ───────────────────────────────────────────────────────────────────────── 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; } }