Something went wrong. Try again.
Mirror of https://github.com/kualta/site — web infrastructure
Something went wrong. Try again.
CSS
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372@import "@fontsource/fredoka/400.css";/* Fredoka carries no Cyrillic and no CJK; these two cover the rest */@import "@fontsource/m-plus-rounded-1c/500.css";@import "@fontsource/m-plus-rounded-1c/700.css";@import "@fontsource/huninn/400.css";@tailwind base;@tailwind components;@tailwind utilities;
html,body { font-family: "Fredoka", "M PLUS Rounded 1c", "Huninn", system-ui, -apple-system, sans-serif;}
:focus-visible { outline: 2px solid var(--text); outline-offset: 2px;}
.dark :focus-visible { outline-color: var(--text-dark);}
/* fields already draw a border, so focus darkens it instead of adding a ring */:is(input:not([type="checkbox"], [type="radio"], [type="range"], [type="file"]), textarea, select):focus-visible { outline: none; border-color: currentColor;}
/* Han characters live in both faces, so Chinese has to ask for Huninn by name or it renders in M PLUS's Japanese glyph forms */:lang(zh),:lang(zh-Hant),:lang(zh-TW) { font-family: "Fredoka", "Huninn", "M PLUS Rounded 1c", system-ui, -apple-system, sans-serif;}
:root { --primary-dark: #3b3b3b; --secondary-dark: #222222; --bg-dark: #181818; --text-dark: #e7e4e3;
--primary: #ffe7f1; --secondary: #ffffff; --bg: #f8f8f8; --text: #191817;
--secondary-text: #9c9c9c;}
html::-webkit-scrollbar { background: transparent; /* Chrome/Safari/Webkit */ width: 0px;} html { scrollbar-width: none; /* Firefox */ -ms-overflow-style: none; /* IE 10+ */ /* a hard flick past either end rubber-bands the whole document, and the page furniture is pinned to the body, so the corners slide off with it and the bounce reads as the layout coming apart */ overscroll-behavior-y: none;}
::selection { background: var(--primary) }
.dark ::selection { background: var(--primary-dark) } .dark .active-bg { background: linear-gradient(var(--secondary-dark), var(--secondary-dark)) padding-box, linear-gradient(45deg, #ff33ccee, #cc77ffee) border-box; border: 1px solid transparent; }
.active-bg { background: linear-gradient(var(--secondary), var(--secondary)) padding-box, linear-gradient(45deg, #ff33ccee, #cc77ffee) border-box; border: 1px solid transparent;}
/* --- vinyl --- */
:root { --ease-out: cubic-bezier(0.23, 1, 0.32, 1); --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1); /* a 33 1/3 rpm record turns once every 1.8s */ --vinyl-rpm: 1.8s;}
.vinyl { position: relative; width: 100%; aspect-ratio: 1; border-radius: 50%;}
/* the record on the deck can be grabbed and turned by hand */.vinyl-deck { cursor: grab; touch-action: none;}
.vinyl-deck.is-scrubbing { cursor: grabbing;}
/* hand rotation lives here so it composes with the playback spin below it */.vinyl-scrub { position: absolute; inset: 0; border-radius: 50%;}
/* a hand turns the record instantly; a jump in the song coasts it round */.vinyl-scrub.is-seeking { transition: transform 620ms var(--ease-out);}
@media (prefers-reduced-motion: reduce) { .vinyl-scrub.is-seeking { transition: none; }}
.vinyl-disc { position: absolute; inset: 0; border-radius: 50%; animation: vinyl-spin var(--vinyl-rpm) linear infinite; animation-play-state: paused;}
/* the deck keeps its animation live so JS can ramp playbackRate up and down — but only once JS is there to hold it at rest, or the record turns on its own between first paint and hydration */.vinyl-disc.is-deck.is-live { animation-play-state: running;}
/* only the record that is actually turning earns a compositing layer */.vinyl-disc.is-playing { animation-play-state: running; will-change: transform;}
@keyframes vinyl-spin { to { transform: rotate(360deg); }}
@media (prefers-reduced-motion: reduce) { .vinyl-disc { animation-duration: 14s; }}
.vinyl-grooves { position: absolute; inset: 0; border-radius: 50%; background: repeating-radial-gradient( circle at 50% 50%, #0d0d0f 0px, #0d0d0f 1px, #17171a 1px, #17171a 2px, #0b0b0d 2px, #0b0b0d 3.4px ), radial-gradient(circle at 50% 50%, #1c1c20 0%, #101013 62%, #050506 100%); box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.06);}
/* the grooves turn underneath; the highlight stays put */.vinyl-sheen { position: absolute; inset: 0; border-radius: 50%; pointer-events: none; mix-blend-mode: screen; background: conic-gradient( from 210deg, rgba(255, 255, 255, 0) 0deg, rgba(255, 255, 255, 0.16) 32deg, rgba(255, 255, 255, 0) 74deg, rgba(255, 255, 255, 0) 180deg, rgba(255, 255, 255, 0.09) 214deg, rgba(255, 255, 255, 0) 258deg, rgba(255, 255, 255, 0) 360deg );}
.vinyl-label { position: absolute; inset: 24%; border-radius: 50%; overflow: hidden; /* the label always sits on black grooves, so the edge needs a light hairline */ box-shadow: 0 0 0 1px oklch(1 0 0 / 0.1);}
.vinyl-label img { display: block; width: 100%; height: 100%; object-fit: cover;}
/* a printed label, set in the score's own serif and sized off the label itself */.vinyl-label { container-type: size;}
.vinyl-label-print { display: flex; width: 100%; height: 100%; flex-direction: column; align-items: center; justify-content: center; padding: 0 12%; text-align: center; font-family: "Times New Roman", Times, serif; line-height: 1.12; color: #191817; background-color: #e7e4e3;}
/* each half owns its side of the spindle, so the hole never lands on a word */.label-top,.label-bottom { display: flex; width: 100%; height: 50%; flex-direction: column; align-items: center;}
.label-top { justify-content: flex-end; padding-bottom: 7cqw;}
.label-bottom { justify-content: flex-start; padding-top: 7cqw;}
.label-title { /* the component sizes this by title length; this is the fallback */ font-size: 11.5cqw; font-weight: 600; text-wrap: balance;}
.label-album { font-size: 7.4cqw;}
.label-part { font-size: 6.2cqw; opacity: 0.8;}
.label-author { margin-top: 2.5cqw; font-size: 6.6cqw; opacity: 0.75;}
/* a blank pressing: flat paper stock, same in both themes */.vinyl-label-fallback { display: flex; width: 100%; height: 100%; align-items: center; justify-content: center; padding: 0 14%; text-align: center; font-size: 0.6rem; line-height: 1.15; color: #191817; background-color: #e7e4e3;}
.vinyl-spindle { position: absolute; inset: 48.4%; border-radius: 50%; background: var(--bg); box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.6);}
.dark .vinyl-spindle { background: var(--bg-dark);}
.vinyl-seek { appearance: none; /* a 3px ribbon is a hard thing to hit, so the padding is reach, not paint: the box grows to a finger's worth and the sliver stays a sliver */ box-sizing: content-box; height: 3px; padding-block: 8px; margin-block: -8px; border-radius: 2px; cursor: pointer; background-color: color-mix(in oklab, var(--secondary-text) 45%, transparent); background-image: linear-gradient(var(--text), var(--text)); background-repeat: no-repeat; background-origin: content-box; background-clip: content-box; background-size: var(--vinyl-progress, 0%) 100%;}
.dark .vinyl-seek { background-image: linear-gradient(var(--text-dark), var(--text-dark));}
/* the fader stays folded away until the ear is reached for */.vinyl-volume { width: 0; opacity: 0; pointer-events: none; transition: width 220ms var(--ease-out), opacity 180ms ease;}
.volume-control:focus-within .vinyl-volume { width: 4.5rem; opacity: 1; pointer-events: auto;}
@media (hover: hover) and (pointer: fine) { .volume-control:hover .vinyl-volume { width: 4.5rem; opacity: 1; pointer-events: auto; }}
/* repeat reads as latched, not merely hovered, so it keeps the full ink */.repeat-button { position: relative; color: var(--secondary-text); transition: color 160ms ease, opacity 160ms ease, transform 140ms var(--ease-out);}
.repeat-button.is-on { color: var(--text);}
.dark .repeat-button.is-on { color: var(--text-dark);}
/* the latch light: it must not move the icon when it comes on */.repeat-button.is-on::after { content: ""; position: absolute; left: 50%; bottom: -5px; width: 3px; height: 3px; transform: translateX(-50%); border-radius: 50%; background-color: currentColor;}
/* the records rail: pinned left, full height, its own scroll */@media (min-width: 1024px) { .rail { max-height: 100%; overflow: hidden; }}
/* the site hides scrollbars everywhere; these panels earn one back */.pretty-scroll { overflow-y: auto; /* setting one axis makes the other compute to auto, and the entry animation slides rows in from the right, which would flash a horizontal scrollbar */ overflow-x: hidden; overscroll-behavior: contain; scrollbar-width: thin; scrollbar-color: color-mix(in oklab, var(--secondary-text) 45%, transparent) transparent;}
.pretty-scroll::-webkit-scrollbar { width: 10px;}
.pretty-scroll::-webkit-scrollbar-track { background: transparent;}
.pretty-scroll::-webkit-scrollbar-thumb { border-radius: 999px; /* the transparent border keeps the thumb slim without shrinking the hit area */ border: 3px solid transparent; background-clip: content-box; background-color: color-mix(in oklab, var(--secondary-text) 40%, transparent); transition: background-color 200ms ease;}
.pretty-scroll::-webkit-scrollbar-thumb:hover { background-color: color-mix(in oklab, var(--secondary-text) 75%, transparent);}
/* Leave breathing room above the bottom corners. */body[data-music-page] :is(.lyrics-rail, .rail) { padding-bottom: calc(2rem + env(safe-area-inset-bottom));}
/* beside the deck the rails sit as far from the bottom as from the sides (lg:p-12) */@media (min-width: 1024px) { body[data-music-page] :is(.lyrics-rail, .rail) { padding-bottom: calc(3rem + env(safe-area-inset-bottom)); }}
/* the words sit opposite the crate, so the deck keeps the middle */@media (min-width: 1024px) { .lyrics-rail { max-height: 100%; overflow: hidden; }}
/* an edge only dissolves while it actually hides something */.scroll-fade { --fade-top: 0px; --fade-bottom: 0px; mask-image: linear-gradient( to bottom, transparent 0, #000 var(--fade-top), #000 calc(100% - var(--fade-bottom)), transparent 100% ); -webkit-mask-image: linear-gradient( to bottom, transparent 0, #000 var(--fade-top), #000 calc(100% - var(--fade-bottom)), transparent 100% );}
.scroll-fade.fade-top { --fade-top: 14%;}
.scroll-fade.fade-bottom { --fade-bottom: 14%;}
.lyric-line { color: var(--text); transition: color 220ms ease, opacity 320ms ease;}
.dark .lyric-line { color: var(--text-dark);}
.lyric-line.is-current { color: var(--text); font-weight: 500;}
.dark .lyric-line.is-current { color: var(--text-dark);}
/* the site's checkbox: a round pill that fills in when ticked */input[type="checkbox"] { appearance: none; -webkit-appearance: none; flex: none; width: 1rem; height: 1rem; border-radius: 999px; border: 1px solid color-mix(in oklab, var(--secondary-text) 55%, transparent); background-color: transparent; background-position: center; background-repeat: no-repeat; background-size: 68%; cursor: pointer; transition: background-color 160ms ease, border-color 160ms ease;}
@media (hover: hover) and (pointer: fine) { input[type="checkbox"]:hover { border-color: var(--secondary-text); }}
input[type="checkbox"]:checked { border-color: transparent; background-color: var(--text); /* the tick is drawn in the page colour, so it reads as a hole in the pill */ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.6 6.3 4.9 8.6 9.4 3.7' fill='none' stroke='%23f8f8f8' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");}
.dark input[type="checkbox"]:checked { background-color: var(--text-dark); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.6 6.3 4.9 8.6 9.4 3.7' fill='none' stroke='%23181818' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");}
/* a lyric is a control now, so it at least takes a pointer */.lyric-line { cursor: pointer;}
.filter-chip { color: var(--secondary-text); border: 1px solid color-mix(in oklab, var(--secondary-text) 30%, transparent); transition: color 160ms ease, border-color 160ms ease, background-color 160ms ease;}
.filter-chip:hover { color: var(--text);}
.dark .filter-chip:hover { color: var(--text-dark);}
.filter-chip.is-on { color: var(--bg); background-color: var(--text); border-color: transparent;}
.dark .filter-chip.is-on { color: var(--bg-dark); background-color: var(--text-dark);}
.queue-item.is-current { background-color: color-mix(in oklab, var(--secondary-text) 18%, transparent);}
/* the column of records beside the deck */.queue > li { animation: queue-in 300ms var(--ease-out) both;}
.queue > li:nth-child(2) { animation-delay: 40ms; }.queue > li:nth-child(3) { animation-delay: 80ms; }.queue > li:nth-child(4) { animation-delay: 120ms; }.queue > li:nth-child(5) { animation-delay: 160ms; }.queue > li:nth-child(n + 6) { animation-delay: 200ms; }
@keyframes queue-in { from { opacity: 0; /* inward from the rail's own padding: sliding the other way would push the rows past the right edge and ask for a horizontal scrollbar */ transform: translateX(-8px); } to { opacity: 1; transform: none; }}
.queue-item { transition: transform 200ms var(--ease-out), background-color 200ms ease;}
@media (hover: hover) and (pointer: fine) { .queue-item:hover { background-color: color-mix(in oklab, var(--secondary-text) 14%, transparent); }
.queue-item:hover .vinyl { transform: scale(1.06); }}
.queue-item .vinyl { transition: transform 200ms var(--ease-out);}
/* swapping records crossfades the label instead of cutting to it */.vinyl-deck .vinyl-label > * { animation: label-in 260ms var(--ease-out) both;}
@keyframes label-in { from { opacity: 0; filter: blur(3px); transform: scale(0.96); } to { opacity: 1; filter: blur(0); transform: none; }}
/* a solid transport key: ink on paper, inverted per theme */.play-button { background-color: var(--text); color: var(--bg); transition: transform 140ms var(--ease-out), opacity 200ms ease;}
.dark .play-button { background-color: var(--text-dark); color: var(--bg-dark);}
.play-button:hover { opacity: 0.85;}
/* the play mark is drawn centered on its triangle's centroid, so it needs no nudge here */.icon-swap { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; filter: blur(4px); transform: scale(0.25); transition: opacity 200ms var(--ease-out), transform 200ms var(--ease-out), filter 200ms var(--ease-out);}
.icon-swap.is-shown { opacity: 1; filter: blur(0px); transform: scale(1);}
/* reduced motion keeps the fades that explain state, drops everything that moves */@media (prefers-reduced-motion: reduce) { .queue > li, .vinyl-deck .vinyl-label > * { animation: reduced-fade 160ms ease both; }
.crate-item:hover .vinyl, .vinyl-seek:active::-webkit-slider-thumb, .vinyl-seek:active::-moz-range-thumb { transform: none; }
.icon-swap { transition-duration: 1ms; }
@keyframes reduced-fade { from { opacity: 0; } to { opacity: 1; } }}
.vinyl-seek::-webkit-slider-thumb { appearance: none; width: 11px; height: 11px; border-radius: 50%; background-color: var(--text); transition: transform 140ms var(--ease-out);}
.dark .vinyl-seek::-webkit-slider-thumb { background-color: var(--text-dark);}
.vinyl-seek:active::-webkit-slider-thumb { transform: scale(1.25);}
.vinyl-seek::-moz-range-thumb { width: 11px; height: 11px; border: none; border-radius: 50%; background-color: var(--text); transition: transform 140ms var(--ease-out);}
.dark .vinyl-seek::-moz-range-thumb { background-color: var(--text-dark);}
.vinyl-seek:active::-moz-range-thumb { transform: scale(1.25);}
.noise { background-image: url("/noise.png"); background-repeat: repeat; opacity: 0.5; width: 100%; height: 100%; margin:auto; position: absolute; top: 0; z-index: -10;}
.dark .noise { filter: invert(0.6);}
/* table-of-contents links should not land flush against the top edge */.prose :is(h1, h2, h3, h4, h5, h6) { scroll-margin-top: 1.5rem;}
/* the subtitle under a post title reads as a heading and is not one; it keeps the size it had as an h3 */.prose .post-subtitle { font-size: 1.25em; font-weight: 600; line-height: 1.6; color: var(--tw-prose-headings);}
.dark .prose .post-subtitle { color: var(--tw-prose-invert-headings);}
/* section headings became h2 so each post's outline runs h1 -> h2 instead of skipping a level; the type scale stays where it was */.prose > h2 { font-size: 1.25em; font-weight: 600; margin-top: 1.6em; margin-bottom: 0.6em;}
/* the header links keep their spacing and scroll once they stop fitting */.nav-rail { --fade-left: 0px; --fade-right: 0px; overflow-x: auto; overflow-y: hidden; overscroll-behavior-x: contain; scrollbar-width: none; mask-image: linear-gradient( to right, transparent 0, #000 var(--fade-left), #000 calc(100% - var(--fade-right)), transparent 100% ); -webkit-mask-image: linear-gradient( to right, transparent 0, #000 var(--fade-left), #000 calc(100% - var(--fade-right)), transparent 100% );}
.nav-rail::-webkit-scrollbar { display: none;}
.nav-rail.fade-left { --fade-left: 2rem;}
.nav-rail.fade-right { --fade-right: 2rem;}
/* min-width holds the row full-width so the links spread when there is room, and max-content lets it grow past the rail so it scrolls when there is not */.nav-links { display: flex; align-items: center; justify-content: space-around; gap: 1.5rem; min-width: 100%; width: max-content; white-space: nowrap;}
@media (min-width: 640px) { .nav-links { gap: 2rem; }}
/* --- table of contents --- */
/* the rail shows itself in full on arrival, withdraws to a column of ticks, and comes back for a pointer or a scroll; every rule below keys off those flags */.toc { --toc-ease: cubic-bezier(0.16, 1, 0.3, 1);}
/* the labels sit over the page's own margin, so they need something to sit on */.toc-scrim { position: absolute; inset: 0; opacity: 0; background-image: linear-gradient(to left, transparent, var(--bg)); /* the wash has to die out before both edges or it reads as a panel */ mask-image: linear-gradient(to bottom, transparent 0, #000 15%, #000 85%, transparent 100%); -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 15%, #000 85%, transparent 100%); transition: opacity 220ms var(--toc-ease);}
/* the page's noise reads through the dark wash, so the panel stays a vignette */.dark .toc-scrim { background-image: linear-gradient( to left, transparent, color-mix(in oklab, var(--bg-dark) 82%, transparent) );}
.toc[data-open] .toc-scrim { opacity: 1;}
/* the pointer only has to reach the edge of the page, not a 2px line */.toc-reach { position: absolute; inset-block: 0; left: 0; width: 140px; pointer-events: auto;}
.toc-bar { width: 2px; opacity: 0; transition: width 350ms var(--toc-ease), opacity 500ms var(--toc-ease);}
/* it fades in quickly and out slowly: arriving is news, leaving is not */.toc[data-visible] .toc-bar { opacity: 1; transition-duration: 350ms, 200ms;}
.toc[data-open] .toc-bar { width: 3px;}
.toc-track { position: absolute; inset-inline: 0; border-radius: 999px; background-color: color-mix(in oklab, var(--text) 16%, transparent); transition: background-color 300ms ease;}
.dark .toc-track { background-color: color-mix(in oklab, var(--text-dark) 16%, transparent);}
.toc[data-open] .toc-track { background-color: color-mix(in oklab, var(--text) 30%, transparent);}
.dark .toc[data-open] .toc-track { background-color: color-mix(in oklab, var(--text-dark) 30%, transparent);}
.toc-fill { position: absolute; inset-inline: 0; border-radius: 999px; background-color: var(--text); transition: opacity 200ms ease-out;}
.dark .toc-fill { background-color: var(--text-dark);}
.toc-label { opacity: 0; /* they slide out of the rail rather than appearing beside it */ transform: translate(-4px, -50%); pointer-events: none; transition: opacity 220ms var(--toc-ease), transform 260ms var(--toc-ease);}
.toc[data-open] .toc-label { opacity: 1; transform: translate(0, -50%); pointer-events: auto;}
.toc-label a { font-size: 12px; line-height: 1.25; letter-spacing: -0.01em; color: var(--secondary-text); transition: color 200ms ease;}
/* a chapter already behind you keeps the full ink; the ones ahead stay quiet */.toc-label.is-read a { color: var(--text);}
.dark .toc-label.is-read a { color: var(--text-dark);}
/* the palette's pink is a background tint, near enough to white to read as ink only on the dark page; light mode borrows its hue and takes the depth it needs */.toc-label a:hover { color: oklch(from var(--primary) 0.62 0.14 h);}
.dark .toc-label a:hover { color: var(--primary);}
@media (prefers-reduced-motion: reduce) { .toc-scrim, .toc-bar, .toc-track, .toc-fill, .toc-label { transition-duration: 1ms; }
.toc-label { transform: translate(0, -50%); }}
.music-player-collapsed { display: contents;}
.music-full[hidden] { display: none;}
.music-deck,.music-mini { transform-origin: top left;}
.music-mini { position: fixed; z-index: 110; right: max(1.5rem, env(safe-area-inset-right)); bottom: max(1.5rem, env(safe-area-inset-bottom)); display: flex; align-items: center; gap: 0.5rem; width: min(23rem, calc(100vw - 3rem)); padding: 1rem 1rem 1.5rem; border: 1px solid color-mix(in oklab, var(--secondary-text) 25%, transparent); border-radius: 1.25rem; background: var(--bg); box-shadow: 0 8px 32px #0002;}
.dark .music-mini { background: var(--secondary-dark); box-shadow: 0 8px 32px #0004;}
.music-mini-button { display: flex; align-items: center; justify-content: center; flex-shrink: 0; width: 2rem; height: 2rem; border-radius: 0.75rem; color: var(--secondary-text); transition: color 160ms ease, background-color 160ms ease;}
@media (hover: hover) and (pointer: fine) { .music-mini-button:hover { color: var(--text); background-color: color-mix(in oklab, var(--secondary-text) 20%, transparent); }
.dark .music-mini-button:hover { color: var(--text-dark); }}
.music-mini-seek { position: absolute; bottom: 0.75rem; left: 1.5rem; width: calc(100% - 3rem);}
@media (min-width: 1024px) { .music-mini { right: max(3rem, env(safe-area-inset-right)); bottom: max(3rem, env(safe-area-inset-bottom)); }}
.music-deck,.music-mini { view-transition-name: music-player;}
::view-transition-group(music-player) { animation-duration: 480ms; animation-timing-function: var(--ease-out);}
@media (prefers-reduced-motion: reduce) { ::view-transition-group(music-player), ::view-transition-old(music-player), ::view-transition-new(music-player) { animation-duration: 1ms; }}
/* The signup form shares the bottom corners’ page anchor. */.newsletter-dock { --newsletter-control-size: 2.75rem; position: absolute; z-index: 110; left: calc(1.5rem + env(safe-area-inset-left)); bottom: calc(1.5rem + env(safe-area-inset-bottom)); display: flex; align-items: flex-start; padding: 0.25rem; background: transparent;}.newsletter-toggle { display: grid; place-items: center; width: var(--newsletter-control-size); height: var(--newsletter-control-size); flex-shrink: 0; border-radius: 0.85rem;}.newsletter-toggle:hover { opacity: 0.65; }.newsletter-expand { overflow: hidden; padding-block: 4px; margin-block: -4px; }.newsletter-panel { width: min(21rem, calc(100vw - 7rem - env(safe-area-inset-left) - env(safe-area-inset-right))); padding: 0 0.5rem 0 0.25rem; }.newsletter-dock .newsletter-fields :is(input, button) { height: var(--newsletter-control-size); padding-block: 0;}.newsletter-form { width: 100%; min-width: 0; }.newsletter-fields { display: flex; gap: 0.4rem; }.newsletter-fields input,.kaomoji-search { flex: 1; min-width: 0; width: 100%; padding: 0.65rem 0.75rem; border: 1px solid color-mix(in srgb, currentColor 18%, transparent); border-radius: 0.7rem; background: var(--secondary); font: inherit; color: inherit; transition: border-color 160ms ease;}.dark .newsletter-fields input,.dark .kaomoji-search { background: var(--secondary-dark); }.newsletter-fields input::placeholder,.kaomoji-search::placeholder { color: inherit; opacity: 0.55; }
body[data-kaomoji-results] { height: 100dvh; min-height: 0; overflow: hidden;}body[data-kaomoji-results] .site-header { flex-shrink: 0; }body[data-kaomoji-results] > main { flex: 1; min-height: 0; height: auto; overflow: hidden; justify-content: flex-start;}.kaomoji-browser { display: flex; flex-direction: column; width: 100%; max-width: 80rem; height: 100%; min-height: 0; padding: 0 1.25rem 3rem; gap: 1rem;}.kaomoji-toolbar { display: flex; align-items: center; gap: 0.65rem; width: 100%; max-width: 36rem; margin-inline: auto; flex-shrink: 0;}.kaomoji-search-field { position: relative; flex: 1; min-width: 0; }.kaomoji-search-field .kaomoji-search { padding-right: 2.75rem; }.kaomoji-search::-webkit-search-cancel-button { -webkit-appearance: none; }.kaomoji-clear { position: absolute; inset: 0 0.25rem 0 auto; width: 2.5rem; display: grid; place-items: center; color: inherit; opacity: 0.55; border: 1px solid transparent; border-radius: 0.7rem;}.kaomoji-clear:hover { opacity: 1; }.kaomoji-button { display: inline-flex; align-items: center; justify-content: center; padding: 0.65rem 0.75rem; border: 1px solid color-mix(in srgb, currentColor 18%, transparent); border-radius: 0.7rem; background: var(--secondary); font: inherit; color: inherit; white-space: nowrap; transition: border-color 160ms ease;}.dark .kaomoji-button { background: var(--secondary-dark); }.kaomoji-button:hover:not(:disabled) { border-color: currentColor; }.kaomoji-button:disabled { opacity: 0.3; cursor: default; }:is(.kaomoji-clear, .kaomoji-button):focus-visible { outline: none; border-color: currentColor; }.kaomoji-results { display: flex; flex-direction: column; flex: 1; min-height: 0; gap: 0.75rem; }.kaomoji-results-scroll { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; scrollbar-gutter: stable both-edges;}.kaomoji-search:is(:focus, :focus-visible) { outline: none; border-color: currentColor; box-shadow: none;}.newsletter-fields button { display: grid; flex-shrink: 0; place-items: center; padding: 0.65rem 0.8rem; border-radius: 0.7rem; background: var(--text); color: var(--bg); font: inherit; white-space: nowrap;}.newsletter-button-label { grid-area: 1 / 1; display: flex; align-items: center; gap: 0.35rem;}.dark .newsletter-fields button { background: var(--text-dark); color: var(--bg-dark); }.newsletter-fields button:disabled { opacity: 0.65; cursor: default; }.newsletter-fields button:not(:disabled):hover { opacity: 0.8; }.newsletter-message { margin: 0.5rem 0 0; font-size: 0.75rem; line-height: 1.4; opacity: 0.75; }.newsletter-message:empty { display: none; }.newsletter-message[data-error="true"] { opacity: 1; }
.newsletter-form[data-compact] .newsletter-fields { gap: 0; }.newsletter-form[data-compact] .newsletter-fields input { height: 2.75rem; }.newsletter-form[data-compact] .newsletter-fields button { width: 0; height: 2.75rem; padding: 0; margin-inline-start: 0; overflow: hidden; opacity: 0; visibility: hidden; transition: width 280ms var(--ease-out), margin-inline-start 280ms var(--ease-out), opacity 160ms ease, visibility 280ms;}.newsletter-form[data-compact][data-has-email] .newsletter-fields button { width: 2.75rem; margin-inline-start: 0.4rem; opacity: 1; visibility: visible;}@media (prefers-reduced-motion: reduce) { .newsletter-form[data-compact] .newsletter-fields button { transition: none; }}
.page-bottom { position: absolute; inset: auto 0 0; height: 0;}.page-bottom[data-home="true"]:not([data-scrolled]) { top: 100dvh; bottom: auto;}.page-bottom[data-home="true"]:not([data-scrolled]) .page-bottom-corner { visibility: hidden;}.home-screen-corners { position: absolute; inset: 100dvh 0 auto; height: 0; pointer-events: none; transition: opacity 200ms ease-out, transform 200ms ease-out;}.home-activity-link { transition: opacity 200ms ease-out, transform 200ms ease-out, visibility 200ms;}body:has(.page-bottom[data-scrolled]) :is(.home-screen-corners, .home-activity-link) { opacity: 0; transform: translateY(-12px);}body:has(.page-bottom[data-scrolled]) .home-activity-link { visibility: hidden; pointer-events: none;}@media (prefers-reduced-motion: reduce) { .home-screen-corners, .home-activity-link { transition: none; }}body:has(.newsletter-dock):not([data-music-page]) > main { padding-bottom: 6rem; }
.feed-back-top { position: fixed; top: calc(1.5rem + env(safe-area-inset-top)); left: calc(1.5rem + env(safe-area-inset-left)); z-index: 100; display: flex; align-items: center; justify-content: center; width: 2.75rem; height: 2.75rem; border-radius: 0.85rem; background: transparent; opacity: 0; visibility: hidden; transform: translateY(-12px); transition: opacity 200ms ease-out, transform 200ms ease-out, visibility 200ms;}
.feed-back-top-tooltip { position: absolute; left: calc(100% + 0.375rem); padding: 0.25rem 0.5rem; border-radius: 0.5rem; background: var(--secondary); color: var(--secondary-text); font: normal 0.75rem monospace; white-space: nowrap; pointer-events: none; opacity: 0; visibility: hidden; transition: opacity 120ms ease, visibility 120ms;}.dark .feed-back-top-tooltip { background: var(--secondary-dark); }body:has(.page-bottom[data-scrolled]) .feed-back-top:is(:hover, :focus-visible) .feed-back-top-tooltip { opacity: 1; visibility: visible;}body:has(.page-bottom[data-scrolled]) .feed-back-top { opacity: 1; visibility: visible; transform: translateY(0);}body:has(.page-bottom[data-scrolled]) .feed-back-top:hover { opacity: 0.7; }
@media (prefers-reduced-motion: reduce) { .feed-back-top, .feed-back-top-tooltip { transition: none; }}
/* A score replaces the platter and lyrics, leaving the record shelf alongside. */.music-score-deck { justify-content: flex-start; gap: 0.75rem; min-width: 0;}.score-player { --score-accent: oklch(from var(--primary) 0.62 0.14 h); display: flex; flex-direction: column; width: 100%; min-height: 0; flex: 1; }.score-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: 0.6rem 1rem; font-size: 0.8rem; color: var(--secondary-text); }.music-checkbox, .score-zoom { display: flex; align-items: center; gap: 0.6rem; }.music-checkbox { width: fit-content; font-size: 0.8rem; color: var(--secondary-text); cursor: pointer; user-select: none; }.music-checkbox:has(input:disabled) { cursor: default; opacity: 0.5; }.score-zoom button, .score-zoom a { display: grid; place-items: center; width: 2rem; height: 2rem; border-radius: 0.5rem; }.score-zoom button:hover, .score-zoom a:hover { background: var(--primary); color: var(--text); }.score-zoom button:disabled { opacity: 0.3; }.score-zoom span { min-width: 3ch; font-variant-numeric: tabular-nums; }.score-scroll { position: relative; overflow: auto; flex: 1; min-height: 20rem; background: transparent; color: var(--text); }.score-scroll:focus-visible { outline: none; }.dark .score-scroll { color: var(--text-dark); }.dark .score-player { --score-accent: var(--primary); }/* alphaTab emits stems and beams as separately colored SVG shapes. */.score-notation svg { fill: currentColor; }.score-notation svg [fill]:not([fill="none"]):not([fill="transparent"]) { fill: currentColor; }.score-notation svg [stroke]:not([stroke="none"]):not([stroke="transparent"]) { stroke: currentColor; }/* alphaTab reserves this default color for staff lines. */.dark .score-player .score-notation svg [fill="#A5A5A5"] { fill: color-mix(in srgb, var(--text-dark) 55%, transparent); }/* alphaTab measures the full container width, including padding. */.score-notation { min-width: 320px; padding: 1rem 0 3rem; }.score-message { padding: 2rem; text-align: center; }.score-message button { text-decoration: underline; }.score-player .at-cursor-bar { background: color-mix(in srgb, var(--score-accent) 12%, transparent); }.score-player .at-cursor-beat { background: var(--score-accent); width: 3px; }.score-player .at-highlight { color: var(--score-accent); fill: currentColor; }.score-player .at-selection div { background: color-mix(in srgb, var(--score-accent) 12%, transparent); }@media (min-width: 1024px) { .music-score-deck { grid-column: span 2; padding-bottom: 2rem; }}@media (max-width: 1023px) { .score-scroll { height: 65vh; flex: none; } .music-score-deck { padding: 1rem; }}@media (min-width: 768px) { .music-score-deck { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); grid-template-rows: auto auto minmax(0, 1fr); align-items: center; column-gap: 2rem; } .music-score-deck > .music-track-heading { grid-column: 1; grid-row: 1; align-items: flex-start; text-align: left; } .music-score-deck > .music-timeline { grid-column: 1 / -1; grid-row: 2; max-width: none; } .music-score-deck > .music-transport { grid-column: 2; grid-row: 1; } .music-score-deck > .score-player { grid-column: 1 / -1; grid-row: 3; height: 100%; }}.score-scroll > .score-message { position: absolute; inset: 0; z-index: 1; background: transparent; }
.post-link-menu { position: relative; }.post-menu-trigger { display: flex; align-items: center; justify-content: center; width: 24px; height: 24px; border: 1px solid transparent; border-radius: 0.5rem; transition: border-color 160ms ease; }.post-menu-trigger:focus-visible { outline: none; border-color: currentColor; }.post-menu-panel { position: fixed; inset: auto; margin: 0; min-width: 13rem; max-width: calc(100vw - 1rem); padding: 0.375rem; border: 1px solid color-mix(in srgb, currentColor 10%, transparent); border-radius: 1rem; background: var(--secondary); color: var(--text); box-shadow: 0 8px 32px #0002; }.dark .post-menu-panel { background: var(--secondary-dark); color: var(--text-dark); box-shadow: 0 8px 32px #0006; }/* items sit one padding-width inside the panel, so their corners follow its curve */.post-menu-panel :is(button, a) { display: block; width: 100%; text-align: left; padding: 0.75rem; border-radius: 0.625rem; transition: background-color 120ms ease; }.post-menu-panel :is(button, a):is(:hover, :focus-visible) { outline: none; background: var(--primary); }.dark .post-menu-panel :is(button, a):is(:hover, :focus-visible) { background: var(--primary-dark); }