/* ── Neobrutalism UI kit tokens (uiCookies) — identical in every set ─────────────
Thick ink borders, hard offset shadows and flat saturated fills on cream paper.
Change a token and the whole kit follows; the border and shadow recipes below are built from them. */
:root{
--nb-paper:#fff6e0; /* page background, cream */
--nb-surface:#ffffff; /* card, field and button faces */
--nb-ink:#111111; /* text, 17.5:1 on paper */
--nb-muted:#454036; /* secondary text, 9.6:1 on paper */
--nb-line:#111111; /* border colour */
--nb-bw:3px; /* border width */
--nb-shadow-x:4px; /* hard shadow offset; pressed controls move this far into it */
--nb-shadow-y:4px;
--nb-shadow-color:#111111;
--nb-radius:12px; /* cards and panels */
--nb-radius-sm:8px; /* buttons and fields */
--nb-focus:#1d4ed8; /* dashed focus outline, 6.2:1 on paper, never the shadow */
--nb-on-fill:#111111; /* text on every palette fill below, 6.8:1 or more */
--nb-on-fill-muted:#2b2822;/* secondary text on fills, 5.2:1 or more */
--nb-lemon:#ffe14d;--nb-pink:#ff90d0;--nb-cyan:#62e2e8;--nb-lime:#b6f36a;
--nb-orange:#ffa94d;--nb-violet:#b9a3ff;--nb-red:#ff6b6b;
--nb-blue:#2448e0;--nb-on-blue:#ffffff; /* the only fill that takes white text, 6.8:1 */
--nb-danger:#b42318; /* destructive text, 6.1:1 on paper */
--nb-font:"Space Grotesk","Archivo","Helvetica Neue",Arial,system-ui,sans-serif;
--nb-font-display:"Archivo Black","Arial Black","Helvetica Neue",Arial,sans-serif;
}
/* Dark brutalism: data-nb="dark" on any wrapper, or a checked .nb-theme-input inside a [data-nb] element.
:is() is a forgiving list, so browsers without :has() still apply data-nb="dark". Fills keep dark text. */
[data-nb]:is([data-nb="dark"],:has(.nb-theme-input:checked)){
--nb-paper:#17150f;--nb-surface:#25221b;--nb-ink:#fbf3dc;--nb-muted:#d6ccb2;--nb-line:#fbf3dc;
--nb-shadow-color:#fbf3dc;--nb-focus:#8db4ff;--nb-danger:#ff9b8f;color-scheme:dark}
/* More contrast requested: secondary text turns full ink */
@media (prefers-contrast:more){:root,[data-nb]:is([data-nb],:has(.nb-theme-input:checked)){--nb-muted:var(--nb-ink);--nb-on-fill-muted:var(--nb-on-fill)}}
:root,[data-nb]{
--nb-border:var(--nb-bw) solid var(--nb-line);
--nb-shadow:var(--nb-shadow-x) var(--nb-shadow-y) 0 0 var(--nb-shadow-color);
--nb-shadow-sm:calc(var(--nb-shadow-x)*.5) calc(var(--nb-shadow-y)*.5) 0 0 var(--nb-shadow-color);
--nb-shadow-lg:calc(var(--nb-shadow-x)*1.75) calc(var(--nb-shadow-y)*1.75) 0 0 var(--nb-shadow-color);
--nb-shadow-hover:calc(var(--nb-shadow-x) + 2px) calc(var(--nb-shadow-y) + 2px) 0 0 var(--nb-shadow-color);
--nb-press:var(--nb-shadow-x) var(--nb-shadow-y); /* translate for a pressed control */
--nb-press-sm:calc(var(--nb-shadow-x)*.5) calc(var(--nb-shadow-y)*.5); /* …for one with the small shadow */
}
[data-nb]{background:var(--nb-paper);color:var(--nb-ink);font-family:var(--nb-font)}
/* Components */
:where([class*="nb-"]),:where([class*="nb-"])::before,:where([class*="nb-"])::after{box-sizing:border-box}
.nb-sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
.nb-ico{width:20px;height:20px;flex:none}
.nb-display{font-family:var(--nb-font-display);font-weight:900;letter-spacing:-.01em;line-height:1.05}
.nb-link{color:inherit;font-weight:700;text-decoration:underline;text-decoration-thickness:2px;text-underline-offset:3px}
.nb-link:hover{text-decoration-thickness:4px}
.nb-link:focus-visible{outline:3px dashed var(--nb-focus);outline-offset:3px;border-radius:2px}
/* Buttons: a face, a thick border and a hard shadow. Hover lifts it, pressing moves it into its shadow */
.nb-btn{--nb-fill:var(--nb-surface);--nb-text:var(--nb-ink);
display:inline-flex;align-items:center;justify-content:center;gap:.5rem;min-height:46px;padding:.6rem 1.2rem;
border:var(--nb-border);border-radius:var(--nb-radius-sm);background:var(--nb-fill);color:var(--nb-text);
font:700 1rem/1.2 var(--nb-font);text-decoration:none;cursor:pointer;box-shadow:var(--nb-shadow);
transition:translate .12s,box-shadow .12s}
.nb-btn:hover{translate:-2px -2px;box-shadow:var(--nb-shadow-hover)}
.nb-btn:active{translate:var(--nb-press);box-shadow:none}
.nb-btn:focus-visible{outline:3px dashed var(--nb-focus);outline-offset:5px}
.nb-btn--lemon{--nb-fill:var(--nb-lemon);--nb-text:var(--nb-on-fill)}
.nb-btn--pink{--nb-fill:var(--nb-pink);--nb-text:var(--nb-on-fill)}
.nb-btn--cyan{--nb-fill:var(--nb-cyan);--nb-text:var(--nb-on-fill)}
.nb-btn--lime{--nb-fill:var(--nb-lime);--nb-text:var(--nb-on-fill)}
.nb-btn--red{--nb-fill:var(--nb-red);--nb-text:var(--nb-on-fill)}
.nb-btn--blue{--nb-fill:var(--nb-blue);--nb-text:var(--nb-on-blue)}
.nb-btn--ink{--nb-fill:var(--nb-ink);--nb-text:var(--nb-paper)}
.nb-btn--sm{min-height:38px;padding:.4rem .85rem;font-size:.875rem}
.nb-btn--lg{min-height:56px;padding:.8rem 1.6rem;font-size:1.15rem}
.nb-btn--block{width:100%}
.nb-btn--icon{width:48px;height:48px;min-height:0;padding:0}
.nb-btn--icon-sm{width:38px;height:38px;box-shadow:var(--nb-shadow-sm)}
.nb-btn--icon-sm:hover{box-shadow:var(--nb-shadow)}
.nb-btn--icon-sm:active{translate:var(--nb-press-sm)}
.nb-btn--round{border-radius:50%}
.nb-btn:disabled{--nb-fill:var(--nb-paper);--nb-text:var(--nb-muted);border-style:dashed;box-shadow:none;translate:none;cursor:not-allowed}
/* Card: a white panel, the thick border and the large hard shadow. Fill variants keep dark text */
.nb-card{position:relative;padding:1.4rem;border:var(--nb-border);border-radius:var(--nb-radius);background:var(--nb-surface);color:var(--nb-ink);box-shadow:var(--nb-shadow-lg);font-family:var(--nb-font)}
.nb-card h2,.nb-card h3,.nb-card p{margin:0}
.nb-card--lemon,.nb-card--pink,.nb-card--cyan,.nb-card--lime,.nb-card--orange,.nb-card--violet{--nb-muted:var(--nb-on-fill-muted);color:var(--nb-on-fill)}
.nb-card--lemon{background:var(--nb-lemon)}.nb-card--pink{background:var(--nb-pink)}.nb-card--cyan{background:var(--nb-cyan)}
.nb-card--lime{background:var(--nb-lime)}.nb-card--orange{background:var(--nb-orange)}.nb-card--violet{background:var(--nb-violet)}
/* Stickers: tilted labels that sit on or over a card */
.nb-sticker{display:inline-flex;align-items:center;gap:.35rem;width:max-content;padding:.3rem .7rem;border:var(--nb-border);border-radius:999px;
background:var(--nb-lemon);color:var(--nb-on-fill);font:800 .75rem/1.1 var(--nb-font);letter-spacing:.06em;text-transform:uppercase;box-shadow:var(--nb-shadow-sm);rotate:-4deg}
.nb-sticker .nb-ico{width:14px;height:14px}
.nb-sticker--pink{background:var(--nb-pink)}.nb-sticker--cyan{background:var(--nb-cyan)}.nb-sticker--lime{background:var(--nb-lime)}
.nb-sticker--orange{background:var(--nb-orange)}.nb-sticker--violet{background:var(--nb-violet)}
.nb-sticker--right{rotate:5deg}
.nb-sticker--corner{position:absolute;top:-16px;right:14px;z-index:2}
.nb-sticker--square{border-radius:4px}
/* Layout helpers */
.nb-cluster{display:flex;flex-wrap:wrap;align-items:center;gap:18px}
.nb-tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:20px}
.nb-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,260px),1fr));gap:28px;align-items:start}
.nb-stack{display:grid;gap:1.25rem}
/* Fields: a white well with the thick border and a small shadow; focus adds a dashed outline */
.nb-field{display:grid;gap:.4rem;min-width:0}
.nb-label{font-weight:800;font-size:.95rem}
.nb-input,.nb-select,.nb-textarea{width:100%;min-height:48px;padding:.65rem .9rem;border:var(--nb-border);border-radius:var(--nb-radius-sm);background:var(--nb-surface);color:var(--nb-ink);
font:500 1rem/1.4 var(--nb-font);box-shadow:var(--nb-shadow-sm);transition:box-shadow .12s}
.nb-textarea{min-height:110px;resize:vertical}
.nb-input::placeholder,.nb-textarea::placeholder{color:var(--nb-muted);opacity:1}
.nb-input:focus,.nb-select:focus,.nb-textarea:focus{outline:3px dashed var(--nb-focus);outline-offset:4px;box-shadow:var(--nb-shadow)}
.nb-hint{font-size:.85rem;color:var(--nb-muted)}
.nb-row{display:flex;justify-content:space-between;align-items:baseline;gap:1rem}
.nb-control{position:relative}
.nb-control>.nb-ico{position:absolute;top:50%;left:14px;translate:0 -50%;pointer-events:none}
.nb-control>.nb-ico~.nb-input{padding-left:2.75rem}
.nb-select{appearance:none;padding-right:2.75rem;cursor:pointer}
.nb-control--select::after{content:"";position:absolute;top:50%;right:18px;width:10px;height:10px;border:solid var(--nb-ink);border-width:0 3px 3px 0;translate:0 -70%;rotate:45deg;pointer-events:none}
.nb-input[aria-invalid="true"]{box-shadow:var(--nb-shadow-x) var(--nb-shadow-y) 0 0 var(--nb-red)}
.nb-error{display:flex;align-items:center;gap:.45rem;width:fit-content;max-width:100%;padding:.3rem .65rem;border:2px solid var(--nb-line);border-radius:6px;background:var(--nb-red);color:var(--nb-on-fill);font-size:.85rem;font-weight:700}
.nb-error .nb-ico{width:16px;height:16px}
/* Checkboxes and radios: a hidden native input and a box that is pushed into its shadow and filled when chosen */
.nb-fieldset{min-width:0;margin:0;padding:0;border:0}
.nb-fieldset legend{margin-bottom:.6rem;padding:0;font-weight:800}
.nb-choices{display:grid;gap:.8rem}
.nb-check{position:relative;display:flex;align-items:center;gap:.75rem;font-weight:600;cursor:pointer}
.nb-check__box{display:grid;place-items:center;flex:none;width:28px;height:28px;border:var(--nb-border);border-radius:6px;background:var(--nb-surface);color:transparent;
box-shadow:var(--nb-shadow-sm);transition:translate .12s,box-shadow .12s}
.nb-check__box .nb-ico{width:18px;height:18px}
.nb-check--radio .nb-check__box{border-radius:50%}
.nb-check--radio .nb-check__box::after{content:"";width:12px;height:12px;border-radius:50%;background:transparent}
.nb-check input:checked+.nb-check__box{translate:var(--nb-press-sm);box-shadow:none;background:var(--nb-lemon);color:var(--nb-on-fill)}
.nb-check--radio input:checked+.nb-check__box::after{background:var(--nb-on-fill)}
.nb-check input:focus-visible+.nb-check__box{outline:3px dashed var(--nb-focus);outline-offset:4px}
/* Range: a native input with a striped fill and a square thumb (a few lines of script paint the WebKit fill) */
.nb-range{--val:50%;width:100%;height:30px;margin:0;background:transparent;appearance:none;-webkit-appearance:none;cursor:pointer}
.nb-range::-webkit-slider-runnable-track{height:12px;border:var(--nb-border);border-radius:999px;
background:linear-gradient(var(--nb-pink),var(--nb-pink)) 0 0/var(--val) 100% no-repeat,var(--nb-surface)}
.nb-range::-webkit-slider-thumb{width:26px;height:26px;margin-top:-7px;border:var(--nb-border);border-radius:6px;background:var(--nb-lemon);box-shadow:var(--nb-shadow-sm);-webkit-appearance:none;appearance:none}
.nb-range::-moz-range-track{height:12px;border:var(--nb-border);border-radius:999px;background:var(--nb-surface)}
.nb-range::-moz-range-progress{height:12px;border:var(--nb-border);border-radius:999px;background:var(--nb-pink)}
.nb-range::-moz-range-thumb{width:20px;height:20px;border:var(--nb-border);border-radius:6px;background:var(--nb-lemon);box-shadow:var(--nb-shadow-sm)}
.nb-range:focus-visible{outline:3px dashed var(--nb-focus);outline-offset:4px;border-radius:6px}
.nb-out{padding:.1rem .5rem;border:2px solid var(--nb-line);border-radius:6px;background:var(--nb-lemon);color:var(--nb-on-fill);font-weight:800;font-variant-numeric:tabular-nums}
/* Login card */
.nb-login{max-width:420px;padding:2rem 1.75rem 1.75rem}
.nb-login__title{margin:0 0 .3rem;font-size:1.9rem}
.nb-login__lead{margin:0 0 1.4rem!important;color:var(--nb-muted)}
.nb-login form{display:grid;gap:1.1rem}
.nb-or{display:flex;align-items:center;gap:.75rem;font-size:.8rem;font-weight:800;letter-spacing:.1em;text-transform:uppercase;color:var(--nb-muted)}
.nb-or::before,.nb-or::after{content:"";flex:1;border-top:var(--nb-border)}
.nb-login__foot{margin-top:1.2rem!important;font-size:.92rem;text-align:center}
@media (prefers-reduced-motion:reduce){[class*="nb-"],[class*="nb-"]::before,[class*="nb-"]::after{transition:none!important;animation:none!important}}
JavaScript
// Range: paint the WebKit fill and keep the visible value and aria-valuetext in step
document.querySelectorAll('[data-nb-range]').forEach(function (r) {
var out = document.querySelector('output[for="' + r.id + '"]');
function paint() {
r.style.setProperty('--val', (r.value - r.min) / (r.max - r.min) * 100 + '%');
var text = (r.dataset.prefix || '') + r.value + (r.dataset.suffix || '');
r.setAttribute('aria-valuetext', text);
if (out) out.textContent = text;
}
r.addEventListener('input', paint); paint();
});
/* ── Neobrutalism UI kit tokens (uiCookies) — identical in every set ─────────────
Thick ink borders, hard offset shadows and flat saturated fills on cream paper.
Change a token and the whole kit follows; the border and shadow recipes below are built from them. */
:root{
--nb-paper:#fff6e0; /* page background, cream */
--nb-surface:#ffffff; /* card, field and button faces */
--nb-ink:#111111; /* text, 17.5:1 on paper */
--nb-muted:#454036; /* secondary text, 9.6:1 on paper */
--nb-line:#111111; /* border colour */
--nb-bw:3px; /* border width */
--nb-shadow-x:4px; /* hard shadow offset; pressed controls move this far into it */
--nb-shadow-y:4px;
--nb-shadow-color:#111111;
--nb-radius:12px; /* cards and panels */
--nb-radius-sm:8px; /* buttons and fields */
--nb-focus:#1d4ed8; /* dashed focus outline, 6.2:1 on paper, never the shadow */
--nb-on-fill:#111111; /* text on every palette fill below, 6.8:1 or more */
--nb-on-fill-muted:#2b2822;/* secondary text on fills, 5.2:1 or more */
--nb-lemon:#ffe14d;--nb-pink:#ff90d0;--nb-cyan:#62e2e8;--nb-lime:#b6f36a;
--nb-orange:#ffa94d;--nb-violet:#b9a3ff;--nb-red:#ff6b6b;
--nb-blue:#2448e0;--nb-on-blue:#ffffff; /* the only fill that takes white text, 6.8:1 */
--nb-danger:#b42318; /* destructive text, 6.1:1 on paper */
--nb-font:"Space Grotesk","Archivo","Helvetica Neue",Arial,system-ui,sans-serif;
--nb-font-display:"Archivo Black","Arial Black","Helvetica Neue",Arial,sans-serif;
}
/* Dark brutalism: data-nb="dark" on any wrapper, or a checked .nb-theme-input inside a [data-nb] element.
:is() is a forgiving list, so browsers without :has() still apply data-nb="dark". Fills keep dark text. */
[data-nb]:is([data-nb="dark"],:has(.nb-theme-input:checked)){
--nb-paper:#17150f;--nb-surface:#25221b;--nb-ink:#fbf3dc;--nb-muted:#d6ccb2;--nb-line:#fbf3dc;
--nb-shadow-color:#fbf3dc;--nb-focus:#8db4ff;--nb-danger:#ff9b8f;color-scheme:dark}
/* More contrast requested: secondary text turns full ink */
@media (prefers-contrast:more){:root,[data-nb]:is([data-nb],:has(.nb-theme-input:checked)){--nb-muted:var(--nb-ink);--nb-on-fill-muted:var(--nb-on-fill)}}
:root,[data-nb]{
--nb-border:var(--nb-bw) solid var(--nb-line);
--nb-shadow:var(--nb-shadow-x) var(--nb-shadow-y) 0 0 var(--nb-shadow-color);
--nb-shadow-sm:calc(var(--nb-shadow-x)*.5) calc(var(--nb-shadow-y)*.5) 0 0 var(--nb-shadow-color);
--nb-shadow-lg:calc(var(--nb-shadow-x)*1.75) calc(var(--nb-shadow-y)*1.75) 0 0 var(--nb-shadow-color);
--nb-shadow-hover:calc(var(--nb-shadow-x) + 2px) calc(var(--nb-shadow-y) + 2px) 0 0 var(--nb-shadow-color);
--nb-press:var(--nb-shadow-x) var(--nb-shadow-y); /* translate for a pressed control */
--nb-press-sm:calc(var(--nb-shadow-x)*.5) calc(var(--nb-shadow-y)*.5); /* …for one with the small shadow */
}
[data-nb]{background:var(--nb-paper);color:var(--nb-ink);font-family:var(--nb-font)}
/* Bootstrap's own variables mapped onto the kit tokens */
:root,[data-nb]{--bs-body-bg:var(--nb-paper);--bs-body-color:var(--nb-ink);--bs-body-font-family:var(--nb-font);--bs-emphasis-color:var(--nb-ink);
--bs-heading-color:var(--nb-ink);--bs-secondary-color:var(--nb-muted);--bs-tertiary-color:var(--nb-muted);
--bs-border-color:var(--nb-line);--bs-border-width:var(--nb-bw);--bs-border-radius:var(--nb-radius-sm);--bs-border-radius-lg:var(--nb-radius);
--bs-focus-ring-color:var(--nb-focus)}
/* Components */
.nb-ico{width:20px;height:20px;flex:none}
.nb-display{font-family:var(--nb-font-display);font-weight:900;letter-spacing:-.01em;line-height:1.05}
.nb-link{color:inherit;font-weight:700;text-decoration:underline;text-decoration-thickness:2px;text-underline-offset:3px}
.nb-link:hover{color:inherit;text-decoration-thickness:4px}
.nb-link:focus-visible{outline:3px dashed var(--nb-focus);outline-offset:3px;border-radius:2px}
/* Buttons: Bootstrap .btn with a .btn-nb variant set through --bs-btn-* variables.
Hover lifts it, pressing (or a checked .btn-check) moves it into its shadow */
.btn-nb{--nb-fill:var(--nb-surface);--nb-text:var(--nb-ink);--nb-btn-shadow:var(--nb-shadow);
--bs-btn-padding-x:1.2rem;--bs-btn-padding-y:.6rem;--bs-btn-font-family:var(--nb-font);--bs-btn-font-size:1rem;--bs-btn-font-weight:700;--bs-btn-line-height:1.2;
--bs-btn-border-width:var(--nb-bw);--bs-btn-border-radius:var(--nb-radius-sm);
--bs-btn-bg:var(--nb-fill);--bs-btn-color:var(--nb-text);--bs-btn-border-color:var(--nb-line);
--bs-btn-hover-bg:var(--nb-fill);--bs-btn-hover-color:var(--nb-text);--bs-btn-hover-border-color:var(--nb-line);
--bs-btn-active-bg:var(--nb-fill);--bs-btn-active-color:var(--nb-text);--bs-btn-active-border-color:var(--nb-line);
--bs-btn-disabled-bg:var(--nb-paper);--bs-btn-disabled-color:var(--nb-muted);--bs-btn-disabled-border-color:var(--nb-line);--bs-btn-disabled-opacity:1;
--bs-btn-focus-box-shadow:var(--nb-btn-shadow);
display:inline-flex;align-items:center;justify-content:center;gap:.5rem;min-height:46px;box-shadow:var(--nb-btn-shadow);transition:translate .12s,box-shadow .12s}
.btn-nb:hover{translate:-2px -2px;--nb-btn-shadow:var(--nb-shadow-hover)}
.btn-nb:active{translate:var(--nb-press);--nb-btn-shadow:none}
.btn-nb:focus-visible,.btn-check:focus-visible+.btn-nb{outline:3px dashed var(--nb-focus);outline-offset:5px;box-shadow:var(--nb-btn-shadow)}
.btn-nb-lemon{--nb-fill:var(--nb-lemon);--nb-text:var(--nb-on-fill)}
.btn-nb-pink{--nb-fill:var(--nb-pink);--nb-text:var(--nb-on-fill)}
.btn-nb-cyan{--nb-fill:var(--nb-cyan);--nb-text:var(--nb-on-fill)}
.btn-nb-lime{--nb-fill:var(--nb-lime);--nb-text:var(--nb-on-fill)}
.btn-nb-red{--nb-fill:var(--nb-red);--nb-text:var(--nb-on-fill)}
.btn-nb-blue{--nb-fill:var(--nb-blue);--nb-text:var(--nb-on-blue)}
.btn-nb-ink{--nb-fill:var(--nb-ink);--nb-text:var(--nb-paper)}
.btn-nb.btn-sm{--bs-btn-padding-x:.85rem;--bs-btn-padding-y:.4rem;--bs-btn-font-size:.875rem;--bs-btn-border-radius:var(--nb-radius-sm);min-height:38px}
.btn-nb.btn-lg{--bs-btn-padding-x:1.6rem;--bs-btn-padding-y:.8rem;--bs-btn-font-size:1.15rem;--bs-btn-border-radius:var(--nb-radius-sm);min-height:56px}
.btn-nb-icon{--bs-btn-padding-x:0;--bs-btn-padding-y:0;width:48px;height:48px;min-height:0}
.btn-nb-icon-sm{width:38px;height:38px;--nb-btn-shadow:var(--nb-shadow-sm)}
.btn-nb-icon-sm:hover{--nb-btn-shadow:var(--nb-shadow)}
.btn-nb-icon-sm:active{translate:var(--nb-press-sm)}
.btn-nb.rounded-circle{border-radius:50%}
.btn-nb:disabled{border-style:dashed;--nb-btn-shadow:none;translate:none}
/* Card: Bootstrap .card restyled through --bs-card-*; fill variants keep dark text */
.card.nb-card{--bs-card-bg:var(--nb-surface);--bs-card-color:var(--nb-ink);--bs-card-border-color:var(--nb-line);--bs-card-border-width:var(--nb-bw);
--bs-card-border-radius:var(--nb-radius);--bs-card-inner-border-radius:calc(var(--nb-radius) - var(--nb-bw));--bs-card-spacer-x:1.4rem;--bs-card-spacer-y:1.4rem;
--bs-card-cap-bg:transparent;box-shadow:var(--nb-shadow-lg);font-family:var(--nb-font)}
.nb-card h2,.nb-card h3,.nb-card p{margin:0}
.card.nb-card-lemon,.card.nb-card-pink,.card.nb-card-cyan,.card.nb-card-lime,.card.nb-card-orange,.card.nb-card-violet{--nb-muted:var(--nb-on-fill-muted);--bs-card-color:var(--nb-on-fill);--bs-heading-color:var(--nb-on-fill)}
.card.nb-card-lemon{--bs-card-bg:var(--nb-lemon)}.card.nb-card-pink{--bs-card-bg:var(--nb-pink)}.card.nb-card-cyan{--bs-card-bg:var(--nb-cyan)}
.card.nb-card-lime{--bs-card-bg:var(--nb-lime)}.card.nb-card-orange{--bs-card-bg:var(--nb-orange)}.card.nb-card-violet{--bs-card-bg:var(--nb-violet)}
/* Stickers: tilted .badge labels that sit on or over a card */
.badge.nb-sticker{--bs-badge-padding-x:.7rem;--bs-badge-padding-y:.3rem;--bs-badge-font-size:.75rem;--bs-badge-font-weight:800;--bs-badge-color:var(--nb-on-fill);--bs-badge-border-radius:999px;
display:inline-flex;align-items:center;gap:.35rem;width:max-content;border:var(--nb-border);background:var(--nb-lemon);font-family:var(--nb-font);line-height:1.1;letter-spacing:.06em;text-transform:uppercase;box-shadow:var(--nb-shadow-sm);rotate:-4deg}
.nb-sticker .nb-ico{width:14px;height:14px}
.badge.nb-sticker-pink{background:var(--nb-pink)}.badge.nb-sticker-cyan{background:var(--nb-cyan)}.badge.nb-sticker-lime{background:var(--nb-lime)}
.badge.nb-sticker-orange{background:var(--nb-orange)}.badge.nb-sticker-violet{background:var(--nb-violet)}
.nb-sticker-right{rotate:5deg}
.nb-sticker-corner{position:absolute;top:-16px;right:14px;z-index:2}
.badge.nb-sticker-square{--bs-badge-border-radius:4px}
.nb-cluster{display:flex;flex-wrap:wrap;align-items:center;gap:18px}
.nb-tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:20px}
.nb-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,260px),1fr));gap:28px;align-items:start}
.nb-stack{display:grid;gap:1.25rem}
/* Fields: Bootstrap .form-control and .form-select with the thick border and a small shadow; focus adds a dashed outline */
.nb-form .form-label{margin-bottom:.4rem;font-weight:800;font-size:.95rem}
.nb-form .form-text{margin-top:.4rem;font-size:.85rem;color:var(--nb-muted)}
.nb-form .form-control,.nb-form .form-select{min-height:48px;padding:.65rem .9rem;border:var(--nb-border);border-radius:var(--nb-radius-sm);background-color:var(--nb-surface);color:var(--nb-ink);
font:500 1rem/1.4 var(--nb-font);box-shadow:var(--nb-shadow-sm);transition:box-shadow .12s}
.nb-form textarea.form-control{min-height:110px}
.nb-form .form-control::placeholder{color:var(--nb-muted);opacity:1}
.nb-form .form-control:focus,.nb-form .form-select:focus{border-color:var(--nb-line);outline:3px dashed var(--nb-focus);outline-offset:4px;box-shadow:var(--nb-shadow)}
.nb-form .form-select{--bs-form-select-bg-img:none;padding-right:2.75rem;cursor:pointer}
.nb-control{position:relative}
.nb-control>.nb-ico{position:absolute;top:50%;left:14px;z-index:1;translate:0 -50%;pointer-events:none}
.nb-form .nb-control>.nb-ico~.form-control{padding-left:2.75rem}
.nb-control-select::after{content:"";position:absolute;top:50%;right:18px;width:10px;height:10px;border:solid var(--nb-ink);border-width:0 3px 3px 0;translate:0 -70%;rotate:45deg;pointer-events:none}
.nb-row{display:flex;justify-content:space-between;align-items:baseline;gap:1rem}
.nb-row .form-label{margin:0}
.nb-form .form-control.is-invalid{border-color:var(--nb-line);background-image:none;padding-right:.9rem;box-shadow:var(--nb-shadow-x) var(--nb-shadow-y) 0 0 var(--nb-red)}
.nb-form .form-control.is-invalid:focus{box-shadow:var(--nb-shadow-x) var(--nb-shadow-y) 0 0 var(--nb-red)}
.nb-form .invalid-feedback{display:flex;align-items:center;gap:.45rem;width:fit-content;max-width:100%;margin-top:.5rem;padding:.3rem .65rem;border:2px solid var(--nb-line);border-radius:6px;background:var(--nb-red);color:var(--nb-on-fill);font-size:.85rem;font-weight:700}
.nb-form .invalid-feedback .nb-ico{width:16px;height:16px}
/* Checkboxes and radios: Bootstrap .form-check-input, pushed into its shadow and filled lemon when chosen */
.nb-form fieldset legend{float:none;width:auto;margin-bottom:.6rem;padding:0;font-size:1rem;font-weight:800}
.nb-choices{display:grid;gap:.8rem}
.nb-check{display:flex;align-items:center;gap:.75rem;min-height:0;margin:0;padding:0}
.nb-check .form-check-label{font-weight:600;cursor:pointer}
.nb-check .form-check-input{--bs-form-check-bg:var(--nb-surface);flex:none;float:none;width:28px;height:28px;margin:0;border:var(--nb-border);border-radius:6px;cursor:pointer;
background-size:18px 18px;box-shadow:var(--nb-shadow-sm);transition:translate .12s,box-shadow .12s}
.nb-check .form-check-input[type=radio]{border-radius:50%}
.nb-check .form-check-input:checked{translate:var(--nb-press-sm);border-color:var(--nb-line);background-color:var(--nb-lemon);box-shadow:none}
.nb-check .form-check-input:checked[type=checkbox]{--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3e%3cpath fill='none' stroke='%23111' stroke-width='2.8' stroke-linecap='round' stroke-linejoin='round' d='m4.5 12.5 5 5L19.5 7'/%3e%3c/svg%3e")}
.nb-check .form-check-input:checked[type=radio]{--bs-form-check-bg-image:radial-gradient(circle,var(--nb-on-fill) 0 6px,transparent 6.5px);background-size:auto}
.nb-check .form-check-input:focus{border-color:var(--nb-line);box-shadow:var(--nb-shadow-sm)}
.nb-check .form-check-input:checked:focus{box-shadow:none}
.nb-check .form-check-input:focus-visible{outline:3px dashed var(--nb-focus);outline-offset:4px}
/* Range: Bootstrap .form-range.nb-range with a pink fill and a square thumb (a few lines of script paint the WebKit fill) */
.form-range.nb-range{--val:50%;height:30px}
.form-range.nb-range::-webkit-slider-runnable-track{height:12px;border:var(--nb-border);border-radius:999px;background:linear-gradient(var(--nb-pink),var(--nb-pink)) 0 0/var(--val) 100% no-repeat,var(--nb-surface)}
.form-range.nb-range::-webkit-slider-thumb{width:26px;height:26px;margin-top:-7px;border:var(--nb-border);border-radius:6px;background:var(--nb-lemon);box-shadow:var(--nb-shadow-sm)}
.form-range.nb-range::-moz-range-track{height:12px;border:var(--nb-border);border-radius:999px;background:var(--nb-surface)}
.form-range.nb-range::-moz-range-progress{height:12px;border:var(--nb-border);border-radius:999px;background:var(--nb-pink)}
.form-range.nb-range::-moz-range-thumb{width:20px;height:20px;border:var(--nb-border);border-radius:6px;background:var(--nb-lemon);box-shadow:var(--nb-shadow-sm)}
.form-range.nb-range:focus::-webkit-slider-thumb{box-shadow:var(--nb-shadow-sm)}
.form-range.nb-range:focus::-moz-range-thumb{box-shadow:var(--nb-shadow-sm)}
.form-range.nb-range:focus-visible{outline:3px dashed var(--nb-focus);outline-offset:4px;border-radius:6px}
.nb-out{padding:.1rem .5rem;border:2px solid var(--nb-line);border-radius:6px;background:var(--nb-lemon);color:var(--nb-on-fill);font-weight:800;font-variant-numeric:tabular-nums}
/* Login card */
.nb-login{max-width:420px;--bs-card-spacer-x:1.75rem;--bs-card-spacer-y:1.75rem}
.nb-login .card-body{padding-top:2rem}
.nb-login__title{margin:0 0 .3rem;font-size:1.9rem}
.nb-login__lead{margin:0 0 1.4rem!important;color:var(--nb-muted)}
.nb-login form{display:grid;gap:1.1rem}
.nb-or{display:flex;align-items:center;gap:.75rem;font-size:.8rem;font-weight:800;letter-spacing:.1em;text-transform:uppercase;color:var(--nb-muted)}
.nb-or::before,.nb-or::after{content:"";flex:1;border-top:var(--nb-border)}
.nb-login__foot{margin-top:1.2rem!important;font-size:.92rem;text-align:center}
@media (prefers-reduced-motion:reduce){[class*="nb-"],[class*="nb-"]::before,[class*="nb-"]::after{transition:none!important;animation:none!important}}
JavaScript
// Range: paint the WebKit fill and keep the visible value and aria-valuetext in step
document.querySelectorAll('[data-nb-range]').forEach(function (r) {
var out = document.querySelector('output[for="' + r.id + '"]');
function paint() {
r.style.setProperty('--val', (r.value - r.min) / (r.max - r.min) * 100 + '%');
var text = (r.dataset.prefix || '') + r.value + (r.dataset.suffix || '');
r.setAttribute('aria-valuetext', text);
if (out) out.textContent = text;
}
r.addEventListener('input', paint); paint();
});
/* ── Neobrutalism UI kit tokens (uiCookies) — identical in every set ─────────────
Thick ink borders, hard offset shadows and flat saturated fills on cream paper.
Change a token and the whole kit follows; the border and shadow recipes below are built from them. */
:root{
--nb-paper:#fff6e0; /* page background, cream */
--nb-surface:#ffffff; /* card, field and button faces */
--nb-ink:#111111; /* text, 17.5:1 on paper */
--nb-muted:#454036; /* secondary text, 9.6:1 on paper */
--nb-line:#111111; /* border colour */
--nb-bw:3px; /* border width */
--nb-shadow-x:4px; /* hard shadow offset; pressed controls move this far into it */
--nb-shadow-y:4px;
--nb-shadow-color:#111111;
--nb-radius:12px; /* cards and panels */
--nb-radius-sm:8px; /* buttons and fields */
--nb-focus:#1d4ed8; /* dashed focus outline, 6.2:1 on paper, never the shadow */
--nb-on-fill:#111111; /* text on every palette fill below, 6.8:1 or more */
--nb-on-fill-muted:#2b2822;/* secondary text on fills, 5.2:1 or more */
--nb-lemon:#ffe14d;--nb-pink:#ff90d0;--nb-cyan:#62e2e8;--nb-lime:#b6f36a;
--nb-orange:#ffa94d;--nb-violet:#b9a3ff;--nb-red:#ff6b6b;
--nb-blue:#2448e0;--nb-on-blue:#ffffff; /* the only fill that takes white text, 6.8:1 */
--nb-danger:#b42318; /* destructive text, 6.1:1 on paper */
--nb-font:"Space Grotesk","Archivo","Helvetica Neue",Arial,system-ui,sans-serif;
--nb-font-display:"Archivo Black","Arial Black","Helvetica Neue",Arial,sans-serif;
}
/* Dark brutalism: data-nb="dark" on any wrapper, or a checked .nb-theme-input inside a [data-nb] element.
:is() is a forgiving list, so browsers without :has() still apply data-nb="dark". Fills keep dark text. */
[data-nb]:is([data-nb="dark"],:has(.nb-theme-input:checked)){
--nb-paper:#17150f;--nb-surface:#25221b;--nb-ink:#fbf3dc;--nb-muted:#d6ccb2;--nb-line:#fbf3dc;
--nb-shadow-color:#fbf3dc;--nb-focus:#8db4ff;--nb-danger:#ff9b8f;color-scheme:dark}
/* More contrast requested: secondary text turns full ink */
@media (prefers-contrast:more){:root,[data-nb]:is([data-nb],:has(.nb-theme-input:checked)){--nb-muted:var(--nb-ink);--nb-on-fill-muted:var(--nb-on-fill)}}
:root,[data-nb]{
--nb-border:var(--nb-bw) solid var(--nb-line);
--nb-shadow:var(--nb-shadow-x) var(--nb-shadow-y) 0 0 var(--nb-shadow-color);
--nb-shadow-sm:calc(var(--nb-shadow-x)*.5) calc(var(--nb-shadow-y)*.5) 0 0 var(--nb-shadow-color);
--nb-shadow-lg:calc(var(--nb-shadow-x)*1.75) calc(var(--nb-shadow-y)*1.75) 0 0 var(--nb-shadow-color);
--nb-shadow-hover:calc(var(--nb-shadow-x) + 2px) calc(var(--nb-shadow-y) + 2px) 0 0 var(--nb-shadow-color);
--nb-press:var(--nb-shadow-x) var(--nb-shadow-y); /* translate for a pressed control */
--nb-press-sm:calc(var(--nb-shadow-x)*.5) calc(var(--nb-shadow-y)*.5); /* …for one with the small shadow */
}
[data-nb]{background:var(--nb-paper);color:var(--nb-ink);font-family:var(--nb-font)}
/* The tokens above, exposed as Tailwind utilities: bg-nb-paper, bg-nb-lemon, text-nb-ink, border-nb-line,
shadow-nb, shadow-nb-sm, shadow-nb-lg, rounded-nb, rounded-nb-sm, font-nb, font-nb-display, outline-nb-focus… */
@theme inline {
--color-nb-paper: var(--nb-paper); --color-nb-surface: var(--nb-surface); --color-nb-ink: var(--nb-ink);
--color-nb-muted: var(--nb-muted); --color-nb-line: var(--nb-line); --color-nb-focus: var(--nb-focus);
--color-nb-on-fill: var(--nb-on-fill); --color-nb-on-fill-muted: var(--nb-on-fill-muted);
--color-nb-lemon: var(--nb-lemon); --color-nb-pink: var(--nb-pink); --color-nb-cyan: var(--nb-cyan); --color-nb-lime: var(--nb-lime);
--color-nb-orange: var(--nb-orange); --color-nb-violet: var(--nb-violet); --color-nb-red: var(--nb-red);
--color-nb-blue: var(--nb-blue); --color-nb-on-blue: var(--nb-on-blue); --color-nb-danger: var(--nb-danger);
--shadow-nb: var(--nb-shadow); --shadow-nb-sm: var(--nb-shadow-sm); --shadow-nb-lg: var(--nb-shadow-lg); --shadow-nb-hover: var(--nb-shadow-hover);
--radius-nb: var(--nb-radius); --radius-nb-sm: var(--nb-radius-sm);
--font-nb: var(--nb-font); --font-nb-display: var(--nb-font-display);
}
/* Starburst sticker outline (Tailwind has no clip-path utility) */
@utility clip-nb-burst {
clip-path: polygon(50% 0%,59.1% 16.1%,75% 6.7%,74.8% 25.2%,93.3% 25%,83.9% 40.9%,100% 50%,83.9% 59.1%,93.3% 75%,74.8% 74.8%,75% 93.3%,59.1% 83.9%,50% 100%,40.9% 83.9%,25% 93.3%,25.2% 74.8%,6.7% 75%,16.1% 59.1%,0% 50%,16.1% 40.9%,6.7% 25%,25.2% 25.2%,25% 6.7%,40.9% 16.1%);
}
JavaScript
// Range: paint the WebKit fill and keep the visible value and aria-valuetext in step
document.querySelectorAll('[data-nb-range]').forEach(function (r) {
var out = document.querySelector('output[for="' + r.id + '"]');
function paint() {
r.style.setProperty('--val', (r.value - r.min) / (r.max - r.min) * 100 + '%');
var text = (r.dataset.prefix || '') + r.value + (r.dataset.suffix || '');
r.setAttribute('aria-valuetext', text);
if (out) out.textContent = text;
}
r.addEventListener('input', paint); paint();
});
About this set
Form controls in the neobrutalist style, and a login card that uses them. Text fields are white wells with a thick border, a small hard shadow and a bold label above; there is a plain field, a search field with an icon, an email field with a hint, and a shop handle in an error state that adds a red offset shadow and a red message box tied to the field with aria-describedby. A native select gets a drawn chevron that follows the text colour, and a bio textarea shows its character count. Checkboxes and radios are hidden native inputs behind square and round boxes that are pushed into their shadow and filled lemon when chosen. A membership price slider is a real range input with a pink fill, a square lemon thumb and the price written beside the label. The login card puts email and password fields, a keep-me-signed-in checkbox, a forgotten-password link, a full-width sign-in button and an email-link option under a tilted Creators only sticker. Focus adds a dashed blue outline and a deeper shadow. A few lines of script paint the slider fill in WebKit and keep its value and aria-valuetext current.
What’s included
Fields with icon, hint and an error state that is more than a colour
Checkboxes and radios that press into their shadow when chosen
Native select and range input styled to match
Login card with labelled fields and autocomplete hints
Bootstrap version on .form-control, .form-select, .form-check-input and .form-range
Accessibility
Every field has a visible label, the error is announced through aria-invalid and aria-describedby and starts with hidden Error text, and the checkboxes and radios sit in fieldsets with legends. Fields and custom boxes show a 3px dashed outline on focus. The slider reports its price through aria-valuetext. Transitions stop under prefers-reduced-motion.
Customise it
Edit the --nb-* tokens at the top for colours, border width, shadow offset and radius; the error accent is --nb-red. Bootstrap reads them through the .nb-form rules on its own form classes, and Tailwind uses border-nb-line, shadow-nb-sm, rounded-nb-sm and focus:outline-nb-focus.