| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233 |
- /*!
- * Spry CSS — a light, no-JavaScript CSS framework of utilities and components.
- *
- * Optional companion to the Spry web application framework.
- * Core + default theme in one file. Themes live in themes/*.css and are
- * activated with <html data-theme="name"> (or the #spry-theme-* radio hooks
- * used by the demo page's CSS-only switcher).
- *
- * Color mode: <html data-mode="light|dark"> (omit for auto).
- */
- /* ==========================================================================
- 1. Default theme — light palette
- --------------------------------------------------------------------------
- Palette blocks use :where() so their specificity is (0,0,0): any active
- theme scope (attribute or radio hook) overrides them, including the dark
- blocks below. Themes can therefore be "fully light" and ignore dark mode.
- ========================================================================== */
- :where(:root) {
- color-scheme: light;
- /* Palette */
- --spry-bg: #f5f7f9;
- --spry-bg-alt: #edf1f5;
- --spry-bg-inset: #eef2f6;
- --spry-surface: #ffffff;
- --spry-surface-hover: #f6f9fc;
- --spry-fg: #1c2733;
- --spry-fg-muted: #5b6b7c;
- --spry-border: #d9e1e9;
- --spry-border-strong: #b9c6d2;
- --spry-accent: #346894;
- --spry-accent-hover: #2a5578;
- --spry-accent-fg: #ffffff;
- --spry-danger: #b3261e;
- --spry-success: #2e7d43;
- --spry-warning: #946200;
- --spry-info: #0b68a3;
- --spry-link: var(--spry-accent);
- --spry-link-hover: var(--spry-accent-hover);
- --spry-backdrop: rgb(15 23 36 / 0.45);
- /* Soft tints are derived — themes only set the base colors above */
- --spry-accent-soft: color-mix(in srgb, var(--spry-accent) 12%, transparent);
- --spry-danger-soft: color-mix(in srgb, var(--spry-danger) 10%, transparent);
- --spry-success-soft: color-mix(in srgb, var(--spry-success) 10%, transparent);
- --spry-warning-soft: color-mix(in srgb, var(--spry-warning) 14%, transparent);
- --spry-info-soft: color-mix(in srgb, var(--spry-info) 10%, transparent);
- --spry-focus-ring: 0 0 0 3px color-mix(in srgb, var(--spry-accent) 25%, transparent);
- /* Typography */
- --spry-font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
- --spry-font-heading: var(--spry-font);
- --spry-font-mono: ui-monospace, "JetBrains Mono", "Fira Code", Menlo, Consolas, monospace;
- --spry-font-size: 1rem;
- --spry-line-height: 1.6;
- --spry-heading-weight: 650;
- --spry-heading-color: var(--spry-fg);
- /* Shape, depth, motion */
- --spry-radius: 0.5rem;
- --spry-radius-sm: 0.3rem;
- --spry-radius-lg: 0.75rem;
- --spry-radius-pill: 999px;
- --spry-border-width: 1px;
- --spry-shadow: 0 1px 2px rgb(16 24 40 / 0.05), 0 1px 3px rgb(16 24 40 / 0.08);
- --spry-shadow-lg: 0 16px 40px -12px rgb(16 24 40 / 0.25);
- --spry-glass: none;
- --spry-transition: 140ms ease;
- --spry-dialog-duration: 180ms;
- --spry-dialog-ease: cubic-bezier(0.2, 0.9, 0.3, 1);
- --spry-dialog-close-duration: 140ms;
- --spry-dialog-close-ease: ease;
- --spry-dialog-in: translateY(0.6rem) scale(0.98);
- /* Spacing scale (0..5) */
- --spry-space-0: 0;
- --spry-space-1: 0.25rem;
- --spry-space-2: 0.5rem;
- --spry-space-3: 1rem;
- --spry-space-4: 1.5rem;
- --spry-space-5: 2.5rem;
- /* Controls (inputs, selects) */
- --spry-control-bg: var(--spry-surface);
- --spry-control-fg: var(--spry-fg);
- --spry-control-border: var(--spry-border);
- --spry-control-radius: var(--spry-radius-sm);
- --spry-control-padding-x: 0.65rem;
- --spry-control-padding-y: 0.45rem;
- /* Buttons */
- --spry-btn-bg: var(--spry-surface);
- --spry-btn-fg: var(--spry-fg);
- --spry-btn-border: var(--spry-border);
- --spry-btn-bg-hover: var(--spry-bg-alt);
- --spry-btn-bg-active: var(--spry-bg-inset);
- /* Cards */
- --spry-card-bg: var(--spry-surface);
- --spry-card-border: var(--spry-border);
- --spry-card-padding: 1.25rem;
- --spry-card-shadow: var(--spry-shadow);
- /* Tables */
- --spry-table-header-bg: var(--spry-bg-alt);
- --spry-table-hover: var(--spry-bg-alt);
- /* Progress */
- --spry-progress-bg: var(--spry-bg-inset);
- --spry-progress-fg: var(--spry-accent);
- --spry-pulse-cap: 0.4em; /* half the default bar height: stadium ends */
- /* Popovers & dialogs */
- --spry-popover-bg: var(--spry-surface);
- --spry-popover-border: var(--spry-border);
- --spry-popover-shadow: var(--spry-shadow-lg);
- accent-color: var(--spry-accent);
- }
- /* --------------------------------------------------------------------------
- Default theme — dark palette.
- Duplicated on purpose: once for forced dark (data-mode="dark" or the
- #spry-mode-dark switcher hook), once for auto dark (system preference,
- unless disabled with data-mode="light" or #spry-mode-light).
- -------------------------------------------------------------------------- */
- :where(:root[data-mode="dark"], html:has(#spry-mode-dark:checked)) {
- color-scheme: dark;
- --spry-bg: #0f141b;
- --spry-bg-alt: #151d27;
- --spry-bg-inset: #1a2330;
- --spry-surface: #161f2b;
- --spry-surface-hover: #1c2734;
- --spry-fg: #dee7f1;
- --spry-fg-muted: #8fa1b5;
- --spry-border: #2a3646;
- --spry-border-strong: #3d4c61;
- --spry-accent: #6ba3d8;
- --spry-accent-hover: #8cbcea;
- --spry-accent-fg: #0b141d;
- --spry-danger: #e5736c;
- --spry-success: #72c08c;
- --spry-warning: #d8a850;
- --spry-info: #6cb6e8;
- --spry-backdrop: rgb(2 6 12 / 0.6);
- --spry-shadow: 0 1px 2px rgb(0 0 0 / 0.3), 0 1px 3px rgb(0 0 0 / 0.4);
- --spry-shadow-lg: 0 16px 40px -12px rgb(0 0 0 / 0.6);
- }
- @media (prefers-color-scheme: dark) {
- :where(:root:not([data-mode="light"]):not(:has(#spry-mode-light:checked)),
- html:has(#spry-mode-auto:checked):not([data-mode="light"])) {
- color-scheme: dark;
- --spry-bg: #0f141b;
- --spry-bg-alt: #151d27;
- --spry-bg-inset: #1a2330;
- --spry-surface: #161f2b;
- --spry-surface-hover: #1c2734;
- --spry-fg: #dee7f1;
- --spry-fg-muted: #8fa1b5;
- --spry-border: #2a3646;
- --spry-border-strong: #3d4c61;
- --spry-accent: #6ba3d8;
- --spry-accent-hover: #8cbcea;
- --spry-accent-fg: #0b141d;
- --spry-danger: #e5736c;
- --spry-success: #72c08c;
- --spry-warning: #d8a850;
- --spry-info: #6cb6e8;
- --spry-backdrop: rgb(2 6 12 / 0.6);
- --spry-shadow: 0 1px 2px rgb(0 0 0 / 0.3), 0 1px 3px rgb(0 0 0 / 0.4);
- --spry-shadow-lg: 0 16px 40px -12px rgb(0 0 0 / 0.6);
- }
- }
- /* ==========================================================================
- 2. Reset & sane defaults
- ========================================================================== */
- *,
- *::before,
- *::after {
- box-sizing: border-box;
- }
- * {
- margin: 0;
- }
- html {
- scroll-behavior: smooth;
- scroll-padding-top: 5rem;
- -webkit-text-size-adjust: 100%;
- text-size-adjust: 100%;
- }
- body {
- font-family: var(--spry-font);
- font-size: var(--spry-font-size);
- line-height: var(--spry-line-height);
- color: var(--spry-fg);
- background: var(--spry-bg);
- -webkit-font-smoothing: antialiased;
- }
- img,
- video,
- svg {
- display: block;
- max-width: 100%;
- height: auto;
- }
- svg:not([width]) {
- width: 1em;
- }
- input,
- button,
- select,
- textarea {
- font: inherit;
- color: inherit;
- }
- button {
- cursor: pointer;
- }
- :where(a) {
- color: var(--spry-link);
- text-underline-offset: 0.2em;
- }
- :where(a:hover) {
- color: var(--spry-link-hover);
- }
- :focus-visible {
- outline: 2px solid var(--spry-accent);
- outline-offset: 2px;
- }
- ::selection {
- background: color-mix(in srgb, var(--spry-accent) 30%, transparent);
- }
- ::placeholder {
- color: var(--spry-fg-muted);
- opacity: 0.8;
- }
- [hidden] {
- display: none !important;
- }
- @media (prefers-reduced-motion: reduce) {
- html {
- scroll-behavior: auto;
- }
- *,
- *::before,
- *::after {
- animation-duration: 0.01ms !important;
- animation-iteration-count: 1 !important;
- transition-duration: 0.01ms !important;
- }
- }
- /* ==========================================================================
- 3. Base typography
- ========================================================================== */
- h1,
- h2,
- h3,
- h4,
- h5,
- h6 {
- font-family: var(--spry-font-heading);
- font-weight: var(--spry-heading-weight);
- color: var(--spry-heading-color);
- line-height: 1.2;
- text-wrap: balance;
- margin-block: 1.6em 0.6em;
- }
- h1,
- .h1 {
- font-size: clamp(1.9rem, 1.4rem + 1.8vw, 2.6rem);
- }
- h2,
- .h2 {
- font-size: clamp(1.5rem, 1.25rem + 1vw, 2rem);
- }
- h3,
- .h3 {
- font-size: 1.35rem;
- }
- h4,
- .h4 {
- font-size: 1.15rem;
- }
- h5,
- .h5 {
- font-size: 1rem;
- }
- h6,
- .h6 {
- font-size: 0.85rem;
- text-transform: uppercase;
- letter-spacing: 0.06em;
- color: var(--spry-fg-muted);
- }
- h1:first-child,
- h2:first-child,
- h3:first-child,
- h4:first-child,
- h5:first-child,
- h6:first-child {
- margin-block-start: 0;
- }
- p,
- ul,
- ol,
- dl,
- blockquote,
- figure,
- pre {
- margin-block: 0 1em;
- }
- :where(li) > ul,
- :where(li) > ol {
- margin-block: 0.25em;
- }
- :where(li + li) {
- margin-block-start: 0.25em;
- }
- :where(dt) {
- font-weight: 600;
- }
- :where(dd) {
- margin-inline-start: 1.5em;
- }
- small {
- font-size: 0.85em;
- }
- b,
- strong {
- font-weight: 700;
- }
- hr {
- border: 0;
- border-top: var(--spry-border-width) solid var(--spry-border);
- margin-block: 2rem;
- }
- blockquote {
- border-inline-start: 3px solid var(--spry-accent);
- padding: 0.25rem 1rem;
- color: var(--spry-fg-muted);
- }
- blockquote > p:last-child {
- margin-bottom: 0;
- }
- :where(code, kbd, samp) {
- font-family: var(--spry-font-mono);
- font-size: 0.9em;
- }
- :where(:not(pre) > code) {
- background: var(--spry-bg-inset);
- border: var(--spry-border-width) solid var(--spry-border);
- border-radius: var(--spry-radius-sm);
- padding: 0.1em 0.35em;
- }
- pre {
- background: var(--spry-bg-inset);
- border: var(--spry-border-width) solid var(--spry-border);
- border-radius: var(--spry-radius);
- padding: 1rem 1.25rem;
- overflow-x: auto;
- line-height: 1.5;
- }
- pre code {
- background: none;
- border: 0;
- padding: 0;
- font-size: 0.875rem;
- }
- kbd {
- background: var(--spry-bg-alt);
- border: var(--spry-border-width) solid var(--spry-border-strong);
- border-bottom-width: 2px;
- border-radius: var(--spry-radius-sm);
- padding: 0.1em 0.4em;
- font-size: 0.8em;
- }
- mark {
- background: var(--spry-warning-soft);
- color: inherit;
- border-radius: 0.2em;
- padding: 0 0.15em;
- }
- abbr[title] {
- text-decoration: underline dotted;
- cursor: help;
- }
- :where(figure) figcaption {
- color: var(--spry-fg-muted);
- font-size: 0.875em;
- margin-top: 0.5rem;
- }
- /* ==========================================================================
- 4. Utilities — layout
- ========================================================================== */
- .container {
- max-width: 72rem;
- margin-inline: auto;
- padding-inline: clamp(1rem, 3vw, 2rem);
- }
- .container-sm {
- max-width: 52rem;
- }
- .container-lg {
- max-width: 96rem;
- }
- .stack {
- display: flex;
- flex-direction: column;
- gap: var(--spry-space-3);
- }
- .cluster {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- gap: var(--spry-space-2);
- }
- .center {
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .with-sidebar {
- display: grid;
- grid-template-columns: minmax(11rem, 16rem) minmax(0, 1fr);
- gap: var(--spry-space-5);
- }
- @media (max-width: 55rem) {
- .with-sidebar {
- grid-template-columns: minmax(0, 1fr);
- }
- }
- .block { display: block; }
- .inline-block { display: inline-block; }
- .inline { display: inline; }
- .hidden { display: none; }
- .visually-hidden {
- position: absolute !important;
- width: 1px;
- height: 1px;
- padding: 0;
- margin: -1px;
- overflow: hidden;
- clip-path: inset(50%);
- white-space: nowrap;
- border: 0;
- }
- .relative { position: relative; }
- .absolute { position: absolute; }
- .fixed { position: fixed; }
- .sticky { position: sticky; }
- .top-0 { top: 0; }
- .right-0 { right: 0; }
- .bottom-0 { bottom: 0; }
- .left-0 { left: 0; }
- .inset-0 { inset: 0; }
- .overflow-auto { overflow: auto; }
- .overflow-hidden { overflow: hidden; }
- .w-25 { width: 25%; }
- .w-50 { width: 50%; }
- .w-75 { width: 75%; }
- .w-100 { width: 100%; }
- .w-auto { width: auto; }
- .h-100 { height: 100%; }
- .mw-100 { max-width: 100%; }
- .min-w-0 { min-width: 0; }
- /* ==========================================================================
- 5. Utilities — spacing
- ========================================================================== */
- .m-0 { margin: var(--spry-space-0); }
- .m-1 { margin: var(--spry-space-1); }
- .m-2 { margin: var(--spry-space-2); }
- .m-3 { margin: var(--spry-space-3); }
- .m-4 { margin: var(--spry-space-4); }
- .m-5 { margin: var(--spry-space-5); }
- .mt-0 { margin-top: var(--spry-space-0); }
- .mt-1 { margin-top: var(--spry-space-1); }
- .mt-2 { margin-top: var(--spry-space-2); }
- .mt-3 { margin-top: var(--spry-space-3); }
- .mt-4 { margin-top: var(--spry-space-4); }
- .mt-5 { margin-top: var(--spry-space-5); }
- .mr-0 { margin-right: var(--spry-space-0); }
- .mr-1 { margin-right: var(--spry-space-1); }
- .mr-2 { margin-right: var(--spry-space-2); }
- .mr-3 { margin-right: var(--spry-space-3); }
- .mr-4 { margin-right: var(--spry-space-4); }
- .mr-5 { margin-right: var(--spry-space-5); }
- .mb-0 { margin-bottom: var(--spry-space-0); }
- .mb-1 { margin-bottom: var(--spry-space-1); }
- .mb-2 { margin-bottom: var(--spry-space-2); }
- .mb-3 { margin-bottom: var(--spry-space-3); }
- .mb-4 { margin-bottom: var(--spry-space-4); }
- .mb-5 { margin-bottom: var(--spry-space-5); }
- .ml-0 { margin-left: var(--spry-space-0); }
- .ml-1 { margin-left: var(--spry-space-1); }
- .ml-2 { margin-left: var(--spry-space-2); }
- .ml-3 { margin-left: var(--spry-space-3); }
- .ml-4 { margin-left: var(--spry-space-4); }
- .ml-5 { margin-left: var(--spry-space-5); }
- .mx-0 { margin-inline: var(--spry-space-0); }
- .mx-1 { margin-inline: var(--spry-space-1); }
- .mx-2 { margin-inline: var(--spry-space-2); }
- .mx-3 { margin-inline: var(--spry-space-3); }
- .mx-4 { margin-inline: var(--spry-space-4); }
- .mx-5 { margin-inline: var(--spry-space-5); }
- .my-0 { margin-block: var(--spry-space-0); }
- .my-1 { margin-block: var(--spry-space-1); }
- .my-2 { margin-block: var(--spry-space-2); }
- .my-3 { margin-block: var(--spry-space-3); }
- .my-4 { margin-block: var(--spry-space-4); }
- .my-5 { margin-block: var(--spry-space-5); }
- .mx-auto { margin-inline: auto; }
- .ml-auto { margin-left: auto; }
- .mt-auto { margin-top: auto; }
- .p-0 { padding: var(--spry-space-0); }
- .p-1 { padding: var(--spry-space-1); }
- .p-2 { padding: var(--spry-space-2); }
- .p-3 { padding: var(--spry-space-3); }
- .p-4 { padding: var(--spry-space-4); }
- .p-5 { padding: var(--spry-space-5); }
- .pt-0 { padding-top: var(--spry-space-0); }
- .pt-1 { padding-top: var(--spry-space-1); }
- .pt-2 { padding-top: var(--spry-space-2); }
- .pt-3 { padding-top: var(--spry-space-3); }
- .pt-4 { padding-top: var(--spry-space-4); }
- .pt-5 { padding-top: var(--spry-space-5); }
- .pr-0 { padding-right: var(--spry-space-0); }
- .pr-1 { padding-right: var(--spry-space-1); }
- .pr-2 { padding-right: var(--spry-space-2); }
- .pr-3 { padding-right: var(--spry-space-3); }
- .pr-4 { padding-right: var(--spry-space-4); }
- .pr-5 { padding-right: var(--spry-space-5); }
- .pb-0 { padding-bottom: var(--spry-space-0); }
- .pb-1 { padding-bottom: var(--spry-space-1); }
- .pb-2 { padding-bottom: var(--spry-space-2); }
- .pb-3 { padding-bottom: var(--spry-space-3); }
- .pb-4 { padding-bottom: var(--spry-space-4); }
- .pb-5 { padding-bottom: var(--spry-space-5); }
- .pl-0 { padding-left: var(--spry-space-0); }
- .pl-1 { padding-left: var(--spry-space-1); }
- .pl-2 { padding-left: var(--spry-space-2); }
- .pl-3 { padding-left: var(--spry-space-3); }
- .pl-4 { padding-left: var(--spry-space-4); }
- .pl-5 { padding-left: var(--spry-space-5); }
- .px-0 { padding-inline: var(--spry-space-0); }
- .px-1 { padding-inline: var(--spry-space-1); }
- .px-2 { padding-inline: var(--spry-space-2); }
- .px-3 { padding-inline: var(--spry-space-3); }
- .px-4 { padding-inline: var(--spry-space-4); }
- .px-5 { padding-inline: var(--spry-space-5); }
- .py-0 { padding-block: var(--spry-space-0); }
- .py-1 { padding-block: var(--spry-space-1); }
- .py-2 { padding-block: var(--spry-space-2); }
- .py-3 { padding-block: var(--spry-space-3); }
- .py-4 { padding-block: var(--spry-space-4); }
- .py-5 { padding-block: var(--spry-space-5); }
- /* ==========================================================================
- 6. Utilities — flex
- ========================================================================== */
- .flex { display: flex; }
- .inline-flex { display: inline-flex; }
- .flex-row { flex-direction: row; }
- .flex-col { flex-direction: column; }
- .flex-col-reverse { flex-direction: column-reverse; }
- .flex-wrap { flex-wrap: wrap; }
- .flex-nowrap { flex-wrap: nowrap; }
- .flex-1 { flex: 1 1 0%; }
- .flex-auto { flex: 1 1 auto; }
- .flex-none { flex: none; }
- .items-start { align-items: flex-start; }
- .items-center { align-items: center; }
- .items-end { align-items: flex-end; }
- .items-stretch { align-items: stretch; }
- .items-baseline { align-items: baseline; }
- .justify-start { justify-content: flex-start; }
- .justify-center { justify-content: center; }
- .justify-end { justify-content: flex-end; }
- .justify-between { justify-content: space-between; }
- .justify-around { justify-content: space-around; }
- .justify-evenly { justify-content: space-evenly; }
- .self-start { align-self: flex-start; }
- .self-center { align-self: center; }
- .self-end { align-self: flex-end; }
- .order-first { order: -1; }
- .order-last { order: 999; }
- /* ==========================================================================
- 7. Utilities — grid
- ========================================================================== */
- .grid {
- display: grid;
- gap: var(--spry-space-3);
- grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--spry-grid-min, 14rem)), 1fr));
- }
- .grid-sm { --spry-grid-min: 9rem; }
- .grid-lg { --spry-grid-min: 20rem; }
- .grid-cols-1 { display: grid; gap: var(--spry-space-3); grid-template-columns: repeat(1, minmax(0, 1fr)); }
- .grid-cols-2 { display: grid; gap: var(--spry-space-3); grid-template-columns: repeat(2, minmax(0, 1fr)); }
- .grid-cols-3 { display: grid; gap: var(--spry-space-3); grid-template-columns: repeat(3, minmax(0, 1fr)); }
- .grid-cols-4 { display: grid; gap: var(--spry-space-3); grid-template-columns: repeat(4, minmax(0, 1fr)); }
- .grid-cols-5 { display: grid; gap: var(--spry-space-3); grid-template-columns: repeat(5, minmax(0, 1fr)); }
- .grid-cols-6 { display: grid; gap: var(--spry-space-3); grid-template-columns: repeat(6, minmax(0, 1fr)); }
- .grid-cols-7 { display: grid; gap: var(--spry-space-3); grid-template-columns: repeat(7, minmax(0, 1fr)); }
- .grid-cols-8 { display: grid; gap: var(--spry-space-3); grid-template-columns: repeat(8, minmax(0, 1fr)); }
- .grid-cols-9 { display: grid; gap: var(--spry-space-3); grid-template-columns: repeat(9, minmax(0, 1fr)); }
- .grid-cols-10 { display: grid; gap: var(--spry-space-3); grid-template-columns: repeat(10, minmax(0, 1fr)); }
- .grid-cols-11 { display: grid; gap: var(--spry-space-3); grid-template-columns: repeat(11, minmax(0, 1fr)); }
- .grid-cols-12 { display: grid; gap: var(--spry-space-3); grid-template-columns: repeat(12, minmax(0, 1fr)); }
- @media (max-width: 45rem) {
- .grid-cols-3,
- .grid-cols-4,
- .grid-cols-5,
- .grid-cols-6,
- .grid-cols-7,
- .grid-cols-8,
- .grid-cols-9,
- .grid-cols-10,
- .grid-cols-11,
- .grid-cols-12 {
- grid-template-columns: repeat(2, minmax(0, 1fr));
- }
- }
- .col-span-1 { grid-column: span 1; }
- .col-span-2 { grid-column: span 2; }
- .col-span-3 { grid-column: span 3; }
- .col-span-4 { grid-column: span 4; }
- .col-span-5 { grid-column: span 5; }
- .col-span-6 { grid-column: span 6; }
- .col-span-7 { grid-column: span 7; }
- .col-span-8 { grid-column: span 8; }
- .col-span-9 { grid-column: span 9; }
- .col-span-10 { grid-column: span 10; }
- .col-span-11 { grid-column: span 11; }
- .col-span-12 { grid-column: span 12; }
- .col-span-full { grid-column: 1 / -1; }
- .gap-0 { gap: var(--spry-space-0); }
- .gap-1 { gap: var(--spry-space-1); }
- .gap-2 { gap: var(--spry-space-2); }
- .gap-3 { gap: var(--spry-space-3); }
- .gap-4 { gap: var(--spry-space-4); }
- .gap-5 { gap: var(--spry-space-5); }
- .gap-x-0 { column-gap: var(--spry-space-0); }
- .gap-x-1 { column-gap: var(--spry-space-1); }
- .gap-x-2 { column-gap: var(--spry-space-2); }
- .gap-x-3 { column-gap: var(--spry-space-3); }
- .gap-x-4 { column-gap: var(--spry-space-4); }
- .gap-x-5 { column-gap: var(--spry-space-5); }
- .gap-y-0 { row-gap: var(--spry-space-0); }
- .gap-y-1 { row-gap: var(--spry-space-1); }
- .gap-y-2 { row-gap: var(--spry-space-2); }
- .gap-y-3 { row-gap: var(--spry-space-3); }
- .gap-y-4 { row-gap: var(--spry-space-4); }
- .gap-y-5 { row-gap: var(--spry-space-5); }
- /* ==========================================================================
- 8. Utilities — text & color
- ========================================================================== */
- .text-xs { font-size: 0.75rem; }
- .text-sm { font-size: 0.875rem; }
- .text-base { font-size: 1rem; }
- .text-lg { font-size: 1.125rem; }
- .text-xl { font-size: 1.25rem; }
- .text-2xl { font-size: 1.5rem; }
- .text-left { text-align: left; }
- .text-center { text-align: center; }
- .text-right { text-align: right; }
- .text-nowrap { white-space: nowrap; }
- .text-muted { color: var(--spry-fg-muted); }
- .text-accent { color: var(--spry-accent); }
- .text-danger { color: var(--spry-danger); }
- .text-success { color: var(--spry-success); }
- .text-warning { color: var(--spry-warning); }
- .text-info { color: var(--spry-info); }
- .fw-light { font-weight: 300; }
- .fw-normal { font-weight: 400; }
- .fw-medium { font-weight: 500; }
- .fw-semibold { font-weight: 600; }
- .fw-bold { font-weight: 700; }
- .fs-italic { font-style: italic; }
- .fs-normal { font-style: normal; }
- .uppercase { text-transform: uppercase; }
- .lowercase { text-transform: lowercase; }
- .capitalize { text-transform: capitalize; }
- .lead {
- font-size: 1.15em;
- line-height: 1.55;
- color: var(--spry-fg-muted);
- }
- .small { font-size: 0.85em; }
- .mono { font-family: var(--spry-font-mono); font-size: 0.92em; }
- .num { font-variant-numeric: tabular-nums; text-align: right; }
- .truncate {
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
- .clamp-2,
- .clamp-3 {
- display: -webkit-box;
- -webkit-box-orient: vertical;
- overflow: hidden;
- }
- .clamp-2 { -webkit-line-clamp: 2; line-clamp: 2; }
- .clamp-3 { -webkit-line-clamp: 3; line-clamp: 3; }
- .measure { max-width: 65ch; }
- .measure-sm { max-width: 45ch; }
- .bg-surface { background: var(--spry-surface); }
- .bg-alt { background: var(--spry-bg-alt); }
- .bg-inset { background: var(--spry-bg-inset); }
- .bg-accent { background: var(--spry-accent); color: var(--spry-accent-fg); }
- .border { border: var(--spry-border-width) solid var(--spry-border); }
- .border-0 { border: 0; }
- .rounded { border-radius: var(--spry-radius); }
- .rounded-sm { border-radius: var(--spry-radius-sm); }
- .rounded-lg { border-radius: var(--spry-radius-lg); }
- .rounded-pill { border-radius: var(--spry-radius-pill); }
- .shadow { box-shadow: var(--spry-shadow); }
- .shadow-lg { box-shadow: var(--spry-shadow-lg); }
- .shadow-none { box-shadow: none; }
- /* ==========================================================================
- 9. Components — topbar & navigation
- ========================================================================== */
- .topbar {
- position: sticky;
- top: 0;
- z-index: 20;
- display: flex;
- align-items: center;
- justify-content: space-between;
- flex-wrap: wrap;
- gap: var(--spry-space-2);
- padding: 0.65rem clamp(1rem, 3vw, 2rem);
- background: var(--spry-surface);
- border-bottom: var(--spry-border-width) solid var(--spry-border);
- backdrop-filter: var(--spry-glass);
- }
- .topbar-brand {
- font-weight: 700;
- font-size: 1.05rem;
- color: var(--spry-fg);
- text-decoration: none;
- letter-spacing: 0.01em;
- }
- .topbar-brand:hover {
- color: var(--spry-accent);
- }
- .nav {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- gap: var(--spry-space-1);
- list-style: none;
- padding: 0;
- margin: 0;
- }
- .nav a {
- display: inline-block;
- padding: 0.35rem 0.75rem;
- border-radius: var(--spry-radius-sm);
- color: var(--spry-fg);
- text-decoration: none;
- font-weight: 500;
- transition: background-color var(--spry-transition), color var(--spry-transition);
- }
- .nav a:hover {
- background: var(--spry-bg-alt);
- color: var(--spry-accent);
- }
- .nav a.active {
- background: var(--spry-accent-soft);
- color: var(--spry-accent);
- font-weight: 600;
- }
- /* Menus with CSS-only submenus */
- .menu,
- .menu ul {
- list-style: none;
- margin: 0;
- padding: 0;
- }
- .menu {
- display: flex;
- flex-direction: column;
- gap: 2px;
- }
- .menu a {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 0.5rem;
- padding: 0.4rem 0.7rem;
- border-radius: var(--spry-radius-sm);
- color: var(--spry-fg);
- text-decoration: none;
- }
- .menu a:hover,
- .menu a.active {
- background: var(--spry-bg-alt);
- color: var(--spry-accent);
- }
- .has-submenu {
- position: relative;
- }
- .has-submenu > a::after {
- content: "▾";
- font-size: 0.75em;
- color: var(--spry-fg-muted);
- }
- .submenu {
- position: absolute;
- top: 100%;
- left: 0;
- z-index: 30;
- min-width: 11rem;
- padding: 0.35rem;
- background: var(--spry-popover-bg);
- border: var(--spry-border-width) solid var(--spry-popover-border);
- border-radius: var(--spry-radius);
- box-shadow: var(--spry-popover-shadow);
- backdrop-filter: var(--spry-glass);
- opacity: 0;
- visibility: hidden;
- transform: translateY(0.3rem);
- transition: opacity var(--spry-transition), transform var(--spry-transition), visibility var(--spry-transition);
- }
- .submenu a,
- .menu a {
- width: 100%;
- }
- .submenu a {
- display: block;
- padding: 0.4rem 0.75rem;
- color: var(--spry-fg);
- text-decoration: none;
- border-radius: var(--spry-radius-sm);
- }
- .submenu a:hover {
- background: var(--spry-accent-soft);
- color: var(--spry-accent);
- }
- .has-submenu:hover > .submenu,
- .has-submenu:focus-within > .submenu {
- opacity: 1;
- visibility: visible;
- transform: none;
- }
- .menu .has-submenu .submenu {
- position: static;
- opacity: 1;
- visibility: visible;
- transform: none;
- border: 0;
- box-shadow: none;
- padding: 0 0 0 1rem;
- margin: 2px 0;
- }
- .menu .has-submenu > a::after {
- content: "▸";
- }
- .menu .has-submenu .submenu a {
- font-size: 0.95em;
- }
- .breadcrumb {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- gap: 0.4rem;
- list-style: none;
- padding: 0;
- margin: 0;
- font-size: 0.9rem;
- }
- .breadcrumb li + li::before {
- content: "/";
- color: var(--spry-fg-muted);
- margin-right: 0.4rem;
- }
- .breadcrumb a {
- color: var(--spry-link);
- text-decoration: none;
- }
- .breadcrumb a:hover {
- text-decoration: underline;
- }
- .breadcrumb [aria-current] {
- color: var(--spry-fg-muted);
- }
- /* ==========================================================================
- 10. Components — tabs (CSS-only, radio based)
- --------------------------------------------------------------------------
- Markup: all radio inputs first (direct children), then .tab-list with one
- label per input (same order), then .tab-panels with one panel per input.
- ========================================================================== */
- .tabs > input[type="radio"] {
- position: absolute;
- opacity: 0;
- pointer-events: none;
- }
- .tab-list {
- display: flex;
- flex-wrap: wrap;
- gap: 0.25rem;
- border-bottom: var(--spry-border-width) solid var(--spry-border);
- }
- .tab-list label {
- padding: 0.5rem 0.9rem;
- border-radius: var(--spry-radius-sm) var(--spry-radius-sm) 0 0;
- cursor: pointer;
- font-weight: 500;
- color: var(--spry-fg-muted);
- transition: background-color var(--spry-transition), color var(--spry-transition), box-shadow var(--spry-transition);
- }
- .tab-list label:hover {
- color: var(--spry-accent);
- background: var(--spry-bg-alt);
- }
- .tab-panels > * {
- display: none;
- padding-top: var(--spry-space-3);
- }
- .tab-panels > :first-child {
- display: block;
- }
- .tabs > input:nth-of-type(1):checked ~ .tab-list label:nth-of-type(1),
- .tabs > input:nth-of-type(2):checked ~ .tab-list label:nth-of-type(2),
- .tabs > input:nth-of-type(3):checked ~ .tab-list label:nth-of-type(3),
- .tabs > input:nth-of-type(4):checked ~ .tab-list label:nth-of-type(4),
- .tabs > input:nth-of-type(5):checked ~ .tab-list label:nth-of-type(5),
- .tabs > input:nth-of-type(6):checked ~ .tab-list label:nth-of-type(6) {
- color: var(--spry-accent);
- font-weight: 600;
- box-shadow: inset 0 -3px 0 var(--spry-accent);
- }
- .tabs > input:nth-of-type(1):checked ~ .tab-panels > :nth-child(1),
- .tabs > input:nth-of-type(2):checked ~ .tab-panels > :nth-child(2),
- .tabs > input:nth-of-type(3):checked ~ .tab-panels > :nth-child(3),
- .tabs > input:nth-of-type(4):checked ~ .tab-panels > :nth-child(4),
- .tabs > input:nth-of-type(5):checked ~ .tab-panels > :nth-child(5),
- .tabs > input:nth-of-type(6):checked ~ .tab-panels > :nth-child(6) {
- display: block;
- }
- .tabs > input:focus-visible ~ .tab-list label {
- outline: 2px solid var(--spry-accent);
- outline-offset: -2px;
- }
- /* ==========================================================================
- 11. Components — buttons
- ========================================================================== */
- /* Bare buttons and submit inputs get the same base look at zero specificity
- (:where), so .btn-* variant classes always win. */
- .btn,
- :where(button, input[type="submit"], input[type="reset"], input[type="button"]) {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- gap: 0.45em;
- padding: var(--spry-control-padding-y) calc(var(--spry-control-padding-x) + 0.15rem);
- border: var(--spry-border-width) solid var(--spry-btn-border);
- border-radius: var(--spry-control-radius);
- background: var(--spry-btn-bg);
- color: var(--spry-btn-fg);
- font-weight: 550;
- font-size: inherit;
- line-height: 1.2;
- text-decoration: none;
- white-space: nowrap;
- cursor: pointer;
- transition: background-color var(--spry-transition), border-color var(--spry-transition),
- color var(--spry-transition), box-shadow var(--spry-transition);
- }
- .btn:hover,
- :where(button, input[type="submit"], input[type="reset"], input[type="button"]):hover {
- background: var(--spry-btn-bg-hover);
- border-color: var(--spry-border-strong);
- }
- .btn:active,
- :where(button, input[type="submit"], input[type="reset"], input[type="button"]):active {
- background: var(--spry-btn-bg-active);
- }
- .btn:focus-visible,
- :where(button, input[type="submit"], input[type="reset"], input[type="button"]):focus-visible {
- outline: 2px solid var(--spry-accent);
- outline-offset: 2px;
- }
- .btn:disabled,
- .btn[aria-disabled="true"],
- :where(button, input[type="submit"], input[type="reset"], input[type="button"]):disabled {
- opacity: 0.55;
- cursor: not-allowed;
- pointer-events: none;
- }
- .btn-primary {
- background: var(--spry-accent);
- border-color: var(--spry-accent);
- color: var(--spry-accent-fg);
- }
- .btn-primary:hover {
- background: var(--spry-accent-hover);
- border-color: var(--spry-accent-hover);
- }
- .btn-danger {
- background: var(--spry-danger);
- border-color: var(--spry-danger);
- color: #fff;
- }
- .btn-danger:hover {
- background: color-mix(in srgb, var(--spry-danger) 85%, black);
- border-color: color-mix(in srgb, var(--spry-danger) 85%, black);
- }
- .btn-outline {
- background: transparent;
- border-color: var(--spry-accent);
- color: var(--spry-accent);
- }
- .btn-outline:hover {
- background: var(--spry-accent-soft);
- border-color: var(--spry-accent-hover);
- color: var(--spry-accent-hover);
- }
- .btn-ghost {
- background: transparent;
- border-color: transparent;
- color: var(--spry-fg);
- }
- .btn-ghost:hover {
- background: var(--spry-bg-alt);
- border-color: transparent;
- }
- .btn-link {
- background: none;
- border: 0;
- color: var(--spry-link);
- text-decoration: underline;
- text-underline-offset: 0.2em;
- padding-inline: 0.25rem;
- }
- .btn-link:hover {
- color: var(--spry-link-hover);
- }
- /* Size modifiers use :is() for (0,2,0) specificity so theme base restyles
- (scoped, (0,1,0)-ish) cannot clobber the small/large padding. */
- :is(.btn, button, input[type="submit"], input[type="reset"], input[type="button"]).btn-sm {
- padding: 0.25rem 0.6rem;
- font-size: 0.85em;
- border-radius: var(--spry-radius-sm);
- }
- :is(.btn, button, input[type="submit"], input[type="reset"], input[type="button"]).btn-lg {
- padding: 0.6rem 1.2rem;
- font-size: 1.1em;
- border-radius: var(--spry-radius);
- }
- .btn-block {
- display: flex;
- width: 100%;
- }
- .btn-group {
- display: inline-flex;
- }
- .btn-group > .btn,
- .btn-group > button {
- border-radius: 0;
- margin-left: calc(-1 * var(--spry-border-width));
- }
- .btn-group > .btn:first-child,
- .btn-group > button:first-child {
- margin-left: 0;
- border-start-start-radius: var(--spry-control-radius);
- border-end-start-radius: var(--spry-control-radius);
- }
- .btn-group > .btn:last-child,
- .btn-group > button:last-child {
- border-start-end-radius: var(--spry-control-radius);
- border-end-end-radius: var(--spry-control-radius);
- }
- /* ==========================================================================
- 12. Components — forms
- ========================================================================== */
- input[type="text"],
- input[type="email"],
- input[type="password"],
- input[type="number"],
- input[type="search"],
- input[type="tel"],
- input[type="url"],
- input[type="date"],
- input[type="time"],
- input[type="datetime-local"],
- input[type="month"],
- input[type="week"],
- input[type="color"],
- select,
- textarea,
- .input {
- width: 100%;
- padding: var(--spry-control-padding-y) var(--spry-control-padding-x);
- background: var(--spry-control-bg);
- color: var(--spry-control-fg);
- border: var(--spry-border-width) solid var(--spry-control-border);
- border-radius: var(--spry-control-radius);
- box-shadow: var(--spry-glass);
- transition: border-color var(--spry-transition), box-shadow var(--spry-transition);
- }
- textarea {
- min-height: 6lh;
- resize: vertical;
- }
- select:not([size]):not([multiple]) {
- width: auto;
- }
- select[multiple] {
- height: auto;
- }
- input[type="color"] {
- width: 3.5rem;
- padding: 0.2rem;
- height: 2.4rem;
- cursor: pointer;
- }
- input[type="search"] {
- border-radius: var(--spry-radius-pill);
- }
- input:hover:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]),
- select:hover,
- textarea:hover {
- border-color: var(--spry-border-strong);
- }
- input:focus:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]),
- select:focus,
- textarea:focus,
- .input:focus {
- outline: none;
- border-color: var(--spry-accent);
- box-shadow: var(--spry-focus-ring);
- }
- input:disabled,
- select:disabled,
- textarea:disabled {
- opacity: 0.55;
- cursor: not-allowed;
- background: var(--spry-bg-inset);
- }
- input[readonly],
- textarea[readonly] {
- background: var(--spry-bg-inset);
- }
- .is-invalid {
- border-color: var(--spry-danger) !important;
- }
- .is-invalid:focus {
- box-shadow: 0 0 0 3px color-mix(in srgb, var(--spry-danger) 25%, transparent) !important;
- }
- .is-valid {
- border-color: var(--spry-success) !important;
- }
- .field {
- display: flex;
- flex-direction: column;
- gap: 0.35rem;
- }
- .field .label,
- .label {
- font-weight: 600;
- font-size: 0.92em;
- }
- .hint {
- font-size: 0.85em;
- color: var(--spry-fg-muted);
- }
- .field .is-invalid ~ .hint {
- color: var(--spry-danger);
- }
- fieldset {
- border: var(--spry-border-width) solid var(--spry-border);
- border-radius: var(--spry-radius);
- padding: 1rem 1.25rem;
- margin: 0 0 1rem;
- min-width: 0;
- }
- legend {
- font-weight: 600;
- padding-inline: 0.4rem;
- }
- /* Checkboxes & radios */
- input[type="checkbox"],
- input[type="radio"] {
- width: 1.05em;
- height: 1.05em;
- flex: none;
- accent-color: var(--spry-accent);
- cursor: pointer;
- }
- .choice {
- display: flex;
- align-items: center;
- gap: 0.5rem;
- cursor: pointer;
- }
- .choices {
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- gap: 0.4rem;
- }
- .choices-inline {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- gap: 0.4rem 1.2rem;
- }
- /* Switch (toggle) */
- .switch {
- display: inline-flex;
- align-items: center;
- gap: 0.6rem;
- cursor: pointer;
- }
- .switch input {
- position: absolute;
- opacity: 0;
- width: 1px;
- height: 1px;
- }
- .switch .track {
- position: relative;
- flex: none;
- width: 2.4rem;
- height: 1.35rem;
- border-radius: var(--spry-radius-pill);
- background: var(--spry-border-strong);
- transition: background-color var(--spry-transition);
- }
- .switch .track::after {
- content: "";
- position: absolute;
- top: 0.175rem;
- left: 0.175rem;
- width: 1rem;
- height: 1rem;
- border-radius: 50%;
- background: var(--spry-surface);
- box-shadow: var(--spry-shadow);
- transition: translate var(--spry-transition);
- }
- .switch input:checked + .track {
- background: var(--spry-accent);
- }
- .switch input:checked + .track::after {
- translate: 1.05rem 0;
- }
- .switch input:focus-visible + .track {
- outline: 2px solid var(--spry-accent);
- outline-offset: 2px;
- }
- .switch input:disabled ~ * {
- opacity: 0.55;
- cursor: not-allowed;
- }
- /* ==========================================================================
- 13. Components — cards
- ========================================================================== */
- .card {
- background: var(--spry-card-bg);
- border: var(--spry-border-width) solid var(--spry-card-border);
- border-radius: var(--spry-radius);
- box-shadow: var(--spry-card-shadow);
- backdrop-filter: var(--spry-glass);
- }
- .card-flat {
- box-shadow: none;
- }
- .card-header,
- .card-body,
- .card-footer {
- padding: var(--spry-card-padding);
- }
- .card-header {
- border-bottom: var(--spry-border-width) solid var(--spry-card-border);
- background: var(--spry-bg-alt);
- border-radius: var(--spry-radius) var(--spry-radius) 0 0;
- }
- .card-footer {
- border-top: var(--spry-border-width) solid var(--spry-card-border);
- background: var(--spry-bg-alt);
- border-radius: 0 0 var(--spry-radius) var(--spry-radius);
- }
- .card-header + .card-body,
- .card-body + .card-footer,
- .card-header:first-child:last-child {
- margin: 0;
- }
- .card-header + .card-body {
- border-radius: 0;
- }
- .card-body + .card-footer {
- border-radius: 0 0 var(--spry-radius) var(--spry-radius);
- }
- .card-title {
- font-size: 1.1rem;
- font-weight: 650;
- margin: 0;
- font-family: var(--spry-font-heading);
- }
- .card-subtitle {
- font-size: 0.875em;
- color: var(--spry-fg-muted);
- margin: 0.15rem 0 0;
- }
- /* ==========================================================================
- 14. Components — alerts & badges
- ========================================================================== */
- .alert {
- display: flex;
- align-items: baseline;
- gap: 0.6rem;
- padding: 0.8rem 1rem;
- border: var(--spry-border-width) solid var(--spry-border);
- border-radius: var(--spry-radius);
- background: var(--spry-surface);
- }
- .alert p {
- margin: 0;
- }
- .alert-danger { background: var(--spry-danger-soft); border-color: color-mix(in srgb, var(--spry-danger) 35%, transparent); color: var(--spry-danger); }
- .alert-success { background: var(--spry-success-soft); border-color: color-mix(in srgb, var(--spry-success) 35%, transparent); color: var(--spry-success); }
- .alert-warning { background: var(--spry-warning-soft); border-color: color-mix(in srgb, var(--spry-warning) 35%, transparent); color: var(--spry-warning); }
- .alert-info { background: var(--spry-info-soft); border-color: color-mix(in srgb, var(--spry-info) 35%, transparent); color: var(--spry-info); }
- .badge {
- display: inline-flex;
- align-items: center;
- gap: 0.3em;
- padding: 0.1em 0.65em;
- border-radius: var(--spry-radius-pill);
- font-size: 0.78em;
- font-weight: 600;
- line-height: 1.6;
- background: var(--spry-accent-soft);
- color: var(--spry-accent);
- white-space: nowrap;
- }
- .badge-danger { background: var(--spry-danger-soft); color: var(--spry-danger); }
- .badge-success { background: var(--spry-success-soft); color: var(--spry-success); }
- .badge-warning { background: var(--spry-warning-soft); color: var(--spry-warning); }
- .badge-info { background: var(--spry-info-soft); color: var(--spry-info); }
- .badge-muted { background: var(--spry-bg-alt); color: var(--spry-fg-muted); border: var(--spry-border-width) solid var(--spry-border); }
- /* ==========================================================================
- 15. Components — tables
- ========================================================================== */
- .table-wrap {
- overflow-x: auto;
- }
- table {
- width: 100%;
- border-collapse: collapse;
- font-size: 0.95em;
- background: var(--spry-surface);
- border: var(--spry-border-width) solid var(--spry-border);
- border-radius: var(--spry-radius);
- }
- caption {
- caption-side: bottom;
- text-align: left;
- color: var(--spry-fg-muted);
- font-size: 0.85em;
- padding-top: 0.5rem;
- }
- th,
- td {
- text-align: left;
- padding: 0.5rem 0.75rem;
- border-bottom: var(--spry-border-width) solid var(--spry-border);
- vertical-align: top;
- }
- thead th {
- background: var(--spry-table-header-bg);
- font-size: 0.75em;
- text-transform: uppercase;
- letter-spacing: 0.06em;
- color: var(--spry-fg-muted);
- border-bottom-width: 2px;
- }
- tbody tr:last-child td,
- tbody tr:last-child th {
- border-bottom: 0;
- }
- .table-striped tbody tr:nth-child(even) {
- background: color-mix(in srgb, var(--spry-bg-alt) 55%, transparent);
- }
- .table-hover tbody tr:hover {
- background: var(--spry-table-hover);
- }
- .table-compact th,
- .table-compact td {
- padding: 0.3rem 0.55rem;
- }
- .table-borderless,
- .table-borderless th,
- .table-borderless td {
- border: 0;
- background: transparent;
- }
- /* ==========================================================================
- 16. Components — progress, meter, spinners
- ========================================================================== */
- progress {
- appearance: none;
- -webkit-appearance: none;
- border: 0;
- width: 100%;
- height: 0.8rem;
- border-radius: var(--spry-radius-pill);
- overflow: hidden;
- background: var(--spry-progress-bg);
- }
- progress::-webkit-progress-bar {
- background: var(--spry-progress-bg);
- border-radius: var(--spry-radius-pill);
- }
- progress::-webkit-progress-value {
- background: var(--spry-progress-fg);
- border-radius: var(--spry-radius-pill);
- transition: width 200ms ease;
- }
- progress::-moz-progress-bar {
- background: var(--spry-progress-fg);
- border-radius: var(--spry-radius-pill);
- }
- /* Indeterminate: a GTK-style "pulse" block bouncing back and forth.
- Chromium ignores author styles on the indeterminate value pseudo
- entirely, so the slug lives on the ELEMENT's background (both engines
- animate element backgrounds reliably) and the track pseudo is made
- transparent so it doesn't paint over it. */
- /* html-prefixed so themed plain `progress { background }` rules (equal or
- lower specificity, loaded later) can never override the indeterminate
- state. The slug is a true stadium — rectangle + two half-disc caps in
- three equally-sized layers (so percentage positions move them in
- lockstep); a single radial would be a lens, which reads pointy. */
- html progress:indeterminate {
- background:
- radial-gradient(var(--spry-pulse-cap) var(--spry-pulse-cap) at var(--spry-pulse-cap) 50%, var(--spry-progress-fg) 97%, transparent 100%) 0% 50% / 25% 100% no-repeat,
- radial-gradient(var(--spry-pulse-cap) var(--spry-pulse-cap) at calc(100% - var(--spry-pulse-cap)) 50%, var(--spry-progress-fg) 97%, transparent 100%) 0% 50% / 25% 100% no-repeat,
- linear-gradient(90deg, transparent 0 var(--spry-pulse-cap), var(--spry-progress-fg) var(--spry-pulse-cap) calc(100% - var(--spry-pulse-cap)), transparent calc(100% - var(--spry-pulse-cap))) 0% 50% / 25% 100% no-repeat,
- var(--spry-progress-bg);
- animation: spry-pulse 1.4s linear infinite alternate;
- }
- html progress:indeterminate::-webkit-progress-bar,
- progress:indeterminate::-webkit-progress-bar {
- background: transparent !important;
- }
- html progress:indeterminate::-moz-progress-bar,
- progress:indeterminate::-moz-progress-bar {
- background: transparent !important;
- }
- .progress-thin {
- height: 0.4rem;
- }
- meter {
- width: 100%;
- height: 1.1rem;
- }
- .spinner {
- display: inline-block;
- width: 1.5rem;
- height: 1.5rem;
- border-radius: 50%;
- border: 0.18em solid var(--spry-border);
- border-top-color: var(--spry-accent);
- animation: spry-spin 0.9s linear infinite;
- vertical-align: -0.25em;
- }
- .spinner-sm { width: 1rem; height: 1rem; border-width: 0.14em; }
- .spinner-lg { width: 2.5rem; height: 2.5rem; border-width: 0.12em; }
- @keyframes spry-spin {
- to { transform: rotate(1turn); }
- }
- @keyframes spry-stripes {
- to { background-position: 1.7rem 0; }
- }
- @keyframes spry-pulse {
- /* three slug layers + the track — identical percentage positions keep
- the stadium parts glued together */
- from { background-position: 0% 50%, 0% 50%, 0% 50%, 0 0; }
- to { background-position: 100% 50%, 100% 50%, 100% 50%, 0 0; }
- }
- /* ==========================================================================
- 17. Components — dialogs
- --------------------------------------------------------------------------
- Native <dialog> (open non-modally with the open attribute, or modally with
- dialog.showModal()). Dialog-like windows without any JavaScript: use
- <div popover> with a popovertarget button (see the popover section).
- ========================================================================== */
- dialog {
- width: min(92vw, 36rem);
- max-height: min(85vh, 100%);
- margin: auto; /* re-assert UA centering — the reset above zeroes margins */
- padding: 0;
- border: var(--spry-border-width) solid var(--spry-popover-border);
- border-radius: var(--spry-radius-lg);
- background: var(--spry-popover-bg);
- color: var(--spry-fg);
- box-shadow: var(--spry-popover-shadow);
- backdrop-filter: var(--spry-glass);
- }
- dialog.sm,
- .dialog-sm { width: min(92vw, 24rem); }
- dialog.lg,
- .dialog-lg { width: min(92vw, 56rem); }
- dialog::backdrop {
- background: var(--spry-backdrop);
- }
- .dialog-titlebar {
- display: flex;
- align-items: center;
- gap: 0.5rem;
- padding: 0.55rem 0.65rem 0.55rem 1rem;
- background: var(--spry-bg-alt);
- border-bottom: var(--spry-border-width) solid var(--spry-border);
- border-radius: var(--spry-radius-lg) var(--spry-radius-lg) 0 0;
- user-select: none;
- }
- .dialog-title {
- font-weight: 650;
- font-size: 0.95rem;
- flex: 1;
- min-width: 0;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
- .dialog-close {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 1.7rem;
- height: 1.7rem;
- flex: none;
- padding: 0;
- border: 0;
- border-radius: var(--spry-radius-sm);
- background: transparent;
- color: var(--spry-fg-muted);
- font-size: 1.1rem;
- line-height: 1;
- cursor: pointer;
- transition: background-color var(--spry-transition), color var(--spry-transition);
- }
- .dialog-close:hover {
- background: var(--spry-danger-soft);
- color: var(--spry-danger);
- }
- .dialog-close:focus-visible {
- outline: 2px solid var(--spry-accent);
- outline-offset: 1px;
- }
- .dialog-body {
- padding: 1.25rem;
- }
- .dialog-footer {
- display: flex;
- justify-content: flex-end;
- flex-wrap: wrap;
- gap: 0.5rem;
- padding: 0.8rem 1.25rem;
- border-top: var(--spry-border-width) solid var(--spry-border);
- background: var(--spry-bg-alt);
- border-radius: 0 0 var(--spry-radius-lg) var(--spry-radius-lg);
- }
- /* Open/close animation. Two-state pattern: the closed state carries the
- "hidden" values and the EXIT timing; the open state carries the ENTRY
- timing — each direction reads the transition of the state it lands in.
- @starting-style makes even a dialog with a static open attribute fade in.
- Browsers without support simply don't animate. */
- dialog,
- [popover] {
- opacity: 0;
- transform: var(--spry-dialog-in);
- transition:
- opacity var(--spry-dialog-close-duration) var(--spry-dialog-close-ease),
- transform var(--spry-dialog-close-duration) var(--spry-dialog-close-ease),
- display var(--spry-dialog-close-duration) allow-discrete,
- overlay var(--spry-dialog-close-duration) allow-discrete;
- }
- dialog[open],
- :popover-open {
- opacity: 1;
- transform: none;
- transition:
- opacity var(--spry-dialog-duration) var(--spry-dialog-ease),
- transform var(--spry-dialog-duration) var(--spry-dialog-ease),
- display var(--spry-dialog-duration) allow-discrete,
- overlay var(--spry-dialog-duration) allow-discrete;
- @starting-style {
- opacity: 0;
- transform: var(--spry-dialog-in);
- }
- }
- dialog::backdrop,
- [popover]::backdrop {
- opacity: 0;
- transition:
- opacity var(--spry-dialog-close-duration) var(--spry-dialog-close-ease),
- display var(--spry-dialog-close-duration) allow-discrete,
- overlay var(--spry-dialog-close-duration) allow-discrete;
- }
- dialog[open]::backdrop,
- :popover-open::backdrop {
- opacity: 1;
- transition:
- opacity var(--spry-dialog-duration) var(--spry-dialog-ease),
- display var(--spry-dialog-duration) allow-discrete,
- overlay var(--spry-dialog-duration) allow-discrete;
- @starting-style {
- opacity: 0;
- }
- }
- /* ==========================================================================
- 18. Components — popovers & menus (no JavaScript)
- --------------------------------------------------------------------------
- Any element with the popover attribute opens/closes via a button with
- popovertarget="id" (or the light-dismiss "auto" behavior by default).
- ========================================================================== */
- [popover] {
- /* re-assert the UA popover centering model (inset 0 + margin auto);
- the reset above zeroes margins, which parks popovers in the top-left */
- margin: auto;
- width: min(92vw, 26rem);
- padding: 1.1rem 1.25rem;
- border: var(--spry-border-width) solid var(--spry-popover-border);
- border-radius: var(--spry-radius-lg);
- background: var(--spry-popover-bg);
- color: var(--spry-fg);
- box-shadow: var(--spry-popover-shadow);
- backdrop-filter: var(--spry-glass);
- }
- [popover]::backdrop {
- background: var(--spry-backdrop);
- }
- /* A popover built from dialog layout parts is a "window": drop the generic
- popover padding so the titlebar/body/footer run flush to the frame. */
- [popover]:has(> .dialog-titlebar, > .dialog-body, > .dialog-footer) {
- padding: 0;
- }
- /* Adwaita-style anchored popovers: give the trigger `.anchor` and the popover
- `.popover-anchor`, and set the same --anchor-name custom property on both.
- The popover drops below the trigger and flips at the viewport edges; its
- backdrop is transparent (menus light-dismiss without a modal scrim).
- Without anchor positioning support, popovers stay centered. */
- .anchor {
- anchor-name: var(--anchor-name, --spry-anchor);
- }
- @supports (position-area: bottom) {
- .popover-anchor {
- position-anchor: var(--anchor-name, --spry-anchor);
- position-area: bottom;
- position-try: flip-block, flip-inline;
- margin: 0.4rem 0 0;
- width: auto;
- max-width: min(92vw, 26rem);
- }
- .popover-anchor::backdrop {
- background: transparent;
- }
- }
- .popover-title {
- font-weight: 650;
- margin-bottom: 0.4rem;
- }
- .popover-menu {
- padding: 0.4rem;
- min-width: 12rem;
- }
- /* Only set display in the open state: an author `display` on a closed
- [popover] overrides the UA's display:none and the menu never hides. */
- .popover-menu:popover-open {
- display: flex;
- flex-direction: column;
- gap: 1px;
- }
- .popover-menu a,
- .popover-menu button {
- display: flex;
- align-items: center;
- gap: 0.6rem;
- width: 100%;
- padding: 0.45rem 0.7rem;
- border: 0;
- border-radius: var(--spry-radius-sm);
- background: none;
- color: var(--spry-fg);
- text-align: left;
- text-decoration: none;
- font-weight: 450;
- }
- .popover-menu a:hover,
- .popover-menu button:hover {
- background: var(--spry-accent-soft);
- color: var(--spry-accent);
- }
- .popover-footer {
- display: flex;
- justify-content: flex-end;
- gap: 0.5rem;
- margin-top: 0.9rem;
- }
- /* ==========================================================================
- 19. Components — expanders (details/summary)
- ========================================================================== */
- details {
- border: var(--spry-border-width) solid var(--spry-border);
- border-radius: var(--spry-radius);
- background: var(--spry-surface);
- }
- details + details {
- margin-top: 0.6rem;
- }
- summary {
- display: flex;
- align-items: center;
- gap: 0.5rem;
- padding: 0.7rem 1rem;
- border-radius: var(--spry-radius);
- font-weight: 600;
- cursor: pointer;
- list-style: none;
- user-select: none;
- transition: background-color var(--spry-transition);
- }
- summary:hover {
- background: var(--spry-bg-alt);
- }
- summary::-webkit-details-marker {
- display: none;
- }
- summary::before {
- content: "";
- width: 0.45rem;
- height: 0.45rem;
- flex: none;
- margin-right: 0.15rem;
- border-right: 2px solid var(--spry-fg-muted);
- border-bottom: 2px solid var(--spry-fg-muted);
- rotate: -45deg;
- transition: rotate var(--spry-transition);
- }
- details[open] > summary::before {
- rotate: 45deg;
- }
- details[open] > summary {
- border-bottom: var(--spry-border-width) solid var(--spry-border);
- border-radius: var(--spry-radius) var(--spry-radius) 0 0;
- }
- details > :not(summary):not(:last-child) {
- padding: 1rem 1rem 0;
- }
- details > :not(summary):last-child {
- padding: 1rem;
- }
- details > *:not(summary) {
- margin: 0;
- }
- .expander-group > details + details {
- margin-top: calc(-1 * var(--spry-border-width));
- border-radius: 0;
- }
- .expander-group > details:first-child {
- border-radius: var(--spry-radius) var(--spry-radius) 0 0;
- }
- .expander-group > details:last-child {
- border-radius: 0 0 var(--spry-radius) var(--spry-radius);
- }
- .expander-group > details:only-child {
- border-radius: var(--spry-radius);
- }
- .expander-group > details[open]:not(:last-child) > summary {
- border-bottom-color: transparent;
- }
- /* ==========================================================================
- 20. Components — hero, avatars, OTP, drawers, pagination
- ========================================================================== */
- .hero {
- display: flex;
- flex-direction: column;
- align-items: center;
- text-align: center;
- gap: var(--spry-space-3);
- padding: calc(var(--spry-space-5) * 1.4) var(--spry-space-3);
- background:
- radial-gradient(60rem 24rem at 50% -8rem, var(--spry-accent-soft), transparent 70%),
- var(--spry-bg-alt);
- border-block: var(--spry-border-width) solid var(--spry-border);
- }
- .hero-compact {
- padding-block: var(--spry-space-4);
- }
- .hero-eyebrow {
- font-size: 0.78rem;
- font-weight: 700;
- letter-spacing: 0.12em;
- text-transform: uppercase;
- color: var(--spry-accent);
- }
- .hero-title {
- font-size: clamp(2rem, 1.2rem + 3vw, 3.25rem);
- max-width: 24ch;
- margin: 0;
- }
- .hero-sub {
- max-width: 52ch;
- color: var(--spry-fg-muted);
- margin: 0;
- }
- .hero-actions {
- display: flex;
- flex-wrap: wrap;
- justify-content: center;
- gap: var(--spry-space-2);
- }
- .avatar {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 2.5rem;
- height: 2.5rem;
- flex: none;
- border-radius: 50%;
- background: var(--spry-accent-soft);
- color: var(--spry-accent);
- font-weight: 650;
- font-size: 0.9rem;
- letter-spacing: 0.02em;
- overflow: hidden;
- user-select: none;
- }
- .avatar img {
- width: 100%;
- height: 100%;
- object-fit: cover;
- }
- .avatar-sm { width: 1.75rem; height: 1.75rem; font-size: 0.68rem; }
- .avatar-lg { width: 3.5rem; height: 3.5rem; font-size: 1.2rem; }
- .avatar-group {
- display: inline-flex;
- align-items: center;
- }
- .avatar-group .avatar {
- border: 2px solid var(--spry-bg);
- margin-left: -0.65rem;
- box-shadow: none;
- }
- .avatar-group .avatar:first-child {
- margin-left: 0;
- }
- /* OTP / verification-code input. One character per field; moving focus
- automatically takes one line of JS in apps that want it. */
- .otp {
- display: inline-flex;
- align-items: center;
- gap: var(--spry-space-2);
- }
- .otp input {
- width: 2.9rem;
- height: 3.4rem;
- padding: 0;
- text-align: center;
- font-family: var(--spry-font-mono);
- font-size: 1.35rem;
- line-height: 1;
- }
- .otp-separator {
- color: var(--spry-fg-muted);
- font-weight: 600;
- }
- /* Drawers: popover-based side panels, no JavaScript. The slide direction
- comes from the same --spry-dialog-in variable the dialog animation reads. */
- .drawer-left,
- .drawer-right {
- width: min(22rem, 90vw);
- height: 100dvh;
- padding: 0;
- margin: 0;
- border-radius: 0;
- border-block: 0;
- box-shadow: var(--spry-shadow-lg);
- }
- .drawer-left {
- inset: 0 auto 0 0;
- border-right-width: var(--spry-border-width);
- --spry-dialog-in: translateX(-100%);
- }
- .drawer-right {
- inset: 0 0 0 auto;
- border-left-width: var(--spry-border-width);
- --spry-dialog-in: translateX(100%);
- }
- .drawer-left .dialog-titlebar,
- .drawer-right .dialog-titlebar {
- border-radius: 0;
- }
- .drawer-left .dialog-footer,
- .drawer-right .dialog-footer {
- border-radius: 0;
- }
- /* Pagination */
- .pagination {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- gap: 0.3rem;
- list-style: none;
- padding: 0;
- margin: 0;
- }
- .pagination a,
- .pagination span {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- min-width: 2.2rem;
- height: 2.2rem;
- padding: 0 0.55rem;
- border: var(--spry-border-width) solid transparent;
- border-radius: var(--spry-radius-sm);
- color: var(--spry-fg);
- text-decoration: none;
- font-weight: 500;
- transition: background-color var(--spry-transition), color var(--spry-transition);
- }
- .pagination a:hover {
- background: var(--spry-bg-alt);
- color: var(--spry-accent);
- }
- .pagination [aria-current="page"] {
- background: var(--spry-accent);
- color: var(--spry-accent-fg);
- font-weight: 650;
- }
- .pagination [aria-disabled="true"] {
- opacity: 0.45;
- pointer-events: none;
- }
|