You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

122 lines
3.4 KiB

  1. import cn from 'classnames'
  2. import { useEffect, useRef, useState } from 'react'
  3. import { BrowserRouter,
  4. Link,
  5. Navigate,
  6. Route,
  7. Routes,
  8. useLocation } from 'react-router-dom'
  9. import bgmSrc from '@/assets/music.mp3'
  10. import ThreadListPage from '@/pages/threads/ThreadListPage'
  11. import ThreadDetailPage from '@/pages/threads/ThreadDetailPage'
  12. const colours = ['bg-fuchsia-500 dark:bg-fuchsia-900',
  13. 'bg-lime-500 dark:bg-lime-900',
  14. 'bg-cyan-500 dark:bg-cyan-900',
  15. 'bg-orange-500 dark:bg-orange-900',
  16. 'bg-pink-500 dark:bg-pink-900',
  17. 'bg-red-500 dark:bg-red-900',
  18. 'bg-blue-500 dark:bg-blue-900',
  19. 'bg-green-500 dark:bg-green-900',
  20. 'bg-yellow-500 dark:bg-yellow-900'] as const
  21. const ScrollToTop = () => {
  22. const { pathname } = useLocation ()
  23. useEffect (() => {
  24. scrollTo (0, 0)
  25. }, [pathname])
  26. return null
  27. }
  28. export default () => {
  29. const bgmRef = useRef<HTMLAudioElement | null> (null)
  30. const [colourIndex, setColourIndex] = useState (0)
  31. const [mute, setMute] = useState (false)
  32. const [playing, setPlaying] = useState (false)
  33. useEffect (() => {
  34. bgmRef.current = new Audio (bgmSrc)
  35. bgmRef.current.loop = true
  36. bgmRef.current.volume = 1
  37. const playBGM = async () => {
  38. if (playing || !(bgmRef.current))
  39. return
  40. try
  41. {
  42. await bgmRef.current.play ()
  43. setPlaying (true)
  44. }
  45. catch
  46. {
  47. setPlaying (false)
  48. }
  49. }
  50. const bgmInterval = setInterval (playBGM, 1000)
  51. document.addEventListener ('click', playBGM)
  52. document.addEventListener ('touchstart', playBGM)
  53. const changeColour = () => {
  54. setColourIndex (Math.floor (Math.random () * colours.length))
  55. }
  56. changeColour ()
  57. const colouringInterval = setInterval (changeColour, 3000)
  58. return () => {
  59. clearInterval (bgmInterval)
  60. clearInterval (colouringInterval)
  61. }
  62. }, [])
  63. return (
  64. <BrowserRouter>
  65. <ScrollToTop />
  66. <div className={cn ('w-screen min-h-screen',
  67. colours[colourIndex],
  68. 'transition-colors duration-[3s] ease-linear')}>
  69. <div className="mx-auto max-w-[960px] px-4">
  70. <header className="pt-6 mb-8">
  71. <h1 className="text-center">
  72. <Link to="/"
  73. className="text-7xl text-transparent whitespace-nowrap
  74. bg-[linear-gradient(90deg,#ff0000,#ff8800,#ffff00,#00ff00,#00ffff,#0000ff,#ff00ff,#ff0000)]
  75. bg-clip-text [transform:skewX(-13.5deg)]
  76. inline-block bg-[length:200%_100%] animate-rainbow-scroll drop-shadow-[0_0_6px_black]
  77. font-serif hover:text-transparent dark:hover:text-transparent">
  78. クソ掲示板
  79. </Link>
  80. </h1>
  81. <div className="text-center my-6">
  82. {playing && (
  83. <a href="#" onClick={ev => {
  84. ev.preventDefault ()
  85. bgmRef.current && setMute (bgmRef.current.muted = !(mute))
  86. }}>
  87. {mute ? 'やっぱり BGM が恋しい人用' : 'BGM がうるさい人用'}
  88. </a>)}
  89. </div>
  90. </header>
  91. <main className="mb-8">
  92. <Routes>
  93. <Route path="/" element={<Navigate to="/threads" replace />} />
  94. <Route path="/threads" element={<ThreadListPage />} />
  95. <Route path="/threads/:id" element={<ThreadDetailPage />} />
  96. </Routes>
  97. </main>
  98. <hr />
  99. <footer className="text-center mt-8 pb-12 text-base text-gray-500 dark:text-gray-300">
  100. © このペィジへの投稿は,すべて,パブリック・ドメインとします.
  101. </footer>
  102. </div>
  103. </div>
  104. </BrowserRouter>)
  105. }