Something went wrong. Try again.
Paper Mario mod hosting site - AT Protocol appview starhaven.dev
Something went wrong. Try again.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608/* Use to apply color to an element and its children. Supported by many components. Each says what color text on it is: white where that reads, --ink where the color is too light for it. */.accent-red { --accent: var(--red); --on-accent: white;}.accent-yellow { --accent: var(--gold); --on-accent: var(--on-gold);}.accent-green { --accent: var(--green); --on-accent: var(--ink);}.accent-blue { --accent: var(--blue); --on-accent: white;}.accent-mauve { --accent: var(--mauve); --on-accent: white;}
/* A popover menu */.menu { --accent: var(--text);
margin: var(--space-2) 0 0; padding: var(--space-2) 0; border: 2px solid var(--outline); border-radius: var(--space-3); background: var(--paper); box-shadow: 0 3px 0 var(--outline); color: var(--text);
position: fixed; inset: auto; top: anchor(bottom); right: anchor(right);
font-size: var(--text-sm);
& > ul { list-style: none; display: flex; flex-direction: column; margin: 0; padding: 0; min-inline-size: 10rem; }
& a, & button { display: flex; align-items: center; justify-content: flex-start; gap: var(--space-2); inline-size: 100%; min-block-size: 2.75rem; padding: var(--space-1) var(--space-4); border: 0; border-radius: 0; background: none; box-shadow: none; color: oklch(from var(--accent) min(l, 0.5) c h); font-weight: 400; text-align: start; text-decoration: none; cursor: pointer;
&:hover { background: var(--crust); }
& svg { inline-size: 1.1em; block-size: 1.1em; flex-shrink: 0; } }}
/* A callout. */[role="alert"] { padding: var(--space-3) var(--space-4); border-radius: 0; border: 4px solid var(--accent); background: oklch(from var(--accent) l c h / 0.15); margin-block-end: var(--space-4);
& > h2 { margin: 0; margin-bottom: var(--space-2); font-size: var(--text-lg); font-weight: 600; }}
/* A field's error message. */.error { color: var(--red);}
/* Off-screen but still in the accessibility tree/tab order, unlike the `hidden` attribute - for content (or a control, like a file input triggered by a styled label) that only needs to be reachable, not seen. */.visually-hidden { position: absolute; inline-size: 1px; block-size: 1px; padding: 0; margin: -1px; overflow: hidden; /* stylelint-disable-next-line plugin/no-unsupported-browser-features -- Only clip-path's url() and box values are partly supported; inset() works everywhere. */ clip-path: inset(50%); white-space: nowrap; border: 0;}
.error-list { list-style-type: none; margin: 0; padding: 0;}
.form-item { display: flex; flex-direction: column; gap: var(--space-1); margin: 0; margin-block-start: var(--space-4); border: none; padding: 0;
/* Not :has(.error) - every field carries an empty error slot, so the mark has to depend on the slot being shown rather than existing. */ &:has(.error:not([hidden])) { border-inline-start: 3px solid var(--red); padding-inline-start: var(--space-3); }
/* :not(.button): a label can also be a button in disguise (a styled file input trigger), and that should look and behave like any other .button, not like a field's own label. */ & label:not(.button), & legend { display: block; inline-size: 100%; font-size: var(--text-sm); font-weight: 700; padding: 0; }
& input, & textarea, & select { padding: 1px var(--space-2); border: 2px solid var(--surface2); border-radius: var(--space-1); background: var(--mantle); box-shadow: inset 0 2px 0 var(--surface0); }
& textarea { min-block-size: 6em; /* stylelint-disable-next-line plugin/no-unsupported-browser-features -- iOS Safari only can't resize it, which costs nothing. */ resize: vertical; }
& .hint { color: var(--grey); text-wrap: pretty; }}
/* A text input with an icon fixed inside its left edge. */.input-icon { position: relative; display: flex; align-items: center;
& svg { position: absolute; inset-inline-start: var(--space-2); inline-size: var(--space-4); block-size: var(--space-4); color: var(--grey); pointer-events: none; }
& input { inline-size: 100%; padding-inline-start: var(--space-6); }}
/* One checkbox and its label, side by side. */.checkbox-item { display: flex; align-items: center; gap: var(--space-2);
& input[type="checkbox"] { inline-size: var(--space-4); block-size: var(--space-4); accent-color: var(--accent); }}
/* A fieldset of radio buttons, one .radio-item per option. */.radio-group { & .radio-item { display: flex; align-items: center; gap: var(--space-2); margin-block-start: var(--space-1); }
& input[type="radio"] { inline-size: var(--space-4); block-size: var(--space-4); accent-color: var(--accent); }
& label { font-size: inherit; font-weight: 400; }}
/* The page content around a form page, which fills the space between the nav and the footer so the form page's background does too. */#page-content:has(> .form-page) { flex: 1; display: flex; flex-direction: column;}
/* A page with a form in the center of it, on a card drawn like a mod's. */.form-page { flex: 1; display: grid; place-items: center;
/* A component that drives its form takes the form's place. */ & > share-form { display: block; width: 100%; max-inline-size: 32rem; }
& > :is(form, section), & > share-form > form { width: 100%; max-inline-size: 32rem; margin-block: var(--space-6); padding: var(--space-6); border: 3px solid var(--outline); border-radius: 0.875rem; background: var(--paper); box-shadow: 0 6px 0 var(--outline);
@media (max-width: 40rem) { padding: var(--space-4); border-radius: var(--space-3); box-shadow: 0 5px 0 var(--outline); } }
@media (max-width: 40rem) { padding-inline: var(--space-4); }}
/* The sign-in and sign-up cards, whose main buttons take the card's full width. */.auth-card { /* Blue like most links, rather than the dark gold the yellow card would give them. */ & a:not(.button) { color: var(--blue); }
/* Wider than most buttons, so taller too. */ & :is(.auth-continue, .bluesky-button) { inline-size: 100%; margin-block-start: var(--space-4); padding-block: var(--space-2); }
& .bluesky-button { --accent: var(--bluesky); --on-accent: white; margin-block-start: 0;
& svg { inline-size: 1.25em; block-size: 1.25em; } }}
/* The small print under a card's main button. */.auth-terms { margin: var(--space-3) 0 0; color: var(--grey); font-size: var(--text-sm); text-align: center; text-wrap: pretty;}
/* At the foot of a card, the way to the other of signing in and signing up. */.auth-switch { margin: var(--space-6) 0 0; color: var(--grey); font-size: var(--text-sm); text-align: center;}
/* A text input with fixed text after what's typed, such as a handle's domain, drawn as part of the field. */.input-suffix { display: flex; align-items: stretch;
& > input { flex: 1; min-inline-size: 0; border-inline-end: none; border-start-end-radius: 0; border-end-end-radius: 0; }
& > span { display: flex; align-items: center; padding: 0 var(--space-2); border: 2px solid var(--surface2); border-inline-start: none; border-start-end-radius: var(--space-1); border-end-end-radius: var(--space-1); background: var(--mantle); box-shadow: inset 0 2px 0 var(--surface0); color: var(--grey); }}
/* A field's label with a (?) at the other end, opening a popover that says more about the field. */.label-row { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2);}
.help-button { anchor-name: --help-button; padding: 0; border: none; box-shadow: none; background: none; color: var(--grey);
& svg { inline-size: 1.25rem; block-size: 1.25rem; }
&:hover { color: var(--text); }
&:active { transform: none; }}
.help-popover { position: fixed; inset: auto; top: anchor(bottom); right: anchor(right); max-inline-size: min(20rem, calc(100vw - 2rem)); margin: var(--space-2) 0 0; padding: var(--space-3) var(--space-4); border: 2px solid var(--outline); border-radius: var(--space-3); background: var(--paper); box-shadow: 0 3px 0 var(--outline); color: var(--text); font-size: var(--text-sm); position-anchor: --help-button;
& > p { margin: var(--space-1) 0 0; text-wrap: pretty; }
& .handle-example { color: var(--example); }}
/* A rule across the card with "or" in a gap in the middle of it. */.auth-or { display: flex; align-items: center; gap: var(--space-3); margin-block: var(--space-4); color: var(--grey); font-size: var(--text-sm);
&::before, &::after { content: ""; flex: 1; border-block-start: 2px solid var(--surface1); }}
/* Where a form's buttons sit. */.form-actions { display: flex; justify-content: flex-start; margin-block-start: var(--space-4); gap: var(--space-2);}
.button,button { display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2); padding: calc(var(--space-1) + 1px) var(--space-4); border-radius: var(--space-1);
border: 2px solid oklch(from var(--accent) calc(l - 0.2) c h); box-shadow: 0 2px 0 oklch(from var(--accent) calc(l - 0.2) c h);
background: var(--accent);
color: var(--on-accent); font: inherit; font-weight: 700; text-align: center; cursor: pointer; transition: transform 0.05s ease, box-shadow 0.05s ease;
text-decoration: none;
/* Drawn in the input fields' grey, which it often sits beside. */ &.secondary { border-color: var(--surface2); box-shadow: 0 2px 0 var(--surface2); background: transparent; color: var(--text); }
&:active { box-shadow: none; transform: translateY(2px); }
&:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
/* An SVG with only a viewBox has no intrinsic size, so it needs one giving to it. The gap above already spaces it from the label. */ & svg { inline-size: 1.1em; block-size: 1.1em; flex-shrink: 0; }}
/* The <upload-dialog> component's inner dialog. Previously scoped by shadow DOM; now in light DOM so htmx/commandfor can reach its contents. */.upload-dialog { padding: var(--space-6); inline-size: min(40rem, 92vw); border: 0; background: var(--base) url("/assets/textures/subtle-paper-pulp-repeat.webp") repeat; background-blend-mode: multiply; box-shadow: var(--space-2) var(--space-2) 0 -2px var(--outline);
&::backdrop { background: oklch(from var(--outline) l c h / 0.5); backdrop-filter: blur(4px); }
& > h2 { margin: 0 0 var(--space-4); }
& progress { inline-size: 100%; margin-block-start: var(--space-3); }}
/* The bar at the top of almost every page: the logo and controls, then a row of binder tabs sitting on a rule. */.site-header { position: relative; inline-size: 100%; max-inline-size: 75rem; margin-inline: auto; padding: var(--space-4) var(--space-6) 0;
/* What the page sits on, behind its tabs, from one side of the window to the other. */ &::before { content: ""; position: absolute; z-index: -1; inset: 0 calc(50% - 50vw); background-color: var(--crust); background-image: url("/assets/textures/subtle-paper-pulp-repeat.webp"); background-blend-mode: multiply; }
@media (max-width: 40rem) { padding: 0.625rem var(--space-3); border-block-end: 3px solid var(--outline); }}
/* The header of a page about one thing, such as signing in: the logo on its own, above a rule the width of the window. */.site-header-bare { padding-block-end: var(--space-4); border-block-end: 3px solid var(--outline); border-image: linear-gradient(var(--outline) 0 0) 0 0 1 0 / 0 0 3px 0 / 0 100vw;
@media (max-width: 40rem) { padding-block-end: 0.625rem; }}
.site-header-bar { display: flex; align-items: center; gap: var(--space-3);
& > * { margin: 0; }
@media (max-width: 40rem) { gap: var(--space-2); }}
/* Only at one size or the other. */.wide-only { @media (max-width: 40rem) { display: none !important; }}
.narrow-only { @media (min-width: 40.0625rem) { display: none !important; }}
/* The star and the STAR HAVEN logotype, glowing like the star does. */.site-logo { display: flex; align-items: center; gap: 0.625rem; margin-inline-end: auto;
& img { /* Sized by the block-size below, at the width and height attributes' ratio, so the header takes its shape before they load. */ inline-size: auto; filter: drop-shadow(0 0 0.375rem oklch(93% 0.1 92)); }
& .site-logo-star { block-size: 2.5rem; }
& .site-logo-text { block-size: 2.25rem; }
@media (max-width: 40rem) { gap: var(--space-2);
& .site-logo-star { block-size: 1.875rem; }
& .site-logo-text { block-size: 1.625rem; } }}
/* A 44px circle holding an icon. */.round-button { display: grid; place-items: center; flex-shrink: 0; inline-size: 2.75rem; block-size: 2.75rem; min-block-size: 0; padding: 0; border: 2px solid var(--outline); border-radius: 50%; box-shadow: none; background: var(--paper); color: var(--text);
&:active { transform: none; }
& svg { inline-size: 1.25rem; block-size: 1.25rem; }}
/* A rounded button for the bar's own actions, with a hard drop shadow. */.pill-button { display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2); block-size: 2.75rem; padding: 0 1.25rem; border: 2px solid var(--outline); border-radius: 999px; box-shadow: 0 3px 0 var(--outline); background: var(--paper); color: var(--text); font-size: 1.0625rem; font-weight: 400;
&:any-link { text-decoration: none; }
&:hover { color: var(--text); }
&:active { box-shadow: none; transform: translateY(3px); }
& svg { inline-size: 1.1em; block-size: 1.1em; }}
.login-button { border-color: oklch(from var(--gold) calc(l - 0.2) c h); box-shadow: 0 3px 0 oklch(from var(--gold) calc(l - 0.2) c h); background: var(--gold);
&, &:hover { color: var(--on-gold); }}
.discord-button { border-color: oklch(from var(--discord) calc(l - 0.2) c h); background: var(--discord);
&.pill-button { box-shadow: 0 3px 0 oklch(from var(--discord) calc(l - 0.2) c h); }
&, &:hover { color: white; }}
.account-button { anchor-name: --account-trigger; overflow: hidden;
& img { inline-size: 100%; block-size: 100%; object-fit: cover; }}
#account-menu { position-anchor: --account-trigger;}
.menu-button { background: var(--gold); color: var(--on-gold);}
/* Folded up into its round button until it has focus or holds a search. */.nav-search { display: flex; align-items: center; block-size: 2.75rem; border: 2px solid var(--outline); border-radius: 999px; background: var(--paper);
& input[type="search"] { inline-size: 0; min-inline-size: 0; padding: 0; border: 0; background: none; color: var(--text); font-size: var(--text-base); opacity: 0; transition: inline-size 0.15s ease, padding 0.15s ease;
&:focus-visible { outline: none; } }
& button { display: grid; place-items: center; inline-size: 2.5rem; block-size: 2.5rem; min-block-size: 0; padding: 0; border: 0; border-radius: 50%; box-shadow: none; background: none; color: var(--text);
&:active { transform: none; }
& svg { inline-size: 1.25rem; block-size: 1.25rem; } }
&:focus-within, &:has(input:not(:placeholder-shown)) { & input[type="search"] { inline-size: min(18rem, 40vw); padding-inline: var(--space-4) 0; opacity: 1; } }
/* The box's own ring, since its input has none. */ &:has(input:focus-visible) { outline: var(--ring); outline-offset: var(--ring-offset); }
@media (max-width: 40rem) { &:focus-within, &:has(input:not(:placeholder-shown)) { & input[type="search"] { inline-size: calc(100vw - 10rem); } } }}
/* On a phone, an open search box takes the wordmark's room. */.site-header-bar:has(.nav-search:focus-within) .site-logo-text { @media (max-width: 40rem) { display: none; }}
/* Binder tabs on a rule that runs the width of the window. The current one is the page's own paper, and joins it by covering the rule. */.site-tabs { display: flex; align-items: flex-end; gap: 0.375rem; margin-block-start: 1.125rem; padding-inline-start: 1.25rem; border-block-end: 3px solid var(--outline); border-image: linear-gradient(var(--outline) 0 0) 0 0 1 0 / 0 0 3px 0 / 0 100vw;
/* As tall as a current tab, so the header is the same height on a page without one. */ &::before { content: "\200b"; margin-block-end: -3px; margin-inline-end: -0.375rem; padding-block: 0.75rem 0.8125rem; border-block-start: 3px solid transparent; font-size: 1.25rem; }}
.site-tab { padding: 0.5625rem 1.375rem; border: 3px solid var(--outline); border-block-end: none; border-radius: var(--space-3) var(--space-3) 0 0; background: var(--accent); color: var(--on-accent); font-size: 1.125rem;
&:any-link { text-decoration: none; }
&:hover { color: var(--on-accent); }
&[aria-current="page"] { margin-block-end: -3px; padding: 0.75rem 1.625rem 0.8125rem; background-color: var(--base); background-image: url("/assets/textures/subtle-paper-pulp-repeat.webp"); background-blend-mode: multiply; color: oklch(from var(--accent) min(l, 0.5) c h); font-size: 1.25rem; }}
/* The homepage's intro is purple paper, so the current tab over it is too. */body:has(.home-intro) .site-tab[aria-current="page"] { background-color: var(--accent); color: var(--on-accent);}
/* The phone menu: a sheet of paper sliding in from the right. */.site-menu { margin: 0 0 0 auto; padding: 0; inline-size: calc(100% - 3.375rem); max-inline-size: 24rem; block-size: 100dvh; max-block-size: none; border: 0; border-inline-start: 3px solid var(--outline); background-color: var(--base); background-image: url("/assets/textures/subtle-paper-pulp-repeat.webp"); background-blend-mode: multiply; box-shadow: -8px 0 0 oklch(from var(--outline) l c h / 0.3); color: var(--text);
&::backdrop { background: oklch(from var(--outline) l c h / 0.4); }
&[open] { animation: site-menu-in 0.2s ease-out; }
& form { display: flex; justify-content: flex-end; }}
@keyframes site-menu-in { from { transform: translateX(100%); }}
.site-menu-sheet { display: flex; flex-direction: column; gap: 0.875rem;
& > * { margin: 0; }
min-block-size: 100%; padding: var(--space-3) var(--space-4) 1.25rem;
& nav { display: flex; flex-direction: column; gap: 0.875rem; }}
.site-menu-tab { display: flex; align-items: center; block-size: 3.5rem; padding: 0 1.125rem; border: 3px solid var(--outline); border-radius: var(--space-3); background: var(--accent); box-shadow: 0 4px 0 var(--outline); color: var(--on-accent); font-size: 1.375rem;
&:any-link { text-decoration: none; }
&:hover { color: var(--on-accent); }
&[aria-current="page"] { background: var(--paper); color: oklch(from var(--accent) min(l, 0.5) c h); }}
.site-menu-actions { display: flex; flex-direction: column; gap: 0.625rem; margin-block-start: auto;
& > * { margin: 0; }
& > .pill-button { block-size: 3.25rem; font-size: 1.1875rem; }
& > .discord-button { block-size: 3rem; box-shadow: none; font-size: 1.0625rem; }}
.site-menu-account { list-style: none; padding: var(--space-2) 0; border: 2px solid var(--outline); border-radius: var(--space-3); background: var(--paper);
& a, & button { display: flex; align-items: center; justify-content: flex-start; gap: var(--space-2); inline-size: 100%; min-block-size: 2.75rem; padding: 0 var(--space-4); border: 0; border-radius: 0; box-shadow: none; background: none; color: var(--text); font-size: 1.0625rem; font-weight: 400;
&:any-link { text-decoration: none; } }
& button { color: oklch(from var(--accent) min(l, 0.5) c h); }
& svg { inline-size: 1.1em; block-size: 1.1em; }}
.site-menu-legal { display: flex; justify-content: center; gap: 1.125rem; margin-block-start: var(--space-1); font-size: var(--text-sm); color: var(--grey);}
.card { padding: var(--space-4); border: 3px solid var(--outline); border-radius: var(--space-3); background: var(--paper); box-shadow: 0 6px 0 var(--outline);}
/* Images on the left, the card on the right, details below both - like a Scratch project page. */.mod-listing { margin: var(--space-6) auto; padding-inline: var(--space-4); width: 100%; max-width: 64rem;
display: grid; grid-template-columns: 2fr 1fr; grid-template-areas: "media card" "details details"; gap: var(--space-4);
@media (max-width: 40rem) { grid-template-columns: 1fr; grid-template-areas: "media" "card" "details"; }
& > media-carousel, & > .mod-image-placeholder { grid-area: media; }
/* What the mod is and how to get it at the top, who made it at the bottom, whatever the card's height. */ & > .card { display: grid; grid-area: card; grid-template-rows: 1fr auto; }
& > .mod-details { grid-area: details; }}
/* Who made a mod: their avatar beside their handle, under the title. */.mod-author { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; gap: var(--space-2); margin-block: var(--space-2) var(--space-3); color: var(--grey); text-decoration: none;
&:hover .mod-author-name { text-decoration: underline; }
& img { inline-size: 1.75rem; block-size: 1.75rem; border-radius: 50%; object-fit: cover; }
& .mod-author-names { display: flex; flex-direction: column; min-inline-size: 0; }
& :is(.mod-author-name, .mod-author-handle) { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
& .mod-author-handle { color: var(--example); font-size: var(--text-sm); }}
.mod-game { color: var(--example); font-size: var(--text-sm);}
.mod-summary { align-self: start;}
.mod-credits-section { align-self: end;}
.mod-credits-heading { margin-block: var(--space-4) 0; color: var(--example); font-size: var(--text-sm);}
.mod-credits { display: grid; gap: var(--space-2); margin-block: var(--space-2) 0; padding: 0; list-style: none;
& .mod-credit { display: flex; align-items: center; gap: var(--space-2); color: var(--grey); }
& img { inline-size: 1.75rem; block-size: 1.75rem; border-radius: 50%; object-fit: cover; }
/* The name and handle on one line, the role on its own beneath. */ & .mod-credit-names { display: flex; flex-direction: column; min-inline-size: 0; overflow-wrap: anywhere; }
/* The name and the handle stay on one line, the handle giving way first. */ & .mod-credit-who { display: flex; align-items: baseline; gap: 0 var(--space-2); min-inline-size: 0; max-inline-size: 100%;
& > * { min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
& .mod-credit-name { flex-shrink: 0; max-inline-size: 100%; } }
& a.mod-credit-name { color: inherit; text-decoration: none;
&:hover { text-decoration: underline; } }
& :is(.mod-credit-handle, .mod-credit-role) { color: var(--example); font-size: var(--text-sm); }}
.mod-image-placeholder { width: 100%; aspect-ratio: 4 / 3; border-radius: var(--space-3); overflow: hidden;}
.mod-details { margin-block: var(--space-3);}
/* What people write about their mods, rendered from Markdown. */.markdown { line-height: 1.6; overflow-wrap: break-word;
& > * + * { margin-block-start: var(--space-3); }
& :is(ul, ol) { padding-inline-start: var(--space-5); }
& blockquote { padding-inline-start: var(--space-3); border-inline-start: 4px solid var(--grey); }
& pre { overflow-x: auto; }
& :is(th, td) { padding: var(--space-1) var(--space-2); border: 1px solid var(--grey); }
& table { border-collapse: collapse; }}
/* A .button placed directly in a .form-item is otherwise still a child of its column flex, which stretches it to the full row's width by default. */.form-item > .button { align-self: flex-start;}
/* One box for a version's download: a link typed into it, or a file uploaded through or dropped on it, which then takes the link's place. */download-fields { display: flex;}
.download-box { display: flex; align-items: stretch; gap: var(--space-2);
&.dropping { outline: 2px dashed var(--outline); outline-offset: 4px; }
/* As tall as the button and the shadow under it, so the two line up. */ & > :is(input[type="url"], .download-file) { margin-block-end: -2px; }
& input[type="url"] { flex: 1; min-inline-size: 0; }
& > .download-upload { flex-shrink: 0; }}
/* A chosen file, in the link field's place and drawn like it. */.download-file { display: flex; flex: 1; align-items: center; gap: var(--space-2); min-inline-size: 0; padding: 1px var(--space-2); border: 2px solid var(--surface2); border-radius: var(--space-1); background: var(--mantle); box-shadow: inset 0 2px 0 var(--surface0);
& > .download-file-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
& > .download-clear { flex-shrink: 0; padding: 0; border: none; box-shadow: none; background: none; color: var(--text); }}
/* A mod's versions, newest first. */.version-list { list-style-type: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-3);}
.version { padding-block-end: var(--space-3); border-block-end: 2px solid var(--surface1);
&:last-child { border-block-end: none; }
& p { color: var(--grey); text-wrap: pretty; }}
/* The version and its download button sit on one line until there is no room for the two of them. */.version-header { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: var(--space-2);
& h3 { margin: 0; }}
/* A mod's sections. Underlines rather than the filled blocks of the top-level nav, so the two strips do not read as the same level of navigation. */.mod-tabs { display: flex; gap: var(--space-4); margin-block-end: var(--space-4); border-block-end: 2px solid var(--grey);
& > a { padding: var(--space-2) var(--space-1); color: var(--grey); text-decoration: none;
/* Reserves the active underline's height on every tab, so selecting one does not shift the row. */ border-block-end: 3px solid transparent; margin-block-end: -2px;
&:hover { color: inherit; }
&.mod-tab-active { color: inherit; border-block-end-color: var(--accent); } }}
/* Three equal sections under the download button: the star toggle, then how often the mod was downloaded and its page viewed. */.mod-engagement { display: grid; grid-template-columns: repeat(3, 1fr); margin-block-start: var(--space-3); overflow: hidden; border: 2px solid var(--surface2); border-radius: var(--space-1); box-shadow: 0 2px 0 var(--surface2);
& > * { display: flex; align-items: center; justify-content: center; gap: var(--space-2); min-inline-size: 0; font-variant-numeric: tabular-nums; font-weight: 700; }
& > * + * { border-inline-start: 2px solid var(--surface2); }
& svg { flex-shrink: 0; inline-size: 1.1em; block-size: 1.1em; }}
.mod-stat { padding-block: var(--space-2); color: var(--grey);}
/* The star toggle fills its section, so the whole section is the button. */.star { display: flex;
& form { display: flex; flex: 1; margin: 0; }
& .star-button { display: flex; flex: 1; align-items: center; justify-content: center; gap: var(--space-2); padding: var(--space-2); border: none; border-radius: 0; box-shadow: none; background: transparent; color: var(--text); font-weight: inherit;
&:hover, &:focus-visible { background: var(--mantle); }
&:active { transform: none; }
&[aria-pressed="true"] { background: oklch(from var(--gold) l c h / 0.3);
& svg { fill: var(--gold); stroke: var(--on-gold); } } }
& .star-count { font-variant-numeric: tabular-nums; font-weight: 700; }}
.download-button { inline-size: 100%; font-size: 1.125rem; padding-block: var(--space-3);}
.search-page { margin: var(--space-6) auto; inline-size: 100%; max-inline-size: 72rem; padding-inline: var(--space-4);}
/* Both grids fill as many columns as fit, landing on 4 at the page's own max width, and fewer as the viewport narrows. */.search-users { list-style-type: none; margin: 0 0 var(--space-5); padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr)); gap: var(--space-4);
& a { display: flex; flex-direction: column; align-items: center; gap: var(--space-2); color: inherit; text-decoration: none; text-align: center; }}
.search-user-names { display: flex; flex-direction: column; min-inline-size: 0; overflow-wrap: anywhere;}
.search-user-handle { color: var(--example); font-size: var(--text-sm);}
.search-avatar { inline-size: 4.5rem; block-size: 4.5rem; border-radius: 50%; background: var(--crust); object-fit: cover;}
/* A GitHub-style profile page: avatar and bio at the top, that actor's mods below. */.actor-page { margin: var(--space-6) auto; inline-size: 100%; max-inline-size: 72rem; padding-inline: var(--space-4);
& h2 { margin-block-end: var(--space-3); }}
.actor-header { display: flex; align-items: flex-start; gap: var(--space-4); margin-block-end: var(--space-5);}
.actor-avatar { inline-size: 8rem; block-size: 8rem; border-radius: 50%; flex-shrink: 0;}
.actor-avatar-img { object-fit: cover;}
/* Wraps the avatar for the owner only, so hovering it to change the photo doesn't look or act like anything for a visitor. */.actor-avatar-edit { position: relative; display: block; flex-shrink: 0; cursor: pointer; border-radius: 50%; overflow: hidden;
& .actor-avatar-overlay { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; background: oklch(0% 0 0 / 0.4); color: oklch(97% 0 0); opacity: 0; transition: opacity 0.15s ease;
& svg { inline-size: 1.5rem; block-size: 1.5rem; } }
&:hover .actor-avatar-overlay, &:focus-within .actor-avatar-overlay { opacity: 1; }}
.actor-header-info { flex: 1; min-inline-size: 0;
& h1 { margin: 0 0 var(--space-2); }}
.actor-handle { margin: calc(var(--space-2) * -1) 0 var(--space-2); color: var(--example);}
.actor-bio { color: var(--grey); text-wrap: pretty;}
.actor-bio-form { max-inline-size: 32rem;}
.actor-name-input { inline-size: 100%; margin-block-end: var(--space-2); padding: var(--space-2); border: 2px solid var(--outline); border-radius: var(--space-2); background: var(--paper); font: inherit;}
.actor-bio-input { inline-size: 100%; min-block-size: 5rem; padding: var(--space-2); border: 2px solid var(--outline); border-radius: var(--space-2); background: var(--paper); font: inherit; /* stylelint-disable-next-line plugin/no-unsupported-browser-features -- iOS Safari only can't resize it, which costs nothing. */ resize: vertical;}
.placeholder { font-size: 2em; margin: auto;}
/* Under every page, however short. */.site-footer { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-5); margin-block-start: auto; padding: 1.25rem; border-block-start: 3px solid var(--outline); color: var(--grey); font-size: 0.9375rem;}
.legal-document { max-inline-size: 42rem; margin-inline: auto; padding: var(--space-6); line-height: 1.6;
& > * + * { margin-block-start: var(--space-4); }
& :is(h1, h2) { text-wrap: balance; }
& > h2 { margin-block-start: var(--space-7); }
& > h2 + * { margin-block-start: var(--space-2); }
& :is(p, li) { text-wrap: pretty; }
& :is(ul, ol) { padding-inline-start: var(--space-5); }
& li + li { margin-block-start: var(--space-2); }
& table { border-collapse: collapse; }
& :is(th, td) { padding: var(--space-1) var(--space-2); border: 1px solid var(--grey); text-align: start; vertical-align: top; }}
/* Mods other accounts have offered to the signed-in one, above every page. */.transfer-offers { list-style: none; margin: 0 auto var(--space-4); padding: var(--space-3) var(--space-4); max-inline-size: 60rem; background: oklch(from var(--accent) 95% calc(c * 0.3) h); border-radius: var(--space-1);
& li { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
& li + li { margin-block-start: var(--space-2); }
& span { flex: 1; }
& form { margin: 0; }}
/* The account a transfer would go to, shown before it's offered. */.transfer-recipient { display: flex; align-items: center; gap: var(--space-3); margin-block-start: var(--space-4);}
.transfer-recipient-names { display: flex; flex-direction: column; min-inline-size: 0; overflow-wrap: anywhere;}
.transfer-recipient-handle { color: var(--example); font-size: var(--text-sm);}
.transfer-avatar { inline-size: 3rem; block-size: 3rem; border-radius: 50%; object-fit: cover;}
/* Stands in for the settings forms while a transfer is being accepted. */.transfer-progress { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--space-3); min-block-size: 24rem; text-align: center;
& p { margin: 0; }
& .transfer-recipient { margin-block-start: 0; }}
.transfer-progress-heading { font-weight: bold;}
.spinner { inline-size: 2.5rem; block-size: 2.5rem; border: 4px solid color-mix(in srgb, currentcolor 20%, transparent); border-block-start-color: currentcolor; border-radius: 50%; animation: spin 0.9s linear infinite;
@media (prefers-reduced-motion: reduce) { animation-duration: 3s; }}
@keyframes spin { to { rotate: 1turn; }}
/* The homepage's welcome, for visitors who aren't signed in: a band of purple paper with a torn bottom edge. */.home-intro { --outline: var(--ink); --ring: 2px solid white;
background-color: var(--mauve); background-image: url("/assets/textures/subtle-paper-pulp-repeat.webp"); background-blend-mode: multiply; color: white; /* stylelint-disable-next-line plugin/no-unsupported-browser-features -- Only clip-path's url() and box values are partly supported; polygon() works everywhere. */ clip-path: polygon( 0 0, 100% 0, 100% calc(100% - 14px), 97% 100%, 93% calc(100% - 10px), 88% 100%, 82% calc(100% - 12px), 76% 100%, 70% calc(100% - 8px), 63% 100%, 57% calc(100% - 12px), 50% 100%, 44% calc(100% - 9px), 37% 100%, 31% calc(100% - 13px), 24% 100%, 18% calc(100% - 9px), 11% 100%, 5% calc(100% - 12px), 0 100% );
@media (max-width: 40rem) { /* stylelint-disable-next-line plugin/no-unsupported-browser-features -- Only clip-path's url() and box values are partly supported; polygon() works everywhere. */ clip-path: polygon( 0 0, 100% 0, 100% calc(100% - 10px), 92% 100%, 80% calc(100% - 8px), 66% 100%, 52% calc(100% - 9px), 38% 100%, 24% calc(100% - 8px), 10% 100%, 0 calc(100% - 6px) ); }}
.home-intro-inner { display: grid; grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); align-items: center; gap: var(--space-7); max-inline-size: 75rem; margin-inline: auto; padding: var(--space-7) var(--space-6) 4rem;
@media (max-width: 40rem) { grid-template-columns: minmax(0, 1fr); padding: 1.375rem var(--space-4) 1.875rem; }}
.home-intro-text { display: flex; flex-direction: column; gap: 1.125rem;
& > * { margin: 0; }
& h1 { font-size: 3.25rem; line-height: 1.02; text-wrap: balance; }
& p { max-inline-size: 30em; color: var(--on-mauve-soft); font-size: 1.25rem; line-height: 1.45; text-wrap: balance; }
@media (max-width: 40rem) { gap: 0.625rem;
& h1 { font-size: 1.875rem; line-height: 1.05; }
& p { font-size: var(--text-base); line-height: 1.4; } }}
/* Three mods' images, scattered like photos on a desk. */.home-shots { position: relative; block-size: 21.25rem;
@media (max-width: 40rem) { display: none; }}
.home-shot { position: absolute; inline-size: 56%; aspect-ratio: 4 / 3; overflow: hidden; border: 4px solid var(--ink); border-radius: var(--space-2); background: white; box-shadow: 0 8px 0 oklch(from var(--ink) l c h / 0.45);
& img { inline-size: 100%; block-size: 100%; object-fit: cover; }
&:nth-child(1) { inset-block-start: 30px; inset-inline-start: 0; inline-size: 58%; rotate: -6deg; }
&:nth-child(2) { inset-block-start: 0; inset-inline-end: 0; rotate: 5deg; }
&:nth-child(3) { inset-block-end: 0; inset-inline-start: 24%; rotate: -1.5deg; }}
.home-star { position: absolute;
& path { stroke: var(--ink); stroke-width: 1.5; stroke-linejoin: round; }}
.home-star-big { inset-block-end: 18%; inset-inline-end: 4%; inline-size: 3.375rem; rotate: 14deg;
& path { fill: var(--yellow); }}
.home-star-small { inset-block-start: 0; inset-inline-start: 2%; inline-size: 2.125rem; rotate: -12deg;
& path { fill: var(--paper); }}
/* The homepage, and every mod's list. */.home-page { display: flex; flex-direction: column; gap: 2.5rem; inline-size: 100%; max-inline-size: 75rem; margin-inline: auto; padding: 1.75rem var(--space-6) 4rem;
& > * { margin: 0; }
@media (max-width: 40rem) { gap: 1.125rem; padding: var(--space-4); }}
.mod-lists { display: flex; flex-direction: column; gap: 2.5rem;
& > * { margin: 0; }
@media (max-width: 40rem) { gap: 1.125rem; }}
/* The featured mods: one big card at a time, with the ones either side peeking in. */.featured { display: flex; flex-direction: column; align-items: center; gap: 0.875rem;
@media (max-width: 60rem) { gap: 0.625rem; }}
/* The stage spans the whole window, so the cards either side of the current one show whole, cut off only by its edges. */.featured-stage { --featured-card: min(66rem, calc(100vw - 9rem)); --featured-gap: var(--space-5);
display: grid; justify-items: center; inline-size: 100vw; margin-inline: calc(50% - 50vw); padding-block: 2px 10px; overflow: hidden;
@media (max-width: 60rem) { --featured-card: 100%;
inline-size: 100%; margin-inline: 0;
/* The slides beside the current one sit a card's width off to either side, which would widen the page and have a phone zoom out to fit them. Clipped across only, so the card's shadow still shows. */ overflow: clip visible; }}
/* Every slide in the one grid cell, each moved along by how many places it is from the current one; the ones either side smaller and faded, and the rest out of sight. */.featured-track { display: grid; inline-size: var(--featured-card); /* stylelint-disable-next-line plugin/no-unsupported-browser-features -- Safari lacks only pinch-zoom, which this doesn't use. */ touch-action: pan-y;}
.featured-slide { grid-area: 1 / 1; translate: calc(var(--offset) * (100% + var(--featured-gap))) 0; transform-origin: calc(50% - var(--offset) * 50%) center; scale: 0.82; opacity: 0; visibility: hidden; transition: translate 0.45s cubic-bezier(0.2, 0.8, 0.2, 1), scale 0.45s cubic-bezier(0.2, 0.8, 0.2, 1), opacity 0.45s ease, visibility 0.45s;
@media (prefers-reduced-motion: reduce) { transition: none; }}
.featured-slide-near { opacity: 0.45; visibility: visible; cursor: pointer;}
.featured-slide-current { z-index: 1; scale: 1; opacity: 1; visibility: visible;}
.featured-card { display: grid; grid-template-columns: minmax(0, 3fr) minmax(0, 2fr); overflow: hidden; border: 3px solid var(--outline); border-radius: 0.875rem; background: var(--paper); box-shadow: 0 6px 0 var(--outline); color: var(--text);
&:any-link { text-decoration: none; }
@media (max-width: 60rem) { grid-template-columns: minmax(0, 1fr); border-radius: var(--space-3); box-shadow: 0 5px 0 var(--outline); }}
.featured-image { position: relative; aspect-ratio: 4 / 3; overflow: hidden; border-inline-end: 3px solid var(--outline);
&[data-youtube] { cursor: pointer; }
& > img { inline-size: 100%; block-size: 100%; object-fit: cover; }
@media (max-width: 60rem) { border-inline-end: none; border-block-end: 3px solid var(--outline); }}
.featured-info { display: grid; grid-template-rows: auto auto 1fr; gap: 0.875rem; padding: 1.375rem 1.375rem 1.25rem;
& > * { margin: 0; }
@media (max-width: 60rem) { padding: 0.625rem 0.875rem var(--space-3); }}
.featured-heading { display: flex; flex-direction: column; gap: var(--space-1);
@media (max-width: 60rem) { gap: 2px; }}
.featured-title { font-size: 1.875rem;
@media (max-width: 60rem) { font-size: 1.375rem; }}
.featured-tagline { display: -webkit-box; overflow: hidden; -webkit-box-orient: vertical; -webkit-line-clamp: 2; min-block-size: 2lh; color: var(--example); text-wrap: pretty; font-size: 0.9375rem;
@media (max-width: 60rem) { font-size: var(--text-sm); }}
.featured-thumbs { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.625rem;
& > div { aspect-ratio: 4 / 3; overflow: hidden; border: 2px solid var(--outline); border-radius: var(--space-2); background: var(--crust); }
& img { inline-size: 100%; block-size: 100%; object-fit: cover; }
@media (max-width: 60rem) { display: none; }}
/* One pill per featured mod, to jump straight to it. Each is drawn small but takes a full-size tap. */.featured-dots { display: flex;
&[hidden] { display: none; }
& > button { all: unset; display: grid; place-items: center; block-size: 2.75rem; padding-inline: 5px; cursor: pointer;
&::before { content: ""; inline-size: 14px; block-size: 8px; border-radius: 4px; background: var(--surface1); transition: inline-size 0.12s ease, background 0.12s ease; }
&:hover::before { background: var(--surface2); }
&:focus-visible { outline: var(--ring); outline-offset: -8px; border-radius: var(--space-2); } }
& > .featured-dot-current::before, & > .featured-dot-current:hover::before { inline-size: 22px; background: var(--outline); }}
/* "Show mods for", then every game in one segmented row. */.game-filter { display: flex; flex-wrap: wrap; align-items: center; gap: 0.875rem;
& > span { color: var(--grey); }
@media (max-width: 40rem) { & > span { display: none; } }}
/* A row of links joined into one pill, the current one filled. */.segmented { display: inline-flex; max-inline-size: 100%; overflow-x: auto; border: 2px solid var(--outline); border-radius: 1.375rem; background: var(--paper);
& > a { display: inline-flex; align-items: center; flex-shrink: 0; block-size: 2.75rem; padding: 0 0.875rem; border-inline-start: 2px solid var(--outline); color: var(--text); font-size: 0.9375rem; white-space: nowrap;
&:any-link { text-decoration: none; }
&:first-child { border-inline-start: none; }
&:hover { background: var(--crust); }
&:focus-visible { outline-color: var(--outline); outline-offset: -5px; }
&[aria-current="true"] { background: var(--outline); color: var(--base);
&:focus-visible { outline-color: var(--base); } }
@media (max-width: 40rem) { padding: 0 var(--space-3); font-size: var(--text-sm); } }}
.mod-section { display: flex; flex-direction: column; gap: 0.875rem;
& > * { margin: 0; }
@media (max-width: 40rem) { gap: 0.625rem; }}
.mod-section-header { display: flex; align-items: baseline; gap: var(--space-4);
& h2 { margin: 0; font-size: 1.75rem; }
& a { margin: 0 0 0 auto; font-size: 1.0625rem; }
@media (max-width: 40rem) { & h2 { font-size: 1.375rem; }
& a { font-size: 0.9375rem; } }}
.mod-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 1.25rem; padding: 0; list-style: none;
@media (max-width: 75rem) { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 50rem) { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-3); }}
/* Narrower than a desktop, the homepage's lists show six: two rows of three, or three rows of two. */.mod-section .mod-grid > li:nth-child(n + 7) { @media (max-width: 75rem) { display: none; }}
.mod-card { display: flex; flex-direction: column; block-size: 100%; overflow: hidden; border: 2px solid var(--surface0); border-radius: var(--space-3); background: var(--paper); color: var(--text);
&:any-link { text-decoration: none; }
@media (max-width: 40rem) { border-radius: 0.625rem; }}
.mod-card-image { aspect-ratio: 4 / 3; overflow: hidden; background: var(--crust);
& > img { inline-size: 100%; block-size: 100%; object-fit: cover; }}
.mod-card-body { display: grid; flex: 1; grid-template-rows: auto auto 1fr; gap: var(--space-1); padding: var(--space-3) 0.875rem 0.875rem;
@media (max-width: 40rem) { padding: var(--space-2) 0.625rem 0.625rem; }}
.mod-card-title { font-size: 1.0625rem;
@media (max-width: 40rem) { font-size: 0.9375rem; }}
.mod-card-description { display: -webkit-box; overflow: hidden; -webkit-box-orient: vertical; -webkit-line-clamp: 2; min-block-size: 2lh; color: var(--example); text-wrap: pretty; font-size: var(--text-sm);}
/* What a mod without an image shows in its place: its game and title on a sheet of paper. */.mod-placeholder { container-type: inline-size; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--space-2); inline-size: 100%; block-size: 100%; padding: 8%; overflow: hidden; background-color: var(--mantle); background-image: url("/assets/textures/subtle-paper-pulp-repeat.webp"); background-blend-mode: multiply; color: var(--grey); text-align: center;}
.mod-placeholder-game { padding: 1cqi 4cqi; border-radius: 999px; background: var(--surface0); color: var(--text); font-size: max(0.75rem, 6cqi);}
.mod-placeholder-title { display: -webkit-box; -webkit-line-clamp: 3; line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; font-size: max(0.875rem, 9cqi); line-height: 1.15; text-wrap: balance;}
/* A mod's author, then its stars pushed to the end. */.mod-card-byline,.featured-byline { display: flex; grid-row: 3; align-items: center; align-self: end; gap: var(--space-2); color: var(--example); font-size: var(--text-sm); min-inline-size: 0;
& .byline-avatar { flex: none; inline-size: 1.25rem; block-size: 1.25rem; border-radius: 50%; object-fit: cover; }
& .byline-name { overflow: hidden; color: var(--grey); text-overflow: ellipsis; white-space: nowrap; }
/* Overlapping, so no one's avatar stands for the rest. */ & .byline-avatars { display: flex; flex: none;
& .byline-avatar { box-shadow: 0 0 0 2px var(--paper); }
& .byline-avatar + .byline-avatar { margin-inline-start: -0.4rem; } }
/* Everyone credited, as many as fit in two lines. */ & .byline-names { display: -webkit-box; min-inline-size: 0; overflow: hidden; color: var(--grey); overflow-wrap: anywhere; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; }
& .byline-stars { display: inline-flex; align-items: center; gap: 0.25rem; margin-inline-start: auto;
& svg { inline-size: 1em; block-size: 1em; }
&.byline-starred svg { fill: var(--gold); stroke: var(--on-gold); } }}
.mod-card-byline { margin-block-start: var(--space-1);}
/* A mod's gallery, one slide at a time. Until its script has run, the first slide stands in for it, so the page keeps its shape once it does. */media-carousel { position: relative; display: block; inline-size: 100%; aspect-ratio: 4 / 3; background: #222; overflow: hidden;
&:not(:defined) > * { inline-size: 100%; block-size: 100%; object-fit: contain; }
&:not(:defined) > :not(:first-child) { display: none; }}
/* A YouTube video playing over a gallery image, and the button that starts it. */media-carousel > [data-youtube] { position: relative;
& > img { inline-size: 100%; block-size: 100%; object-fit: contain; }}
.video-player { position: absolute; inset: 0; background: black;
& iframe { inline-size: 100%; block-size: 100%; border: 0; }}
.video-play { all: unset; position: absolute; inset: 0; margin: auto; display: grid; place-items: center; inline-size: 3.5rem; block-size: 3.5rem; border-radius: 50%; background: oklch(0% 0 0 / 0.65); color: white; cursor: pointer;
&:hover, &:focus-visible { background: oklch(0% 0 0 / 0.85); }
&:has(~ .video-player) { display: none; }
& svg { inline-size: 1.75rem; block-size: 1.75rem; fill: currentcolor; }}