Exosphere is a set of small, modular, self-hostable community tools built on the AT Protocol. app.exosphere.site
at main 628 lines 14 kB view raw
1import { globalStyle, style } from "@vanilla-extract/css"; 2import { vars } from "./theme.css.ts"; 3 4globalStyle("html, body", { 5 backgroundColor: vars.color.surface, 6 margin: 0, 7}); 8 9// Mobile-first breakpoints (min-width) 10const bp = { 11 sm: "screen and (min-width: 480px)", 12 md: "screen and (min-width: 768px)", 13}; 14 15// ---- Root ---- 16 17export const themeRoot = style({ 18 minHeight: "100vh", 19 display: "flex", 20 flexDirection: "column", 21 fontFamily: vars.font.body, 22 lineHeight: 1.6, 23 color: vars.color.text, 24 backgroundColor: vars.color.bg, 25 transition: "background-color 0.2s, color 0.2s", 26}); 27 28// ---- Layout ---- 29 30export const container = style({ 31 maxWidth: "640px", 32 marginInline: "auto", 33 paddingInline: vars.space.md, 34 paddingBlockEnd: vars.space.xl, 35}); 36 37export const stack = style({ 38 display: "flex", 39 flexDirection: "column", 40 gap: vars.space.md, 41}); 42 43export const stackSm = style({ 44 display: "flex", 45 flexDirection: "column", 46 gap: vars.space.sm, 47}); 48 49export const stackLg = style({ 50 display: "flex", 51 flexDirection: "column", 52 gap: vars.space.lg, 53}); 54 55export const cluster = style({ 56 display: "flex", 57 flexWrap: "wrap", 58 gap: vars.space.sm, 59}); 60 61export const row = style({ 62 display: "flex", 63 alignItems: "center", 64 justifyContent: "space-between", 65}); 66 67export const section = style({ 68 marginBlockStart: vars.space.lg, 69 display: "flex", 70 flexDirection: "column", 71 gap: vars.space.md, 72}); 73 74// ---- Header ---- 75 76export const header = style({ 77 borderBlockEnd: `1px solid ${vars.color.border}`, 78 backgroundColor: vars.color.surface, 79 paddingBlock: vars.space.sm, 80 marginBlockEnd: vars.space.lg, 81 boxShadow: `0 1px 3px ${vars.color.shadow}, 0 1px 2px ${vars.color.shadow}`, 82 transition: "background-color 0.2s, border-color 0.2s, box-shadow 0.2s", 83 "@media": { 84 [bp.sm]: { 85 paddingBlock: vars.space.md, 86 marginBlockEnd: vars.space.xl, 87 }, 88 }, 89}); 90 91export const headerInner = style({ 92 marginInline: "auto", 93 paddingInline: vars.space.lg, 94 display: "flex", 95 alignItems: "center", 96 justifyContent: "space-between", 97}); 98 99export const headerTitle = style({ 100 fontFamily: vars.font.heading, 101 fontWeight: 700, 102 fontSize: "1rem", 103 color: vars.color.text, 104 letterSpacing: "-0.01em", 105 ":hover": { textDecoration: "none" }, 106 "@media": { 107 [bp.sm]: { 108 fontSize: "1.125rem", 109 }, 110 }, 111}); 112 113export const headerNav = style({ 114 display: "flex", 115 alignItems: "center", 116 gap: vars.space.sm, 117 "@media": { 118 [bp.sm]: { 119 gap: vars.space.md, 120 }, 121 }, 122}); 123 124// ---- Cards ---- 125 126export const card = style({ 127 backgroundColor: vars.color.surface, 128 border: `1px solid ${vars.color.border}`, 129 borderRadius: vars.radius.lg, 130 paddingBlock: vars.space.md, 131 paddingInline: vars.space.lg, 132 boxShadow: `0 1px 3px ${vars.color.shadow}, 0 1px 2px ${vars.color.shadow}`, 133 transition: "background-color 0.2s, border-color 0.2s, box-shadow 0.2s", 134 ":hover": { 135 backgroundColor: vars.color.surfaceHover, 136 }, 137 "@media": { 138 [bp.sm]: { 139 paddingBlock: vars.space.lg, 140 paddingInline: vars.space.xl, 141 }, 142 }, 143}); 144 145export const cardLink = style({ 146 display: "flex", 147 flexDirection: "column", 148 gap: vars.space.xs, 149 textDecoration: "none", 150 color: "inherit", 151 backgroundColor: vars.color.surface, 152 border: `1px solid ${vars.color.border}`, 153 borderRadius: vars.radius.lg, 154 paddingBlock: vars.space.md, 155 paddingInline: vars.space.lg, 156 boxShadow: `0 1px 3px ${vars.color.shadow}, 0 1px 2px ${vars.color.shadow}`, 157 transition: "border-color 0.15s, box-shadow 0.2s, background-color 0.2s, transform 0.15s", 158 ":hover": { 159 backgroundColor: vars.color.surfaceHover, 160 borderColor: vars.color.primary, 161 boxShadow: `0 4px 12px ${vars.color.shadowStrong}, 0 2px 4px ${vars.color.shadow}`, 162 transform: "translateY(-1px)", 163 textDecoration: "none", 164 }, 165 "@media": { 166 [bp.sm]: { 167 paddingBlock: vars.space.lg, 168 paddingInline: vars.space.xl, 169 }, 170 }, 171}); 172 173export const cardNarrow = style({ 174 backgroundColor: vars.color.surface, 175 border: `1px solid ${vars.color.border}`, 176 borderRadius: vars.radius.lg, 177 paddingBlock: vars.space.lg, 178 paddingInline: vars.space.xl, 179 maxWidth: "400px", 180 marginInline: "auto", 181 boxShadow: `0 1px 3px ${vars.color.shadow}, 0 1px 2px ${vars.color.shadow}`, 182 transition: "background-color 0.2s, border-color 0.2s, box-shadow 0.2s", 183 "@media": { 184 [bp.sm]: { 185 paddingBlock: vars.space.xl, 186 paddingInline: vars.space.xxl, 187 }, 188 }, 189}); 190 191export const cardFlat = style({ 192 borderBlockStart: `1px solid ${vars.color.border}`, 193 paddingBlock: vars.space.sm, 194 "@media": { 195 [bp.sm]: { 196 paddingBlock: vars.space.md, 197 }, 198 }, 199}); 200 201// ---- Buttons ---- 202 203const btnBase = { 204 boxSizing: "border-box" as const, 205 display: "inline-flex" as const, 206 alignItems: "center" as const, 207 justifyContent: "center" as const, 208 borderRadius: vars.radius.sm, 209 fontWeight: 500, 210 lineHeight: 1.5, 211 cursor: "pointer", 212 fontFamily: vars.font.body, 213 minBlockSize: "44px", 214 whiteSpace: "nowrap" as const, 215 transition: "background-color 0.15s, opacity 0.15s, box-shadow 0.15s, transform 0.1s", 216}; 217 218export const button = style({ 219 ...btnBase, 220 paddingBlock: vars.space.sm, 221 paddingInline: vars.space.lg, 222 border: "none", 223 fontSize: "0.875rem", 224 backgroundColor: vars.color.primary, 225 color: "#fff", 226 boxShadow: `0 1px 2px ${vars.color.shadow}`, 227 ":hover": { 228 backgroundColor: vars.color.primaryHover, 229 textDecoration: "none", 230 boxShadow: `0 2px 6px ${vars.color.shadowStrong}`, 231 }, 232 ":disabled": { opacity: 0.5, cursor: "not-allowed" }, 233 ":active": { transform: "scale(0.98)" }, 234}); 235 236export const buttonSecondary = style({ 237 ...btnBase, 238 paddingBlock: vars.space.sm, 239 paddingInline: vars.space.lg, 240 border: `1px solid ${vars.color.border}`, 241 fontSize: "0.875rem", 242 backgroundColor: vars.color.surface, 243 color: vars.color.text, 244 ":hover": { 245 backgroundColor: vars.color.bg, 246 textDecoration: "none", 247 borderColor: vars.color.primary, 248 }, 249 ":active": { transform: "scale(0.98)" }, 250}); 251 252export const buttonDanger = style({ 253 ...btnBase, 254 paddingBlock: "6px", 255 paddingInline: vars.space.md, 256 border: "none", 257 fontSize: "0.75rem", 258 minBlockSize: "36px", 259 backgroundColor: vars.color.danger, 260 color: "#fff", 261 ":hover": { opacity: 0.9 }, 262 ":active": { transform: "scale(0.98)" }, 263}); 264 265// ---- Forms ---- 266 267export const formStack = style({ 268 display: "flex", 269 flexDirection: "column", 270 gap: vars.space.lg, 271}); 272 273export const label = style({ 274 display: "block", 275 fontSize: "0.875rem", 276 fontWeight: 500, 277 marginBlockEnd: vars.space.xs, 278}); 279 280export const labelHint = style({ 281 fontWeight: 400, 282 color: vars.color.textMuted, 283}); 284 285const inputBase = { 286 boxSizing: "border-box" as const, 287 width: "100%" as const, 288 paddingBlock: "10px", 289 paddingInline: vars.space.md, 290 borderRadius: vars.radius.sm, 291 border: `1px solid ${vars.color.border}`, 292 fontSize: "1rem", 293 lineHeight: 1.5, 294 color: vars.color.text, 295 backgroundColor: vars.color.surface, 296 outline: "none" as const, 297 fontFamily: vars.font.body, 298 transition: "border-color 0.15s, box-shadow 0.15s", 299}; 300 301const inputFocus = { 302 borderColor: vars.color.primary, 303 boxShadow: `0 0 0 3px ${vars.color.focusRing}`, 304}; 305 306export const input = style({ 307 ...inputBase, 308 ":focus": inputFocus, 309}); 310 311export const select = style({ 312 ...inputBase, 313 ":focus": inputFocus, 314}); 315 316export const textarea = style({ 317 ...inputBase, 318 resize: "vertical" as const, 319 minHeight: "80px", 320 fontFamily: vars.font.body, 321 ":focus": inputFocus, 322}); 323 324// ---- Typography ---- 325 326export const pageTitle = style({ 327 fontFamily: vars.font.heading, 328 fontSize: "1.25rem", 329 fontWeight: 700, 330 letterSpacing: "-0.02em", 331 "@media": { 332 [bp.sm]: { 333 fontSize: "1.5rem", 334 }, 335 }, 336}); 337 338export const sectionTitle = style({ 339 fontFamily: vars.font.heading, 340 fontSize: "1rem", 341 fontWeight: 600, 342 letterSpacing: "-0.01em", 343 "@media": { 344 [bp.sm]: { 345 fontSize: "1.125rem", 346 }, 347 }, 348}); 349 350export const cardTitle = style({ 351 fontFamily: vars.font.heading, 352 fontSize: "1rem", 353 fontWeight: 600, 354 letterSpacing: "-0.01em", 355 "@media": { 356 [bp.sm]: { 357 fontSize: "1.0625rem", 358 }, 359 }, 360}); 361 362export const cardHeading = style({ 363 fontFamily: vars.font.heading, 364 fontSize: "1.125rem", 365 fontWeight: 600, 366 marginBlockEnd: vars.space.sm, 367 letterSpacing: "-0.01em", 368 "@media": { 369 [bp.sm]: { 370 fontSize: "1.25rem", 371 }, 372 }, 373}); 374 375export const muted = style({ 376 color: vars.color.textMuted, 377 fontSize: "0.8125rem", 378}); 379 380export const description = style({ 381 color: vars.color.textMuted, 382 fontSize: "0.875rem", 383 lineHeight: 1.5, 384 display: "-webkit-box", 385 WebkitLineClamp: 2, 386 WebkitBoxOrient: "vertical", 387 overflow: "hidden", 388}); 389 390export const subsectionTitle = style({ 391 fontSize: "0.875rem", 392 fontWeight: 500, 393 color: vars.color.textMuted, 394}); 395 396export const collapsibleHeading = style({ 397 display: "flex", 398 alignItems: "center", 399 gap: vars.space.sm, 400 cursor: "pointer", 401 userSelect: "none", 402 background: "none", 403 border: "none", 404 padding: 0, 405 fontFamily: vars.font.heading, 406 fontSize: "1rem", 407 fontWeight: 600, 408 color: vars.color.text, 409 ":hover": { 410 color: vars.color.primary, 411 }, 412}); 413 414export const chevron = style({ 415 display: "inline-block", 416 fontSize: "0.75rem", 417 transition: "transform 0.15s", 418}); 419 420export const chevronExpanded = style({ 421 transform: "rotate(180deg)", 422}); 423 424export const introText = style({ 425 color: vars.color.textMuted, 426 fontSize: "0.875rem", 427 lineHeight: 1.6, 428 marginBlockEnd: vars.space.lg, 429}); 430 431export const badge = style({ 432 display: "inline-block", 433 paddingBlock: "2px", 434 paddingInline: vars.space.sm, 435 borderRadius: vars.radius.sm, 436 fontSize: "0.75rem", 437 fontWeight: 500, 438 backgroundColor: vars.color.primaryLight, 439 color: vars.color.primary, 440}); 441 442globalStyle(`${badge}[data-status="not-planned"]`, { 443 backgroundColor: vars.color.dangerLight, 444 color: vars.color.danger, 445}); 446 447globalStyle(`${badge}[data-status="approved"]`, { 448 backgroundColor: vars.color.primaryLight, 449 color: vars.color.primary, 450}); 451 452globalStyle(`${badge}[data-status="in-progress"]`, { 453 backgroundColor: vars.color.warningLight, 454 color: vars.color.warning, 455}); 456 457globalStyle(`${badge}[data-status="done"]`, { 458 backgroundColor: vars.color.successLight, 459 color: vars.color.success, 460}); 461 462export const errorText = style({ 463 color: vars.color.danger, 464 fontSize: "0.875rem", 465}); 466 467// ---- Misc ---- 468 469export const metaRow = style({ 470 display: "flex", 471 flexWrap: "wrap", 472 alignItems: "center", 473 gap: vars.space.md, 474 fontSize: "0.875rem", 475}); 476 477export const buttonInline = style({ 478 background: "none", 479 border: "none", 480 color: vars.color.primary, 481 fontSize: "0.875rem", 482 fontWeight: 500, 483 fontFamily: vars.font.body, 484 cursor: "pointer", 485 paddingBlock: 0, 486 paddingInline: vars.space.sm, 487 ":hover": { textDecoration: "underline" }, 488}); 489 490export const buttonDangerInline = style({ 491 background: "none", 492 border: "none", 493 color: vars.color.danger, 494 fontSize: "0.875rem", 495 fontWeight: 500, 496 fontFamily: vars.font.body, 497 cursor: "pointer", 498 paddingBlock: 0, 499 paddingInline: vars.space.sm, 500 ":hover": { textDecoration: "underline" }, 501}); 502 503export const inlineTag = style({ 504 marginInlineStart: vars.space.sm, 505}); 506 507// ---- Responsive utilities ---- 508 509export const hiddenMobile = style({ 510 display: "none", 511 "@media": { 512 [bp.md]: { 513 display: "initial", 514 }, 515 }, 516}); 517 518// ---- Theme toggle ---- 519 520// ---- Tab navigation ---- 521 522export const tabNav = style({ 523 display: "flex", 524 gap: vars.space.xs, 525 borderBlockEnd: `1px solid ${vars.color.border}`, 526 paddingBlockEnd: vars.space.xs, 527}); 528 529export const tabNavLink = style({ 530 paddingBlock: vars.space.xs, 531 paddingInline: vars.space.md, 532 borderRadius: vars.radius.sm, 533 fontSize: "0.875rem", 534 fontWeight: 500, 535 color: vars.color.textMuted, 536 textDecoration: "none", 537 transition: "color 0.15s, background-color 0.15s", 538 ":hover": { 539 color: vars.color.text, 540 backgroundColor: vars.color.surfaceHover, 541 textDecoration: "none", 542 }, 543}); 544 545export const tabNavActive = style({ 546 paddingBlock: vars.space.xs, 547 paddingInline: vars.space.md, 548 borderRadius: vars.radius.sm, 549 fontSize: "0.875rem", 550 fontWeight: 500, 551 color: vars.color.primary, 552 backgroundColor: vars.color.primaryLight, 553}); 554 555// ---- Theme toggle ---- 556 557export const themeToggle = style({ 558 display: "inline-flex", 559 alignItems: "center", 560 border: `1px solid ${vars.color.border}`, 561 borderRadius: vars.radius.sm, 562 overflow: "hidden", 563 padding: "2px", 564 gap: "2px", 565 backgroundColor: vars.color.bg, 566 transition: "background-color 0.2s, border-color 0.2s", 567}); 568 569export const themeToggleBtn = style({ 570 display: "inline-flex", 571 alignItems: "center", 572 justifyContent: "center", 573 inlineSize: "28px", 574 blockSize: "28px", 575 border: "none", 576 borderRadius: "6px", 577 cursor: "pointer", 578 backgroundColor: "transparent", 579 color: vars.color.textMuted, 580 padding: 0, 581 transition: "background-color 0.15s, color 0.15s", 582 ":hover": { color: vars.color.text }, 583}); 584 585export const themeToggleBtnActive = style({ 586 backgroundColor: vars.color.surface, 587 color: vars.color.text, 588 boxShadow: `0 1px 2px ${vars.color.shadow}`, 589}); 590 591// ---- Footer ---- 592 593export const mainContent = style({ 594 flex: 1, 595}); 596 597export const footer = style({ 598 borderBlockStart: `1px solid ${vars.color.border}`, 599 paddingBlock: vars.space.lg, 600 marginBlockStart: vars.space.xxl, 601 transition: "background-color 0.2s, border-color 0.2s", 602}); 603 604export const footerInner = style({ 605 maxWidth: "640px", 606 marginInline: "auto", 607 paddingInline: vars.space.md, 608 display: "flex", 609 flexWrap: "wrap", 610 alignItems: "center", 611 justifyContent: "center", 612 gap: vars.space.sm, 613 fontSize: "0.8125rem", 614 color: vars.color.textMuted, 615}); 616 617export const footerSep = style({ 618 color: vars.color.border, 619}); 620 621export const footerLink = style({ 622 color: vars.color.textMuted, 623 textDecoration: "none", 624 transition: "color 0.15s", 625 ":hover": { 626 color: vars.color.primary, 627 }, 628});