clone

a deno framework for local-first web apps that open instantly, work offline and sync when users choose

main
lofi / website / src / css / node-landing.css
node-landing.css 14.5K
1/* ============================================================2 /node landing — lofi-node's own furniture, on the shared3 forest/lime tokens from landing.css (scoped under .landing).4 Three interactive rigs: the playable node (np-), the ticket5 anatomy (ta-), and the shared-store merge (sm-).67 The page carries its own, more natural type palette: Young8 Serif's hand-hewn display, Alegreya Sans' humanist body, and9 Spline Sans Mono's soft console — swapped in through the10 font variables so every shared rule follows along.11 ============================================================ */12/* Font faces load from the site-wide stylesheet link (docusaurus.config.ts);13 an @import here would serialize the font CSS behind the app bundle. */1415.landing--node {16 --f-display: "Young Serif", "Iowan Old Style", Georgia, serif;17 --f-body: "Alegreya Sans", "Gill Sans", system-ui, sans-serif;18 --f-mono: "Spline Sans Mono", ui-monospace, "SF Mono", Menlo, monospace;19 font-size: 17.5px;20}21.landing--node h1,22.landing--node h2,23.landing--node h3 {24 font-weight: 400; /* Young Serif ships one weight; let it breathe */25 letter-spacing: 0;26}2728/* ---------- the lofi-node mark ---------- */29.landing--node .node-hero {30 padding: 3.4rem 0 1rem;31}32.landing--node .node-hero-top {33 display: flex;34 align-items: baseline;35 justify-content: space-between;36 gap: 1.4rem 2.4rem;37 flex-wrap: wrap;38 margin-bottom: 2rem;39}40/* The root page's mark is brand Nunito; this page's mark wears its own41 face — a gothic serif with dark-fantasy blood, still legible as a42 wordmark. */43.landing--node .nmark {44 font-family: "Grenze Gotisch", "Young Serif", Georgia, serif;45 font-weight: 500;46 letter-spacing: 0.01em;47}48.landing--node .node-hero-side {49 display: grid;50 justify-items: end;51 gap: 0.8rem;52}53.landing--node .node-hero-side .cta-row {54 margin: 0;55}56.landing--node .node-hero .np {57 max-width: 920px;58 margin: 0 auto;59}60.landing--node .node-hero .np-scene {61 min-height: 168px;62}63@media (max-width: 860px) {64 .landing--node .node-hero-side {65 justify-items: start;66 }67}6869/* ---------- the playable node ---------- */70.landing .np {71 background: rgba(34, 51, 44, 0.55);72 border: 1px solid var(--forest-2);73 border-radius: var(--radius);74 padding: 1.2rem 1.2rem 1.1rem;75 box-shadow: 0 24px 60px rgba(0, 0, 0, 0.28);76}7778.landing .np-scene {79 display: flex;80 align-items: center;81 justify-content: center;82 gap: 0;83 min-height: 132px;84 padding: 0.6rem 0.2rem 1rem;85}8687/* Resting opacity stays legible: the scene must read at a glance before any88 interaction. Turning on brightens; it doesn't reveal. */89.landing .np-device {90 display: grid;91 justify-items: center;92 gap: 0.2rem;93 opacity: 0.6;94 transition: opacity 0.3s ease;95 min-width: 64px;96 position: relative;97}98.landing .np-device.is-on {99 opacity: 1;100}101/* An SVG outline, not U+25AF — mobile fonts substitute that codepoint102 inconsistently (missing-glyph boxes or emoji fallbacks). */103.landing .np-device-glyph {104 width: 2rem;105 height: 3.25rem;106 color: var(--mint);107}108.landing .np-device-label,109.landing .np-home-label {110 font-family: var(--f-mono);111 font-size: 0.62rem;112 letter-spacing: 0.14em;113 text-transform: uppercase;114 color: var(--ink-dim);115}116.landing .np-chip {117 position: absolute;118 top: -1.4rem;119 font-family: var(--f-mono);120 font-size: 0.58rem;121 color: var(--forest);122 background: var(--lime);123 border-radius: 999px;124 padding: 0.05rem 0.45rem;125 white-space: nowrap;126}127.landing .np-chip--dead {128 background: var(--jade);129 color: var(--cream);130 text-decoration: line-through;131}132/* The freshly issued ticket hovers above the home's roof until it is133 pasted into the app — then it reappears pinned to the phone. */134.landing .np-chip--ticket {135 top: -1.6rem;136 left: 50%;137 transform: translateX(-50%);138}139140/* No grid gap: the roof must sit flush on the body. The label carries its141 own spacing instead. */142.landing .np-home {143 display: grid;144 justify-items: center;145 gap: 0;146 opacity: 0.6;147 transition: opacity 0.3s ease;148 position: relative;149}150.landing .np-home .np-home-label {151 margin-top: 0.3rem;152}153.landing .np-home.is-on {154 opacity: 1;155}156.landing .np-home-roof {157 width: 0;158 height: 0;159 border-left: 34px solid transparent;160 border-right: 34px solid transparent;161 border-bottom: 20px solid var(--jade);162}163.landing .np-home-body {164 display: grid;165 gap: 0.3rem;166 background: var(--forest);167 border: 1px solid var(--jade);168 border-top: 0;169 padding: 0.45rem;170 width: 68px;171}172.landing .np-part {173 font-family: var(--f-mono);174 font-size: 0.6rem;175 letter-spacing: 0.08em;176 /* Left-aligned on a shared inset: centered words of different lengths177 never share an edge, which reads as misalignment at this size. */178 text-align: left;179 color: var(--ink-dim);180 border: 1px dashed var(--jade);181 border-radius: 4px;182 padding: 0.18rem 0.2rem 0.18rem 0.45rem;183 opacity: 0.75;184 transition: opacity 0.3s ease, border-color 0.3s ease;185 position: relative;186}187.landing .np-part.is-on {188 opacity: 1;189 color: var(--cream);190 border-style: solid;191}192.landing .np-led {193 position: absolute;194 top: 50%;195 right: 0.3rem;196 transform: translateY(-50%);197 width: 5px;198 height: 5px;199 border-radius: 50%;200 background: var(--lime);201}202203/* Links are drawn as a repeating dash gradient rather than a dashed border204 so the live animation can move the PATTERN (segments streaming toward the205 store) while the element — and the rtt label inside it — stays put. */206.landing .np-link {207 flex: 1 1 30px;208 max-width: 92px;209 height: 2px;210 --np-link-ink: var(--jade);211 background-image: repeating-linear-gradient(212 90deg,213 var(--np-link-ink) 0 6px,214 transparent 6px 12px215 );216 margin: 0 0.5rem;217 position: relative;218}219.landing .np-link.is-live {220 --np-link-ink: var(--mint);221}222.landing .np-link.is-cut {223 --np-link-ink: var(--jade);224 background-image: repeating-linear-gradient(225 90deg,226 var(--np-link-ink) 0 2px,227 transparent 2px 8px228 );229}230.landing .np-link--tunnel {231 max-width: 76px;232}233.landing .np-link--tunnel.is-live {234 --np-link-ink: var(--lime);235}236/* Two stacked lines so the label never cuts into the houses beside it. */237.landing .np-rtt {238 position: absolute;239 top: 0.35rem;240 left: 50%;241 transform: translateX(-50%);242 font-family: var(--f-mono);243 font-size: 0.56rem;244 line-height: 1.5;245 text-align: center;246 white-space: nowrap;247 color: var(--ink-dim);248}249@media (prefers-reduced-motion: no-preference) {250 .landing .np-led {251 animation: np-breathe 1.6s ease-in-out infinite;252 }253 .landing .np-link.is-live:not(.is-cut) {254 animation: np-march 0.8s linear infinite;255 }256 @keyframes np-breathe {257 50% {258 opacity: 0.35;259 }260 }261 /* One full dash period per loop — a seamless rightward stream. */262 @keyframes np-march {263 to {264 background-position-x: 12px;265 }266 }267}268269/* Lines pin to the bottom and grow upward, like a real terminal. The height270 is fixed at the 7-line steady state (padding + 7 × 1.6 × 0.72rem) so271 appended lines clip off the top instead of shifting the buttons below. */272.landing .np-console {273 font-family: var(--f-mono);274 font-size: 0.72rem;275 line-height: 1.6;276 border-top: 1px solid var(--forest-2);277 padding-top: 0.7rem;278 height: calc(0.7rem + 7 * 1.6 * 0.72rem);279 overflow: hidden;280 display: flex;281 flex-direction: column;282 justify-content: flex-end;283}284.landing .np-console p {285 margin: 0;286 color: var(--ink-dim);287 max-width: none;288}289.landing .np-console p[data-kind="cmd"] {290 color: var(--cream);291}292.landing .np-console p[data-kind="cmd"] b {293 color: var(--lime);294 font-weight: 500;295}296.landing .np-console p[data-kind="warn"] {297 color: var(--mint);298}299300.landing .np-actions {301 display: flex;302 flex-wrap: wrap;303 gap: 0.45rem;304 margin-top: 0.8rem;305}306.landing .np-btn {307 font-family: var(--f-mono);308 font-size: 0.72rem;309 color: var(--cream);310 background: var(--forest);311 border: 1px solid var(--jade);312 border-radius: 999px;313 padding: 0.32rem 0.85rem;314 cursor: pointer;315 transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;316}317.landing .np-btn:hover {318 border-color: var(--mint);319}320.landing .np-btn--go {321 background: var(--lime);322 border-color: var(--lime);323 color: var(--forest);324 font-weight: 600;325}326.landing .np-btn--go:hover {327 background: var(--mint);328 border-color: var(--mint);329}330.landing .np-btn--ghost {331 border-color: transparent;332 color: var(--ink-dim);333}334.landing .np-btn--ghost:hover {335 border-color: var(--jade);336 color: var(--cream);337}338339/* ---------- ticket anatomy ---------- */340.landing .ta-wire {341 font-family: var(--f-mono);342 font-size: 0.78rem;343 color: var(--ink-dim);344 display: flex;345 align-items: baseline;346 gap: 0.4rem;347 flex-wrap: wrap;348 margin-bottom: 0.9rem;349}350.landing .ta-prefix {351 color: var(--lime);352}353.landing .ta-blob {354 overflow: hidden;355 text-overflow: ellipsis;356 white-space: nowrap;357 max-width: 34ch;358 opacity: 0.7;359}360.landing .ta-open {361 color: var(--mint);362 font-size: 0.68rem;363 letter-spacing: 0.12em;364 text-transform: uppercase;365}366367.landing .ta-body {368 display: grid;369 grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr);370 gap: 1.6rem;371 align-items: start;372}373.landing .ta-json {374 font-family: var(--f-mono);375 font-size: 0.8rem;376 line-height: 2;377 background: rgba(34, 51, 44, 0.5);378 border: 1px solid var(--forest-2);379 border-radius: var(--radius);380 padding: 1.1rem 1.2rem;381 margin: 0;382 color: var(--jade);383 overflow-x: auto;384}385.landing .ta-json code {386 background: none;387 border: 0;388 padding: 0;389 font: inherit;390 color: inherit;391}392.landing .ta-field {393 font: inherit;394 color: var(--cream);395 background: none;396 border: 0;397 border-bottom: 1px dashed var(--jade);398 padding: 0.05rem 0.1rem;399 cursor: pointer;400 transition: background 0.15s ease, color 0.15s ease;401}402.landing .ta-field:hover {403 background: rgba(153, 229, 189, 0.12);404}405.landing .ta-field.is-active {406 background: var(--lime);407 color: var(--forest);408 border-bottom-color: transparent;409 border-radius: 4px;410}411.landing .ta-secret {412 letter-spacing: 0.06em;413}414415.landing .ta-card {416 border-left: 2px solid var(--lime);417 padding: 0.2rem 0 0.2rem 1.1rem;418 min-height: 11rem;419}420.landing .ta-card h3 {421 margin-bottom: 0.45rem;422}423.landing .ta-card p {424 font-size: 0.92rem;425 color: var(--ink-dim);426 margin-bottom: 0.7rem;427}428.landing .ta-scope {429 display: inline-flex;430 border: 1px solid var(--jade);431 border-radius: 999px;432 overflow: hidden;433 margin-bottom: 0.6rem;434}435.landing .ta-scope button {436 font-family: var(--f-mono);437 font-size: 0.72rem;438 background: none;439 border: 0;440 color: var(--ink-dim);441 padding: 0.25rem 0.8rem;442 cursor: pointer;443}444.landing .ta-scope button.is-active {445 background: var(--lime);446 color: var(--forest);447 font-weight: 600;448}449.landing .ta-inject {450 font-family: var(--f-mono);451 font-size: 0.74rem;452 color: var(--ink-dim);453}454.landing .ta-inject.is-on {455 color: var(--mint);456}457458@media (max-width: 860px) {459 .landing .ta-body {460 grid-template-columns: 1fr;461 }462 .landing .ta-card {463 min-height: 0;464 }465}466467/* ---------- shared-store merge ---------- */468.landing .sm {469 background: rgba(34, 51, 44, 0.55);470 border: 1px solid var(--forest-2);471 border-radius: var(--radius);472 padding: 1.2rem 1.3rem 1.1rem;473}474.landing .sm-bar {475 display: flex;476 align-items: baseline;477 gap: 0.8rem;478 flex-wrap: wrap;479 margin-bottom: 1rem;480}481.landing .sm-status {482 font-family: var(--f-mono);483 font-size: 0.74rem;484 display: inline-flex;485 align-items: center;486 gap: 0.4rem;487 border: 1px solid var(--jade);488 border-radius: 999px;489 padding: 0.2rem 0.75rem;490 color: var(--cream);491}492.landing .sm-status i {493 width: 7px;494 height: 7px;495 border-radius: 50%;496 background: var(--jade);497}498.landing .sm-status[data-code="ok"] i {499 background: var(--lime);500}501.landing .sm-status[data-code="no_schema"] i,502.landing .sm-status[data-code="schema_drift"] i {503 background: var(--mint);504}505.landing .sm-note {506 font-family: var(--f-mono);507 font-size: 0.72rem;508 color: var(--ink-dim);509}510511.landing .sm-shelf {512 display: flex;513 gap: 1rem;514 flex-wrap: wrap;515 align-items: stretch;516 min-height: 7.2rem;517 border-top: 1px dashed var(--forest-2);518 border-bottom: 1px dashed var(--forest-2);519 padding: 0.9rem 0;520}521/* The empty store is a vacant slice: same footprint and radius as a real522 one, dashed outline, waiting to be filled by the first provisioning. */523.landing .sm-empty {524 flex: 1 1 100%;525 display: grid;526 place-items: center;527 min-height: 4.6rem;528 border: 1px dashed var(--jade);529 border-radius: 10px;530 font-family: var(--f-mono);531 font-size: 0.76rem;532 color: var(--ink-dim);533 font-style: italic;534}535.landing .sm-slice {536 display: flex;537 align-items: center;538 align-content: flex-start;539 gap: 0.5rem;540 flex-wrap: wrap;541 border: 1px solid var(--jade);542 border-radius: 10px;543 padding: 0.6rem 0.8rem;544 /* Slices share the full shelf width on desktop and wrap on small screens. */545 flex: 1 1 280px;546}547.landing .sm-slice--notes {548 border-color: var(--lime);549}550@media (prefers-reduced-motion: no-preference) {551 .landing .sm-slice--notes {552 animation: sm-arrive 0.35s ease-out;553 }554 @keyframes sm-arrive {555 from {556 transform: translateX(14px);557 opacity: 0;558 }559 }560}561.landing .sm-ns {562 font-family: var(--f-mono);563 font-size: 0.68rem;564 letter-spacing: 0.1em;565 color: var(--ink-dim);566}567.landing .sm-table {568 font-family: var(--f-mono);569 font-size: 0.76rem;570 color: var(--forest);571 background: var(--mint);572 border-radius: 6px;573 padding: 0.28rem 0.6rem;574}575.landing .sm-slice--notes .sm-table {576 background: var(--lime);577}578.landing .sm-table.is-contested {579 background: none;580 color: var(--mint);581 border: 1px dashed var(--mint);582 /* The border replaces the fill; keep the chip's box the same size. */583 padding: calc(0.28rem - 1px) calc(0.6rem - 1px);584}585.landing .sm-seal {586 flex-basis: 100%;587 font-family: var(--f-mono);588 font-size: 0.62rem;589 letter-spacing: 0.06em;590 color: var(--ink-dim);591}592.landing .sm-actions {593 display: flex;594 flex-wrap: wrap;595 gap: 0.45rem;596 margin-top: 0.9rem;597}598599/* ---------- page-level bits ---------- */600/* Footnotes directly under the rigs are single pointers, not body prose;601 let them use the band's full width instead of the 62ch reading measure. */602.landing--node .band > .intro-note {603 max-width: none;604 margin-bottom: 0;605 padding-top: 1rem;606}607608.landing .node-paths {609 columns: 2;610 column-gap: 3rem;611 max-width: none;612}613.landing .node-paths li {614 break-inside: avoid;615 margin-bottom: 0.55rem;616 color: var(--ink-dim);617}618.landing .node-paths li b {619 color: var(--cream);620 font-weight: 600;621}622@media (max-width: 760px) {623 .landing .node-paths {624 columns: 1;625 }626}