jam-buddy / apps /web /app /globals.css
salgadev's picture
Sync from GitHub 46f60997
dcc72ff verified
Raw
History Blame Contribute Delete
10.5 kB
@tailwind base;
@tailwind components;
@tailwind utilities;
/*
* Jam Buddy hardware-sampler rack.
* Self-contained dark panel with a rotary-knob look and trigger pads.
* Kept as plain CSS so it doesn't fight the Tailwind theme tokens, and
* accessibility is preserved (range inputs, focus rings, aria-live).
*/
.jambuddy-rack {
background: linear-gradient(180deg, #1c1e2b 0%, #12131b 100%);
border: 1px solid #2a2d3d;
}
.jambuddy-pad {
border-radius: 0.5rem;
border: 1px solid #2a2d3d;
background: #1f2130;
color: #7f829c;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 0.7rem;
text-transform: uppercase;
letter-spacing: 0.08em;
padding: 0.6rem 0;
transition: background 0.15s, color 0.15s, box-shadow 0.15s;
}
.jambuddy-pad:hover {
background: #2a2d3d;
}
.jambuddy-pad--on {
background: #f4a261;
color: #12131b;
box-shadow: 0 0 12px rgba(244, 162, 97, 0.4);
font-weight: bold;
}
/* Rotary knob = a styled <input type=range>. A metallic potentiometer: a
* brushed-aluminum body with a knurled edge, a gloss highlight, and a real
* pointer line that rotates with the value (read off --knob-pct). */
.jambuddy-knob {
-webkit-appearance: none;
appearance: none;
width: 3.5rem;
height: 3.5rem;
border-radius: 9999px;
background:
radial-gradient(circle at 50% 50%, #dfe3ea 0%, #aeb4c0 34%, #5b6270 68%, #3a3f4a 100%);
border: 2px solid #2a2d3d;
cursor: pointer;
position: relative;
box-shadow:
inset 0 2px 3px rgba(255, 255, 255, 0.5),
inset 0 -4px 8px rgba(0, 0, 0, 0.45),
0 3px 6px rgba(0, 0, 0, 0.6);
/* Pointer line + knurl tick marks drawn on top of the metal body. */
}
/* The arc: a conic-gradient slice rotated to the knob's angle. --knob-rot is
* passed as an angle (e.g. 150deg), not a percentage, so the sweep is valid. */
.jambuddy-knob::before {
content: "";
position: absolute;
inset: 6px;
border-radius: 9999px;
background:
conic-gradient(
from -135deg,
transparent 0deg,
transparent calc(var(--knob-rot, 0deg) - 6deg),
#12131b calc(var(--knob-rot, 0deg) - 6deg) var(--knob-rot, 0deg),
transparent var(--knob-rot, 0deg) 360deg
);
opacity: 0.9;
}
/* The pointer line itself (the "needle"). */
.jambuddy-knob::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
width: 6px;
height: 26px;
border-radius: 2px;
background: linear-gradient(180deg, #14161d, #3a3f4a 45%, #14161d);
box-shadow: 0 0 2px rgba(0, 0, 0, 0.8);
transform: translate(-50%, -100%) rotate(var(--knob-rot, 0deg));
transform-origin: 50% 100%;
}
/* Range thumb hidden — the pointer is drawn by ::after so the native thumb is
invisible; keep it transparent to preserve drag interaction. */
.jambuddy-knob::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 0;
height: 0;
}
.jambuddy-knob::-moz-range-thumb {
width: 0;
height: 0;
}
/* The big trigger button */
.jambuddy-trigger {
border-radius: 0.5rem;
border: none;
background: linear-gradient(180deg, #f4a261 0%, #e07b3a 100%);
color: #12131b;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 1rem;
font-weight: 700;
letter-spacing: 0.12em;
padding: 0.9rem 0;
cursor: pointer;
box-shadow: 0 2px 0 #a85a24;
}
.jambuddy-trigger:hover {
filter: brightness(1.05);
}
.jambuddy-trigger:disabled {
opacity: 0.5;
cursor: wait;
}
/* Sampler/sequencer transport pad — a big backlit pad with an LED color
* (set via --pad-c) that brightens when pressed. */
.jambuddy-padbig {
border-radius: 0.6rem;
border: 2px solid rgba(0, 0, 0, 0.6);
background:
radial-gradient(circle at 50% 30%, rgba(255, 255, 255, 0.06), transparent 60%),
linear-gradient(180deg, #2a2d3d 0%, #1a1c28 100%);
color: var(--pad-c, #f4a261);
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 1.1rem;
font-weight: 800;
letter-spacing: 0.12em;
padding: 1.1rem 0;
cursor: pointer;
box-shadow:
inset 0 -4px 8px rgba(0, 0, 0, 0.6),
inset 0 1px 0 rgba(255, 255, 255, 0.15),
0 3px 0 rgba(0, 0, 0, 0.5);
transition: transform 0.08s, box-shadow 0.08s, filter 0.15s;
}
.jambuddy-padbig:hover {
filter: brightness(1.12);
}
.jambuddy-padbig[aria-pressed="true"],
.jambuddy-padbig:active {
transform: translateY(2px);
box-shadow:
inset 0 2px 6px rgba(0, 0, 0, 0.6),
0 0 16px var(--pad-c, #f4a261),
0 1px 0 rgba(0, 0, 0, 0.5);
}
.jambuddy-padbig:disabled {
opacity: 0.4;
cursor: wait;
}
.jambuddy-padbig__label {
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
}
/* Icon download button pinned beside a waveform — sized to match the track,
* vertically centered, with a visible border and hover glow. */
.jambuddy-save {
display: inline-flex;
align-items: center;
justify-content: center;
align-self: center;
width: 3rem;
height: 3rem;
border-radius: 0.5rem;
border: 1px solid #2a2d3d;
background: linear-gradient(180deg, #2a2d3d 0%, #1a1c28 100%);
color: #5fd38a;
cursor: pointer;
transition: filter 0.15s, box-shadow 0.15s, transform 0.08s;
}
.jambuddy-save:hover {
filter: brightness(1.2);
box-shadow: 0 0 10px rgba(95, 211, 138, 0.3);
}
.jambuddy-save:active {
transform: translateY(1px);
}
.jambuddy-save:disabled {
opacity: 0.4;
cursor: default;
box-shadow: none;
}
/* Big RED record button — a physical record arm with a glowing tip. */
.jambuddy-record {
border-radius: 0.5rem;
border: 1px solid #7a1c1c;
background: linear-gradient(180deg, #d64545 0%, #a11f1f 55%, #7a1414 100%);
color: #ffe9e9;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 1rem;
font-weight: 800;
letter-spacing: 0.12em;
padding: 0.9rem 0;
cursor: pointer;
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.35),
inset 0 -3px 6px rgba(0, 0, 0, 0.4),
0 2px 0 #5c0f0f;
position: relative;
}
.jambuddy-record:hover {
filter: brightness(1.08);
}
.jambuddy-record[aria-pressed="true"] {
background: linear-gradient(180deg, #e05252 0%, #b52424 55%, #8a1616 100%);
box-shadow:
inset 0 2px 4px rgba(0, 0, 0, 0.55),
0 0 14px rgba(224, 60, 60, 0.6),
0 1px 0 #5c0f0f;
transform: translateY(1px);
}
.jambuddy-record:disabled {
opacity: 0.5;
cursor: wait;
}
/* Physical hardware toggle for the engine (API / Local). A labelled checkbox
* hidden inside; the visual is a metal slide switch with a chunky lever that
* travels between two detent positions (API left, Local right), matching the
* hardware-sampler aesthetic. Keyboard/screenreader usable via the input. */
.engine-toggle {
position: relative;
display: inline-flex;
align-items: center;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 0.68rem;
text-transform: uppercase;
letter-spacing: 0.06em;
}
.engine-toggle input {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}
/* The switch body — a recessed metal channel with a center detent notch. */
.engine-toggle__switch {
position: relative;
display: inline-flex;
align-items: center;
width: 6.5rem;
height: 2rem;
border-radius: 0.4rem;
background: linear-gradient(180deg, #4a4f5a 0%, #2c3038 60%, #1e2127 100%);
border: 1px solid #17191e;
box-shadow:
inset 0 2px 5px rgba(0, 0, 0, 0.7),
0 1px 1px rgba(255, 255, 255, 0.12);
padding: 0 0.35rem;
}
/* Labels pinned to each side of the lever travel. */
.engine-toggle__label {
position: absolute;
top: 50%;
transform: translateY(-50%);
color: #8b92a0;
font-weight: 700;
z-index: 1;
pointer-events: none;
}
.engine-toggle__label--api {
left: 0.45rem;
}
.engine-toggle__label--local {
right: 0.3rem;
}
/* The chunky metal lever — slides to whichever side is active. */
.engine-toggle__lever {
position: absolute;
top: 50%;
left: 0.3rem;
width: 2.6rem;
height: 1.5rem;
border-radius: 0.25rem;
background: linear-gradient(180deg, #f0e9dc 0%, #cfc7b8 40%, #a89f8e 100%);
border: 1px solid #6b6352;
box-shadow:
inset 0 1px 1px rgba(255, 255, 255, 0.7),
inset 0 -3px 5px rgba(0, 0, 0, 0.35),
0 2px 3px rgba(0, 0, 0, 0.5);
transform: translateY(-50%);
transition: left 0.18s cubic-bezier(0.34, 1.56, 0.64, 1);
cursor: pointer;
}
/* A grip groove on the lever. */
.engine-toggle__lever::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 1rem;
height: 3px;
border-radius: 2px;
background: rgba(0, 0, 0, 0.25);
box-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
}
/* Checked (API) -> lever slides left under the API label; active label glows. */
.engine-toggle input:checked + .engine-toggle__switch .engine-toggle__lever {
left: 0.3rem;
}
.engine-toggle input:not(:checked) + .engine-toggle__switch .engine-toggle__lever {
left: calc(100% - 2.9rem);
}
.engine-toggle input:checked + .engine-toggle__switch .engine-toggle__label--api {
color: #f4a261;
text-shadow: 0 0 6px rgba(244, 162, 97, 0.6);
}
.engine-toggle input:not(:checked) + .engine-toggle__switch .engine-toggle__label--local {
color: #5fd38a;
text-shadow: 0 0 6px rgba(95, 211, 138, 0.6);
}
.engine-toggle input:focus-visible + .engine-toggle__switch {
outline: 3px solid var(--focus);
outline-offset: 2px;
}
.engine-toggle input:disabled + .engine-toggle__switch {
opacity: 0.5;
cursor: not-allowed;
}
:root {
--bg: #ffffff;
--fg: #1a1a1a;
--accent: #0066cc;
--focus: #ffd700;
--limb-kick: #d62828;
--limb-snare: #1d3557;
--limb-hihat: #f4a261;
--limb-ride: #2a9d8f;
--limb-crash: #e76f51;
--limb-china: #8338ec;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #1a1a1a;
--fg: #f0f0f0;
--accent: #66b2ff;
}
}
@media (prefers-contrast: more) {
:root {
--fg: #000000;
--bg: #ffffff;
--accent: #0000ee;
--focus: #ff00ff;
}
}
/* Always-visible focus outline — WCAG 2.4.7 */
:focus-visible {
outline: 3px solid var(--focus);
outline-offset: 2px;
}
/* Skip links — pattern from docs/05-accessibility.md §Keyboard navigation */
.skip-link {
position: absolute;
top: -40px;
left: 0;
background: var(--accent);
color: var(--bg);
padding: 0.5rem 1rem;
z-index: 100;
}
.skip-link:focus {
top: 0;
}
/* Reduced motion — docs/05-accessibility.md §Reduced motion */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}