mirror of
https://github.com/Nexoniarz/Niveus.git
synced 2026-11-03 04:34:26 +00:00
Theme: light is a dimmer brand-tinted stone with deeper shade, so it reads as the same material as dark; every neutral now carries --nv-brand in both appearances. Native controls (accent-color, caret-color) take the brand. Time picker: 24-hour by default; data-nv-format-toggle adds a third wheel of 24H / AM / PM. Swiping it rebuilds the hour wheel for the new format. The selection lens is a raised glass bar, not a flat band. Motion: drum effect on the wheels as a scroll-driven animation, with a script fallback for engines without it (Firefox); rubber-band at both ends of a wheel that springs back; calendar months slide locally instead of via a page-wide transition; a chosen day springs into place. Firefox: wheel notches arrive in lines, not pixels, which made the rubber-band invisible; deltaMode is now normalised. The selected-day pop no longer plays on page load. Also: hover only inside @media (hover: hover) on the slider, the Glaze inner glow is a token, and the focus note in GUIDELINES.md is corrected (popovers do not trap focus). SPDX identifier added to niveus.js. check.py, build.py and build.py --check pass. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
149 lines
4.9 KiB
CSS
149 lines
4.9 KiB
CSS
/* ==========================================================================
|
||
Niveus — Time picker
|
||
--------------------------------------------------------------------------
|
||
<div class="nv-timepicker nv-surface" data-nv-timepicker
|
||
data-nv-value="09:30" data-nv-step="5" data-nv-format-toggle></div>
|
||
|
||
Wheels, as on a phone: each column scrolls and snaps, and whatever sits in
|
||
the lens across the middle is the value. Scrolling, snapping, momentum and
|
||
touch are the browser's; niveus.js builds the columns, reads which row is
|
||
centred, and exposes the value as data-nv-value and an `nv:time` event.
|
||
|
||
The lens is the selection colour laid over the centre row, so it follows the
|
||
brand. Rows fade towards the top and bottom with a mask rather than a
|
||
coloured gradient, which keeps the fade right on any surface — solid, or
|
||
Glaze with something moving behind it.
|
||
|
||
The format is 24-hour unless told otherwise. With data-nv-format-toggle a
|
||
third wheel reads 24H / AM / PM: swipe it to AM or PM and the hour wheel
|
||
becomes 1–12; swipe back to 24H and it returns to 0–23. The value stays
|
||
24-hour either way, so a form never has to know which the person used.
|
||
|
||
Without niveus.js the element is empty. For a no-script fallback, use a
|
||
native <input type="time" class="nv-input">.
|
||
========================================================================== */
|
||
|
||
.nv-timepicker {
|
||
--_row: 2.25rem;
|
||
--_rows: 5;
|
||
|
||
display: inline-flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: var(--nv-space-2);
|
||
padding: var(--nv-space-2) var(--nv-space-3);
|
||
border-radius: var(--nv-radius-xl);
|
||
color: var(--nv-color-text-primary);
|
||
}
|
||
|
||
.nv-timepicker__wheels {
|
||
position: relative;
|
||
display: flex;
|
||
gap: var(--nv-space-1);
|
||
}
|
||
|
||
/* The lens: a bar of tinted glass lying across the centre row. It is raised
|
||
like the keys elsewhere in the system — lit on top, shaded below, with a
|
||
contact line — not a flat band of colour, so it reads as a thing the wheels
|
||
pass under. */
|
||
.nv-timepicker__wheels::before {
|
||
content: "";
|
||
position: absolute;
|
||
inset-inline: calc(var(--nv-space-2) * -1);
|
||
inset-block-start: 50%;
|
||
block-size: var(--_row);
|
||
translate: 0 -50%;
|
||
background-color: var(--nv-color-selection);
|
||
background-image: var(--nv-sheen-raised);
|
||
border-radius: var(--nv-radius-md);
|
||
box-shadow: var(--nv-bevel), var(--nv-outline), var(--nv-elevation-1);
|
||
pointer-events: none;
|
||
}
|
||
|
||
.nv-timepicker__column {
|
||
position: relative;
|
||
inline-size: 3.5rem;
|
||
block-size: calc(var(--_row) * var(--_rows));
|
||
margin: 0;
|
||
padding: calc(var(--_row) * (var(--_rows) - 1) / 2) 0;
|
||
list-style: none;
|
||
overflow-y: auto;
|
||
overscroll-behavior: contain;
|
||
scroll-snap-type: y mandatory;
|
||
scrollbar-width: none;
|
||
mask-image: linear-gradient(
|
||
transparent,
|
||
currentcolor 28%,
|
||
currentcolor 72%,
|
||
transparent
|
||
);
|
||
/* Depth for the drum effect below: rows turn away from the viewer as they
|
||
leave the centre. */
|
||
perspective: 14rem;
|
||
}
|
||
|
||
.nv-timepicker__column::-webkit-scrollbar {
|
||
display: none;
|
||
}
|
||
|
||
.nv-timepicker__column:focus-visible {
|
||
outline: none;
|
||
border-radius: var(--nv-radius-md);
|
||
box-shadow: var(--nv-focus-ring);
|
||
}
|
||
|
||
.nv-timepicker__option {
|
||
display: grid;
|
||
place-items: center;
|
||
block-size: var(--_row);
|
||
scroll-snap-align: center;
|
||
color: var(--nv-color-text-tertiary);
|
||
font: var(--nv-font-label);
|
||
font-size: var(--nv-font-size-lg);
|
||
font-variant-numeric: tabular-nums;
|
||
cursor: pointer;
|
||
/* --nv-pull is how far the wheel has been dragged past its end. Letting go
|
||
removes it, and the spring easing carries the rows back with a small
|
||
overshoot — the way a phone's list gives at its limit and settles. */
|
||
translate: 0 var(--nv-pull, 0);
|
||
transition:
|
||
var(--nv-transition-colors),
|
||
translate var(--nv-duration-slower) var(--nv-ease-spring);
|
||
}
|
||
|
||
.nv-timepicker__column[data-nv-pulling] .nv-timepicker__option {
|
||
transition: var(--nv-transition-colors);
|
||
}
|
||
|
||
/* A drum: each row shrinks, fades and tilts away in proportion to how far it
|
||
is from the lens. Driven by the scroll position itself, so it needs no
|
||
script and tracks a finger exactly. Where scroll-driven animation is missing
|
||
the rows are simply flat, which is still correct. */
|
||
@supports (animation-timeline: view()) {
|
||
@media (prefers-reduced-motion: no-preference) {
|
||
.nv-timepicker__option {
|
||
animation: nv-wheel-drum linear both;
|
||
animation-timeline: view(block);
|
||
}
|
||
}
|
||
}
|
||
|
||
@keyframes nv-wheel-drum {
|
||
0% { scale: 0.78; opacity: 0.25; rotate: x 52deg; }
|
||
50% { scale: 1; opacity: 1; rotate: x 0deg; }
|
||
100% { scale: 0.78; opacity: 0.25; rotate: x -52deg; }
|
||
}
|
||
|
||
.nv-timepicker__option[aria-selected="true"] {
|
||
color: var(--nv-color-text-primary);
|
||
font-weight: var(--nv-font-weight-semibold);
|
||
}
|
||
|
||
.nv-timepicker__separator {
|
||
display: grid;
|
||
place-items: center;
|
||
align-self: center;
|
||
color: var(--nv-color-text-tertiary);
|
||
font: var(--nv-font-label);
|
||
font-size: var(--nv-font-size-lg);
|
||
}
|