/* shrooms — the same palette as the app, the daemon's status output and the
   Basecamp view, because a colour that means one thing in one place and
   something else elsewhere is worse than no colour at all. */

:root {
    --void: #07090b;
    --panel: #0e1216;
    --line: #1c2229;
    --ash: #6b7680;
    --bone: #d6dde3;

    --phosphor: #35f0a0;  /* connected, carrying traffic */
    --amber: #f0b429;     /* reaching, not there yet */
    --rust: #e05252;      /* failed */
    --violet: #9a7bff;    /* relayed */

    --sky: #5aa9ff;       /* mesh tints: never a status colour */
    --blossom: #ff6fb5;
    --chartreuse: #c8e64a;

    --mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;
    --measure: 68ch;
}

* { box-sizing: border-box; }

/* --- the psychedelia ---------------------------------------------------
   Loud in the chrome, calm under the words. Everything here sits behind a
   page that owns its own background: colour and motion carry the character,
   contrast is left alone, because this is documentation and somebody has to
   be able to read it at two in the morning while their mesh is down. */

@keyframes drift-a {
    0%   { transform: translate3d(-12vw, -8vh, 0) scale(1); }
    50%  { transform: translate3d(14vw, 10vh, 0) scale(1.25); }
    100% { transform: translate3d(-12vw, -8vh, 0) scale(1); }
}
@keyframes drift-b {
    0%   { transform: translate3d(18vw, 12vh, 0) scale(1.15); }
    50%  { transform: translate3d(-10vw, -14vh, 0) scale(0.9); }
    100% { transform: translate3d(18vw, 12vh, 0) scale(1.15); }
}
@keyframes spin-slow { to { transform: rotate(360deg); } }
@keyframes hue { to { filter: hue-rotate(360deg); } }
@keyframes shimmer { to { background-position: 200% center; } }
@keyframes breathe { 50% { opacity: 0.85; transform: scale(1.03); } }

/* Aurora: three blurred blobs of colour, drifting on periods that never
   coincide, so the background never repeats a frame you have already seen. */
.aurora {
    position: fixed;
    inset: -20vh -20vw;
    z-index: 0;
    pointer-events: none;
    filter: blur(90px) saturate(140%);
    opacity: 0.5;
}

.aurora i {
    position: absolute;
    display: block;
    border-radius: 50%;
    mix-blend-mode: screen;
}

.aurora i:nth-child(1) {
    width: 46vw; height: 46vw; left: 4vw; top: 2vh;
    background: radial-gradient(circle, rgba(53,240,160,0.55), transparent 68%);
    animation: drift-a 41s ease-in-out infinite;
}
.aurora i:nth-child(2) {
    width: 52vw; height: 52vw; right: 2vw; top: 18vh;
    background: radial-gradient(circle, rgba(154,123,255,0.5), transparent 68%);
    animation: drift-b 57s ease-in-out infinite;
}
.aurora i:nth-child(3) {
    width: 38vw; height: 38vw; left: 26vw; bottom: -8vh;
    background: radial-gradient(circle, rgba(255,111,181,0.4), transparent 68%);
    animation: drift-a 73s ease-in-out infinite reverse;
}

/* A lattice, very faint, breathing.
   Of the four shapes people reliably report on psilocybin — Klüver called them
   form constants: lattices and honeycombs, tunnels, spirals, cobwebs — the
   honeycomb is the one that belongs here anyway, because it is also what a
   mesh looks like drawn flat. So this is not a costume: it is the same figure
   the graph draws, at one twentieth of the contrast, slowly breathing. */
.lattice {
    position: fixed;
    inset: -10vh -10vw;
    z-index: 0;
    pointer-events: none;
    opacity: 0.055;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='104' viewBox='0 0 120 104'%3E%3Cg fill='none' stroke='%2335F0A0' stroke-width='1.2'%3E%3Cpath d='M30 0 L0 17 L0 52 L30 69 L60 52 L60 17 Z'/%3E%3Cpath d='M90 0 L60 17 L60 52 L90 69 L120 52 L120 17 Z'/%3E%3Cpath d='M30 69 L0 86 L0 121 L30 138 L60 121 L60 86 Z'/%3E%3Cpath d='M90 69 L60 86 L60 121 L90 138 L120 121 L120 86 Z'/%3E%3C/g%3E%3C/svg%3E");
    animation: swell 27s ease-in-out infinite;
}

/* Surfaces that breathe rather than sit still is the other thing people
   describe — walls and floors undulating gently. A one-per-cent scale is
   enough to suggest it and small enough that nobody trying to read has to
   fight it. */
@keyframes swell {
    50% { transform: scale(1.035) translateY(-8px); opacity: 0.085; }
}

/* Grain, so the gradients have a surface instead of banding across a wide
   screen. An SVG turbulence rather than an image: no request, no asset. */
.grain {
    position: fixed;
    inset: 0;
    z-index: 3;
    pointer-events: none;
    opacity: 0.16;
    mix-blend-mode: overlay;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E");
}

html { scroll-behavior: smooth; }

/* The type is the app's, not a web designer's idea of it (Theme.kt):
   monospace throughout, because addresses and keys are read character by
   character and a proportional font makes fd3b and fd8b look alike; a large
   title in Light with negative tracking; and section labels that are small,
   uppercase and widely tracked rather than big and bold. The app looks the way
   it does because of that contrast — one quiet large thing, one quiet small
   thing — and the first version of this site threw it away for ordinary web
   headings. */

body {
    margin: 0;
    /* A wash under the aurora, so the dark is never flat black. */
    background:
        radial-gradient(120vw 80vh at 15% -10%, rgba(53,240,160,0.10), transparent 60%),
        radial-gradient(100vw 70vh at 90% 10%, rgba(154,123,255,0.12), transparent 60%),
        radial-gradient(90vw 60vh at 50% 110%, rgba(255,111,181,0.08), transparent 60%),
        var(--void);
    color: var(--bone);
    font-family: var(--mono);
    font-size: 14px;
    line-height: 1.7;
    -webkit-font-smoothing: antialiased;
}

/* --- the canvas behind everything ------------------------------------- */

#field {
    position: fixed;
    inset: 0;
    z-index: 0;
    /* Decoration, and decoration must never eat a click. */
    pointer-events: none;
}

.page {
    position: relative;
    z-index: 1;
    max-width: 1000px;
    margin: 0 auto;
    padding: 0 24px 96px;
}

/* --- masthead ---------------------------------------------------------- */

nav {
    letter-spacing: 0.08em;
    text-transform: lowercase;
    backdrop-filter: blur(10px);
    position: sticky;
    top: 0;
    z-index: 2;
    display: flex;
    align-items: center;
    gap: 18px;
    flex-wrap: wrap;

    /* Padded on the sides, and pulled out by the same amount, so the bar's
       blurred backdrop still spans the column while the mark and the last link
       are not touching its edges. Without the negative margin the bar would
       simply be narrower than the text under it, which looks like a mistake
       from the other direction. */
    padding: 12px 16px;
    margin: 0 -16px;
    border-radius: 0 0 14px 14px;

    background: linear-gradient(var(--void) 70%, transparent);
    font-size: 13px;
}

nav .mark {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    color: var(--phosphor);
    font-weight: 700;
    letter-spacing: 0.04em;
    text-decoration: none;

    /* Three things, all of which move the word off the mark's centre by a
       pixel or two, which is exactly enough to look wrong:
       line-height, because the default leaves the glyphs sitting high in a
       taller line box; the bottom border every other nav link has, inherited
       from `nav a` below and cancelled here; and the padding that would come
       with it. */
    line-height: 1;
    border-bottom: 0;
    padding-bottom: 0;
}

.nav-logo {
    /* Block, so it is not also an inline box with a baseline of its own — the
       usual reason an image beside text sits low even inside a centred flex
       row. */
    display: block;
    flex: none;
    width: 24px;
    height: 24px;
    border-radius: 7px;

    /* The drawing sits low inside its square: the cap starts around a quarter
       of the way down and the stem runs almost to the bottom edge, so its
       optical centre is below its geometric one. Aligning the boxes therefore
       reads as the word sitting high. A pixel back up costs nothing and fixes
       what the eye actually sees. */
    position: relative;
    top: -1px;

    /* A little of the hero's glow, so the masthead is recognisably the same
       object rather than a shrunken copy of it. */
    filter: drop-shadow(0 0 8px rgba(53, 240, 160, 0.45));
}

nav a {
    color: var(--ash);
    text-decoration: none;
    border-bottom: 1px solid transparent;
}

nav a:hover { color: var(--bone); border-bottom-color: var(--line); }
nav a.here { color: var(--bone); border-bottom-color: var(--phosphor); }
nav .spacer { flex: 1 1 auto; }

/* --- type -------------------------------------------------------------- */

h1, h2, h3 { line-height: 1.25; }

h1 {
    font-size: clamp(2.1rem, 6vw, 3.4rem);
    margin: 0.5em 0 0.25em;
    /* displaySmall: Light, and tracked in rather than out. */
    font-weight: 300;
    letter-spacing: -0.03em;
    /* The one place the colour is allowed to be the point. */
    background: linear-gradient(100deg,
        var(--phosphor), var(--chartreuse), var(--blossom),
        var(--violet), var(--sky), var(--phosphor));
    background-size: 200% auto;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    animation: shimmer 14s linear infinite;
}

h2 {
    font-size: 1.15rem;
    font-weight: 500;
    margin: 3.4em 0 0.7em;
    color: var(--bone);
    letter-spacing: -0.01em;
    display: flex;
    align-items: baseline;
    gap: 10px;
}

/* A cap and a stem, drawn in two characters, because a section marker that is
   a mushroom is the whole joke and it costs nothing. */
h2::before {
    content: "";
    flex: none;
    width: 14px; height: 14px;
    border-radius: 14px 14px 3px 3px;
    background: linear-gradient(180deg, var(--phosphor), rgba(53,240,160,0.25));
    box-shadow: 0 0 14px rgba(53, 240, 160, 0.55);
    animation: breathe 6s ease-in-out infinite;
}

/* labelSmall, exactly: 10sp, tracked 1.2, medium. It is what every section
   heading in the app is, and the reason its screens read as calm. */
h3 {
    font-size: 0.72rem;
    font-weight: 500;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    margin: 2.4em 0 0.5em;
    color: var(--ash);
}

.card h3 { color: var(--bone); }

p, li { max-width: var(--measure); color: #b9c2ca; }

.lede {
    font-size: 1.05rem;
    line-height: 1.6;
    color: var(--bone);
    max-width: var(--measure);
}

a { color: var(--phosphor); text-decoration-color: rgba(53, 240, 160, 0.35); }
a:hover { text-decoration-color: var(--phosphor); }

strong { color: var(--bone); }
em { color: var(--bone); font-style: italic; }

code {
    font-size: 0.9em;
    color: var(--chartreuse);
    background: rgba(255, 255, 255, 0.04);
    padding: 0.1em 0.35em;
    border-radius: 4px;
}

pre {
    position: relative;
    background: linear-gradient(180deg, rgba(16,21,26,0.95), rgba(10,13,17,0.95));
    border: 1px solid var(--line);
    border-radius: 10px;
    padding: 16px 18px;
    overflow-x: auto;
    font-size: 13px;
    line-height: 1.5;
    box-shadow: inset 0 0 60px rgba(53, 240, 160, 0.04);
    backdrop-filter: blur(4px);
}

pre code { background: none; padding: 0; color: #cfd8de; }

/* The shell prompt, drawn but not text.
   A `$ ` typed into the block is selected by a triple click and pasted into a
   terminal along with the command, where it runs nothing. A pseudo-element is
   not text: not selected, not copied, and not read out as something to type. */
.cmd::before {
    content: "$ ";
    color: var(--ash);
    user-select: none;
    -webkit-user-select: none;
}

/* The host a command is meant for — `laptop $ …`. Drawn, not text, for the
   same reason the dollar is: it cannot be typed and must not be copied. */
.host {
    color: var(--ash);
    user-select: none;
    -webkit-user-select: none;
}

/* The copy button. Quiet until the block is hovered, because it is an
   affordance rather than a feature, and a page of buttons reads as a form. */
.snip { position: relative; }

.snip .copy {
    position: absolute;
    top: 8px;
    right: 8px;
    padding: 3px 9px;
    font: inherit;
    font-size: 11px;
    color: var(--ash);
    background: rgba(14, 18, 22, 0.9);
    border: 1px solid var(--line);
    border-radius: 7px;
    cursor: pointer;
    user-select: none;
    -webkit-user-select: none;
    opacity: 0;
    transition: opacity 140ms ease, color 140ms ease, border-color 140ms ease;
}

.snip:hover .copy, .snip .copy:focus-visible { opacity: 1; }

.snip .copy:hover {
    color: var(--phosphor);
    border-color: rgba(53, 240, 160, 0.45);
}

/* Never hidden where there is no hover to reveal it. */
@media (hover: none) {
    .snip .copy { opacity: 1; }
}
pre .p { color: var(--phosphor); }  /* prompts and good news */
pre .c { color: var(--ash); }       /* comments */
pre .a { color: var(--amber); }     /* warnings */

blockquote {
    margin: 1.6em 0;
    padding: 2px 0 2px 18px;
    border-left: 2px solid var(--violet);
    color: var(--bone);
}

hr { border: 0; border-top: 1px solid var(--line); margin: 3em 0; }

/* --- hero -------------------------------------------------------------- */

.hero { padding: 8vh 0 2vh; position: relative; }

/* The mark, grown rather than placed: it rises as the page loads and then
   keeps breathing, on a period that shares no factor with the aurora's, so the
   two never lock into a rhythm. */
.shroom {
    width: 200px;
    height: 200px;
    margin: 0 0 -14px -10px;
    animation: sprout 1400ms cubic-bezier(0.2, 0.9, 0.2, 1) both,
               bob 11s ease-in-out 1400ms infinite;
    transform-origin: 50% 100%;
}

/* The mark is an <img> of the generated file rather than inline markup, so
   the site cannot drift from the app's icon — which means its insides cannot
   be animated from here. The glow around it can, and that is enough: it is
   the cap's mesh that should look alive, and it already does. */
.shroom img {
    display: block;
    width: 100%;
    height: auto;
    border-radius: 26px;
    animation: glow 9s ease-in-out infinite;
}

@keyframes glow {
    0%, 100% { filter: drop-shadow(0 0 18px rgba(53, 240, 160, 0.35)); }
    33%      { filter: drop-shadow(0 0 34px rgba(154, 123, 255, 0.42)); }
    66%      { filter: drop-shadow(0 0 26px rgba(255, 111, 181, 0.35)); }
}

@keyframes sprout {
    from { transform: translateY(26px) scaleY(0.55) scaleX(0.85); opacity: 0; }
    to   { transform: none; opacity: 1; }
}
@keyframes bob {
    50% { transform: translateY(-8px) rotate(-1.2deg); }
}
@media (max-width: 640px) {
    .shroom { width: 132px; height: 132px; }
}

.hero .sub {
    color: var(--ash);
    font-size: 1rem;
    letter-spacing: 0.02em;
}

.cta { display: flex; gap: 12px; flex-wrap: wrap; margin: 2em 0 0; }

.cta a {
    display: inline-block;
    padding: 9px 16px;
    border: 1px solid var(--line);
    border-radius: 10px;
    background: var(--panel);
    color: var(--bone);
    text-decoration: none;
    font-size: 13px;
}

.cta a.primary {
    border-color: rgba(53, 240, 160, 0.45);
    color: var(--void);
    background: linear-gradient(100deg, var(--phosphor), var(--chartreuse));
    font-weight: 600;
    box-shadow: 0 0 30px rgba(53, 240, 160, 0.28);
}

.cta a:hover {
    border-color: var(--phosphor);
    box-shadow: 0 0 26px rgba(53, 240, 160, 0.3);
    transform: translateY(-1px);
}

.cta a { transition: transform 150ms ease, box-shadow 200ms ease, border-color 200ms ease; }

/* --- cards ------------------------------------------------------------- */

.grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: 14px;
    margin: 1.4em 0;
}

.card {
    position: relative;
    background: linear-gradient(180deg, rgba(20,26,32,0.92), rgba(14,18,22,0.92));
    border: 1px solid var(--line);
    border-radius: 14px;
    padding: 16px 18px;
    backdrop-filter: blur(6px);
    transition: transform 160ms ease, box-shadow 200ms ease, border-color 200ms ease;
}

.card:hover {
    transform: translateY(-2px);
    border-color: rgba(154, 123, 255, 0.5);
    box-shadow: 0 10px 40px rgba(154, 123, 255, 0.16);
}

.card h3 { margin: 0 0 0.4em; font-size: 0.72rem; }

/* A whole card that is a link, for the chooser: the target is the card, and a
   link inside one means aiming at three words instead of at the box somebody
   is already pointing at. */
a.card {
    display: block;
    color: inherit;
}

/* No underline anywhere inside a card. `text-decoration: none` on the anchor
   is not enough: the property inherits into the children, so the heading and
   the paragraph carried the line as well and the whole box read as one long
   link. */
a.card, a.card * {
    text-decoration: none;
}

a.card h3 {
    color: var(--phosphor);
}

a.card::after {
    content: " →";
    color: var(--phosphor);
    opacity: 0.55;
    font-size: 0.8rem;
}
.card p { margin: 0; font-size: 0.9rem; color: var(--ash); max-width: none; }
.card a { text-decoration: none; }

/* A tinted rule at the top of a card, per mesh-tint, purely to carry the
   palette through the page the way the graph does. */
.card.t1 { border-top: 2px solid var(--sky); }
.card.t2 { border-top: 2px solid var(--blossom); }
.card.t3 { border-top: 2px solid var(--chartreuse); }
.card.t4 { border-top: 2px solid var(--violet); }

/* --- callouts ---------------------------------------------------------- */

.note, .warn {
    border: 1px solid var(--line);
    border-left-width: 3px;
    border-radius: 8px;
    padding: 12px 16px;
    margin: 1.6em 0;
    background: rgba(255, 255, 255, 0.02);
}

.note { border-left-color: var(--sky); }
.warn { border-left-color: var(--amber); }
.note p, .warn p { margin: 0; }
.note p + p, .warn p + p { margin-top: 0.6em; }

/* --- tables ------------------------------------------------------------ */

table { border-collapse: collapse; width: 100%; margin: 1.4em 0; font-size: 13px; }
th, td { text-align: left; padding: 8px 12px; border-bottom: 1px solid var(--line); vertical-align: top; }
th {
    color: var(--ash);
    font-weight: 500;
    font-size: 0.72rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
}
td code { white-space: nowrap; }

/* --- status dots, reused from the app's meaning ------------------------ */

.dot { display: inline-block; width: 9px; height: 9px; border-radius: 50%; margin-right: 7px; }
.dot.on { background: var(--phosphor); }
.dot.mid { background: var(--amber); }
.dot.off { background: var(--ash); }
.dot.relay { background: var(--violet); }

footer {
    margin-top: 6em;
    padding-top: 2em;
    border-top: 1px solid var(--line);
    color: var(--ash);
    font-size: 12px;
}

footer a { color: var(--ash); }

/* Motion is decoration; anybody who has asked for less of it gets none. */
/* Motion is decoration, and anybody who has asked for less of it gets the
   colours without the movement — not a stripped page. */
@media (prefers-reduced-motion: reduce) {
    #field { display: none; }
    html { scroll-behavior: auto; }
    .aurora i, h1, h2::before, .shroom, .shroom *, .lattice { animation: none; }
    .aurora { opacity: 0.35; }
}

/* The scrollbar is chrome too. */
* { scrollbar-color: rgba(53, 240, 160, 0.35) transparent; scrollbar-width: thin; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb {
    background: linear-gradient(180deg, rgba(53,240,160,0.5), rgba(154,123,255,0.5));
    border-radius: 10px;
}
::-webkit-scrollbar-track { background: transparent; }

::selection { background: rgba(154, 123, 255, 0.35); color: var(--bone); }
