/* ── 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}
/* Icon toggle: a checkbox behind a round face. On = pressed into its shadow, pink, and the outline icon turns solid */
.nb-itoggle{position:relative;display:inline-flex}
.nb-itoggle__face{display:grid;place-items:center;width:48px;height:48px;border:var(--nb-border);border-radius:50%;background:var(--nb-surface);color:var(--nb-ink);
box-shadow:var(--nb-shadow);cursor:pointer;transition:translate .12s,box-shadow .12s}
.nb-itoggle__face .nb-ico{width:22px;height:22px}
.nb-itoggle__face .nb-ico+.nb-ico,.nb-itoggle input:checked+.nb-itoggle__face .nb-ico:first-child{display:none}
.nb-itoggle input:checked+.nb-itoggle__face{translate:var(--nb-press);box-shadow:none;background:var(--nb-pink);color:var(--nb-on-fill)}
.nb-itoggle input:checked+.nb-itoggle__face .nb-ico+.nb-ico{display:block}
.nb-itoggle input:focus-visible+.nb-itoggle__face{outline:3px dashed var(--nb-focus);outline-offset:5px}
/* 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}
/* 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}
/* Music player: a range seek bar, a checkbox play/pause, and shuffle and repeat toggles that press in and gain a dot */
.nb-player{max-width:380px}
.nb-player__art{position:relative;height:190px;margin-bottom:1.1rem;border:var(--nb-border);border-radius:var(--nb-radius-sm);overflow:hidden;background:var(--nb-violet)}
.nb-player__art svg{display:block;width:100%;height:100%}
.nb-player__art .nb-sticker{position:absolute;right:12px;bottom:12px}
.nb-player__title{font-size:1.4rem}
.nb-player__artist{margin-top:.2rem!important;font-weight:600;color:var(--nb-muted)}
.nb-times{display:flex;justify-content:space-between;margin-top:.15rem;font-size:.85rem;font-weight:700;font-variant-numeric:tabular-nums}
.nb-seek{margin-top:1rem}
.nb-controls{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-top:1rem}
.nb-play .nb-itoggle__face{width:62px;height:62px;background:var(--nb-lemon);color:var(--nb-on-fill)}
.nb-play .nb-itoggle__face .nb-ico{width:26px;height:26px}
.nb-play input:checked+.nb-itoggle__face{background:var(--nb-lemon)}
.nb-mini{position:relative;display:inline-flex}
.nb-mini__face{position:relative;display:grid;place-items:center;width:42px;height:42px;border:var(--nb-bw) solid transparent;border-radius:var(--nb-radius-sm);color:var(--nb-ink);cursor:pointer}
.nb-mini__face:hover{border-color:var(--nb-line)}
.nb-mini input:checked+.nb-mini__face{border-color:var(--nb-line);background:var(--nb-cyan);color:var(--nb-on-fill)}
.nb-mini input:checked+.nb-mini__face::after{content:"";position:absolute;bottom:3px;width:5px;height:5px;border-radius:50%;background:currentColor}
.nb-mini input:focus-visible+.nb-mini__face{outline:3px dashed var(--nb-focus);outline-offset:3px}
/* Weather card */
.nb-weather__top{display:flex;align-items:flex-start;justify-content:space-between;gap:1rem}
.nb-weather__place{font-weight:800}
.nb-weather__temp{margin-top:.2rem!important;font-size:3.6rem}
.nb-weather__sun{display:grid;place-items:center;width:84px;height:84px;flex:none;border:var(--nb-border);border-radius:50%;background:var(--nb-lemon);color:var(--nb-on-fill);box-shadow:var(--nb-shadow);rotate:-8deg}
.nb-weather__sun .nb-ico{width:44px;height:44px}
.nb-weather__desc{margin-top:.2rem!important;font-weight:700}
.nb-hours{display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin:1.2rem 0 0;padding:0;list-style:none}
.nb-hours li{display:grid;justify-items:center;gap:.3rem;padding:.55rem .2rem;border:2px solid var(--nb-line);border-radius:var(--nb-radius-sm);background:var(--nb-surface);color:var(--nb-ink);font-size:.85rem;font-weight:700}
.nb-hours li[aria-current="time"]{background:var(--nb-ink);color:var(--nb-paper)}
.nb-hours b{font-size:1.05rem}
.nb-weather__facts{display:flex;flex-wrap:wrap;gap:.4rem 1.2rem;margin:1rem 0 0!important;font-size:.88rem;font-weight:700}
.nb-weather__facts span{display:inline-flex;align-items:center;gap:.3rem}
.nb-weather__facts .nb-ico{width:16px;height:16px}
/* Mini calendar: a real table; today is ink and aria-current, event days are pink with hidden text */
.nb-cal{max-width:360px}
.nb-cal__head{display:flex;align-items:center;justify-content:space-between;gap:.75rem;margin-bottom:.9rem}
.nb-cal__title{font-size:1.25rem}
.nb-cal table{width:100%;border-collapse:separate;border-spacing:4px;table-layout:fixed;text-align:center}
.nb-cal caption{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap}
.nb-cal th{padding:0 0 .25rem;font-size:.72rem;font-weight:800;letter-spacing:.06em;text-transform:uppercase;color:var(--nb-muted)}
.nb-cal td{height:38px;padding:0;border:2px solid transparent;border-radius:6px;font-weight:700;font-variant-numeric:tabular-nums}
.nb-cal td.is-event{border-color:var(--nb-line);background:var(--nb-pink);color:var(--nb-on-fill)}
.nb-cal td[aria-current="date"]{border-color:var(--nb-line);background:var(--nb-ink);color:var(--nb-paper);box-shadow:var(--nb-shadow-sm)}
.nb-cal__legend{display:flex;flex-wrap:wrap;gap:.4rem 1rem;margin-top:.8rem!important;font-size:.82rem;font-weight:700}
.nb-cal__legend span{display:inline-flex;align-items:center;gap:.35rem}
.nb-cal__legend i{width:14px;height:14px;border:2px solid var(--nb-line);border-radius:3px}
/* Progress: labelled bars with a hatched fill and the value written out */
.nb-progress{display:grid;gap:.45rem}
.nb-progress__top{display:flex;justify-content:space-between;gap:1rem;font-weight:800}
.nb-progress__top span{font-variant-numeric:tabular-nums}
.nb-bar{height:24px;border:var(--nb-border);border-radius:999px;background:var(--nb-surface);overflow:hidden}
.nb-bar__fill{display:block;width:var(--p);height:100%;border-right:var(--nb-border);background:repeating-linear-gradient(-45deg,transparent 0 8px,rgba(17,17,17,.22) 8px 11px),var(--nb-tone,var(--nb-lime))}
.nb-bar--pink{--nb-tone:var(--nb-pink)}.nb-bar--cyan{--nb-tone:var(--nb-cyan)}.nb-bar--lemon{--nb-tone:var(--nb-lemon)}
.nb-steps{display:grid;grid-template-columns:repeat(4,1fr);gap:6px;margin:0;padding:0;list-style:none;counter-reset:nb-step}
.nb-steps li{display:grid;gap:.35rem;font-size:.8rem;font-weight:800}
.nb-steps li::before{content:"";height:14px;border:var(--nb-border);border-radius:4px;background:var(--nb-surface)}
.nb-steps li.is-done::before{background:var(--nb-lime)}
.nb-steps li[aria-current="step"]::before{background:repeating-linear-gradient(-45deg,var(--nb-lemon) 0 6px,var(--nb-surface) 6px 10px)}
@media (prefers-reduced-motion:reduce){[class*="nb-"],[class*="nb-"]::before,[class*="nb-"]::after{transition:none!important;animation:none!important}}
JavaScript
// Seek bar: paint the WebKit fill and keep the visible time and aria-valuetext in step
document.querySelectorAll('[data-nb-seek]').forEach(function (r) {
var now = document.querySelector('[data-nb-elapsed="' + r.id + '"]');
function mmss(s) { return Math.floor(s / 60) + ':' + String(Math.floor(s % 60)).padStart(2, '0'); }
function paint() {
r.style.setProperty('--val', r.value / r.max * 100 + '%');
r.setAttribute('aria-valuetext', mmss(r.value) + ' of ' + mmss(r.max));
if (now) now.textContent = mmss(r.value);
}
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}
/* Icon toggle: .btn-check + a round .btn. On = pressed into its shadow, pink, and the outline icon turns solid */
.btn-check+.btn-nb-icon .nb-ico{width:22px;height:22px}
.btn-check+.btn-nb-icon .nb-ico+.nb-ico,.btn-check:checked+.btn-nb-icon .nb-ico:first-child{display:none}
.btn-check:checked+.btn-nb-icon .nb-ico+.nb-ico{display:block}
.btn-check:checked+.btn-nb-icon{--nb-fill:var(--nb-pink);--nb-text:var(--nb-on-fill);translate:var(--nb-press);--nb-btn-shadow: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}
/* 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}
/* Music player: a .form-range seek bar, a .btn-check play/pause, and shuffle and repeat toggles that press in and gain a dot */
.nb-player{max-width:380px}
.nb-player__art{position:relative;height:190px;margin-bottom:1.1rem;border:var(--nb-border);border-radius:var(--nb-radius-sm);overflow:hidden;background:var(--nb-violet)}
.nb-player__art svg{display:block;width:100%;height:100%}
.nb-player__art .nb-sticker{position:absolute;right:12px;bottom:12px}
.nb-player__title{font-size:1.4rem}
.nb-player__artist{margin-top:.2rem!important;font-weight:600;color:var(--nb-muted)}
.nb-times{display:flex;justify-content:space-between;margin-top:.15rem;font-size:.85rem;font-weight:700;font-variant-numeric:tabular-nums}
.nb-seek{margin-top:1rem}
.nb-controls{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-top:1rem}
.btn-nb-icon.nb-play{--nb-fill:var(--nb-lemon);--nb-text:var(--nb-on-fill);width:62px;height:62px}
.btn-check:checked+.btn-nb-icon.nb-play{--nb-fill:var(--nb-lemon)}
.btn-nb-icon.nb-play .nb-ico{width:26px;height:26px}
.nb-mini{position:relative;display:grid;place-items:center;width:42px;height:42px;border:var(--nb-bw) solid transparent;border-radius:var(--nb-radius-sm);color:var(--nb-ink);cursor:pointer}
.nb-mini:hover{border-color:var(--nb-line)}
.btn-check:checked+.nb-mini{border-color:var(--nb-line);background:var(--nb-cyan);color:var(--nb-on-fill)}
.btn-check:checked+.nb-mini::after{content:"";position:absolute;bottom:3px;width:5px;height:5px;border-radius:50%;background:currentColor}
.btn-check:focus-visible+.nb-mini{outline:3px dashed var(--nb-focus);outline-offset:3px}
/* Weather card */
.nb-weather__top{display:flex;align-items:flex-start;justify-content:space-between;gap:1rem}
.nb-weather__place{font-weight:800}
.nb-weather__temp{margin-top:.2rem!important;font-size:3.6rem}
.nb-weather__sun{display:grid;place-items:center;width:84px;height:84px;flex:none;border:var(--nb-border);border-radius:50%;background:var(--nb-lemon);color:var(--nb-on-fill);box-shadow:var(--nb-shadow);rotate:-8deg}
.nb-weather__sun .nb-ico{width:44px;height:44px}
.nb-weather__desc{margin-top:.2rem!important;font-weight:700}
.nb-hours{display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin:1.2rem 0 0;padding:0;list-style:none}
.nb-hours li{display:grid;justify-items:center;gap:.3rem;padding:.55rem .2rem;border:2px solid var(--nb-line);border-radius:var(--nb-radius-sm);background:var(--nb-surface);color:var(--nb-ink);font-size:.85rem;font-weight:700}
.nb-hours li[aria-current="time"]{background:var(--nb-ink);color:var(--nb-paper)}
.nb-hours b{font-size:1.05rem}
.nb-weather__facts{display:flex;flex-wrap:wrap;gap:.4rem 1.2rem;margin:1rem 0 0!important;font-size:.88rem;font-weight:700}
.nb-weather__facts span{display:inline-flex;align-items:center;gap:.3rem}
.nb-weather__facts .nb-ico{width:16px;height:16px}
/* Mini calendar: a real table; today is ink and aria-current, event days are pink with hidden text */
.nb-cal{max-width:360px}
.nb-cal__head{display:flex;align-items:center;justify-content:space-between;gap:.75rem;margin-bottom:.9rem}
.nb-cal__title{font-size:1.25rem}
.nb-cal table{width:100%;border-collapse:separate;border-spacing:4px;table-layout:fixed;text-align:center}
.nb-cal caption{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap}
.nb-cal th{padding:0 0 .25rem;font-size:.72rem;font-weight:800;letter-spacing:.06em;text-transform:uppercase;color:var(--nb-muted)}
.nb-cal td{height:38px;padding:0;border:2px solid transparent;border-radius:6px;font-weight:700;font-variant-numeric:tabular-nums}
.nb-cal td.is-event{border-color:var(--nb-line);background:var(--nb-pink);color:var(--nb-on-fill)}
.nb-cal td[aria-current="date"]{border-color:var(--nb-line);background:var(--nb-ink);color:var(--nb-paper);box-shadow:var(--nb-shadow-sm)}
.nb-cal__legend{display:flex;flex-wrap:wrap;gap:.4rem 1rem;margin-top:.8rem!important;font-size:.82rem;font-weight:700}
.nb-cal__legend span{display:inline-flex;align-items:center;gap:.35rem}
.nb-cal__legend i{width:14px;height:14px;border:2px solid var(--nb-line);border-radius:3px}
/* Progress: Bootstrap .progress with a hatched .progress-bar and the value written out */
.nb-progress{display:grid;gap:.45rem}
.nb-progress__top{display:flex;justify-content:space-between;gap:1rem;font-weight:800}
.nb-progress__top span{font-variant-numeric:tabular-nums}
.progress.nb-bar{--bs-progress-height:24px;--bs-progress-bg:var(--nb-surface);--bs-progress-border-radius:999px;--bs-progress-bar-bg:var(--nb-tone,var(--nb-lime));border:var(--nb-border)}
.nb-bar .progress-bar{border-right:var(--nb-border);background-image:repeating-linear-gradient(-45deg,transparent 0 8px,rgba(17,17,17,.22) 8px 11px)}
.nb-bar-pink{--nb-tone:var(--nb-pink)}.nb-bar-cyan{--nb-tone:var(--nb-cyan)}.nb-bar-lemon{--nb-tone:var(--nb-lemon)}
.nb-steps{display:grid;grid-template-columns:repeat(4,1fr);gap:6px;margin:0;padding:0;list-style:none}
.nb-steps li{display:grid;gap:.35rem;font-size:.8rem;font-weight:800}
.nb-steps li::before{content:"";height:14px;border:var(--nb-border);border-radius:4px;background:var(--nb-surface)}
.nb-steps li.is-done::before{background:var(--nb-lime)}
.nb-steps li[aria-current="step"]::before{background:repeating-linear-gradient(-45deg,var(--nb-lemon) 0 6px,var(--nb-surface) 6px 10px)}
@media (prefers-reduced-motion:reduce){[class*="nb-"],[class*="nb-"]::before,[class*="nb-"]::after{transition:none!important;animation:none!important}}
JavaScript
// Seek bar: paint the WebKit fill and keep the visible time and aria-valuetext in step
document.querySelectorAll('[data-nb-seek]').forEach(function (r) {
var now = document.querySelector('[data-nb-elapsed="' + r.id + '"]');
function mmss(s) { return Math.floor(s / 60) + ':' + String(Math.floor(s % 60)).padStart(2, '0'); }
function paint() {
r.style.setProperty('--val', r.value / r.max * 100 + '%');
r.setAttribute('aria-valuetext', mmss(r.value) + ' of ' + mmss(r.max));
if (now) now.textContent = mmss(r.value);
}
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
// Seek bar: paint the WebKit fill and keep the visible time and aria-valuetext in step
document.querySelectorAll('[data-nb-seek]').forEach(function (r) {
var now = document.querySelector('[data-nb-elapsed="' + r.id + '"]');
function mmss(s) { return Math.floor(s / 60) + ':' + String(Math.floor(s % 60)).padStart(2, '0'); }
function paint() {
r.style.setProperty('--val', r.value / r.max * 100 + '%');
r.setAttribute('aria-valuetext', mmss(r.value) + ' of ' + mmss(r.max));
if (now) now.textContent = mmss(r.value);
}
r.addEventListener('input', paint); paint();
});
About this set
Four widgets in the neobrutalist style, built to work as well as look loud. A music player shows record art drawn in inline SVG with a Now playing sticker, a seek bar that is a real range input reading 1:42 of 3:58, and shuffle, previous, play, next and repeat controls; play is a checkbox that turns into pause and sinks into its shadow, and shuffle and repeat fill cyan and gain a dot when on. A cyan weather card shows the temperature in big type, a tilted sun badge, wind, humidity and UV, and four hourly tiles with the current hour in ink and aria-current. A mini calendar is a real table for October 2026 with a Monday-first header, today in ink with a shadow and release days in pink, each with hidden text such as today or print drop. A progress card has hatched bars for an upload, a members goal and a print run, each a labelled progressbar with its value written out, plus a four-step launch checklist with done and current steps. Use them for creator dashboards, media apps and status pages. A few lines of vanilla JavaScript keep the seek time and its aria-valuetext in step; without it the widgets still show their values.
What’s included
Music player with a range seek bar and checkbox play, shuffle and repeat
Weather card with the current hour marked by aria-current
Mini calendar as a real table with today and events in text
Hatched progress bars exposed as labelled progressbars
Step checklist with done and current steps
Accessibility
The seek bar has a label and announces 1:42 of 3:58 through aria-valuetext, play, shuffle and repeat are named checkboxes, and the progress bars are progressbars with names and value text. The calendar has a caption, column headers with full day names and hidden text for today and events, and the weather icons have text equivalents. Focus shows a 3px dashed outline, and transitions stop under prefers-reduced-motion.
Customise it
Change the --nb-* tokens for colour, border and shadow; each bar takes --p for its width and a colour class such as .nb-bar--pink. The Bootstrap version reuses .form-range for the seek bar and .progress for the bars, and Tailwind sets bar widths with w-[72%].