copilot-swe-agent[bot] ArnavSingh76533 commited on
Commit
ce09e2f
·
1 Parent(s): 4b0bad1

Modernize UI design with updated colors, typography, and components

Browse files

Co-authored-by: ArnavSingh76533 <160649079+ArnavSingh76533@users.noreply.github.com>

components/Footer.tsx CHANGED
@@ -9,16 +9,16 @@ interface Props {
9
 
10
  const Footer: FC<Props> = ({ error }) => {
11
  return (
12
- <footer className={"flex flex-col bg-dark-900 py-1 px-4"}>
13
- {error && <div>Error {error}</div>}
14
- <div className={"text-sm flex flex-col gap-1 sm:flex-row sm:items-center"}>
15
- <div className={"flex flex-row items-center"}>
16
  <IconCopyright sizeClassName={"h-3 w-3"}/>
17
  <NewTabLink href={"t.me/yucant"}>Charlie</NewTabLink>
18
- 2022,
19
  </div>
20
 
21
- <div>
22
  Icons by
23
  <NewTabLink href={"https://heroicons.com"}>Heroicons</NewTabLink>
24
  and
@@ -26,10 +26,10 @@ const Footer: FC<Props> = ({ error }) => {
26
  </div>
27
 
28
  <NewTabLink
29
- className={"ml-auto flex items-center"}
30
  href={"shivam413-Streamer.hf.space"}
31
  >
32
- <IconGithub className={"mr-1"} /> Github
33
  </NewTabLink>
34
  </div>
35
  </footer>
 
9
 
10
  const Footer: FC<Props> = ({ error }) => {
11
  return (
12
+ <footer className={"flex flex-col bg-gradient-to-r from-dark-900 to-dark-800 py-3 px-6 border-t border-dark-700/50 mt-auto"}>
13
+ {error && <div className="text-red-400 font-medium mb-2">Error {error}</div>}
14
+ <div className={"text-sm flex flex-col gap-2 sm:flex-row sm:items-center text-dark-400"}>
15
+ <div className={"flex flex-row items-center gap-1"}>
16
  <IconCopyright sizeClassName={"h-3 w-3"}/>
17
  <NewTabLink href={"t.me/yucant"}>Charlie</NewTabLink>
18
+ 2022
19
  </div>
20
 
21
+ <div className="flex flex-row items-center gap-1">
22
  Icons by
23
  <NewTabLink href={"https://heroicons.com"}>Heroicons</NewTabLink>
24
  and
 
26
  </div>
27
 
28
  <NewTabLink
29
+ className={"ml-auto flex items-center gap-1 hover:text-primary-500 transition-colors"}
30
  href={"shivam413-Streamer.hf.space"}
31
  >
32
+ <IconGithub className={"w-4 h-4"} /> Github
33
  </NewTabLink>
34
  </div>
35
  </footer>
components/Navbar.tsx CHANGED
@@ -12,11 +12,11 @@ const Navbar = ({ roomId }: { roomId?: string }) => {
12
  const [showShare, setShowShare] = useState(false)
13
 
14
  return (
15
- <div className={"py-1 px-2 flex flex-row gap-1 items-stretch bg-dark-900"}>
16
  <Link
17
  href={"/"}
18
  className={
19
- "flex p-1 shrink-0 flex-row gap-1 items-center rounded action"
20
  }
21
  >
22
  <Image
@@ -24,8 +24,9 @@ const Navbar = ({ roomId }: { roomId?: string }) => {
24
  alt={"Web-SyncPlay logo"}
25
  width={36}
26
  height={36}
 
27
  />
28
- <span className={"hide-below-sm"}>{getSiteName()}</span>
29
  </Link>
30
  {roomId && (
31
  <>
@@ -34,21 +35,21 @@ const Navbar = ({ roomId }: { roomId?: string }) => {
34
  show={showShare}
35
  close={() => setShowShare(false)}
36
  >
37
- <div>Share this link to let more people join in on the fun</div>
38
  <InputClipboardCopy
39
- className={"bg-dark-1000"}
40
  value={getSiteDomain() + "/room/" + roomId}
41
  />
42
  </Modal>
43
  <Button
44
  tooltip={"Share the room link"}
45
  id={"navbar"}
46
- actionClasses={"hover:bg-primary-800 active:bg-primary-700"}
47
- className={"ml-auto p-2 bg-primary-900"}
48
  onClick={() => setShowShare(true)}
49
  >
50
  <div className={"flex items-center mx-1"}>
51
- <IconShare className={"mr-1"} />
52
  Share
53
  </div>
54
  </Button>
@@ -60,6 +61,9 @@ const Navbar = ({ roomId }: { roomId?: string }) => {
60
  place={"bottom"}
61
  style={{
62
  backgroundColor: "var(--dark-700)",
 
 
 
63
  }}
64
  />
65
  </div>
 
12
  const [showShare, setShowShare] = useState(false)
13
 
14
  return (
15
+ <div className={"py-2 px-4 flex flex-row gap-2 items-stretch bg-gradient-to-r from-dark-900 to-dark-800 border-b border-dark-700/50 shadow-lg"}>
16
  <Link
17
  href={"/"}
18
  className={
19
+ "flex p-2 shrink-0 flex-row gap-2 items-center rounded-lg action"
20
  }
21
  >
22
  <Image
 
24
  alt={"Web-SyncPlay logo"}
25
  width={36}
26
  height={36}
27
+ className="rounded-lg"
28
  />
29
+ <span className={"hide-below-sm font-semibold text-lg"}>{getSiteName()}</span>
30
  </Link>
31
  {roomId && (
32
  <>
 
35
  show={showShare}
36
  close={() => setShowShare(false)}
37
  >
38
+ <div className="text-dark-300 mb-3">Share this link to let more people join in on the fun</div>
39
  <InputClipboardCopy
40
+ className={"bg-dark-1000 rounded-lg"}
41
  value={getSiteDomain() + "/room/" + roomId}
42
  />
43
  </Modal>
44
  <Button
45
  tooltip={"Share the room link"}
46
  id={"navbar"}
47
+ actionClasses={"hover:bg-primary-700 active:bg-primary-800 shadow-md hover:shadow-glow"}
48
+ className={"ml-auto px-4 py-2 bg-primary-600 font-medium"}
49
  onClick={() => setShowShare(true)}
50
  >
51
  <div className={"flex items-center mx-1"}>
52
+ <IconShare className={"mr-2"} />
53
  Share
54
  </div>
55
  </Button>
 
61
  place={"bottom"}
62
  style={{
63
  backgroundColor: "var(--dark-700)",
64
+ borderRadius: "0.5rem",
65
+ padding: "0.5rem 0.75rem",
66
+ fontSize: "0.875rem",
67
  }}
68
  />
69
  </div>
components/Room.tsx CHANGED
@@ -72,14 +72,15 @@ const Room: FC<Props> = ({ id }) => {
72
  }
73
 
74
  return (
75
- <div className={"flex flex-col sm:flex-row gap-1"}>
76
  <div className={"grow"}>
77
  <Player roomId={id} socket={socket} />
78
 
79
- <div className={"flex flex-row gap-1 p-1"}>
80
  <Button
81
  tooltip={"Do a forced manual sync"}
82
- className={"p-2 flex flex-row gap-1 items-center"}
 
83
  onClick={() => {
84
  console.log("Fetching update", socket?.id)
85
  socket?.emit("fetch")
@@ -105,7 +106,7 @@ const Room: FC<Props> = ({ id }) => {
105
  </div>
106
 
107
  {/* Chat + YouTube Search */}
108
- <div className="grid grid-cols-1 md:grid-cols-2 gap-2 p-1">
109
  <ChatPanel socket={socket} />
110
  <YoutubeSearch socket={socket} />
111
  </div>
 
72
  }
73
 
74
  return (
75
+ <div className={"flex flex-col sm:flex-row gap-2"}>
76
  <div className={"grow"}>
77
  <Player roomId={id} socket={socket} />
78
 
79
+ <div className={"flex flex-row gap-2 p-2 bg-dark-900/50 rounded-lg border border-dark-700/50 mt-2"}>
80
  <Button
81
  tooltip={"Do a forced manual sync"}
82
+ className={"px-3 py-2 flex flex-row gap-2 items-center"}
83
+ actionClasses={"bg-dark-800 hover:bg-dark-700 active:bg-dark-600 border border-dark-700/50"}
84
  onClick={() => {
85
  console.log("Fetching update", socket?.id)
86
  socket?.emit("fetch")
 
106
  </div>
107
 
108
  {/* Chat + YouTube Search */}
109
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-3 p-2 mt-2">
110
  <ChatPanel socket={socket} />
111
  <YoutubeSearch socket={socket} />
112
  </div>
components/action/Button.tsx CHANGED
@@ -30,7 +30,7 @@ const Button: FC<Props> = ({
30
  onClick={onClick}
31
  type={type}
32
  disabled={disabled}
33
- className={classNames("p-2 rounded", actionClasses, className)}
34
  >
35
  {children}
36
  </button>
 
30
  onClick={onClick}
31
  type={type}
32
  disabled={disabled}
33
+ className={classNames("px-3 py-2 rounded-lg font-medium transition-all duration-200", actionClasses, className)}
34
  >
35
  {children}
36
  </button>
components/chat/ChatPanel.tsx CHANGED
@@ -49,20 +49,27 @@ const ChatPanel: FC<Props> = ({ socket, className }) => {
49
  }
50
 
51
  return (
52
- <div className={className ?? "flex flex-col h-64 border rounded-md"}>
53
- <div className="flex-1 overflow-y-auto p-2 space-y-2 bg-neutral-900/30">
54
  {messages.map((m) => (
55
- <div key={m.id} className="text-sm">
56
- <span className="font-semibold">{m.name}</span>
57
- <span className="opacity-60">{new Date(m.ts).toLocaleTimeString()}</span>
58
- <div className="break-words">{m.text}</div>
 
 
 
59
  </div>
60
  ))}
61
- {messages.length === 0 && <div className="opacity-60 text-sm">No messages yet</div>}
 
 
 
 
62
  </div>
63
- <div className="p-2 flex gap-2">
64
  <input
65
- className="input flex-1 bg-neutral-800 p-2 rounded-md outline-none"
66
  placeholder="Type a message…"
67
  value={text}
68
  onChange={(e) => setText(e.target.value)}
@@ -70,7 +77,10 @@ const ChatPanel: FC<Props> = ({ socket, className }) => {
70
  if (e.key === "Enter") send()
71
  }}
72
  />
73
- <button className="btn bg-primary-700 hover:bg-primary-600 px-3 rounded-md" onClick={send}>
 
 
 
74
  Send
75
  </button>
76
  </div>
 
49
  }
50
 
51
  return (
52
+ <div className={className ?? "flex flex-col h-64 border border-dark-700/50 rounded-xl overflow-hidden shadow-lg bg-dark-900"}>
53
+ <div className="flex-1 overflow-y-auto p-4 space-y-3 bg-dark-900/50">
54
  {messages.map((m) => (
55
+ <div key={m.id} className="text-sm bg-dark-800/50 rounded-lg p-3 border border-dark-700/30">
56
+ <div className="flex items-center gap-2 mb-1">
57
+ <span className="font-semibold text-primary-400">{m.name}</span>
58
+ <span className="text-dark-500 text-xs">•</span>
59
+ <span className="text-dark-500 text-xs">{new Date(m.ts).toLocaleTimeString()}</span>
60
+ </div>
61
+ <div className="break-words text-dark-200">{m.text}</div>
62
  </div>
63
  ))}
64
+ {messages.length === 0 && (
65
+ <div className="text-dark-500 text-sm text-center py-8">
66
+ No messages yet. Be the first to say hello! 👋
67
+ </div>
68
+ )}
69
  </div>
70
+ <div className="p-3 flex gap-2 bg-dark-800/50 border-t border-dark-700/50">
71
  <input
72
+ className="input flex-1 bg-dark-800 border border-dark-700/50 focus:border-primary-500/50 p-2.5 rounded-lg outline-none transition-all duration-200"
73
  placeholder="Type a message…"
74
  value={text}
75
  onChange={(e) => setText(e.target.value)}
 
77
  if (e.key === "Enter") send()
78
  }}
79
  />
80
+ <button
81
+ className="btn bg-primary-600 hover:bg-primary-700 active:bg-primary-800 px-4 rounded-lg font-medium transition-all duration-200 shadow-md hover:shadow-glow"
82
+ onClick={send}
83
+ >
84
  Send
85
  </button>
86
  </div>
components/input/InputSlider.module.css CHANGED
@@ -5,12 +5,12 @@
5
  /* input range slider */
6
  .slider input[type="range"] {
7
  --value: 0%;
8
- --lower: var(--primary);
9
- --upper: rgba(255, 255, 255, 0.5);
10
- --size: 5px;
11
- --active-size: 8px;
12
- --thumb-size: 7px;
13
- --thumb-active-size: 10px;
14
  --thumb-color: #fff;
15
 
16
  cursor: pointer;
@@ -28,7 +28,7 @@
28
  touch-action: manipulation;
29
  -webkit-appearance: none;
30
  -moz-appearance: none;
31
- transition: height ease-in-out 0.1s;
32
  }
33
  .slider input[type="range"]:hover,
34
  .slider input[type="range"]:active {
@@ -43,12 +43,14 @@
43
  background-color: var(--thumb-color);
44
  border-radius: 100%;
45
  cursor: pointer;
46
- transition: width ease-in-out 0.1s, height ease-in-out 0.1s;
 
47
  }
48
  .slider input[type="range"]:hover::-moz-range-thumb,
49
  .slider input[type="range"]:active::-moz-range-thumb {
50
  width: var(--thumb-active-size);
51
  height: var(--thumb-active-size);
 
52
  }
53
 
54
  .slider input[type="range"]::-webkit-slider-thumb {
@@ -57,13 +59,15 @@
57
  background-color: var(--thumb-color);
58
  border-radius: 100%;
59
  cursor: pointer;
 
60
  -webkit-appearance: none;
61
- transition: width ease-in-out 0.1s, height ease-in-out 0.1s;
62
  }
63
  .slider input[type="range"]:hover::-webkit-slider-thumb,
64
  .slider input[type="range"]:active::-webkit-slider-thumb {
65
  width: var(--thumb-active-size);
66
  height: var(--thumb-active-size);
 
67
  }
68
 
69
  .slider input[type="range"]::-ms-thumb {
@@ -72,12 +76,14 @@
72
  background-color: var(--thumb-color);
73
  border-radius: 100%;
74
  cursor: pointer;
75
- transition: width ease-in-out 0.1s, height ease-in-out 0.1s;
 
76
  }
77
  .slider input[type="range"]:hover::-ms-thumb,
78
  .slider input[type="range"]:active::-ms-thumb {
79
  width: var(--thumb-active-size);
80
  height: var(--thumb-active-size);
 
81
  }
82
 
83
  /* edge */
@@ -89,7 +95,7 @@
89
  width: 100%;
90
  height: var(--size);
91
  cursor: pointer;
92
- transition: width ease-in-out 0.1s, height ease-in-out 0.1s;
93
  }
94
 
95
  .slider input[type="range"]::-ms-fill-lower,
 
5
  /* input range slider */
6
  .slider input[type="range"] {
7
  --value: 0%;
8
+ --lower: #10b981;
9
+ --upper: rgba(255, 255, 255, 0.3);
10
+ --size: 6px;
11
+ --active-size: 10px;
12
+ --thumb-size: 12px;
13
+ --thumb-active-size: 16px;
14
  --thumb-color: #fff;
15
 
16
  cursor: pointer;
 
28
  touch-action: manipulation;
29
  -webkit-appearance: none;
30
  -moz-appearance: none;
31
+ transition: height ease-in-out 0.2s;
32
  }
33
  .slider input[type="range"]:hover,
34
  .slider input[type="range"]:active {
 
43
  background-color: var(--thumb-color);
44
  border-radius: 100%;
45
  cursor: pointer;
46
+ box-shadow: 0 2px 8px rgba(16, 185, 129, 0.4);
47
+ transition: width ease-in-out 0.2s, height ease-in-out 0.2s, box-shadow ease-in-out 0.2s;
48
  }
49
  .slider input[type="range"]:hover::-moz-range-thumb,
50
  .slider input[type="range"]:active::-moz-range-thumb {
51
  width: var(--thumb-active-size);
52
  height: var(--thumb-active-size);
53
+ box-shadow: 0 0 16px rgba(16, 185, 129, 0.6);
54
  }
55
 
56
  .slider input[type="range"]::-webkit-slider-thumb {
 
59
  background-color: var(--thumb-color);
60
  border-radius: 100%;
61
  cursor: pointer;
62
+ box-shadow: 0 2px 8px rgba(16, 185, 129, 0.4);
63
  -webkit-appearance: none;
64
+ transition: width ease-in-out 0.2s, height ease-in-out 0.2s, box-shadow ease-in-out 0.2s;
65
  }
66
  .slider input[type="range"]:hover::-webkit-slider-thumb,
67
  .slider input[type="range"]:active::-webkit-slider-thumb {
68
  width: var(--thumb-active-size);
69
  height: var(--thumb-active-size);
70
+ box-shadow: 0 0 16px rgba(16, 185, 129, 0.6);
71
  }
72
 
73
  .slider input[type="range"]::-ms-thumb {
 
76
  background-color: var(--thumb-color);
77
  border-radius: 100%;
78
  cursor: pointer;
79
+ box-shadow: 0 2px 8px rgba(16, 185, 129, 0.4);
80
+ transition: width ease-in-out 0.2s, height ease-in-out 0.2s, box-shadow ease-in-out 0.2s;
81
  }
82
  .slider input[type="range"]:hover::-ms-thumb,
83
  .slider input[type="range"]:active::-ms-thumb {
84
  width: var(--thumb-active-size);
85
  height: var(--thumb-active-size);
86
+ box-shadow: 0 0 16px rgba(16, 185, 129, 0.6);
87
  }
88
 
89
  /* edge */
 
95
  width: 100%;
96
  height: var(--size);
97
  cursor: pointer;
98
+ transition: width ease-in-out 0.2s, height ease-in-out 0.2s;
99
  }
100
 
101
  .slider input[type="range"]::-ms-fill-lower,
components/input/InputText.tsx CHANGED
@@ -23,15 +23,15 @@ const InputText: FC<Props> = ({
23
  return (
24
  <div
25
  className={classNames(
26
- "rounded grow flex flex-row items-center bg-dark-800 action",
27
  className
28
  )}
29
  >
30
- {icon && <div className={"ml-1"}>{icon}</div>}
31
  <input
32
  ref={inputRef}
33
  size={1}
34
- className={"grow rounded bg-dark-800 px-2 py-1.5" + className}
35
  placeholder={placeholder}
36
  value={value}
37
  onChange={(event) => onChange(event.target.value)}
@@ -39,9 +39,11 @@ const InputText: FC<Props> = ({
39
  required={required}
40
  onFocus={() => inputRef.current?.select()}
41
  />
42
- <div className={"p-1 cursor-pointer"} onClick={() => onChange("")}>
43
- <IconClose />
44
- </div>
 
 
45
  </div>
46
  )
47
  }
 
23
  return (
24
  <div
25
  className={classNames(
26
+ "rounded-lg grow flex flex-row items-center bg-dark-800 border border-dark-700/50 focus-within:border-primary-500/50 transition-all duration-200",
27
  className
28
  )}
29
  >
30
+ {icon && <div className={"ml-2"}>{icon}</div>}
31
  <input
32
  ref={inputRef}
33
  size={1}
34
+ className={"grow rounded-lg bg-transparent px-3 py-2.5 outline-none " + className}
35
  placeholder={placeholder}
36
  value={value}
37
  onChange={(event) => onChange(event.target.value)}
 
39
  required={required}
40
  onFocus={() => inputRef.current?.select()}
41
  />
42
+ {value && (
43
+ <div className={"p-2 cursor-pointer hover:text-red-400 transition-colors"} onClick={() => onChange("")}>
44
+ <IconClose />
45
+ </div>
46
+ )}
47
  </div>
48
  )
49
  }
components/input/InputUrl.tsx CHANGED
@@ -40,12 +40,12 @@ const InputUrl: FC<Props> = ({
40
  className={classNames("flex flex-col", className)}
41
  >
42
  <div
43
- className={"rounded grow flex flex-row items-center bg-dark-800 action"}
44
  >
45
  <input
46
  ref={inputRef}
47
  size={1}
48
- className={classNames("grow rounded bg-dark-800 p-2")}
49
  placeholder={placeholder}
50
  value={url}
51
  onChange={(event) => {
@@ -54,25 +54,27 @@ const InputUrl: FC<Props> = ({
54
  type={"text"}
55
  onFocus={() => inputRef.current?.select()}
56
  />
57
- <div className={"p-1 cursor-pointer"} onClick={() => onChange("")}>
58
- <IconClose />
59
- </div>
 
 
60
  <div>
61
  <button
62
  type={"submit"}
63
  data-tooltip-content={tooltip}
64
  className={classNames(
65
- "p-2 rounded-r",
66
  valid
67
- ? "bg-primary-900 hover:bg-primary-800"
68
- : "bg-red-600 hover:bg-red-500"
69
  )}
70
  >
71
  {children}
72
  </button>
73
  </div>
74
  </div>
75
- {!valid && <div className={"text-red-600"}>Invalid url</div>}
76
  </form>
77
  )
78
  }
 
40
  className={classNames("flex flex-col", className)}
41
  >
42
  <div
43
+ className={"rounded-lg grow flex flex-row items-center bg-dark-800 border border-dark-700/50 focus-within:border-primary-500/50 transition-all duration-200 overflow-hidden"}
44
  >
45
  <input
46
  ref={inputRef}
47
  size={1}
48
+ className={classNames("grow bg-transparent p-2.5 outline-none")}
49
  placeholder={placeholder}
50
  value={url}
51
  onChange={(event) => {
 
54
  type={"text"}
55
  onFocus={() => inputRef.current?.select()}
56
  />
57
+ {url && (
58
+ <div className={"p-2 cursor-pointer hover:text-red-400 transition-colors"} onClick={() => onChange("")}>
59
+ <IconClose />
60
+ </div>
61
+ )}
62
  <div>
63
  <button
64
  type={"submit"}
65
  data-tooltip-content={tooltip}
66
  className={classNames(
67
+ "px-4 py-2.5 font-medium transition-all duration-200",
68
  valid
69
+ ? "bg-primary-600 hover:bg-primary-700 active:bg-primary-800"
70
+ : "bg-red-600 hover:bg-red-700 active:bg-red-800"
71
  )}
72
  >
73
  {children}
74
  </button>
75
  </div>
76
  </div>
77
+ {!valid && <div className={"text-red-400 text-sm mt-1"}>Invalid url</div>}
78
  </form>
79
  )
80
  }
components/modal/Modal.tsx CHANGED
@@ -28,32 +28,38 @@ const Modal: FC<Props> = ({ title, show, close, children }) => {
28
  e.stopPropagation()
29
  close()
30
  }}
31
- className={"absolute top-0 left-0 h-full w-full bg-black/50"}
32
  />
33
- <div className={"flex justify-center h-full items-center"}>
34
- <div className={"relative bg-dark-800 shadow rounded z-50 min-w-[30%]"}>
35
  <div
36
  className={
37
- "flex justify-between items-center p-2 border-b-2 border-b-dark-1000"
38
  }
39
  >
40
- <div className={"p-2 mr-2"}>
41
- <h2 className={"text-xl"}>{title}</h2>
42
  </div>
43
- <Button tooltip={"Close modal"} id={"closeModal1"} onClick={close}>
 
 
 
 
 
44
  <IconClose />
45
  </Button>
46
  </div>
47
- <div className={"p-4"}>{children}</div>
48
  <div
49
  className={
50
- "flex justify-end items-center p-2 border-t-2 border-t-dark-1000"
51
  }
52
  >
53
  <Button
54
  tooltip={"Close modal"}
55
  id={"closeModal2"}
56
- className={"p-2 bg-dark-600"}
 
57
  onClick={close}
58
  >
59
  Close
@@ -62,8 +68,24 @@ const Modal: FC<Props> = ({ title, show, close, children }) => {
62
  </div>
63
  </div>
64
 
65
- <Tooltip anchorId={"closeModal1"} />
66
- <Tooltip anchorId={"closeModal2"} />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  </div>
68
  )
69
  }
 
28
  e.stopPropagation()
29
  close()
30
  }}
31
+ className={"absolute top-0 left-0 h-full w-full bg-black/60 backdrop-blur-sm"}
32
  />
33
+ <div className={"flex justify-center h-full items-center p-4"}>
34
+ <div className={"relative bg-dark-800 shadow-2xl rounded-xl z-50 min-w-[30%] max-w-2xl w-full border border-dark-700/50"}>
35
  <div
36
  className={
37
+ "flex justify-between items-center p-4 border-b border-dark-700/50"
38
  }
39
  >
40
+ <div className={"px-2"}>
41
+ <h2 className={"text-xl font-semibold text-primary-400"}>{title}</h2>
42
  </div>
43
+ <Button
44
+ tooltip={"Close modal"}
45
+ id={"closeModal1"}
46
+ onClick={close}
47
+ actionClasses={"hover:bg-dark-700 active:bg-dark-600"}
48
+ >
49
  <IconClose />
50
  </Button>
51
  </div>
52
+ <div className={"p-6"}>{children}</div>
53
  <div
54
  className={
55
+ "flex justify-end items-center p-4 border-t border-dark-700/50"
56
  }
57
  >
58
  <Button
59
  tooltip={"Close modal"}
60
  id={"closeModal2"}
61
+ className={"px-4 py-2"}
62
+ actionClasses={"bg-dark-700 hover:bg-dark-600 active:bg-dark-500"}
63
  onClick={close}
64
  >
65
  Close
 
68
  </div>
69
  </div>
70
 
71
+ <Tooltip
72
+ anchorId={"closeModal1"}
73
+ style={{
74
+ backgroundColor: "var(--dark-700)",
75
+ borderRadius: "0.5rem",
76
+ padding: "0.5rem 0.75rem",
77
+ fontSize: "0.875rem",
78
+ }}
79
+ />
80
+ <Tooltip
81
+ anchorId={"closeModal2"}
82
+ style={{
83
+ backgroundColor: "var(--dark-700)",
84
+ borderRadius: "0.5rem",
85
+ padding: "0.5rem 0.75rem",
86
+ fontSize: "0.875rem",
87
+ }}
88
+ />
89
  </div>
90
  )
91
  }
components/playlist/PlaylistItem.tsx CHANGED
@@ -71,15 +71,19 @@ const PlaylistItem: FC<Props> = ({
71
  {...provided.draggableProps}
72
  style={provided.draggableProps.style}
73
  className={classNames(
74
- "p-1 rounded flex flex-col",
75
- snapshot.isDragging ? "bg-dark-700" : "bg-dark-900"
 
 
 
 
76
  )}
77
  >
78
- <div className={"flex flex-row gap-1 items-center"}>
79
  <div
80
  className={classNames(
81
- "p-1 transition-colors hover:text-primary-900",
82
- snapshot.isDragging && "text-primary-800"
83
  )}
84
  {...provided.dragHandleProps}
85
  >
@@ -96,16 +100,18 @@ const PlaylistItem: FC<Props> = ({
96
  placeholder={"Set a title"}
97
  value={title}
98
  />
99
- ) :
100
- titleGen(item, index)
101
- }
 
 
102
  </div>
103
  <DeleteButton
104
  tooltip={"Delete " + title}
105
  onClick={() => deleteItem(index)}
106
  />
107
  </div>
108
- <div className={"flex flex-row items-center"}>
109
  <ControlButton
110
  tooltip={playing ? "Currently playing" : "Play item now"}
111
  onClick={() => {
@@ -117,17 +123,17 @@ const PlaylistItem: FC<Props> = ({
117
  >
118
  {playing ? (
119
  <IconDisk
120
- className={"animate-spin animate-pulse text-purple-700"}
121
  />
122
  ) : (
123
  <IconPlay
124
- className={"text-primary-900 hover:text-primary-900"}
125
  />
126
  )}
127
  </ControlButton>
128
  <NewTabLink
129
  href={item.src[0].src}
130
- className={"flex flex-row gap-1"}
131
  >
132
  <div className={"line-clamp-2"}>{getDomain(item.src[0].src)}</div>
133
  <IconNewTab className={"shrink-0"} />
 
71
  {...provided.draggableProps}
72
  style={provided.draggableProps.style}
73
  className={classNames(
74
+ "p-3 rounded-lg flex flex-col border transition-all duration-200",
75
+ snapshot.isDragging
76
+ ? "bg-dark-700 border-primary-500/50 shadow-glow"
77
+ : playing
78
+ ? "bg-dark-800 border-primary-600/50"
79
+ : "bg-dark-800/50 border-dark-700/50 hover:bg-dark-800"
80
  )}
81
  >
82
+ <div className={"flex flex-row gap-2 items-center mb-2"}>
83
  <div
84
  className={classNames(
85
+ "p-1 transition-colors hover:text-primary-500 cursor-grab active:cursor-grabbing",
86
+ snapshot.isDragging && "text-primary-500"
87
  )}
88
  {...provided.dragHandleProps}
89
  >
 
100
  placeholder={"Set a title"}
101
  value={title}
102
  />
103
+ ) : (
104
+ <span className={classNames("text-sm font-medium", playing && "text-primary-400")}>
105
+ {titleGen(item, index)}
106
+ </span>
107
+ )}
108
  </div>
109
  <DeleteButton
110
  tooltip={"Delete " + title}
111
  onClick={() => deleteItem(index)}
112
  />
113
  </div>
114
+ <div className={"flex flex-row items-center gap-2"}>
115
  <ControlButton
116
  tooltip={playing ? "Currently playing" : "Play item now"}
117
  onClick={() => {
 
123
  >
124
  {playing ? (
125
  <IconDisk
126
+ className={"animate-spin animate-pulse text-purple-500"}
127
  />
128
  ) : (
129
  <IconPlay
130
+ className={"text-primary-500 hover:text-primary-400"}
131
  />
132
  )}
133
  </ControlButton>
134
  <NewTabLink
135
  href={item.src[0].src}
136
+ className={"flex flex-row gap-1 items-center text-xs text-dark-400 hover:text-primary-500 transition-colors"}
137
  >
138
  <div className={"line-clamp-2"}>{getDomain(item.src[0].src)}</div>
139
  <IconNewTab className={"shrink-0"} />
components/playlist/PlaylistMenu.tsx CHANGED
@@ -67,12 +67,12 @@ const PlaylistMenu: FC<Props> = ({ socket }) => {
67
  }
68
 
69
  return (
70
- <div className={classNames("flex flex-col", expanded && "sm:w-[300px]")}>
71
  <ControlButton
72
  tooltip={expanded ? "Hide playlist" : "Show playlist"}
73
  onClick={() => setExpanded(!expanded)}
74
  interaction={() => {}}
75
- className={"flex flex-row gap-1"}
76
  >
77
  <IconChevron
78
  direction={expanded ? "up" : "down"}
@@ -83,13 +83,13 @@ const PlaylistMenu: FC<Props> = ({ socket }) => {
83
  </div>
84
  </ControlButton>
85
  {expanded && (
86
- <>
87
  <InputUrl
88
  url={url}
89
  placeholder={"Add url..."}
90
  tooltip={"Add url to the playlist"}
91
  onChange={setUrl}
92
- className={"my-1"}
93
  onSubmit={() => addItem(url)}
94
  >
95
  Add
@@ -132,7 +132,7 @@ const PlaylistMenu: FC<Props> = ({ socket }) => {
132
  {...provided.droppableProps}
133
  ref={provided.innerRef}
134
  className={classNames(
135
- "flex flex-col rounded gap-1",
136
  snapshot.isDraggingOver && "bg-dark-800"
137
  )}
138
  >
@@ -177,7 +177,7 @@ const PlaylistMenu: FC<Props> = ({ socket }) => {
177
  )}
178
  </Droppable>
179
  </DragDropContext>
180
- </>
181
  )}
182
  </div>
183
  )
 
67
  }
68
 
69
  return (
70
+ <div className={classNames("flex flex-col bg-dark-900/50 rounded-xl border border-dark-700/50 overflow-hidden", expanded && "sm:w-[320px]")}>
71
  <ControlButton
72
  tooltip={expanded ? "Hide playlist" : "Show playlist"}
73
  onClick={() => setExpanded(!expanded)}
74
  interaction={() => {}}
75
+ className={"flex flex-row gap-2 items-center justify-center bg-dark-800 hover:bg-dark-700 p-3 font-medium"}
76
  >
77
  <IconChevron
78
  direction={expanded ? "up" : "down"}
 
83
  </div>
84
  </ControlButton>
85
  {expanded && (
86
+ <div className="p-2">
87
  <InputUrl
88
  url={url}
89
  placeholder={"Add url..."}
90
  tooltip={"Add url to the playlist"}
91
  onChange={setUrl}
92
+ className={"mb-2"}
93
  onSubmit={() => addItem(url)}
94
  >
95
  Add
 
132
  {...provided.droppableProps}
133
  ref={provided.innerRef}
134
  className={classNames(
135
+ "flex flex-col rounded-lg gap-2 min-h-[100px] p-2",
136
  snapshot.isDraggingOver && "bg-dark-800"
137
  )}
138
  >
 
177
  )}
178
  </Droppable>
179
  </DragDropContext>
180
+ </div>
181
  )}
182
  </div>
183
  )
components/search/YoutubeSearch.tsx CHANGED
@@ -106,11 +106,13 @@ const YoutubeSearch: FC<Props> = ({ socket }) => {
106
  const addToPlaylist = (url: string) => socket?.emit("addToPlaylist", url)
107
 
108
  return (
109
- <div className="flex flex-col gap-2">
 
 
110
  {/* Search row: input + Search button with fixed width so actions align below */}
111
  <div className="grid grid-cols-[1fr_auto] gap-2">
112
  <input
113
- className="input bg-neutral-800 p-2 rounded-md outline-none"
114
  placeholder="Search YouTube (e.g., Sira)"
115
  value={q}
116
  onChange={(e) => setQ(e.target.value)}
@@ -120,37 +122,37 @@ const YoutubeSearch: FC<Props> = ({ socket }) => {
120
  />
121
  <button
122
  onClick={search}
123
- className={`btn bg-primary-700 hover:bg-primary-600 px-3 rounded-md justify-center ${ACTION_BTN_WIDTH}`}
124
  >
125
  {loading ? "Searching…" : "Search"}
126
  </button>
127
  </div>
128
 
129
- {error && <div className="text-red-400 text-sm">{error}</div>}
130
 
131
- <div className="grid gap-2">
132
  {results.map((r) => (
133
  <div
134
  key={r.id}
135
- className="grid grid-cols-[auto_1fr_auto_auto] items-center gap-3 p-2 rounded-md border"
136
  >
137
  {/* Thumb */}
138
  {r.thumbnails?.[0]?.url ? (
139
  // eslint-disable-next-line @next/next/no-img-element
140
- <img src={r.thumbnails[0].url} alt="" className="w-16 h-9 object-cover rounded-sm" />
141
  ) : (
142
- <div className="w-16 h-9 bg-neutral-800 rounded-sm" />
143
  )}
144
 
145
  {/* Info */}
146
  <div className="min-w-0">
147
- <div className="truncate">{r.title}</div>
148
- <div className="opacity-60 text-xs truncate">{r.url}</div>
149
  </div>
150
 
151
  {/* Add (small) */}
152
  <button
153
- className={`btn bg-blue-700 hover:bg-blue-600 px-2 py-1 rounded-md text-xs justify-center ${ADD_BTN_WIDTH}`}
154
  onClick={() => addToPlaylist(r.url)}
155
  title="Add to playlist"
156
  >
@@ -159,7 +161,7 @@ const YoutubeSearch: FC<Props> = ({ socket }) => {
159
 
160
  {/* Play (aligned under Search button) */}
161
  <button
162
- className={`btn bg-green-700 hover:bg-green-600 px-3 py-1 rounded-md justify-center ${ACTION_BTN_WIDTH}`}
163
  onClick={() => playNow(r.url)}
164
  title="Play now"
165
  >
 
106
  const addToPlaylist = (url: string) => socket?.emit("addToPlaylist", url)
107
 
108
  return (
109
+ <div className="flex flex-col gap-3 bg-dark-900 border border-dark-700/50 rounded-xl p-4 shadow-lg">
110
+ <h3 className="text-lg font-semibold text-primary-400 mb-1">YouTube Search</h3>
111
+
112
  {/* Search row: input + Search button with fixed width so actions align below */}
113
  <div className="grid grid-cols-[1fr_auto] gap-2">
114
  <input
115
+ className="input bg-dark-800 border border-dark-700/50 focus:border-primary-500/50 p-2.5 rounded-lg outline-none transition-all duration-200"
116
  placeholder="Search YouTube (e.g., Sira)"
117
  value={q}
118
  onChange={(e) => setQ(e.target.value)}
 
122
  />
123
  <button
124
  onClick={search}
125
+ className={`btn bg-primary-600 hover:bg-primary-700 active:bg-primary-800 px-4 rounded-lg justify-center font-medium transition-all duration-200 shadow-md hover:shadow-glow ${ACTION_BTN_WIDTH}`}
126
  >
127
  {loading ? "Searching…" : "Search"}
128
  </button>
129
  </div>
130
 
131
+ {error && <div className="text-red-400 text-sm bg-red-900/20 border border-red-700/30 rounded-lg p-2">{error}</div>}
132
 
133
+ <div className="grid gap-2 max-h-96 overflow-y-auto">
134
  {results.map((r) => (
135
  <div
136
  key={r.id}
137
+ className="grid grid-cols-[auto_1fr_auto_auto] items-center gap-3 p-3 rounded-lg border border-dark-700/50 bg-dark-800/50 hover:bg-dark-800 transition-all duration-200"
138
  >
139
  {/* Thumb */}
140
  {r.thumbnails?.[0]?.url ? (
141
  // eslint-disable-next-line @next/next/no-img-element
142
+ <img src={r.thumbnails[0].url} alt="" className="w-20 h-12 object-cover rounded-md border border-dark-700/30" />
143
  ) : (
144
+ <div className="w-20 h-12 bg-dark-700 rounded-md border border-dark-700/30" />
145
  )}
146
 
147
  {/* Info */}
148
  <div className="min-w-0">
149
+ <div className="truncate font-medium text-dark-200">{r.title}</div>
150
+ <div className="text-dark-500 text-xs truncate">{r.url}</div>
151
  </div>
152
 
153
  {/* Add (small) */}
154
  <button
155
+ className={`btn bg-accent-600 hover:bg-accent-700 active:bg-accent-800 px-3 py-1.5 rounded-lg text-xs justify-center font-medium transition-all duration-200 ${ADD_BTN_WIDTH}`}
156
  onClick={() => addToPlaylist(r.url)}
157
  title="Add to playlist"
158
  >
 
161
 
162
  {/* Play (aligned under Search button) */}
163
  <button
164
+ className={`btn bg-primary-600 hover:bg-primary-700 active:bg-primary-800 px-3 py-1.5 rounded-lg justify-center font-medium transition-all duration-200 shadow-md hover:shadow-glow ${ACTION_BTN_WIDTH}`}
165
  onClick={() => playNow(r.url)}
166
  title="Play now"
167
  >
pages/global.css CHANGED
@@ -3,9 +3,14 @@
3
  @tailwind utilities;
4
 
5
  :root {
6
- --primary: #2ca73c;
7
- --accent: #4d7ea8;
8
- --dark-700: #313131;
 
 
 
 
 
9
  }
10
 
11
  html,
@@ -14,6 +19,10 @@ body {
14
  padding: 0;
15
  min-height: 100vh;
16
  color: white;
 
 
 
 
17
  @apply stroke-white fill-white bg-dark-1000;
18
  }
19
 
@@ -21,6 +30,7 @@ body {
21
  @apply outline-none;
22
  @apply hover:bg-dark-700 hover:drop-shadow-lg;
23
  @apply active:bg-dark-600;
 
24
  }
25
 
26
  .action > * {
@@ -38,3 +48,27 @@ body {
38
  .hide-below-sm {
39
  @apply hidden sm:inline-block;
40
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  @tailwind utilities;
4
 
5
  :root {
6
+ --primary: #10b981;
7
+ --primary-hover: #059669;
8
+ --accent: #3b82f6;
9
+ --accent-hover: #2563eb;
10
+ --dark-700: #1f2937;
11
+ --dark-800: #111827;
12
+ --dark-900: #0f172a;
13
+ --dark-1000: #020617;
14
  }
15
 
16
  html,
 
19
  padding: 0;
20
  min-height: 100vh;
21
  color: white;
22
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
23
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
24
+ -webkit-font-smoothing: antialiased;
25
+ -moz-osx-font-smoothing: grayscale;
26
  @apply stroke-white fill-white bg-dark-1000;
27
  }
28
 
 
30
  @apply outline-none;
31
  @apply hover:bg-dark-700 hover:drop-shadow-lg;
32
  @apply active:bg-dark-600;
33
+ @apply transition-all duration-200 ease-in-out;
34
  }
35
 
36
  .action > * {
 
48
  .hide-below-sm {
49
  @apply hidden sm:inline-block;
50
  }
51
+
52
+ /* Modern button styles */
53
+ button, .btn {
54
+ @apply transition-all duration-200 ease-in-out;
55
+ @apply font-medium;
56
+ }
57
+
58
+ button:focus-visible, .btn:focus-visible {
59
+ @apply ring-2 ring-primary-500 ring-offset-2 ring-offset-dark-1000;
60
+ }
61
+
62
+ /* Modern input styles */
63
+ input, textarea {
64
+ @apply transition-all duration-200 ease-in-out;
65
+ }
66
+
67
+ input:focus, textarea:focus {
68
+ @apply ring-2 ring-primary-500 ring-opacity-50;
69
+ }
70
+
71
+ /* Smooth scrolling */
72
+ html {
73
+ scroll-behavior: smooth;
74
+ }
pages/index.tsx CHANGED
@@ -15,10 +15,10 @@ export default function Index() {
15
 
16
  return (
17
  <Layout meta={{ robots: "index, archive, follow" }} showNavbar={false}>
18
- <div className={"self-center flex justify-center"}>
19
  <form
20
  className={
21
- "flex flex-col gap-4 justify-center rounded shadow p-3 bg-dark-900 m-8"
22
  }
23
  onSubmit={async (e) => {
24
  e.preventDefault()
@@ -28,7 +28,13 @@ export default function Index() {
28
  }
29
  }}
30
  >
31
- <h1 className={"text-2xl"}>Got invited?</h1>
 
 
 
 
 
 
32
  <InputText
33
  value={room}
34
  placeholder={"Enter a room ID"}
@@ -36,12 +42,13 @@ export default function Index() {
36
  setRoom(value.toLowerCase().replace(/[^a-z]/g, ""))
37
  }
38
  />
39
- <div className={"flex gap-2 justify-end"}>
 
40
  <Button
41
  tooltip={"Create a new personal room"}
42
- className={"p-2"}
43
  actionClasses={
44
- "bg-primary-900 hover:bg-primary-800 active:bg-primary-700"
45
  }
46
  onClick={() => {
47
  fetch("/api/generate")
@@ -67,11 +74,11 @@ export default function Index() {
67
  </Button>
68
  <Button
69
  tooltip={room.length < 4 ? "Invalid room id" : "Join room"}
70
- className={"p-2"}
71
  actionClasses={
72
  room.length >= 4
73
- ? "bg-primary-900 hover:bg-primary-800 active:bg-primary-700"
74
- : "bg-dark-700 hover:bg-dark-600 active:bg-red-700 cursor-not-allowed"
75
  }
76
  disabled={room.length < 4}
77
  type={"submit"}
@@ -79,19 +86,31 @@ export default function Index() {
79
  Join room
80
  </Button>
81
  </div>
82
- <small className={"text-neutral-600"}>
83
- <div>Currently connected:</div>
84
- <div className={"flex flex-row gap-2"}>
85
- <div>Rooms: {data?.rooms || 0}</div>
86
- <div>Users: {data?.users || 0}</div>
87
- </div>
88
- </small>
 
 
 
 
 
 
 
 
 
89
  </form>
90
  </div>
91
 
92
  <Tooltip
93
  style={{
94
  backgroundColor: "var(--dark-700)",
 
 
 
95
  }}
96
  />
97
  </Layout>
 
15
 
16
  return (
17
  <Layout meta={{ robots: "index, archive, follow" }} showNavbar={false}>
18
+ <div className={"self-center flex justify-center items-center min-h-[70vh]"}>
19
  <form
20
  className={
21
+ "flex flex-col gap-6 justify-center rounded-xl shadow-2xl p-8 bg-gradient-to-br from-dark-800 to-dark-900 m-8 max-w-md w-full border border-dark-700/50"
22
  }
23
  onSubmit={async (e) => {
24
  e.preventDefault()
 
28
  }
29
  }}
30
  >
31
+ <div className="text-center">
32
+ <h1 className={"text-3xl font-bold bg-gradient-to-r from-primary-500 to-accent-500 bg-clip-text text-transparent mb-2"}>
33
+ Welcome to Streamer
34
+ </h1>
35
+ <p className="text-dark-400 text-sm">Join or create a room to watch together</p>
36
+ </div>
37
+
38
  <InputText
39
  value={room}
40
  placeholder={"Enter a room ID"}
 
42
  setRoom(value.toLowerCase().replace(/[^a-z]/g, ""))
43
  }
44
  />
45
+
46
+ <div className={"flex gap-3 justify-end"}>
47
  <Button
48
  tooltip={"Create a new personal room"}
49
+ className={"px-4 py-2.5 font-medium"}
50
  actionClasses={
51
+ "bg-accent-600 hover:bg-accent-700 active:bg-accent-800 shadow-lg hover:shadow-xl"
52
  }
53
  onClick={() => {
54
  fetch("/api/generate")
 
74
  </Button>
75
  <Button
76
  tooltip={room.length < 4 ? "Invalid room id" : "Join room"}
77
+ className={"px-4 py-2.5 font-medium"}
78
  actionClasses={
79
  room.length >= 4
80
+ ? "bg-primary-600 hover:bg-primary-700 active:bg-primary-800 shadow-lg hover:shadow-xl hover:shadow-glow"
81
+ : "bg-dark-700 hover:bg-dark-600 active:bg-red-700 cursor-not-allowed opacity-50"
82
  }
83
  disabled={room.length < 4}
84
  type={"submit"}
 
86
  Join room
87
  </Button>
88
  </div>
89
+
90
+ <div className={"mt-2 pt-4 border-t border-dark-700/50"}>
91
+ <small className={"text-dark-400"}>
92
+ <div className="font-medium text-dark-300 mb-1">Currently active:</div>
93
+ <div className={"flex flex-row gap-4 text-sm"}>
94
+ <div className="flex items-center gap-1">
95
+ <span className="inline-block w-2 h-2 bg-primary-500 rounded-full"></span>
96
+ <span>{data?.rooms || 0} Rooms</span>
97
+ </div>
98
+ <div className="flex items-center gap-1">
99
+ <span className="inline-block w-2 h-2 bg-accent-500 rounded-full"></span>
100
+ <span>{data?.users || 0} Users</span>
101
+ </div>
102
+ </div>
103
+ </small>
104
+ </div>
105
  </form>
106
  </div>
107
 
108
  <Tooltip
109
  style={{
110
  backgroundColor: "var(--dark-700)",
111
+ borderRadius: "0.5rem",
112
+ padding: "0.5rem 0.75rem",
113
+ fontSize: "0.875rem",
114
  }}
115
  />
116
  </Layout>
tailwind.config.js CHANGED
@@ -7,23 +7,36 @@ module.exports = {
7
  extend: {
8
  colors: {
9
  dark: {
10
- 400: "#666666",
11
- 500: "#505050",
12
- 600: "#414141",
13
- 700: "#313131",
14
- 800: "#212121",
15
- 900: "#121212",
16
- 1000: "#060606",
17
  },
18
  primary: {
19
- 700: "#33c146",
20
- 800: "#2CA73C",
21
- 900: "#2ba13b",
 
 
22
  },
23
  accent: {
24
- 900: "#4D7EA8",
 
 
 
 
25
  },
26
  },
 
 
 
 
 
 
 
27
  },
28
  },
29
  }
 
7
  extend: {
8
  colors: {
9
  dark: {
10
+ 400: "#64748b",
11
+ 500: "#475569",
12
+ 600: "#334155",
13
+ 700: "#1f2937",
14
+ 800: "#111827",
15
+ 900: "#0f172a",
16
+ 1000: "#020617",
17
  },
18
  primary: {
19
+ 500: "#10b981",
20
+ 600: "#059669",
21
+ 700: "#047857",
22
+ 800: "#065f46",
23
+ 900: "#064e3b",
24
  },
25
  accent: {
26
+ 500: "#3b82f6",
27
+ 600: "#2563eb",
28
+ 700: "#1d4ed8",
29
+ 800: "#1e40af",
30
+ 900: "#1e3a8a",
31
  },
32
  },
33
+ fontFamily: {
34
+ sans: ['-apple-system', 'BlinkMacSystemFont', '"Segoe UI"', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', '"Fira Sans"', '"Droid Sans"', '"Helvetica Neue"', 'sans-serif'],
35
+ },
36
+ boxShadow: {
37
+ 'glow': '0 0 20px rgba(16, 185, 129, 0.3)',
38
+ 'glow-lg': '0 0 30px rgba(16, 185, 129, 0.4)',
39
+ },
40
  },
41
  },
42
  }