feat(ui): add the ToBePlayed logo and name
The logo is a traced SVG whose colors follow the accent color; violet shows it as designed. Favicon, PWA and Apple icons use it with fixed colors. The header and landing page say ToBePlayed instead of Game Log. Below 380 px the header shows only the logo, and the home link keeps its name for screen readers.
No files matched your search
@@ -1,7 +1,7 @@
|
|||||||
# To Be Played – Frontend
|
# To Be Played – Frontend
|
||||||
|
|
||||||
Web app of **To Be Played**, a Steam backlog tracker: import your Steam library, decide what to play, and track what
|
Web app of **To Be Played**, a Steam backlog tracker: import your Steam library, decide what to play, and track what
|
||||||
you finished. The app is shown as "Game Log" in the UI, in German and English, light and dark.
|
you finished. The UI is in German and English, with light and dark mode.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 15 KiB |
@@ -1,10 +1,23 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="255 102.5 690 690">
|
||||||
<rect width="512" height="512" rx="112" fill="#7f22fe"/>
|
<defs>
|
||||||
<g transform="translate(88 88) scale(14)" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<clipPath id="tbp-outline"><path d="M 429 235.6 C 392.6 239.4, 357.1 259.4, 337.1 287.5 C 308.1 328.3, 278.1 428.0, 267.0 520.5 C 260.7 572.6, 267.7 612.7, 287.0 635.5 C 311.9 665.0, 350.6 669.1, 386 646.1 C 393.4 641.2, 416.6 618.4, 430.8 602 C 465.6 561.5, 470.2 557.3, 483.9 553.8 C 493.4 551.3, 706.0 551.0, 715.4 553.5 C 729.4 557.1, 735.2 562.2, 765.0 597 C 805.6 644.4, 826.8 659.2, 856.5 660.6 C 896.8 662.7, 926.3 633.4, 933.3 584.3 C 943.2 515.9, 904.8 351.0, 865.1 291 C 843.3 258.0, 805.4 237.4, 762.4 235.2 C 744.0 234.3, 734.0 237.0, 714 248.5 C 690.1 262.0, 691.4 261.9, 600.3 261.9 C 508.8 262.0, 511.5 262.3, 485.1 247.6 C 464.7 236.2, 451.2 233.3, 429 235.6"/></clipPath>
|
||||||
<line x1="6" x2="10" y1="11" y2="11"/>
|
<linearGradient id="tbp-ring" gradientUnits="userSpaceOnUse" x1="265" y1="660" x2="935" y2="235"><stop offset="0" stop-color="#7467f9"/><stop offset="0.15" stop-color="#7467f9"/><stop offset="0.42" stop-color="#0192ef"/><stop offset="0.7" stop-color="#17c7a4"/><stop offset="1" stop-color="#17c7a4"/></linearGradient>
|
||||||
<line x1="8" x2="8" y1="9" y2="13"/>
|
<linearGradient id="tbp-list" gradientUnits="userSpaceOnUse" x1="674" y1="326" x2="525" y2="489"><stop offset="0" stop-color="#17c7a4"/><stop offset="0.45" stop-color="#1dacad"/><stop offset="0.7" stop-color="#0192ef"/><stop offset="1" stop-color="#4f6ad1"/></linearGradient>
|
||||||
<line x1="15" x2="15.01" y1="12" y2="12"/>
|
<linearGradient id="tbp-dpad" gradientUnits="userSpaceOnUse" x1="478" y1="336" x2="363" y2="451"><stop offset="0" stop-color="#0192ef"/><stop offset="1" stop-color="#2a83e4"/></linearGradient>
|
||||||
<line x1="18" x2="18.01" y1="10" y2="10"/>
|
<linearGradient id="tbp-buttons" gradientUnits="userSpaceOnUse" x1="843" y1="326" x2="708" y2="463"><stop offset="0" stop-color="#17c7a4"/><stop offset="1" stop-color="#15b4ae"/></linearGradient>
|
||||||
<path d="M17.32 5H6.68a4 4 0 0 0-3.978 3.59c-.006.052-.01.101-.017.152C2.604 9.416 2 14.456 2 16a3 3 0 0 0 3 3c1 0 1.5-.5 2-1l1.414-1.414A2 2 0 0 1 9.828 16h4.344a2 2 0 0 1 1.414.586L17 18c.5.5 1 1 2 1a3 3 0 0 0 3-3c0-1.545-.604-6.584-.685-7.258-.007-.05-.011-.1-.017-.151A4 4 0 0 0 17.32 5z"/>
|
<linearGradient id="tbp-bar" gradientUnits="userSpaceOnUse" x1="476" y1="610" x2="723" y2="610"><stop offset="0" stop-color="#7467f9"/><stop offset="0.45" stop-color="#0192ef"/><stop offset="0.75" stop-color="#1dacad"/><stop offset="1" stop-color="#17c7a4"/></linearGradient>
|
||||||
|
</defs>
|
||||||
|
<path fill="url(#tbp-ring)" d="M 429 235.6 C 392.6 239.4, 357.1 259.4, 337.1 287.5 C 308.1 328.3, 278.1 428.0, 267.0 520.5 C 260.7 572.6, 267.7 612.7, 287.0 635.5 C 311.9 665.0, 350.6 669.1, 386 646.1 C 393.4 641.2, 416.6 618.4, 430.8 602 C 465.6 561.5, 470.2 557.3, 483.9 553.8 C 493.4 551.3, 706.0 551.0, 715.4 553.5 C 729.4 557.1, 735.2 562.2, 765.0 597 C 805.6 644.4, 826.8 659.2, 856.5 660.6 C 896.8 662.7, 926.3 633.4, 933.3 584.3 C 943.2 515.9, 904.8 351.0, 865.1 291 C 843.3 258.0, 805.4 237.4, 762.4 235.2 C 744.0 234.3, 734.0 237.0, 714 248.5 C 690.1 262.0, 691.4 261.9, 600.3 261.9 C 508.8 262.0, 511.5 262.3, 485.1 247.6 C 464.7 236.2, 451.2 233.3, 429 235.6"/>
|
||||||
|
<path fill="#2d3641" d="M 585.8 263.2 C 584.9 263.9, 580.7 269.6, 576.5 276 C 572.3 282.3, 567.8 288.0, 566.6 288.7 C 562.6 291.0, 519.9 290.4, 509.2 287.9 C 501.0 286.0, 498.2 285.1, 485.2 280.1 C 482.2 279.0, 475.7 275.6, 470.5 272.5 C 457.3 264.6, 454.9 264, 440.1 264 C 396.7 264, 358.2 292.8, 341.6 337.7 C 340.2 341.2, 338.8 344.9, 333.8 357.2 C 324.3 380.8, 312.9 420.7, 306.4 452.4 C 305.0 459.5, 303.6 465.7, 303.4 466.0 C 303.2 466.4, 302.5 469.8, 301.9 473.6 C 301.4 477.4, 300.5 482.9, 300 486 C 296.7 505.4, 295.9 510.6, 293.7 527 C 292.3 538.2, 292.2 570.5, 293.7 580 C 296.2 596.5, 302.2 611.4, 310.0 620.2 C 322.7 634.7, 347.7 636.7, 367.5 624.8 C 373.4 621.3, 398.0 597.7, 405.9 588 C 408.4 584.9, 411.6 581.3, 413 580 C 414.3 578.6, 417.5 575.0, 420 572 C 422.4 568.9, 425.6 565.3, 427.0 564 C 428.4 562.6, 432.0 558.5, 435.0 555.0 C 449.9 537.3, 457.6 532.3, 478.9 526.3 C 487.4 524.0, 487.9 524, 536.1 524 C 562.9 524, 585.5 524.2, 586.4 524.6 C 589.3 525.7, 588.0 529.8, 581.9 538.1 C 573.3 550.1, 573.6 551.4, 585.7 551.7 C 596.4 552.1, 598.0 551.4, 601.8 545.6 C 609.4 533.8, 616.2 525.1, 618.2 524.6 C 619.4 524.2, 641.6 524.0, 667.5 524.0 C 718.4 523.9, 717.4 523.9, 732.8 529.8 C 744.3 534.2, 755.3 542.9, 766.0 556 C 768.4 559.0, 772.0 563.0, 774 565 C 775.9 566.9, 779.5 570.9, 781.9 574 C 784.4 577.0, 787.1 580.1, 788 581 C 788.8 581.8, 791.5 584.9, 794.0 587.9 C 800.4 595.8, 822.3 617.4, 827.4 620.9 C 839.6 629.3, 847.4 632, 860.0 632 C 871.1 632, 871.9 631.8, 878.0 628.7 C 890.8 622.4, 898.9 610.6, 903.3 591.9 C 907.6 573.9, 908.2 568.5, 907.6 551 C 907.0 533.0, 905.7 520.1, 902.0 498 C 897.3 470.2, 897.4 470.7, 894.0 454.5 C 885.8 415.6, 871.5 367.1, 862.0 345.9 C 850.1 319.4, 847.3 313.9, 842.8 307.4 C 823.2 279.0, 794.4 263.9, 759.5 264.0 C 744.5 264.0, 744.4 264.0, 738.3 267.2 C 696.2 289.5, 694.3 290, 638.0 290 C 589.6 290, 592.2 290.9, 602 276.0 C 608.2 266.5, 608.7 265.1, 606.8 263.2 C 605.1 261.5, 587.9 261.5, 585.8 263.2"/>
|
||||||
|
<g fill="#192330" clip-path="url(#tbp-outline)">
|
||||||
|
<rect x="502" y="289" width="195" height="233"/>
|
||||||
|
<path d="M593.6 250H616.6L588 295H563Z"/>
|
||||||
|
<path d="M595.3 516H620.3L591.4 560H567.4Z"/>
|
||||||
</g>
|
</g>
|
||||||
|
<path fill="url(#tbp-dpad)" d="M 407.1 337.3 C 402.7 339.8, 402.0 342.5, 402.0 357.5 C 401.9 375.8, 402.7 375, 385.1 375 C 365.0 375, 362.9 376.7, 363.0 394.2 C 363.0 412.1, 364.9 413.9, 384.7 413.9 C 402.4 414.0, 402 413.5, 402 430.9 C 402 450.7, 403.1 452, 421 452 C 438.9 452, 440.4 450.2, 440.4 429.8 C 440.4 420.7, 440.8 416.7, 441.7 415.8 C 442.6 414.8, 447.6 414.3, 458.1 414 L 473.1 413.5 476.1 410.1 L 479.1 406.7 478.8 393.9 C 478.3 375.9, 478.0 375.6, 459.7 375.2 C 439.3 374.8, 440 375.4, 440 356.4 C 440 342.6, 439.0 339.2, 434.5 337.1 C 430.8 335.5, 410.2 335.6, 407.1 337.3"/>
|
||||||
|
<g fill="url(#tbp-buttons)">
|
||||||
|
<circle cx="775.5" cy="347.5" r="22"/><circle cx="729.5" cy="394" r="22"/><circle cx="821.5" cy="393.5" r="22"/><circle cx="775.5" cy="441" r="22"/>
|
||||||
|
</g>
|
||||||
|
<path fill="url(#tbp-list)" fill-rule="evenodd" d="M 563.5 333.5 C 559.7 337.6, 555.9 342.0, 555.1 343.2 C 550.8 349.4, 549.2 349.9, 545 346 C 537.8 339.3, 532.7 344.9, 539.2 352.2 C 541.0 354.2, 543.4 357.0, 544.6 358.4 C 547.8 362.1, 551.3 361.7, 554.7 357.4 C 556.2 355.4, 559.6 351.5, 562.3 348.6 C 565.0 345.8, 568.3 341.9, 569.8 340.0 C 571.3 338.0, 573.5 335.4, 574.7 334.2 C 577.2 331.6, 577.6 329.0, 575.8 327.2 C 573.2 324.6, 570.4 326.0, 563.5 333.5 M 525.4 329.2 C 525.1 329.9, 525.0 340.2, 525.2 352.0 L 525.5 373.5 547.2 373.7 L 569.0 374.0 570.0 371.2 C 571.2 368.3, 570.3 350.3, 569.0 349.5 C 566.9 348.2, 564.1 353.7, 563.8 359.5 L 563.5 365.5 556.3 366 C 552.4 366.2, 545.4 366.2, 540.8 366 L 532.5 365.5 532.2 351.1 C 531.8 333.1, 531.1 334, 545.4 334 C 555.2 334, 556.9 333.7, 558.4 332.0 C 559.4 330.9, 559.9 329.6, 559.5 329.0 C 558.5 327.4, 526.0 327.6, 525.4 329.2 M 591.6 335.8 C 589.4 338.2, 589.5 340.1, 592.0 342.8 C 594.0 344.9, 594.2 345, 631.2 344.9 C 651.7 344.9, 669.5 344.6, 670.8 344.3 C 673.7 343.5, 675.2 339.8, 674 336.5 L 673.0 334 633.1 334 C 595.8 334, 593.2 334.1, 591.6 335.8 M 591.6 357.8 C 589.4 360.2, 589.5 362.2, 591.8 364.3 C 593.4 365.8, 596.4 366.0, 617.5 365.7 L 641.5 365.5 641.8 362.6 C 642.5 356.3, 641.2 356, 616 356 C 595.2 356, 593.1 356.1, 591.6 357.8 M 567.4 387.2 C 547.5 409.5, 548.5 408.6, 545.4 404.5 C 543.0 401.3, 541.5 400.8, 538.4 402.0 C 534.2 403.6, 535.9 407.7, 544.3 416.7 C 548.5 421.2, 550.1 420.8, 555.6 414.2 C 558.3 411.1, 561.4 407.5, 562.5 406.3 C 563.7 405.2, 566.4 402.0, 568.5 399.3 C 570.7 396.7, 573.5 393.4, 574.7 392.2 C 577.2 389.6, 577.6 387.0, 575.8 385.2 C 573.6 383.0, 570.4 383.9, 567.4 387.2 M 525.4 387.2 C 525.1 387.9, 525.0 398.2, 525.2 410.0 L 525.5 431.5 546.8 431.7 C 561.7 431.9, 568.6 431.6, 569.6 430.8 C 571.6 429.2, 571.6 407, 569.5 407 C 566.8 407, 564.1 412.3, 563.8 418.5 L 563.5 424.5 548 424.5 L 532.5 424.5 532.5 409 L 532.5 393.5 544.1 393.2 C 554.8 392.9, 555.8 392.7, 557.9 390.5 C 559.1 389.2, 559.9 387.6, 559.5 387.0 C 558.5 385.4, 526.0 385.6, 525.4 387.2 M 591.6 393.8 C 589.3 396.3, 589.5 398.2, 592.3 400.8 C 594.6 402.9, 595.0 403, 631.5 402.9 C 651.8 402.9, 669.5 402.6, 670.8 402.3 C 674.1 401.4, 675.4 397.3, 673.5 394.3 L 671.9 392 632.6 392 C 595.8 392, 593.2 392.1, 591.6 393.8 M 592 416 C 589.5 418.4, 589.4 421.1, 591.7 423.3 C 594.1 425.4, 638.0 426.0, 640.5 423.9 C 642.7 422.1, 642.3 417.3, 639.7 415.5 C 635.9 412.8, 594.7 413.2, 592 416 M 525.6 444.6 C 525.3 445.0, 525 454.9, 525 466.6 L 525 488.0 527.5 489.0 C 531.4 490.4, 568.3 490.2, 569.8 488.8 C 571.4 487.1, 571.4 446.8, 569.8 445.2 C 568.5 443.9, 526.9 443.4, 525.6 444.6 M 592 452 C 589.4 454.5, 589.4 457.2, 592.1 459.5 C 595.5 462.7, 668.3 463.0, 672.3 459.9 C 675.6 457.3, 675.7 455.0, 672.6 452.1 C 669.2 448.8, 595.2 448.7, 592 452 M 532.4 452.2 C 532.1 453, 532.0 459.8, 532.2 467.5 L 532.5 481.5 548 481.5 L 563.5 481.5 563.5 466.5 L 563.5 451.5 548.2 451.2 C 536.2 451.0, 532.8 451.2, 532.4 452.2 M 596.8 472.9 C 590.0 474.2, 588.0 478.0, 592 482 C 595.0 485.0, 637.6 485.0, 640.4 482.0 C 642.8 479.3, 642.1 474.8, 639 473.2 C 636.2 471.9, 603.5 471.6, 596.8 472.9"/>
|
||||||
|
<path fill="url(#tbp-bar)" fill-rule="evenodd" d="M 532.4 584 C 478.7 584.5, 481.1 584.1, 477.2 595 C 475.5 599.7, 475.6 618.4, 477.4 623.5 C 480.2 631.7, 483.8 633.9, 494.3 634.1 C 499.3 634.2, 504.8 634.4, 506.5 634.5 C 508.1 634.7, 554.3 635.2, 609.0 635.6 L 708.6 636.3 713.9 633.9 C 722.2 630.1, 724.0 625.7, 723.9 608.7 C 723.9 594.3, 723.0 591.4, 717.1 586.2 L 714.5 584.0 657.5 583.8 C 626.1 583.8, 595.5 583.6, 589.5 583.6 C 583.4 583.5, 557.7 583.7, 532.4 584 M 491.5 591.1 C 484.5 593.6, 484 595.0, 484 609.0 L 484 621.8 487.0 624.8 L 490.0 627.8 598.6 627.8 C 716.1 627.8, 710.7 628.1, 714.3 622.5 C 716.3 619.5, 716.7 600.6, 714.8 596.6 C 712.7 592.0, 714.4 592.1, 604.5 591.6 C 546.7 591.3, 498.3 590.9, 497 590.6 C 495.6 590.3, 493.1 590.5, 491.5 591.1 M 491.6 597.7 C 490.3 599.2, 490 601.6, 490 609.1 C 490 621.9, 490.1 622.1, 504.6 621.7 L 515.5 621.5 515.5 609 L 515.5 596.5 504.4 596.2 C 494.6 595.9, 493.1 596.1, 491.6 597.7 M 524.4 597.2 C 524.1 598.0, 524.0 603.7, 524.2 610.0 L 524.5 621.5 537 621.5 L 549.5 621.5 549.5 609 L 549.5 596.5 537.2 596.2 C 527.6 596.0, 524.8 596.2, 524.4 597.2 M 557.9 598.5 C 557.4 599.9, 557 604.6, 557 609 C 557 613.3, 557.4 618.0, 557.9 619.4 L 558.9 622.0 571.2 621.7 L 583.5 621.5 583.5 609 L 583.5 596.5 571.2 596.2 L 558.9 595.9 557.9 598.5 M 592.1 597.3 C 591.3 598.3, 591.0 602.5, 591.2 610.1 L 591.5 621.5 621.5 621.5 L 651.5 621.5 651.5 609 L 651.5 596.5 622.3 596.2 C 598.3 596.0, 593.0 596.2, 592.1 597.3"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 773 B After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 49 KiB |
@@ -1,6 +1,6 @@
|
|||||||
import {Link, NavLink, useLocation} from 'react-router-dom';
|
import {Link, NavLink, useLocation} from 'react-router-dom';
|
||||||
import {useTranslation} from 'react-i18next';
|
import {useTranslation} from 'react-i18next';
|
||||||
import {Dices, Gamepad2, Languages, LayoutDashboard, Library, LogOut, Moon, Settings, Sun} from 'lucide-react';
|
import {Dices, Languages, LayoutDashboard, Library, LogOut, Moon, Settings, Sun} from 'lucide-react';
|
||||||
|
|
||||||
import {Button} from '@/components/ui/button';
|
import {Button} from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
} from '@/components/ui/dropdown-menu';
|
} from '@/components/ui/dropdown-menu';
|
||||||
import {usePreferences} from '@/context/PreferencesContext.jsx';
|
import {usePreferences} from '@/context/PreferencesContext.jsx';
|
||||||
import {cn} from '@/lib/utils';
|
import {cn} from '@/lib/utils';
|
||||||
|
import Logo from './Logo.jsx';
|
||||||
import PageContainer from './PageContainer.jsx';
|
import PageContainer from './PageContainer.jsx';
|
||||||
import SteamAvatar from './SteamAvatar.jsx';
|
import SteamAvatar from './SteamAvatar.jsx';
|
||||||
import useSteamProfile from '../hooks/useSteamProfile.js';
|
import useSteamProfile from '../hooks/useSteamProfile.js';
|
||||||
@@ -43,11 +44,10 @@ export default function AppHeader({isAuthenticated, onLogout}) {
|
|||||||
return (
|
return (
|
||||||
<header className="sticky top-0 z-50 border-b bg-background/80 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
<header className="sticky top-0 z-50 border-b bg-background/80 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||||
<PageContainer className="flex h-16 items-center gap-2 px-4 md:px-8">
|
<PageContainer className="flex h-16 items-center gap-2 px-4 md:px-8">
|
||||||
<Link to={isAuthenticated ? '/dashboard' : '/'} className="mr-2 flex items-center gap-2 font-bold tracking-tight whitespace-nowrap">
|
<Link to={isAuthenticated ? '/dashboard' : '/'} aria-label="ToBePlayed" className="mr-2 flex items-center gap-2 font-bold tracking-tight whitespace-nowrap">
|
||||||
<span className="flex size-8 items-center justify-center rounded-lg bg-primary text-primary-foreground">
|
<Logo className="h-8 w-auto shrink-0"/>
|
||||||
<Gamepad2 className="size-5"/>
|
{/* On a narrow phone the menu and buttons need the room; the logo alone stays. */}
|
||||||
</span>
|
<span className="hidden text-lg min-[380px]:inline">ToBePlayed</span>
|
||||||
<span className="text-lg">Game Log</span>
|
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{isAuthenticated && (
|
{isAuthenticated && (
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// @vitest-environment jsdom
|
||||||
|
import {afterEach, describe, expect, it, vi} from 'vitest';
|
||||||
|
import {cleanup, render, screen} from '@testing-library/react';
|
||||||
|
import {MemoryRouter} from 'react-router-dom';
|
||||||
|
import AppHeader from './AppHeader.jsx';
|
||||||
|
import {PreferencesProvider} from '../context/PreferencesContext.jsx';
|
||||||
|
|
||||||
|
vi.mock('react-i18next', () => import('../test/i18n-mock.js'));
|
||||||
|
|
||||||
|
afterEach(cleanup);
|
||||||
|
|
||||||
|
describe('AppHeader', () => {
|
||||||
|
// jsdom applies no media queries, so the test hides the name the way the 380px breakpoint does.
|
||||||
|
it('keeps a name on the home link when a narrow phone hides the app name', () => {
|
||||||
|
render(
|
||||||
|
<MemoryRouter>
|
||||||
|
<PreferencesProvider><AppHeader isAuthenticated={false} onLogout={() => {}}/></PreferencesProvider>
|
||||||
|
</MemoryRouter>,
|
||||||
|
);
|
||||||
|
screen.getByText('ToBePlayed').style.display = 'none';
|
||||||
|
|
||||||
|
expect(screen.getByRole('link', {name: 'ToBePlayed'})).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import {useId} from 'react';
|
||||||
|
|
||||||
|
import {BODY, CHECKLIST, DPAD, OUTLINE, PROGRESS_BAR} from './logoPaths.js';
|
||||||
|
|
||||||
|
// Each color is [themed CSS, fixed hex]. The hex is the logo as designed; it also stands in where a browser
|
||||||
|
// cannot read color-mix, which would otherwise make that stop black.
|
||||||
|
const violet = ['var(--logo-violet, #7467f9)', '#7467f9'];
|
||||||
|
const blue = ['var(--logo-blue, #0192ef)', '#0192ef'];
|
||||||
|
const teal = ['var(--logo-teal, #17c7a4)', '#17c7a4'];
|
||||||
|
const mix = ([a], [b], percentOfB, hex) => [`color-mix(in oklch, ${a}, ${b} ${percentOfB}%)`, hex];
|
||||||
|
|
||||||
|
function Gradient({id, from, to, stops}) {
|
||||||
|
return (
|
||||||
|
<linearGradient id={id} gradientUnits="userSpaceOnUse" x1={from[0]} y1={from[1]} x2={to[0]} y2={to[1]}>
|
||||||
|
{stops.map(([offset, [color, hex]]) => <stop key={offset} offset={offset} stopColor={hex} style={{stopColor: color}}/>)}
|
||||||
|
</linearGradient>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ToBePlayed logo. Its three colors come from --logo-violet, --logo-blue and --logo-teal, which style.css sets
|
||||||
|
* per accent color.
|
||||||
|
*/
|
||||||
|
export default function Logo({className, title}) {
|
||||||
|
// Every copy on the page needs its own ids, or a hidden copy can take the gradients of a visible one with it.
|
||||||
|
const id = useId().replace(/[^a-zA-Z0-9_-]/g, '');
|
||||||
|
const ref = (name) => `${name}-${id}`;
|
||||||
|
const url = (name) => `url(#${ref(name)})`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="255 225 690 445" className={className}
|
||||||
|
role={title ? 'img' : undefined} aria-label={title} aria-hidden={title ? undefined : true}>
|
||||||
|
<defs>
|
||||||
|
<clipPath id={ref('outline')}><path d={OUTLINE}/></clipPath>
|
||||||
|
<Gradient id={ref('ring')} from={[265, 660]} to={[935, 235]}
|
||||||
|
stops={[[0, violet], [0.15, violet], [0.42, blue], [0.7, teal], [1, teal]]}/>
|
||||||
|
<Gradient id={ref('list')} from={[674, 326]} to={[525, 489]}
|
||||||
|
stops={[[0, teal], [0.45, mix(blue, teal, 45, '#1dacad')], [0.7, blue], [1, mix(violet, blue, 50, '#4f6ad1')]]}/>
|
||||||
|
<Gradient id={ref('dpad')} from={[478, 336]} to={[363, 451]}
|
||||||
|
stops={[[0, blue], [1, mix(blue, violet, 20, '#2a83e4')]]}/>
|
||||||
|
<Gradient id={ref('buttons')} from={[843, 326]} to={[708, 463]}
|
||||||
|
stops={[[0, teal], [1, mix(teal, blue, 25, '#15b4ae')]]}/>
|
||||||
|
<Gradient id={ref('bar')} from={[476, 610]} to={[723, 610]}
|
||||||
|
stops={[[0, violet], [0.45, blue], [0.75, mix(blue, teal, 45, '#1dacad')], [1, teal]]}/>
|
||||||
|
</defs>
|
||||||
|
<path fill={url('ring')} d={OUTLINE}/>
|
||||||
|
<path fill="#2d3641" d={BODY}/>
|
||||||
|
<g fill="#192330" clipPath={url('outline')}>
|
||||||
|
<rect x="502" y="289" width="195" height="233"/>
|
||||||
|
<path d="M593.6 250H616.6L588 295H563Z"/>
|
||||||
|
<path d="M595.3 516H620.3L591.4 560H567.4Z"/>
|
||||||
|
</g>
|
||||||
|
<path fill={url('dpad')} d={DPAD}/>
|
||||||
|
<g fill={url('buttons')}>
|
||||||
|
<circle cx="775.5" cy="347.5" r="22"/>
|
||||||
|
<circle cx="729.5" cy="394" r="22"/>
|
||||||
|
<circle cx="821.5" cy="393.5" r="22"/>
|
||||||
|
<circle cx="775.5" cy="441" r="22"/>
|
||||||
|
</g>
|
||||||
|
<path fill={url('list')} fillRule="evenodd" d={CHECKLIST}/>
|
||||||
|
<path fill={url('bar')} fillRule="evenodd" d={PROGRESS_BAR}/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
// @vitest-environment jsdom
|
||||||
|
import {afterEach, describe, expect, it} from 'vitest';
|
||||||
|
import {cleanup, render} from '@testing-library/react';
|
||||||
|
import Logo from './Logo.jsx';
|
||||||
|
|
||||||
|
afterEach(cleanup);
|
||||||
|
|
||||||
|
describe('Logo', () => {
|
||||||
|
it('gives each copy its own gradients, and each copy points only at its own', () => {
|
||||||
|
const {container} = render(<><Logo/><Logo/></>);
|
||||||
|
const [first, second] = container.querySelectorAll('svg');
|
||||||
|
const ids = (svg) => [...svg.querySelectorAll('[id]')].map((element) => element.id);
|
||||||
|
const references = (svg) => [...svg.querySelectorAll('*')]
|
||||||
|
.flatMap((element) => [element.getAttribute('fill'), element.getAttribute('clip-path')])
|
||||||
|
.filter((value) => value?.startsWith('url(#'))
|
||||||
|
.map((value) => value.slice(5, -1));
|
||||||
|
|
||||||
|
expect(ids(first).filter((id) => ids(second).includes(id))).toEqual([]);
|
||||||
|
for (const svg of [first, second]) {
|
||||||
|
expect(references(svg).length).toBeGreaterThan(5);
|
||||||
|
expect(references(svg).every((reference) => ids(svg).includes(reference))).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is hidden from screen readers unless it has a title', () => {
|
||||||
|
const {container, getByRole} = render(<><Logo/><Logo title="ToBePlayed"/></>);
|
||||||
|
|
||||||
|
expect(container.querySelector('svg').getAttribute('aria-hidden')).toBe('true');
|
||||||
|
expect(getByRole('img', {name: 'ToBePlayed'})).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('gives every gradient stop a fixed color for browsers without color-mix', () => {
|
||||||
|
const {container} = render(<Logo/>);
|
||||||
|
const stops = [...container.querySelectorAll('stop')];
|
||||||
|
|
||||||
|
expect(stops.length).toBeGreaterThan(10);
|
||||||
|
expect(stops.every((stop) => /^#[0-9a-f]{6}$/.test(stop.getAttribute('stop-color')))).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// Traced from the ToBePlayed logo (a JPEG); used by Logo.jsx and, with fixed colors, by public/favicon.svg.
|
||||||
|
export const OUTLINE = 'M 429 235.6 C 392.6 239.4, 357.1 259.4, 337.1 287.5 C 308.1 328.3, 278.1 428.0, 267.0 520.5 C 260.7 572.6, 267.7 612.7, 287.0 635.5 C 311.9 665.0, 350.6 669.1, 386 646.1 C 393.4 641.2, 416.6 618.4, 430.8 602 C 465.6 561.5, 470.2 557.3, 483.9 553.8 C 493.4 551.3, 706.0 551.0, 715.4 553.5 C 729.4 557.1, 735.2 562.2, 765.0 597 C 805.6 644.4, 826.8 659.2, 856.5 660.6 C 896.8 662.7, 926.3 633.4, 933.3 584.3 C 943.2 515.9, 904.8 351.0, 865.1 291 C 843.3 258.0, 805.4 237.4, 762.4 235.2 C 744.0 234.3, 734.0 237.0, 714 248.5 C 690.1 262.0, 691.4 261.9, 600.3 261.9 C 508.8 262.0, 511.5 262.3, 485.1 247.6 C 464.7 236.2, 451.2 233.3, 429 235.6';
|
||||||
|
export const BODY = 'M 585.8 263.2 C 584.9 263.9, 580.7 269.6, 576.5 276 C 572.3 282.3, 567.8 288.0, 566.6 288.7 C 562.6 291.0, 519.9 290.4, 509.2 287.9 C 501.0 286.0, 498.2 285.1, 485.2 280.1 C 482.2 279.0, 475.7 275.6, 470.5 272.5 C 457.3 264.6, 454.9 264, 440.1 264 C 396.7 264, 358.2 292.8, 341.6 337.7 C 340.2 341.2, 338.8 344.9, 333.8 357.2 C 324.3 380.8, 312.9 420.7, 306.4 452.4 C 305.0 459.5, 303.6 465.7, 303.4 466.0 C 303.2 466.4, 302.5 469.8, 301.9 473.6 C 301.4 477.4, 300.5 482.9, 300 486 C 296.7 505.4, 295.9 510.6, 293.7 527 C 292.3 538.2, 292.2 570.5, 293.7 580 C 296.2 596.5, 302.2 611.4, 310.0 620.2 C 322.7 634.7, 347.7 636.7, 367.5 624.8 C 373.4 621.3, 398.0 597.7, 405.9 588 C 408.4 584.9, 411.6 581.3, 413 580 C 414.3 578.6, 417.5 575.0, 420 572 C 422.4 568.9, 425.6 565.3, 427.0 564 C 428.4 562.6, 432.0 558.5, 435.0 555.0 C 449.9 537.3, 457.6 532.3, 478.9 526.3 C 487.4 524.0, 487.9 524, 536.1 524 C 562.9 524, 585.5 524.2, 586.4 524.6 C 589.3 525.7, 588.0 529.8, 581.9 538.1 C 573.3 550.1, 573.6 551.4, 585.7 551.7 C 596.4 552.1, 598.0 551.4, 601.8 545.6 C 609.4 533.8, 616.2 525.1, 618.2 524.6 C 619.4 524.2, 641.6 524.0, 667.5 524.0 C 718.4 523.9, 717.4 523.9, 732.8 529.8 C 744.3 534.2, 755.3 542.9, 766.0 556 C 768.4 559.0, 772.0 563.0, 774 565 C 775.9 566.9, 779.5 570.9, 781.9 574 C 784.4 577.0, 787.1 580.1, 788 581 C 788.8 581.8, 791.5 584.9, 794.0 587.9 C 800.4 595.8, 822.3 617.4, 827.4 620.9 C 839.6 629.3, 847.4 632, 860.0 632 C 871.1 632, 871.9 631.8, 878.0 628.7 C 890.8 622.4, 898.9 610.6, 903.3 591.9 C 907.6 573.9, 908.2 568.5, 907.6 551 C 907.0 533.0, 905.7 520.1, 902.0 498 C 897.3 470.2, 897.4 470.7, 894.0 454.5 C 885.8 415.6, 871.5 367.1, 862.0 345.9 C 850.1 319.4, 847.3 313.9, 842.8 307.4 C 823.2 279.0, 794.4 263.9, 759.5 264.0 C 744.5 264.0, 744.4 264.0, 738.3 267.2 C 696.2 289.5, 694.3 290, 638.0 290 C 589.6 290, 592.2 290.9, 602 276.0 C 608.2 266.5, 608.7 265.1, 606.8 263.2 C 605.1 261.5, 587.9 261.5, 585.8 263.2';
|
||||||
|
export const DPAD = 'M 407.1 337.3 C 402.7 339.8, 402.0 342.5, 402.0 357.5 C 401.9 375.8, 402.7 375, 385.1 375 C 365.0 375, 362.9 376.7, 363.0 394.2 C 363.0 412.1, 364.9 413.9, 384.7 413.9 C 402.4 414.0, 402 413.5, 402 430.9 C 402 450.7, 403.1 452, 421 452 C 438.9 452, 440.4 450.2, 440.4 429.8 C 440.4 420.7, 440.8 416.7, 441.7 415.8 C 442.6 414.8, 447.6 414.3, 458.1 414 L 473.1 413.5 476.1 410.1 L 479.1 406.7 478.8 393.9 C 478.3 375.9, 478.0 375.6, 459.7 375.2 C 439.3 374.8, 440 375.4, 440 356.4 C 440 342.6, 439.0 339.2, 434.5 337.1 C 430.8 335.5, 410.2 335.6, 407.1 337.3';
|
||||||
|
export const CHECKLIST = 'M 563.5 333.5 C 559.7 337.6, 555.9 342.0, 555.1 343.2 C 550.8 349.4, 549.2 349.9, 545 346 C 537.8 339.3, 532.7 344.9, 539.2 352.2 C 541.0 354.2, 543.4 357.0, 544.6 358.4 C 547.8 362.1, 551.3 361.7, 554.7 357.4 C 556.2 355.4, 559.6 351.5, 562.3 348.6 C 565.0 345.8, 568.3 341.9, 569.8 340.0 C 571.3 338.0, 573.5 335.4, 574.7 334.2 C 577.2 331.6, 577.6 329.0, 575.8 327.2 C 573.2 324.6, 570.4 326.0, 563.5 333.5 M 525.4 329.2 C 525.1 329.9, 525.0 340.2, 525.2 352.0 L 525.5 373.5 547.2 373.7 L 569.0 374.0 570.0 371.2 C 571.2 368.3, 570.3 350.3, 569.0 349.5 C 566.9 348.2, 564.1 353.7, 563.8 359.5 L 563.5 365.5 556.3 366 C 552.4 366.2, 545.4 366.2, 540.8 366 L 532.5 365.5 532.2 351.1 C 531.8 333.1, 531.1 334, 545.4 334 C 555.2 334, 556.9 333.7, 558.4 332.0 C 559.4 330.9, 559.9 329.6, 559.5 329.0 C 558.5 327.4, 526.0 327.6, 525.4 329.2 M 591.6 335.8 C 589.4 338.2, 589.5 340.1, 592.0 342.8 C 594.0 344.9, 594.2 345, 631.2 344.9 C 651.7 344.9, 669.5 344.6, 670.8 344.3 C 673.7 343.5, 675.2 339.8, 674 336.5 L 673.0 334 633.1 334 C 595.8 334, 593.2 334.1, 591.6 335.8 M 591.6 357.8 C 589.4 360.2, 589.5 362.2, 591.8 364.3 C 593.4 365.8, 596.4 366.0, 617.5 365.7 L 641.5 365.5 641.8 362.6 C 642.5 356.3, 641.2 356, 616 356 C 595.2 356, 593.1 356.1, 591.6 357.8 M 567.4 387.2 C 547.5 409.5, 548.5 408.6, 545.4 404.5 C 543.0 401.3, 541.5 400.8, 538.4 402.0 C 534.2 403.6, 535.9 407.7, 544.3 416.7 C 548.5 421.2, 550.1 420.8, 555.6 414.2 C 558.3 411.1, 561.4 407.5, 562.5 406.3 C 563.7 405.2, 566.4 402.0, 568.5 399.3 C 570.7 396.7, 573.5 393.4, 574.7 392.2 C 577.2 389.6, 577.6 387.0, 575.8 385.2 C 573.6 383.0, 570.4 383.9, 567.4 387.2 M 525.4 387.2 C 525.1 387.9, 525.0 398.2, 525.2 410.0 L 525.5 431.5 546.8 431.7 C 561.7 431.9, 568.6 431.6, 569.6 430.8 C 571.6 429.2, 571.6 407, 569.5 407 C 566.8 407, 564.1 412.3, 563.8 418.5 L 563.5 424.5 548 424.5 L 532.5 424.5 532.5 409 L 532.5 393.5 544.1 393.2 C 554.8 392.9, 555.8 392.7, 557.9 390.5 C 559.1 389.2, 559.9 387.6, 559.5 387.0 C 558.5 385.4, 526.0 385.6, 525.4 387.2 M 591.6 393.8 C 589.3 396.3, 589.5 398.2, 592.3 400.8 C 594.6 402.9, 595.0 403, 631.5 402.9 C 651.8 402.9, 669.5 402.6, 670.8 402.3 C 674.1 401.4, 675.4 397.3, 673.5 394.3 L 671.9 392 632.6 392 C 595.8 392, 593.2 392.1, 591.6 393.8 M 592 416 C 589.5 418.4, 589.4 421.1, 591.7 423.3 C 594.1 425.4, 638.0 426.0, 640.5 423.9 C 642.7 422.1, 642.3 417.3, 639.7 415.5 C 635.9 412.8, 594.7 413.2, 592 416 M 525.6 444.6 C 525.3 445.0, 525 454.9, 525 466.6 L 525 488.0 527.5 489.0 C 531.4 490.4, 568.3 490.2, 569.8 488.8 C 571.4 487.1, 571.4 446.8, 569.8 445.2 C 568.5 443.9, 526.9 443.4, 525.6 444.6 M 592 452 C 589.4 454.5, 589.4 457.2, 592.1 459.5 C 595.5 462.7, 668.3 463.0, 672.3 459.9 C 675.6 457.3, 675.7 455.0, 672.6 452.1 C 669.2 448.8, 595.2 448.7, 592 452 M 532.4 452.2 C 532.1 453, 532.0 459.8, 532.2 467.5 L 532.5 481.5 548 481.5 L 563.5 481.5 563.5 466.5 L 563.5 451.5 548.2 451.2 C 536.2 451.0, 532.8 451.2, 532.4 452.2 M 596.8 472.9 C 590.0 474.2, 588.0 478.0, 592 482 C 595.0 485.0, 637.6 485.0, 640.4 482.0 C 642.8 479.3, 642.1 474.8, 639 473.2 C 636.2 471.9, 603.5 471.6, 596.8 472.9';
|
||||||
|
export const PROGRESS_BAR = 'M 532.4 584 C 478.7 584.5, 481.1 584.1, 477.2 595 C 475.5 599.7, 475.6 618.4, 477.4 623.5 C 480.2 631.7, 483.8 633.9, 494.3 634.1 C 499.3 634.2, 504.8 634.4, 506.5 634.5 C 508.1 634.7, 554.3 635.2, 609.0 635.6 L 708.6 636.3 713.9 633.9 C 722.2 630.1, 724.0 625.7, 723.9 608.7 C 723.9 594.3, 723.0 591.4, 717.1 586.2 L 714.5 584.0 657.5 583.8 C 626.1 583.8, 595.5 583.6, 589.5 583.6 C 583.4 583.5, 557.7 583.7, 532.4 584 M 491.5 591.1 C 484.5 593.6, 484 595.0, 484 609.0 L 484 621.8 487.0 624.8 L 490.0 627.8 598.6 627.8 C 716.1 627.8, 710.7 628.1, 714.3 622.5 C 716.3 619.5, 716.7 600.6, 714.8 596.6 C 712.7 592.0, 714.4 592.1, 604.5 591.6 C 546.7 591.3, 498.3 590.9, 497 590.6 C 495.6 590.3, 493.1 590.5, 491.5 591.1 M 491.6 597.7 C 490.3 599.2, 490 601.6, 490 609.1 C 490 621.9, 490.1 622.1, 504.6 621.7 L 515.5 621.5 515.5 609 L 515.5 596.5 504.4 596.2 C 494.6 595.9, 493.1 596.1, 491.6 597.7 M 524.4 597.2 C 524.1 598.0, 524.0 603.7, 524.2 610.0 L 524.5 621.5 537 621.5 L 549.5 621.5 549.5 609 L 549.5 596.5 537.2 596.2 C 527.6 596.0, 524.8 596.2, 524.4 597.2 M 557.9 598.5 C 557.4 599.9, 557 604.6, 557 609 C 557 613.3, 557.4 618.0, 557.9 619.4 L 558.9 622.0 571.2 621.7 L 583.5 621.5 583.5 609 L 583.5 596.5 571.2 596.2 L 558.9 595.9 557.9 598.5 M 592.1 597.3 C 591.3 598.3, 591.0 602.5, 591.2 610.1 L 591.5 621.5 621.5 621.5 L 651.5 621.5 651.5 609 L 651.5 596.5 622.3 596.2 C 598.3 596.0, 593.0 596.2, 592.1 597.3';
|
||||||
@@ -9,6 +9,7 @@ import {Button} from '@/components/ui/button';
|
|||||||
import {Card} from '@/components/ui/card';
|
import {Card} from '@/components/ui/card';
|
||||||
import {Skeleton} from '@/components/ui/skeleton';
|
import {Skeleton} from '@/components/ui/skeleton';
|
||||||
import GameCover from '../components/GameCover.jsx';
|
import GameCover from '../components/GameCover.jsx';
|
||||||
|
import Logo from '../components/Logo.jsx';
|
||||||
import StatCard from '../components/StatCard.jsx';
|
import StatCard from '../components/StatCard.jsx';
|
||||||
import {hasSession} from '../utils/auth.js';
|
import {hasSession} from '../utils/auth.js';
|
||||||
|
|
||||||
@@ -59,7 +60,7 @@ export default function LandingPage() {
|
|||||||
<div className="pointer-events-none absolute inset-0 bg-gradient-to-b from-primary/15 via-primary/5 to-transparent"/>
|
<div className="pointer-events-none absolute inset-0 bg-gradient-to-b from-primary/15 via-primary/5 to-transparent"/>
|
||||||
<div className="relative mx-auto max-w-3xl px-4 py-20 text-center md:py-28">
|
<div className="relative mx-auto max-w-3xl px-4 py-20 text-center md:py-28">
|
||||||
<Badge variant="outline" className="mb-6 gap-1.5 bg-background/60">
|
<Badge variant="outline" className="mb-6 gap-1.5 bg-background/60">
|
||||||
<Gamepad2 className="size-3 text-primary"/> Game Log
|
<span className="flex"><Logo className="h-4 w-auto"/></span> ToBePlayed
|
||||||
</Badge>
|
</Badge>
|
||||||
<h1 className="text-4xl font-bold tracking-tight md:text-6xl">{t('MAIN.WELCOME')}</h1>
|
<h1 className="text-4xl font-bold tracking-tight md:text-6xl">{t('MAIN.WELCOME')}</h1>
|
||||||
<p className="mx-auto mt-6 max-w-2xl text-lg text-muted-foreground">{t('MAIN.DESCRIPTION')}</p>
|
<p className="mx-auto mt-6 max-w-2xl text-lg text-muted-foreground">{t('MAIN.DESCRIPTION')}</p>
|
||||||
|
|||||||
@@ -182,6 +182,38 @@
|
|||||||
--color-ring: var(--ring);
|
--color-ring: var(--ring);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The three logo colors per accent (components/Logo.jsx). Violet is the logo as designed; the others use
|
||||||
|
neighbouring hues, because a mix of a warm accent with the logo's teal turns olive. */
|
||||||
|
[data-accent="blue"] {
|
||||||
|
--logo-violet: #3b82f6;
|
||||||
|
--logo-blue: #06b6d4;
|
||||||
|
--logo-teal: #14b8a6;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-accent="green"] {
|
||||||
|
--logo-violet: #22c55e;
|
||||||
|
--logo-blue: #14b8a6;
|
||||||
|
--logo-teal: #0ea5e9;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-accent="orange"] {
|
||||||
|
--logo-violet: #f97316;
|
||||||
|
--logo-blue: #f59e0b;
|
||||||
|
--logo-teal: #eab308;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-accent="pink"] {
|
||||||
|
--logo-violet: #ec4899;
|
||||||
|
--logo-blue: #a855f7;
|
||||||
|
--logo-teal: #6366f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-accent="red"] {
|
||||||
|
--logo-violet: #ef4444;
|
||||||
|
--logo-blue: #f97316;
|
||||||
|
--logo-teal: #f59e0b;
|
||||||
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
* {
|
* {
|
||||||
@apply border-border outline-ring/50;
|
@apply border-border outline-ring/50;
|
||||||
|
|||||||