/* slosh.foo — one page, one stylesheet, no build step. * * The palette is contrib/themes/sl0p.kdl — hot pink on near-black — so the * page and the thing it describes agree. Deliberately not a landing page: * no gradients, no cards, no icons, nothing floats in on scroll. */ :root { --bg: #0b0b0e; --panel: #141418; --code-bg: #1c1c22; --fg: #e8e8ea; --dim: #8a8a95; --faint: #45454a; --pink: #ff5fd7; --dead: #ff875f; /* The feature tour's geometry, up here because the floating logo (a fixed * element, outside the tour) centres itself on the tour's stage column. */ --tour-w: min(94vw, 88rem); --tour-gap: clamp(1.5rem, 4vw, 4.5rem); --mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace; --sans: system-ui, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif; } * { box-sizing: border-box; } html { scroll-behavior: smooth; } body { margin: 0; background: var(--bg); color: var(--fg); font: 16px/1.65 var(--sans); -webkit-font-smoothing: antialiased; } a { color: var(--pink); text-decoration: none; } a:hover { text-decoration: underline; } code, kbd, pre { font-family: var(--mono); } code { font-size: 0.9em; } kbd { padding: 0.05em 0.35em; border: 1px solid var(--faint); border-radius: 3px; background: var(--panel); color: var(--pink); font-size: 0.85em; } /* ---- the floating logo ---------------------------------------------- */ /* The hero logo again, small, in the corner the stage's v-centering leaves * empty on desktop. Faded in by the observer in index.html once the big one * is off screen; a link back to the top. Hidden on one-column screens, * where that corner is content, not air. */ .logo-float { position: fixed; /* The resting value only: the script centres it in the air between the * viewport top and the pinned terminal's top edge, which no fixed number * tracks across viewport heights. */ top: 13.8rem; /* Centred on the stage column: the tour is layout-centred and the stage * is the 3fr of its 3fr/2fr split, so the terminal's midline sits at * 50% - tourW/2 + (tourW - gap) * 3/10. 50%, not 50vw: a fixed element's * percentages exclude the scrollbar, like the layout the tour centres in; * vw would drag the logo half a scrollbar to the right. The translateX * does the logo's own half. */ left: calc(50% - var(--tour-w) / 2 + (var(--tour-w) - var(--tour-gap)) * 0.3); z-index: 4; color: var(--pink); opacity: 0; transform: translateX(-50%) translateY(-0.4rem); transition: opacity 0.25s, transform 0.25s; pointer-events: none; } .logo-float.show { opacity: 1; transform: translateX(-50%); pointer-events: auto; } /* A link, but not dressed as one: underlining seven rows of block glyphs * (the global a:hover) is a mess, and a colour flash is not much better. * The pointer cursor alone says clickable. */ .logo-float:hover { text-decoration: none; } .logo-float pre { margin: 0; font-family: var(--mono); font-size: 12px; line-height: 1em; /* Block glyphs' ink can round a hair narrower than the advance width, * leaving pinstripe gaps between ██; tuck each glyph into the last. */ letter-spacing: -0.05em; /* The colour cycles, slowly: a gradient of the pink's neighbours clipped * to the ink and slid along it -- the logo's own little colour pass, in * the spirit of the shaders it advertises. Horizontal on purpose: the * loop shifts by exactly one tile (at background-size 200%, position * 200% is one tile width), and only a 90deg gradient meets its own edge * seamlessly. */ background: linear-gradient(90deg, var(--pink) 0%, #c95fff 18%, #5fafff 38%, #5fd7d7 52%, #87ffd7 62%, #ff87e7 82%, var(--pink) 100%); background-size: 200% 100%; -webkit-background-clip: text; background-clip: text; color: transparent; } /* Two motions. The position slide scrolls the gradient through the ink -- * except in WebKit, which caches a text-clipped paint and never repaints it * for a background-position animation, leaving the wash frozen. The hue * rotation is the motion Safari does honour: filters run post-paint on the * compositor, so every pixel cycles the wheel even over the cached clip. * Chrome and Firefox get both, WebKit gets the rotation alone. */ .logo-float.show pre { animation: logo-cycle 8s linear infinite, logo-hue 12s linear infinite; } @keyframes logo-cycle { from { background-position: 0% 0; } to { background-position: 200% 0; } } @keyframes logo-hue { from { filter: hue-rotate(0deg); } to { filter: hue-rotate(360deg); } } @media (prefers-reduced-motion: reduce) { .logo-float.show pre { animation: none; } } @media (max-width: 54rem) { .logo-float { display: none; } } /* ---- the hero ------------------------------------------------------- */ main { max-width: 60rem; margin: 0 auto; padding: 0 1.25rem 4rem; } .hero { padding: 3.5rem 0 1rem; text-align:center !important; } .hero .logo { color: var(--pink); line-height: 1em; letter-spacing: -0.05em; /* see .logo-float pre */ } .hero h1 { margin: 0; color: var(--pink); font-family: var(--mono); font-size: clamp(2.2rem, 6vw, 3.4rem); font-weight: 600; letter-spacing: 0.04em; } .tagline { margin: 0.5rem 0 0; color: var(--dim); font-size: 1.05rem; } .hero-links { margin: 2rem 0 0; } .btn { display: inline-block; margin-right: 0.75rem; padding: 0.35rem 1.1rem; background: var(--pink); color: var(--panel); font-family: var(--mono); font-size: 14px; } .btn:hover { background: #fff; text-decoration: none; } .btn.ghost { background: transparent; border: 1px solid var(--faint); color: var(--dim); } .btn.ghost:hover { border-color: var(--pink); color: var(--pink); background: transparent; } /* ---- sections ------------------------------------------------------- */ section { margin: 3.5rem 0 0; } section h2 { margin: 0 0 1rem; padding-bottom: 0.4rem; border-bottom: 1px solid var(--panel); color: var(--fg); font-family: var(--mono); font-size: 1.1rem; font-weight: 600; } section p { max-width: 44rem; } section > p:last-child { margin-bottom: 0; } .aside { color: var(--dim); font-size: 0.95rem; } /* ---- the install box ------------------------------------------------- */ /* Pick a platform, read only its instructions. Hidden radios, labels drawn * like a slosh tab strip, one panel each; the switching is CSS. */ .install { margin: 1rem 0; border: 1px solid var(--panel); border-radius: 4px; background: var(--bg); } .install > .os { position: absolute; width: 1px; height: 1px; opacity: 0; } .os-tabs { display: flex; gap: 0.35rem; overflow-x: auto; padding: 0.45rem 0.6rem; border-bottom: 1px solid var(--panel); font-family: var(--mono); font-size: 13px; } .os-tabs label { padding: 0.05rem 0.7rem; border-radius: 3px; color: var(--dim); cursor: pointer; white-space: nowrap; } .os-tabs label:hover { color: var(--pink); } .install > .os:focus-visible ~ .os-tabs { outline: 1px solid var(--pink); } .os-panel { display: none; padding: 0.4rem 1rem 0.9rem; } .os-panel .cmd { margin: 0.6rem 0; } .os-panel .aside { margin: 0.6rem 0 0; font-size: 0.9rem; } /* the checked radio lights its tab and shows its panel */ #os-linux:checked ~ .os-tabs label[for="os-linux"], #os-macos:checked ~ .os-tabs label[for="os-macos"], #os-arch:checked ~ .os-tabs label[for="os-arch"], #os-windows:checked ~ .os-tabs label[for="os-windows"], #os-source:checked ~ .os-tabs label[for="os-source"] { background: var(--pink); color: var(--panel); } #os-linux:checked ~ #install-linux, #os-macos:checked ~ #install-macos, #os-arch:checked ~ #install-arch, #os-windows:checked ~ #install-windows, #os-source:checked ~ #install-source { display: block; } /* ---- command blocks ------------------------------------------------- */ .cmd { position: relative; margin: 1rem 0; border: 1px solid var(--code-bg); border-radius: 4px; background: var(--code-bg); } .cmd pre { margin: 0; padding: 0.8rem 1rem; overflow-x: auto; font-size: 13.5px; line-height: 1.7; } .cmd .prompt { color: var(--faint); user-select: none; } .cmd .c { color: var(--dim); } .copy { position: absolute; top: 0.45rem; right: 0.45rem; padding: 0.1rem 0.6rem; border: 1px solid var(--faint); border-radius: 3px; background: var(--panel); color: var(--dim); font-family: var(--mono); font-size: 12px; cursor: pointer; } .copy:hover { border-color: var(--pink); color: var(--pink); } /* ---- the feature tour ----------------------------------------------- */ /* A terminal pinned to the left half, vertically centred; the features * scroll by on the right. Wider than the text column on purpose — the * recordings are 100 columns and want the room. */ .scrolly { width: var(--tour-w); margin-left: calc((100% - var(--tour-w)) / 2); display: grid; grid-template-columns: minmax(0, 3fr) minmax(0, 2fr); gap: var(--tour-gap); /* Tuck the tour up under the hero. The stage's 100vh box centres the * terminal, so before it pins, everything above the terminal is air — * about 45vh minus half the player (≤ ~500px tall at this column width), * minus the optical lift. Overlap the hero by that much, less ~2rem of * breathing room; min() keeps it at 0 on viewports short enough that the * air runs out. Pinned geometry is scroll-invariant, so alignment inside * the tour is untouched. */ margin-top: min(0rem, 17.5rem - 45vh); /* ...and the same at the exit, where the air below the terminal is larger * still (the optical lift rides it high — ~54.5vh minus half the player). * This collapses with the next section's 3.5rem margin-top, which the * constant accounts for; the result is ~2.75rem from the terminal's final * resting bottom to the install heading at any viewport height. */ margin-bottom: min(0rem, 14.5rem - 54.5vh); /* The overlapped regions are transparent air, but they would still sit on * top of the hero's buttons above and the install tabs below — let clicks * fall through, and switch hit-testing back on for the things inside that * want it. */ pointer-events: none; } .stage-pin [data-sloshcast], .scrolly-steps .step { pointer-events: auto; } .stage-pin { position: sticky; top: 0; height: 100vh; display: flex; flex-direction: column; justify-content: center; align-items: center; /* Sit a touch above true centre: the steps on the right centre their text * on the viewport, and against them a dead-centred terminal reads as low * (the tab strip weights its top). border-box, so this lifts the centred * content rather than growing the 100vh box. */ padding-bottom: clamp(2.5rem, 9vh, 6rem); } .stage-pin [data-sloshcast] { display: none; } .stage-pin .stage-active { display: block; } /* Breathing room at both ends of the steps column. Without it the column is * exactly steps*50vh tall and the sticky stage has no travel margin: the * terminal is still riding below centre when the first step reaches the * middle of the viewport, and has already been dragged up past centre by the * container's bottom edge when the last one does. 25vh each side means the * stage is pinned (centred) from before the first step centres until after * the last step does. */ .scrolly-steps { padding: 25vh 0; } .step { min-height: 50vh; display: flex; flex-direction: column; justify-content: center; opacity: 0.3; transition: opacity 0.25s; } .step.active { opacity: 1; } .step h3 { margin: 0; color: var(--fg); font-size: clamp(1.4rem, 2.2vw, 1.9rem); font-weight: 650; line-height: 1.25; } .step p { margin: 0.8rem 0 0; color: var(--dim); font-size: clamp(1.05rem, 1.4vw, 1.2rem); line-height: 1.6; } .step ul { margin: 1rem 0 0; padding-left: 1.2em; color: var(--dim); font-size: clamp(0.98rem, 1.25vw, 1.1rem); line-height: 1.55; } .step li { margin: 0.35rem 0; } .step li::marker { color: var(--pink); } /* The tour's exit: back up to the install box, or into the browser demo. */ .step-cta { min-height: 45vh; } .step-cta .hero-links { margin-top: 1.5rem; } @media (max-width: 54rem) { /* One column: the terminal sticks under the nav, the features scroll * beneath it. The sticky moves up to .scrolly-stage here: in block layout * .stage-pin's parent is only as tall as the player, so a sticky inside it * has nowhere to stick; .scrolly-stage is bounded by the whole tour. */ .scrolly { display: block; margin-top: 0; margin-bottom: 0; pointer-events: auto; } .scrolly-stage { position: sticky; top: 0; z-index: 2; padding: 0.6rem 0; background: var(--bg); } .stage-pin { position: static; height: auto; padding: 0; } /* One column: the stage sticks above the steps, so the travel margin is * pointless air here. */ .scrolly-steps { padding: 0; } .step { min-height: 50vh; } .step:first-child { padding-top: 1.5rem; } } /* ---- the points list ------------------------------------------------ */ .points { margin: 0.5rem 0 1.25rem; } .points dt { margin-top: 1.1rem; color: var(--fg); font-weight: 600; } .points dt:first-child { margin-top: 0; } .points dd { margin: 0.15rem 0 0; max-width: 44rem; color: var(--dim); } /* ---- the docs list -------------------------------------------------- */ .doclist { margin: 0.5rem 0 0; padding: 0; list-style: none; columns: 2; column-gap: 2.5rem; } .doclist li { margin: 0.35rem 0; break-inside: avoid; font-size: 0.95rem; } .doclist a { font-family: var(--mono); font-size: 14px; } .doclist span { color: var(--faint); } /* ---- small screens -------------------------------------------------- */ @media (max-width: 44rem) { .doclist { columns: 1; } }