The pages and components now use the daisyUI wrappers and the neo-* utilities instead of Bulma and the neomorph classes: - buttons are DButton, with pressed states as Active; - nav links are btn / btn-active; - the filter and composer selects are DSelect with a prefix icon; - the composer's fields are DInput and DTextarea; - the message options and scope pickers are DDropdown, which now close on an outside click and on Escape; - the settings and admin sections are DCollapse; - the dark-mode switch is a DToggle with sun and moon icons; - the hue slider is a DRange on the rainbow track. DropdownButton, OpenDownContainer and LoadingData are gone. The loader is DLoading, whose animation runs; the old one's keyframes were never defined. Icons coloured with Bulma's has-text-* now get daisyUI's text-info, success, warning and error, which had no rules before. Tailwind 3 to 4 fixes: - space-x/y become gap, which also handles the flex-col-reverse feeds; - !rounded-lg becomes rounded-lg!; - drop-shadow becomes drop-shadow-sm; - text-md becomes text-base. The thread page marks its message with Content's new Highlighted parameter rather than a second shadow size. neo.css: a select's prefix icon gets the room daisyUI leaves for a span.label, and the inner select lets the well show through. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
327 lines
11 KiB
CSS
327 lines
11 KiB
CSS
/* Neumorphism on top of daisyUI.
|
|
|
|
Each element gets two shadows: a light one up-left and a dark one down-right, or both inset for a pressed look. The
|
|
colours are the theme's --neo-light / --neo-dark (theme.css); the offset and blur are --neo-o / --neo-b.
|
|
|
|
- neo-raised / neo-inset / neo-flat draw the shadows. neo-2xs, neo-xs (the default), neo-sm and neo-md set the
|
|
size, and Tailwind variants work on them (md:neo-sm).
|
|
- neo-gradient / neo-gradient-pressed are the 145deg gradients raised and pressed controls use.
|
|
- neo-hue-track turns a daisyUI range into the rainbow hue slider.
|
|
|
|
The daisyUI components are restyled with @utility <component>, as daisyUI recommends. Tailwind emits those rules
|
|
straight into the utilities layer, while daisyUI's own rules sit in sublayers of it (daisyui.l1.l2.l3 base,
|
|
l1.l2 modifiers, l1 states, daisyui disabled). So these overrides beat every daisyUI rule, variants included, and
|
|
each one excludes the variants it must leave alone. :where() keeps those exclusions at specificity (0,1,0).
|
|
To drop a neo shadow at a call site use shadow-none! or neo-flat!.
|
|
|
|
Never write @utility collapse: it brings back Tailwind's own collapse utility (visibility: collapse), which daisyUI
|
|
otherwise suppresses, and hides every collapse. DCollapse adds neo-raised in its markup instead. */
|
|
|
|
/* Not inherited, so a child doesn't pick up a parent's neo-sm size. */
|
|
@property --neo-o {
|
|
syntax: "<length>";
|
|
inherits: false;
|
|
initial-value: 3px;
|
|
}
|
|
|
|
@property --neo-b {
|
|
syntax: "<length>";
|
|
inherits: false;
|
|
initial-value: 6px;
|
|
}
|
|
|
|
@utility neo-raised {
|
|
box-shadow: calc(-1 * var(--neo-o)) calc(-1 * var(--neo-o)) var(--neo-b) var(--neo-light), var(--neo-o) var(--neo-o) var(--neo-b) var(--neo-dark);
|
|
}
|
|
|
|
@utility neo-inset {
|
|
box-shadow: inset var(--neo-o) var(--neo-o) var(--neo-b) var(--neo-dark), inset calc(-1 * var(--neo-o)) calc(-1 * var(--neo-o)) var(--neo-b) var(--neo-light);
|
|
}
|
|
|
|
@utility neo-flat {
|
|
box-shadow: none;
|
|
}
|
|
|
|
@utility neo-2xs {
|
|
--neo-o: 2px;
|
|
--neo-b: 4px;
|
|
}
|
|
|
|
@utility neo-xs {
|
|
--neo-o: 3px;
|
|
--neo-b: 6px;
|
|
}
|
|
|
|
@utility neo-sm {
|
|
--neo-o: 6px;
|
|
--neo-b: 12px;
|
|
}
|
|
|
|
@utility neo-md {
|
|
--neo-o: 8px;
|
|
--neo-b: 16px;
|
|
}
|
|
|
|
@utility neo-gradient {
|
|
background-image: linear-gradient(145deg, var(--neo-grad-light), var(--neo-grad-dark));
|
|
}
|
|
|
|
@utility neo-gradient-pressed {
|
|
background-image: linear-gradient(145deg, var(--neo-grad-dark), var(--neo-grad-light));
|
|
}
|
|
|
|
@utility neo-hue-track {
|
|
--range-fill: 0;
|
|
--range-bg: transparent;
|
|
|
|
&::-webkit-slider-runnable-track {
|
|
background-image: linear-gradient(to right, hsl(0 90% 80%), hsl(60 90% 80%), hsl(120 90% 80%), hsl(180 90% 80%), hsl(240 90% 80%), hsl(300 90% 80%), hsl(359 90% 80%));
|
|
box-shadow: inset 2px 2px 4px var(--neo-dark), inset -2px -2px 4px var(--neo-light);
|
|
}
|
|
|
|
&::-moz-range-track {
|
|
background-image: linear-gradient(to right, hsl(0 90% 80%), hsl(60 90% 80%), hsl(120 90% 80%), hsl(180 90% 80%), hsl(240 90% 80%), hsl(300 90% 80%), hsl(359 90% 80%));
|
|
box-shadow: inset 2px 2px 4px var(--neo-dark), inset -2px -2px 4px var(--neo-light);
|
|
}
|
|
}
|
|
|
|
/* Buttons: raised, with a gradient built from whatever colour the button has; pressed (active, checked) they sink in
|
|
with the gradient reversed. The original neoBtn* classes had no hover state, so daisyUI's hover tint is dropped. */
|
|
@utility btn {
|
|
&:not(:where(.btn-ghost, .btn-link, .btn-outline, .btn-dash, .btn-soft, .btn-disabled, :disabled, [disabled], [aria-disabled="true"])) {
|
|
--btn-bg: var(--btn-color, var(--color-base-100));
|
|
--btn-border: transparent;
|
|
background-image: linear-gradient(145deg, color-mix(in oklab, var(--btn-bg), white var(--neo-lift)), color-mix(in oklab, var(--btn-bg), black var(--neo-sink)));
|
|
box-shadow: -3px -3px 6px var(--neo-light), 3px 3px 6px var(--neo-dark);
|
|
|
|
&:where(:focus-visible) {
|
|
background-image: linear-gradient(-45deg, color-mix(in oklab, var(--btn-bg), white var(--neo-lift)), color-mix(in oklab, var(--btn-bg), black var(--neo-sink)));
|
|
}
|
|
|
|
&:where(.btn-sm, .btn-xs) {
|
|
box-shadow: -2px -2px 4px var(--neo-light), 2px 2px 4px var(--neo-dark);
|
|
}
|
|
|
|
&:where(:active, .btn-active, :checked, [aria-pressed="true"], [aria-current="page"]) {
|
|
background-image: linear-gradient(145deg, color-mix(in oklab, var(--btn-bg), black var(--neo-sink)), color-mix(in oklab, var(--btn-bg), white var(--neo-lift)));
|
|
box-shadow: inset 3px 3px 6px var(--neo-dark), inset -3px -3px 6px var(--neo-light);
|
|
translate: none;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Text fields: a pressed well, darker while focused, with no focus ring and no border unless a colour or a failed
|
|
validation asks for one. */
|
|
@utility input {
|
|
&:not(:where(.input-ghost)) {
|
|
background-color: var(--color-base-100);
|
|
background-image: linear-gradient(145deg, var(--neo-grad-dark), var(--neo-grad-light));
|
|
box-shadow: inset 2px 2px 4px var(--neo-dark), inset -2px -2px 4px var(--neo-light);
|
|
|
|
&:focus, &:focus-within {
|
|
outline: none;
|
|
background-image: linear-gradient(145deg, var(--neo-grad-darker), var(--neo-grad-lighter));
|
|
box-shadow: inset 2px 2px 4px var(--neo-dark), inset -2px -2px 4px var(--neo-light);
|
|
}
|
|
}
|
|
|
|
&:not(:where(.input-ghost, .input-neutral, .input-primary, .input-secondary, .input-accent, .input-info, .input-success, .input-warning, .input-error, .validator:user-invalid, [aria-invalid="true"])) {
|
|
border-color: transparent;
|
|
}
|
|
}
|
|
|
|
/* daisyUI draws the select's arrow with background-image, so the well here is the background colour alone. */
|
|
@utility select {
|
|
&:not(:where(.select-ghost)) {
|
|
background-color: var(--color-base-100);
|
|
box-shadow: inset 2px 2px 4px var(--neo-dark), inset -2px -2px 4px var(--neo-light);
|
|
|
|
&:focus, &:focus-within {
|
|
outline: none;
|
|
box-shadow: inset 3px 3px 6px var(--neo-dark), inset -3px -3px 6px var(--neo-light);
|
|
}
|
|
}
|
|
|
|
&:not(:where(.select-ghost, .select-neutral, .select-primary, .select-secondary, .select-accent, .select-info, .select-success, .select-warning, .select-error, .validator:user-invalid, [aria-invalid="true"])) {
|
|
border-color: transparent;
|
|
}
|
|
|
|
/* label.select: the inner select inherits the label's background and pulls itself .75rem to the left, which
|
|
daisyUI expects a span.label to absorb. Let the well show through and give any other prefix (an icon) the same
|
|
room. */
|
|
& > select {
|
|
background-color: transparent;
|
|
}
|
|
|
|
& > :first-child:not(select, .label) {
|
|
flex-shrink: 0;
|
|
margin-inline-end: .75rem;
|
|
}
|
|
}
|
|
|
|
@utility textarea {
|
|
&:not(:where(.textarea-ghost)) {
|
|
background-color: var(--color-base-100);
|
|
background-image: linear-gradient(145deg, var(--neo-grad-dark), var(--neo-grad-light));
|
|
box-shadow: inset 2px 2px 4px var(--neo-dark), inset -2px -2px 4px var(--neo-light);
|
|
|
|
&:focus, &:focus-within {
|
|
outline: none;
|
|
background-image: linear-gradient(145deg, var(--neo-grad-darker), var(--neo-grad-lighter));
|
|
box-shadow: inset 2px 2px 4px var(--neo-dark), inset -2px -2px 4px var(--neo-light);
|
|
}
|
|
}
|
|
|
|
&:not(:where(.textarea-ghost, .textarea-neutral, .textarea-primary, .textarea-secondary, .textarea-accent, .textarea-info, .textarea-success, .textarea-warning, .textarea-error, .validator:user-invalid, [aria-invalid="true"])) {
|
|
border-color: transparent;
|
|
}
|
|
}
|
|
|
|
@utility file-input {
|
|
&:not(:where(.file-input-ghost)) {
|
|
background-color: var(--color-base-100);
|
|
background-image: linear-gradient(145deg, var(--neo-grad-dark), var(--neo-grad-light));
|
|
box-shadow: inset 2px 2px 4px var(--neo-dark), inset -2px -2px 4px var(--neo-light);
|
|
}
|
|
|
|
&:not(:where(.file-input-ghost, .file-input-neutral, .file-input-primary, .file-input-secondary, .file-input-accent, .file-input-info, .file-input-success, .file-input-warning, .file-input-error, .validator:user-invalid, [aria-invalid="true"])) {
|
|
border-color: transparent;
|
|
}
|
|
}
|
|
|
|
/* Selectors: an inset well while unchecked; once checked they keep daisyUI's filled look. */
|
|
@utility checkbox {
|
|
&:not(:where(:checked, :indeterminate, [aria-checked="true"], :disabled)) {
|
|
border-color: transparent;
|
|
background-image: linear-gradient(145deg, var(--neo-grad-dark), var(--neo-grad-light));
|
|
box-shadow: inset 2px 2px 4px var(--neo-dark), inset -2px -2px 4px var(--neo-light);
|
|
}
|
|
}
|
|
|
|
@utility radio {
|
|
&:not(:where(:checked, [aria-checked="true"], :disabled)) {
|
|
border-color: transparent;
|
|
background-image: linear-gradient(145deg, var(--neo-grad-dark), var(--neo-grad-light));
|
|
box-shadow: inset 2px 2px 4px var(--neo-dark), inset -2px -2px 4px var(--neo-light);
|
|
}
|
|
}
|
|
|
|
@utility toggle {
|
|
&:not(:where(:disabled)) {
|
|
border-color: transparent;
|
|
background-image: linear-gradient(145deg, var(--neo-grad-dark), var(--neo-grad-light));
|
|
box-shadow: inset 2px 2px 4px var(--neo-dark), inset -2px -2px 4px var(--neo-light);
|
|
|
|
&::before {
|
|
box-shadow: -1px -1px 2px var(--neo-light), 1px 1px 2px var(--neo-dark);
|
|
}
|
|
}
|
|
}
|
|
|
|
@utility range {
|
|
&::-webkit-slider-runnable-track {
|
|
box-shadow: inset 2px 2px 4px var(--neo-dark), inset -2px -2px 4px var(--neo-light);
|
|
}
|
|
|
|
&::-moz-range-track {
|
|
box-shadow: inset 2px 2px 4px var(--neo-dark), inset -2px -2px 4px var(--neo-light);
|
|
}
|
|
}
|
|
|
|
/* Surfaces: raised like the old .neomorph panels. */
|
|
@utility card {
|
|
&:not(:where(.card-border, .card-dash)) {
|
|
box-shadow: -3px -3px 6px var(--neo-light), 3px 3px 6px var(--neo-dark);
|
|
}
|
|
}
|
|
|
|
@utility modal-box {
|
|
box-shadow: -6px -6px 12px var(--neo-light), 6px 6px 12px var(--neo-dark);
|
|
}
|
|
|
|
@utility dropdown-content {
|
|
background-color: var(--color-base-100);
|
|
box-shadow: -3px -3px 6px var(--neo-light), 3px 3px 6px var(--neo-dark);
|
|
}
|
|
|
|
@utility alert {
|
|
&:not(:where(.alert-outline, .alert-dash, .alert-soft)) {
|
|
box-shadow: -3px -3px 6px var(--neo-light), 3px 3px 6px var(--neo-dark);
|
|
}
|
|
}
|
|
|
|
@utility stats {
|
|
box-shadow: -3px -3px 6px var(--neo-light), 3px 3px 6px var(--neo-dark);
|
|
}
|
|
|
|
@utility timeline-box {
|
|
border-color: transparent;
|
|
box-shadow: -2px -2px 4px var(--neo-light), 2px 2px 4px var(--neo-dark);
|
|
}
|
|
|
|
@utility badge {
|
|
&:not(:where(.badge-ghost, .badge-outline, .badge-dash, .badge-soft)) {
|
|
box-shadow: -2px -2px 4px var(--neo-light), 2px 2px 4px var(--neo-dark);
|
|
}
|
|
}
|
|
|
|
@utility kbd {
|
|
box-shadow: -2px -2px 4px var(--neo-light), 2px 2px 4px var(--neo-dark);
|
|
}
|
|
|
|
@utility tabs-box {
|
|
background-color: var(--color-base-100);
|
|
box-shadow: inset 2px 2px 4px var(--neo-dark), inset -2px -2px 4px var(--neo-light);
|
|
}
|
|
|
|
@utility chat-bubble {
|
|
box-shadow: 2px 2px 4px var(--neo-dark);
|
|
}
|
|
|
|
@utility dock {
|
|
box-shadow: 0 -3px 6px var(--neo-light), 0 -1px 3px var(--neo-dark);
|
|
}
|
|
|
|
/* The active menu entry is pressed in rather than filled with the neutral colour. */
|
|
@utility menu {
|
|
--menu-active-bg: transparent;
|
|
--menu-active-fg: var(--color-base-content);
|
|
|
|
& :where(li > .menu-active, li > [aria-current="page"]) {
|
|
box-shadow: inset 2px 2px 4px var(--neo-dark), inset -2px -2px 4px var(--neo-light);
|
|
}
|
|
}
|
|
|
|
/* Wells: tracks, placeholders and the OTP digit boxes sink in. */
|
|
@utility otp {
|
|
& > span {
|
|
box-shadow: inset 2px 2px 4px var(--neo-dark), inset -2px -2px 4px var(--neo-light);
|
|
}
|
|
}
|
|
|
|
@utility progress {
|
|
box-shadow: inset 2px 2px 4px var(--neo-dark), inset -2px -2px 4px var(--neo-light);
|
|
}
|
|
|
|
@utility skeleton {
|
|
&:not(:where(.skeleton-text)) {
|
|
box-shadow: inset 2px 2px 4px var(--neo-dark), inset -2px -2px 4px var(--neo-light);
|
|
}
|
|
}
|
|
|
|
@utility mockup-browser {
|
|
box-shadow: -3px -3px 6px var(--neo-light), 3px 3px 6px var(--neo-dark);
|
|
}
|
|
|
|
@utility mockup-window {
|
|
box-shadow: -3px -3px 6px var(--neo-light), 3px 3px 6px var(--neo-dark);
|
|
}
|
|
|
|
@utility mockup-phone {
|
|
box-shadow: -3px -3px 6px var(--neo-light), 3px 3px 6px var(--neo-dark);
|
|
}
|
|
|
|
@utility mockup-code {
|
|
box-shadow: -3px -3px 6px var(--neo-light), 3px 3px 6px var(--neo-dark);
|
|
}
|