Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c066fdc31c | |||
| c96629b704 | |||
| 41612f5d39 | |||
| e1573ba9f0 | |||
| b9c4c817f8 | |||
| 384363c8dd | |||
| def9918047 | |||
| cafa0cccc4 | |||
| 8b907d5a86 | |||
| 13f93fb332 | |||
| 801750da96 | |||
| 82623b0078 | |||
| d3e2d9bdf9 | |||
| 9c1e8e03bb |
@@ -12,3 +12,446 @@ fclean :
|
||||
|
||||
re : fclean up
|
||||
|
||||
|
||||
|
||||
# ╭────────────────────────────────────────────────────────────────────────────╮
|
||||
# │─██████████████─██████████████─██████████████─██████─────────██████████████─│
|
||||
# │─██░░░░░░░░░░██─██░░░░░░░░░░██─██░░░░░░░░░░██─██░░██─────────██░░░░░░░░░░██─│
|
||||
# │─██████░░██████─██░░██████░░██─██░░██████░░██─██░░██─────────██░░██████████─│
|
||||
# │─────██░░██─────██░░██──██░░██─██░░██──██░░██─██░░██─────────██░░██─────────│
|
||||
# │─────██░░██─────██░░██──██░░██─██░░██──██░░██─██░░██─────────██░░██████████─│
|
||||
# │─────██░░██─────██░░██──██░░██─██░░██──██░░██─██░░██─────────██░░░░░░░░░░██─│
|
||||
# │─────██░░██─────██░░██──██░░██─██░░██──██░░██─██░░██─────────██████████░░██─│
|
||||
# │─────██░░██─────██░░██──██░░██─██░░██──██░░██─██░░██─────────────────██░░██─│
|
||||
# │─────██░░██─────██░░██████░░██─██░░██████░░██─██░░██████████─██████████░░██─│
|
||||
# │─────██░░██─────██░░░░░░░░░░██─██░░░░░░░░░░██─██░░░░░░░░░░██─██░░░░░░░░░░██─│
|
||||
# │─────██████─────██████████████─██████████████─██████████████─██████████████─│
|
||||
# ╰────────────────────────────────────────────────────────────────────────────╯
|
||||
|
||||
# --------------------------------------------------------------------------------- >
|
||||
VALGRIND = valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes -s --track-fds=yes --trace-children=yes $(V_FLAG)
|
||||
|
||||
# ↑さ↓ぎょう を ↓ほ↑ぞん
|
||||
# Default git push
|
||||
git: fclean
|
||||
@$(call random_shmol_cat_blink, 作業を保存してるかな.., いいね、いいねえー , $(CLS), );
|
||||
@current_date=$$(date); \
|
||||
git add .; \
|
||||
git commit -m "^^._, work in progress, small changes"; \
|
||||
git push
|
||||
|
||||
# Git Push that asks for commit msg
|
||||
git2: fclean
|
||||
@$(call random_shmol_cat_blink, 作業を保存してるかな.., いいね、いいねえー , $(CLS), );
|
||||
@read -p "Enter commit message: " msg; \
|
||||
[ -z "$$msg" ] && msg=$$(date); \
|
||||
git add .; \
|
||||
git commit -m "$$msg"; \
|
||||
git push
|
||||
|
||||
# Git Push use the content of .gitmsg to push
|
||||
# if .gitmsg empty, return error
|
||||
# clear .gitmsg on succesfull push.
|
||||
GIT_MSG_FILE = ../.gitmsg
|
||||
git3: fclean
|
||||
@$(call random_shmol_cat_blink, 作業を保存してるかな.., いいね、いいねえー , $(CLS), );
|
||||
@{ \
|
||||
msg="$$(cat $(GIT_MSG_FILE) 2>/dev/null)"; \
|
||||
[ -z "$$msg" ] && { $(call random_shmol_cat_blink, error, file is empty, , ); exit 1; }; \
|
||||
git add . && \
|
||||
git commit -m "$$msg" && \
|
||||
git push && \
|
||||
: > $(GIT_MSG_FILE) && \
|
||||
$(call random_shmol_cat_blink, success!, $(GIT_MSG_FILE) cleared., , ); \
|
||||
}
|
||||
|
||||
|
||||
.SILENT: $(NAME)
|
||||
|
||||
|
||||
# ╭────────────────────────────────────────────────────────────────────────────────────╮
|
||||
# │─██████████████─████████████████───██████████─██████──────────██████─██████████████─│
|
||||
# │─██░░░░░░░░░░██─██░░░░░░░░░░░░██───██░░░░░░██─██░░██████████──██░░██─██░░░░░░░░░░██─│
|
||||
# │─██░░██████░░██─██░░████████░░██───████░░████─██░░░░░░░░░░██──██░░██─██████░░██████─│
|
||||
# │─██░░██──██░░██─██░░██────██░░██─────██░░██───██░░██████░░██──██░░██─────██░░██─────│
|
||||
# │─██░░██████░░██─██░░████████░░██─────██░░██───██░░██──██░░██──██░░██─────██░░██─────│
|
||||
# │─██░░░░░░░░░░██─██░░░░░░░░░░░░██─────██░░██───██░░██──██░░██──██░░██─────██░░██─────│
|
||||
# │─██░░██████████─██░░██████░░████─────██░░██───██░░██──██░░██──██░░██─────██░░██─────│
|
||||
# │─██░░██─────────██░░██──██░░██───────██░░██───██░░██──██░░██████░░██─────██░░██─────│
|
||||
# │─██░░██─────────██░░██──██░░██████─████░░████─██░░██──██░░░░░░░░░░██─────██░░██─────│
|
||||
# │─██░░██─────────██░░██──██░░░░░░██─██░░░░░░██─██░░██──██████████░░██─────██░░██─────│
|
||||
# │─██████─────────██████──██████████─██████████─██████──────────██████─────██████─────│
|
||||
# ╰────────────────────────────────────────────────────────────────────────────────────╯
|
||||
|
||||
# C_213
|
||||
PURPLE = \033[38;5;97m
|
||||
# C_430
|
||||
GOLD = \033[38;5;178m
|
||||
# C_040
|
||||
GREEN1 = \033[38;5;40m
|
||||
# C_045
|
||||
BLUE1 = \033[38;5;45m
|
||||
|
||||
# $(C_105), $(C_510), $(C_025)
|
||||
# $(RED), $(GOLD), $(BLUE1)
|
||||
|
||||
test_color666:
|
||||
@$(call random_cat, $(call pad_word, 12, The⠀Cake), $(call pad_word, 14, Is⠀A⠀Lie⠀...), $(CLS), $(RESET));
|
||||
@$(call random_cat, $(call pad_word, 13, The⠀Cake), $(call pad_word, 15, Is⠀A⠀Lie⠀...), , $(RESET));
|
||||
|
||||
|
||||
# $(call pad_word, 12, The⠀Cake)
|
||||
pad_word = $(BLINK)$(shell printf "%$(1)s" "$(2)")$(RESET)
|
||||
# improve with: STRING1=$$(printf "\033[38;5;%dm" $$(shuf -i 0-255 -n 1));
|
||||
|
||||
# --------------------------------------------------------------------------------- >
|
||||
# @$(call print_cat, $(CLEAR), $(body), $(eye), $(txt), $(call pad_word, 12, "The⠀Cake"), $(call pad_word, 12, "Is⠀A⠀Lie..."));
|
||||
# print_cat (resest?)(C_c)_sCtt$padded_txt_top))($(padded_txt_bot))
|
||||
define print_cat
|
||||
echo -e "$(1)$(2)\
|
||||
\t\t\t\t\t ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠒⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n\
|
||||
\t\t\t\t\t ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠇⠀⠘⡄⠀⠀⠀⠀⠀⠀⣀⠀⠀\n\
|
||||
\t\t\t\t\t ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡜⠀⠀⠀⠁⠉⠉⠉⠒⠊⠉⠀⡇⠀\n\
|
||||
\t\t\t\t\t ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡜$(3)$(BLINK)⣀⡀$(RESET)$(2)⠀⠀⠀⠀⠀⠀⠀⢰⠁⠀\n\
|
||||
\t\t\t\t\t ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠲⢴⠁$(3)$(BLINK)⠛⠁$(RESET)$(2)$(3)$(BLINK)⢀⣄$(RESET)$(2)⠀⠀⠀⢸⠀⠀\n\
|
||||
\t\t\t\t\t ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠑⠺⡀⠀⠀⢶⠤$(3)$(BLINK)⠈⠋$(RESET)$(2)⠀⠀⠀⡘⠀⠀\n\
|
||||
\t\t\t\t\t ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⢄⡀⠀⠀⠀⠠⣉⠑⠂⠀⢠⠃⠀⠀\n\
|
||||
\t\t\t\t\t ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠊⠀⠀⠀⠀⠀⠀⠁⠀⠀⠈⢆⠀⠀\n\
|
||||
\t\t\t\t\t ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⡆⠀\n\
|
||||
\t\t\t\t\t ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠤⠒⠒⠃⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⠀\n\
|
||||
\t\t\t\t\t ⠀⠔⠑⠄⠀⠀⠀⠀⠀⠀⠀⠀⡎⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇\n\
|
||||
\t\t\t\t\t ⠸⡀⠀⢣⠀⠀⠀⠀⠀⠀⠀⠀⡇$(4)$(5)$(2)⠀⠀⠀⠀⠀⡇\n\
|
||||
\t\t\t\t\t ⠀⠱⡀⠀⠳⡀⠀⠀⠀⠀⠀⠀⢃$(4)$(6)$(2)⠀⠀⡸⠀\n\
|
||||
\t\t\t\t\t ⠀⠀⠑⢄⠀⠈⠒⢄⡀⠀⠀⠀⠸⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡰⠁⠀\n\
|
||||
\t\t\t\t\t ⠀⠀⠀⠀⠑⠦⣀⠀⠈⠉⠐⠒⠒⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⢢⠀\n\
|
||||
\t\t\t\t\t ⠀⠀⠀⠀⠀⠀⠀⠉⠐⠒⠠⠤⠤⠤⠤⠔⠂⠠⠤⠤⠤⠄⠐⠒⠂⠉⠉⠉⠉⠉⠁\n$(RESET)"
|
||||
endef
|
||||
|
||||
# --------------------------------------------------------------------------------- >
|
||||
# @$(call random_cat, $(call pad_word, 12, txt1), $(call pad_word, 12, txt2), $(CLS), $(RESET));
|
||||
# print_cat (resest?)(C_c)_sCtt$padded_txt_top))($(padded_txt_bot))
|
||||
define random_cat
|
||||
COLOR=$$(printf "\033[38;5;%dm" $$(shuf -i 0-255 -n 1)); \
|
||||
COLOR2=$$(printf "\033[38;5;%dm" $$(shuf -i 0-255 -n 1)); \
|
||||
COLOR3=$$(printf "\033[38;5;%dm" $$(shuf -i 0-255 -n 1)); \
|
||||
echo -e "$(3)$${COLOR}\
|
||||
\t\t\t\t\t ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠒⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n\
|
||||
\t\t\t\t\t ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠇⠀⠘⡄⠀⠀⠀⠀⠀⠀⣀⠀⠀\n\
|
||||
\t\t\t\t\t ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡜⠀⠀⠀⠁⠉⠉⠉⠒⠊⠉⠀⡇⠀\n\
|
||||
\t\t\t\t\t ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡜⠀$${COLOR2}$(BLINK)⣀⡀$(RESET)$${COLOR}⠀⠀⠀⠀⠀⠀⠀⠀⢰⠁⠀\n\
|
||||
\t\t\t\t\t ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠲⢴⠁⠀$${COLOR2}$(BLINK)⠛⠁$(RESET)$${COLOR}$${COLOR2}$(BLINK)⠀⠀⢀⣄$(RESET)$${COLOR}⠀⠀⠀⠀⢸⠀⠀\n\
|
||||
\t\t\t\t\t ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠑⠺⡀⠀⠀⢶⠤$${COLOR2}$(BLINK)⠀⠈⠋$(RESET)$${COLOR}⠀⠀⠀⠀⡘⠀⠀\n\
|
||||
\t\t\t\t\t ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⢄⡀⠀⠀⠀⠠⣉⠑⠂⠀⢠⠃⠀⠀\n\
|
||||
\t\t\t\t\t ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠊⠀⠀⠀⠀⠀⠀⠁⠀⠀⠈⢆⠀⠀\n\
|
||||
\t\t\t\t\t ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⡆⠀\n\
|
||||
\t\t\t\t\t ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠤⠒⠒⠃⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⠀\n\
|
||||
\t\t\t\t\t ⠀⠔⠑⠄⠀⠀⠀⠀⠀⠀⠀⠀⡎⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇\n\
|
||||
\t\t\t\t\t ⠸⡀⠀⢣⠀⠀⠀⠀⠀⠀⠀⠀⡇$${COLOR3}$(1)$${COLOR}⠀⠀⠀⠀⠀⡇\n\
|
||||
\t\t\t\t\t ⠀⠱⡀⠀⠳⡀⠀⠀⠀⠀⠀⠀⢃$${COLOR3}$(2)$${COLOR}⠀⠀⡸⠀\n\
|
||||
\t\t\t\t\t ⠀⠀⠑⢄⠀⠈⠒⢄⡀⠀⠀⠀⠸⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡰⠁⠀\n\
|
||||
\t\t\t\t\t ⠀⠀⠀⠀⠑⠦⣀⠀⠈⠉⠐⠒⠒⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⢢⠀\n\
|
||||
\t\t\t\t\t ⠀⠀⠀⠀⠀⠀⠀⠉⠐⠒⠠⠤⠤⠤⠤⠔⠂⠠⠤⠤⠤⠄⠐⠒⠂⠉⠉⠉⠉⠉⠁\n$(4)"
|
||||
endef
|
||||
|
||||
# --------------------------------------------------------------------------------- >
|
||||
# @$(call shmol_cat_color, $(C_c), $(C_t), txt1, txt2, $(CLS), $(RESET));
|
||||
define shmol_cat_color
|
||||
echo -e "$(5)$(2)\
|
||||
\tにゃ~$(1)\t⠀╱|、\n\
|
||||
\t\t(˚ˎ。7⠀⠀⠀$(2)~ $(3) ~$(1)\n\
|
||||
\t\t⠀|、˜\\\\\t\t$(2)$(4)$(1)\n\
|
||||
\t\t⠀じしˍ)ノ\n$(6)"
|
||||
endef
|
||||
# --------------------------------------------------------------------------------- >
|
||||
# @$(call random_shmol_cat, text 1, text 2, $(CLS), $(RESET));
|
||||
# $(1)= $(CLEAR); $(2)= text1; $(3)= text2; $(4)= $(RESET)
|
||||
define random_shmol_cat
|
||||
COLOR=$$(printf "\033[38;5;%dm" $$(shuf -i 1-255 -n 1)); \
|
||||
COLOR2=$$(printf "\033[38;5;%dm" $$(shuf -i 0-255 -n 1)); \
|
||||
echo -e "$(3)$${COLOR2}\
|
||||
\tにゃ~$${COLOR}\t⠀╱|、\n\
|
||||
\t\t(˚ˎ。7⠀⠀⠀$${COLOR2}~ $(1) ~$${COLOR}\n\
|
||||
\t\t⠀|、˜\\\\\t\t$${COLOR2}~ $(2)$${COLOR}\n\
|
||||
\t\t⠀じしˍ)ノ\n$(4)"
|
||||
endef
|
||||
|
||||
# // <!> - - - - - - - - - - - </!>
|
||||
# --------------------------------------------------------------------------------- >
|
||||
rscs:
|
||||
@$(call random_shmol_cat_surligne, text 1, text 2, $(CLS), $(RESET));
|
||||
|
||||
define random_shmol_cat_surligne
|
||||
COLOR=$$(printf "\033[0m\033[38;5;%dm" $$(shuf -i 0-255 -n 1)); \
|
||||
COLOR2=$$(printf "\033[48;5;%dm" $$(shuf -i 0-255 -n 1)); \
|
||||
echo -e "$(3)$${COLOR2}\
|
||||
\tにゃ~$${COLOR}\t⠀╱|、\n\
|
||||
\t\t(˚ˎ。7⠀⠀⠀$${COLOR2}~ $(1) ~$${COLOR}\n\
|
||||
\t\t⠀|、˜\\\\\t\t$${COLOR2}~ $(2)$${COLOR}\n\
|
||||
\t\t⠀じしˍ)ノ\n$(4)"
|
||||
endef
|
||||
|
||||
rscb:
|
||||
@$(call random_shmol_cat_blink, text 1, text 2, $(CLS), $(RESET));
|
||||
|
||||
define random_shmol_cat_blink
|
||||
COLOR=$$(printf "\033[0m\033[38;5;%dm" $$(shuf -i 0-255 -n 1)); \
|
||||
COLOR2=$$(printf "\e[5m\033[38;5;%dm" $$(shuf -i 0-255 -n 1)); \
|
||||
echo -e "$(3)\n$${COLOR2}\
|
||||
\tにゃ~$${COLOR}\t⠀╱|、\n\
|
||||
\t\t(˚ˎ。7⠀⠀⠀$${COLOR2}~ $(1) ~$${COLOR}\n\
|
||||
\t\t⠀|、˜\\\\\t\t$${COLOR2}~ $(2)$${COLOR}\n\
|
||||
\t\t⠀じしˍ)ノ\n$(4)"
|
||||
endef
|
||||
# // <!> - - - - - - - - - - - </!>
|
||||
# --------------------------------------------------------------------------------- >
|
||||
# @$(call shmol_cat_error, $(RED), $(RED_L));
|
||||
# $(1) = $(C_c)$2) = $(C_ttN CLS
|
||||
define shmol_cat_error
|
||||
echo -e "$(2)\
|
||||
\tにゃ~$(1)\t⠀╱|、\n\
|
||||
\t\t(˚ˎ。7⠀⠀⠀$(2)~ somshin wen wong ~$(1)\n\
|
||||
\t\t⠀|、˜\\\\\n\
|
||||
\t\t⠀じしˍ)ノ\n$(RESET)"
|
||||
endef
|
||||
|
||||
# Define all 256 colors
|
||||
CLEAR = \033[2J\033[H
|
||||
CLS = \033[2J\033[H
|
||||
RESET = \033[0m
|
||||
BLINK = \033[5m
|
||||
# U+2800 to U+28FF Braile
|
||||
# <Esc>[38;5;ColorNumberm
|
||||
BLACK = \033[38;5;0m
|
||||
RED = \033[38;5;1m
|
||||
GREEN = \033[38;5;2m
|
||||
YELLOW = \033[38;5;3m
|
||||
BLUE = \033[38;5;4m
|
||||
MAGENTA = \033[38;5;5m
|
||||
CYAN = \033[38;5;6m
|
||||
GRAY = \033[38;5;7m
|
||||
|
||||
BLACK_L = \033[38;5;8m
|
||||
RED_L = \033[38;5;9m
|
||||
GREEN_L = \033[38;5;10m
|
||||
YELLOW_L = \033[38;5;11m
|
||||
BLUE_L = \033[38;5;12m
|
||||
MAGENTA_L = \033[38;5;13m
|
||||
CYAN_L = \033[38;5;14m
|
||||
WHITE = \033[38;5;15m
|
||||
|
||||
C_000 = \033[38;5;16m
|
||||
C_001 = \033[38;5;17m
|
||||
C_002 = \033[38;5;18m
|
||||
C_003 = \033[38;5;19m
|
||||
C_004 = \033[38;5;20m
|
||||
C_005 = \033[38;5;21m
|
||||
C_010 = \033[38;5;22m
|
||||
C_011 = \033[38;5;23m
|
||||
C_012 = \033[38;5;24m
|
||||
C_013 = \033[38;5;25m
|
||||
C_014 = \033[38;5;26m
|
||||
C_015 = \033[38;5;27m
|
||||
C_020 = \033[38;5;28m
|
||||
C_021 = \033[38;5;29m
|
||||
C_022 = \033[38;5;30m
|
||||
C_023 = \033[38;5;31m
|
||||
C_024 = \033[38;5;32m
|
||||
C_025 = \033[38;5;33m
|
||||
C_030 = \033[38;5;34m
|
||||
C_031 = \033[38;5;35m
|
||||
C_032 = \033[38;5;36m
|
||||
C_033 = \033[38;5;37m
|
||||
C_034 = \033[38;5;38m
|
||||
C_035 = \033[38;5;39m
|
||||
C_040 = \033[38;5;40m
|
||||
C_041 = \033[38;5;41m
|
||||
C_042 = \033[38;5;42m
|
||||
C_043 = \033[38;5;43m
|
||||
C_044 = \033[38;5;44m
|
||||
C_045 = \033[38;5;45m
|
||||
C_050 = \033[38;5;46m
|
||||
C_051 = \033[38;5;47m
|
||||
C_052 = \033[38;5;48m
|
||||
C_053 = \033[38;5;49m
|
||||
C_054 = \033[38;5;50m
|
||||
C_055 = \033[38;5;51m
|
||||
C_100 = \033[38;5;52m
|
||||
C_101 = \033[38;5;53m
|
||||
C_102 = \033[38;5;54m
|
||||
C_103 = \033[38;5;55m
|
||||
C_104 = \033[38;5;56m
|
||||
C_105 = \033[38;5;57m
|
||||
C_110 = \033[38;5;58m
|
||||
C_111 = \033[38;5;59m
|
||||
C_112 = \033[38;5;60m
|
||||
C_113 = \033[38;5;61m
|
||||
C_114 = \033[38;5;62m
|
||||
C_115 = \033[38;5;63m
|
||||
C_120 = \033[38;5;64m
|
||||
C_121 = \033[38;5;65m
|
||||
C_122 = \033[38;5;66m
|
||||
C_123 = \033[38;5;67m
|
||||
C_124 = \033[38;5;68m
|
||||
C_125 = \033[38;5;69m
|
||||
C_130 = \033[38;5;70m
|
||||
C_131 = \033[38;5;71m
|
||||
C_132 = \033[38;5;72m
|
||||
C_133 = \033[38;5;73m
|
||||
C_134 = \033[38;5;74m
|
||||
C_135 = \033[38;5;75m
|
||||
C_140 = \033[38;5;76m
|
||||
C_141 = \033[38;5;77m
|
||||
C_142 = \033[38;5;78m
|
||||
C_143 = \033[38;5;79m
|
||||
C_144 = \033[38;5;80m
|
||||
C_145 = \033[38;5;81m
|
||||
C_150 = \033[38;5;82m
|
||||
C_151 = \033[38;5;83m
|
||||
C_152 = \033[38;5;84m
|
||||
C_153 = \033[38;5;85m
|
||||
C_154 = \033[38;5;86m
|
||||
C_155 = \033[38;5;87m
|
||||
C_200 = \033[38;5;88m
|
||||
C_201 = \033[38;5;89m
|
||||
C_202 = \033[38;5;90m
|
||||
C_203 = \033[38;5;91m
|
||||
C_204 = \033[38;5;92m
|
||||
C_205 = \033[38;5;93m
|
||||
C_210 = \033[38;5;94m
|
||||
C_211 = \033[38;5;95m
|
||||
C_212 = \033[38;5;96m
|
||||
C_213 = \033[38;5;97m
|
||||
C_214 = \033[38;5;98m
|
||||
C_215 = \033[38;5;99m
|
||||
C_220 = \033[38;5;100m
|
||||
C_221 = \033[38;5;101m
|
||||
C_222 = \033[38;5;102m
|
||||
C_223 = \033[38;5;103m
|
||||
C_224 = \033[38;5;104m
|
||||
C_225 = \033[38;5;105m
|
||||
C_230 = \033[38;5;106m
|
||||
C_231 = \033[38;5;107m
|
||||
C_232 = \033[38;5;108m
|
||||
C_233 = \033[38;5;109m
|
||||
C_234 = \033[38;5;110m
|
||||
C_235 = \033[38;5;111m
|
||||
C_240 = \033[38;5;112m
|
||||
C_241 = \033[38;5;113m
|
||||
C_242 = \033[38;5;114m
|
||||
C_243 = \033[38;5;115m
|
||||
C_244 = \033[38;5;116m
|
||||
C_245 = \033[38;5;117m
|
||||
C_250 = \033[38;5;118m
|
||||
C_251 = \033[38;5;119m
|
||||
C_252 = \033[38;5;120m
|
||||
C_253 = \033[38;5;121m
|
||||
C_254 = \033[38;5;122m
|
||||
C_255 = \033[38;5;123m
|
||||
C_300 = \033[38;5;124m
|
||||
C_301 = \033[38;5;125m
|
||||
C_302 = \033[38;5;126m
|
||||
C_303 = \033[38;5;127m
|
||||
C_304 = \033[38;5;128m
|
||||
C_305 = \033[38;5;129m
|
||||
C_310 = \033[38;5;130m
|
||||
C_311 = \033[38;5;131m
|
||||
C_312 = \033[38;5;132m
|
||||
C_313 = \033[38;5;133m
|
||||
C_314 = \033[38;5;134m
|
||||
C_315 = \033[38;5;135m
|
||||
C_320 = \033[38;5;136m
|
||||
C_321 = \033[38;5;137m
|
||||
C_322 = \033[38;5;138m
|
||||
C_323 = \033[38;5;139m
|
||||
C_324 = \033[38;5;140m
|
||||
C_325 = \033[38;5;141m
|
||||
C_330 = \033[38;5;142m
|
||||
C_331 = \033[38;5;143m
|
||||
C_332 = \033[38;5;144m
|
||||
C_333 = \033[38;5;145m
|
||||
C_334 = \033[38;5;146m
|
||||
C_335 = \033[38;5;147m
|
||||
C_340 = \033[38;5;148m
|
||||
C_341 = \033[38;5;149m
|
||||
C_342 = \033[38;5;150m
|
||||
C_343 = \033[38;5;151m
|
||||
C_344 = \033[38;5;152m
|
||||
C_345 = \033[38;5;153m
|
||||
C_350 = \033[38;5;154m
|
||||
C_351 = \033[38;5;155m
|
||||
C_352 = \033[38;5;156m
|
||||
C_353 = \033[38;5;157m
|
||||
C_354 = \033[38;5;158m
|
||||
C_355 = \033[38;5;159m
|
||||
C_400 = \033[38;5;160m
|
||||
C_401 = \033[38;5;161m
|
||||
C_402 = \033[38;5;162m
|
||||
C_403 = \033[38;5;163m
|
||||
C_404 = \033[38;5;164m
|
||||
C_405 = \033[38;5;165m
|
||||
C_410 = \033[38;5;166m
|
||||
C_411 = \033[38;5;167m
|
||||
C_412 = \033[38;5;168m
|
||||
C_413 = \033[38;5;169m
|
||||
C_414 = \033[38;5;170m
|
||||
C_415 = \033[38;5;171m
|
||||
C_420 = \033[38;5;172m
|
||||
C_421 = \033[38;5;173m
|
||||
C_422 = \033[38;5;174m
|
||||
C_423 = \033[38;5;175m
|
||||
C_424 = \033[38;5;176m
|
||||
C_425 = \033[38;5;177m
|
||||
C_430 = \033[38;5;178m
|
||||
C_431 = \033[38;5;179m
|
||||
C_432 = \033[38;5;180m
|
||||
C_433 = \033[38;5;181m
|
||||
C_434 = \033[38;5;182m
|
||||
C_435 = \033[38;5;183m
|
||||
C_440 = \033[38;5;184m
|
||||
C_441 = \033[38;5;185m
|
||||
C_442 = \033[38;5;186m
|
||||
C_443 = \033[38;5;187m
|
||||
C_444 = \033[38;5;188m
|
||||
C_445 = \033[38;5;189m
|
||||
C_450 = \033[38;5;190m
|
||||
C_451 = \033[38;5;191m
|
||||
C_452 = \033[38;5;192m
|
||||
C_453 = \033[38;5;193m
|
||||
C_454 = \033[38;5;194m
|
||||
C_455 = \033[38;5;195m
|
||||
C_500 = \033[38;5;196m
|
||||
C_501 = \033[38;5;197m
|
||||
C_502 = \033[38;5;198m
|
||||
C_503 = \033[38;5;199m
|
||||
C_504 = \033[38;5;200m
|
||||
C_505 = \033[38;5;201m
|
||||
C_510 = \033[38;5;202m
|
||||
C_511 = \033[38;5;203m
|
||||
C_512 = \033[38;5;204m
|
||||
C_513 = \033[38;5;205m
|
||||
C_514 = \033[38;5;206m
|
||||
C_515 = \033[38;5;207m
|
||||
C_520 = \033[38;5;208m
|
||||
C_521 = \033[38;5;209m
|
||||
C_522 = \033[38;5;210m
|
||||
C_523 = \033[38;5;211m
|
||||
C_524 = \033[38;5;212m
|
||||
C_525 = \033[38;5;213m
|
||||
C_530 = \033[38;5;214m
|
||||
C_531 = \033[38;5;215m
|
||||
C_532 = \033[38;5;216m
|
||||
C_533 = \033[38;5;217m
|
||||
C_534 = \033[38;5;218m
|
||||
C_535 = \033[38;5;219m
|
||||
C_540 = \033[38;5;220m
|
||||
C_541 = \033[38;5;221m
|
||||
C_542 = \033[38;5;222m
|
||||
C_543 = \033[38;5;223m
|
||||
C_544 = \033[38;5;224m
|
||||
C_545 = \033[38;5;225m
|
||||
C_550 = \033[38;5;226m
|
||||
C_551 = \033[38;5;227m
|
||||
C_552 = \033[38;5;228m
|
||||
C_553 = \033[38;5;229m
|
||||
C_554 = \033[38;5;230m
|
||||
C_555 = \033[38;5;231m
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
volumes:
|
||||
data:
|
||||
pgdata:
|
||||
|
||||
networks:
|
||||
transcendence:
|
||||
@@ -12,7 +12,7 @@ services:
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- data:/var/lib/postgresql/data/pg15/
|
||||
- pgdata:/var/lib/postgresql
|
||||
env_file:
|
||||
- ../.env
|
||||
networks:
|
||||
@@ -38,7 +38,7 @@ services:
|
||||
container_name: frontend
|
||||
build: ./srcs/frontend/
|
||||
ports:
|
||||
- "8080:80"
|
||||
- "8443:443"
|
||||
depends_on:
|
||||
- backend
|
||||
networks:
|
||||
|
||||
@@ -127,7 +127,7 @@ async function createTables()
|
||||
status VARCHAR(20) DEFAULT 'waiting',
|
||||
max_players INT DEFAULT 8,
|
||||
current_round INT DEFAULT 0,
|
||||
max_rounds INT DEFAULT 3,
|
||||
max_rounds INT DEFAULT 5,
|
||||
round_duration INT DEFAULT 90,
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
started_at TIMESTAMP,
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
FROM node:20-alpine
|
||||
|
||||
RUN apk add --no-cache openssl
|
||||
RUN mkdir -p /etc/backend/.ssl
|
||||
RUN openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
|
||||
-keyout /etc/backend/.ssl/key.pem \
|
||||
-out /etc/backend/.ssl/cert.pem \
|
||||
-subj "/CN=localhost" \
|
||||
-addext "subjectAltName=DNS:localhost,IP:127.0.0.1"
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import express from 'express';
|
||||
import http from 'http';
|
||||
import https from 'https';
|
||||
import fs from 'fs';
|
||||
import cors from 'cors';
|
||||
import {Server} from 'socket.io';
|
||||
import authRouter from './routes/auth.js';
|
||||
@@ -13,7 +14,11 @@ import setupSocketIO from './services/socket.js';
|
||||
import avatarService from './services/avatar.js';
|
||||
|
||||
const app = express();
|
||||
const server = http.createServer(app);
|
||||
const httpsOptions = {
|
||||
key: fs.readFileSync('/etc/backend/.ssl/key.pem'),
|
||||
cert: fs.readFileSync('/etc/backend/.ssl/cert.pem')
|
||||
};
|
||||
const server = https.createServer(httpsOptions, app);
|
||||
const io = new Server(server,
|
||||
{
|
||||
cors:
|
||||
|
||||
@@ -26,6 +26,17 @@ router.post('/login', async(req, res) =>
|
||||
res.status(result.status).json(result.data);
|
||||
});
|
||||
|
||||
router.post('/logout', async(req, res) =>
|
||||
{
|
||||
const authHeader = req.headers['authorization'];
|
||||
const token = authHeader && authHeader.split(' ')[1];
|
||||
if (!token)
|
||||
return (res.status(401).json({error: 'Missing token'}));
|
||||
|
||||
const result = await authService.logout(token);
|
||||
res.status(result.status).json(result.data);
|
||||
});
|
||||
|
||||
router.get('/github', (req, res) => {
|
||||
const githubAuthUrl = `https://github.com/login/oauth/authorize?` +
|
||||
`client_id=${process.env.GITHUB_CLIENT_ID}&` +
|
||||
|
||||
@@ -25,7 +25,7 @@ router.post('/upload', authenticateToken, upload.single('avatar'), async(req, re
|
||||
res.status(result.status).json(result.data);
|
||||
});
|
||||
|
||||
router.delete('/', authenticateToken, async(req, res) =>
|
||||
router.delete('/delete', authenticateToken, async(req, res) =>
|
||||
{
|
||||
const result = await avatarService.deleteAvatar(req.user.userId);
|
||||
res.status(result.status).json(result.data);
|
||||
|
||||
@@ -2,6 +2,30 @@ import bcrypt from 'bcrypt';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import {query} from '../db.js';
|
||||
|
||||
async function logout(token)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!token)
|
||||
return ({status: 400, data: {error: 'Missing token'}});
|
||||
try
|
||||
{
|
||||
jwt.verify(token, process.env.JWT_SECRET);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return ({status: 401, data: {error: 'Invalid token'}});
|
||||
}
|
||||
|
||||
return ({status: 200, data: {message: 'Logged out'}});
|
||||
}
|
||||
catch (err)
|
||||
{
|
||||
console.error(err);
|
||||
return ({status: 500, data: {error: 'Server error'}});
|
||||
}
|
||||
}
|
||||
|
||||
async function login(username, password)
|
||||
{
|
||||
try
|
||||
@@ -60,4 +84,4 @@ async function register(username, password)
|
||||
}
|
||||
};
|
||||
|
||||
export default {register, login};
|
||||
export default {register, login, logout};
|
||||
|
||||
@@ -69,6 +69,9 @@ async function deleteAvatar(userId) {
|
||||
if (currentAvatar === null)
|
||||
return ({status: 404, data: {error: 'User not found'}});
|
||||
|
||||
if (currentAvatar === DEFAULT_AVATAR)
|
||||
return ({status: 400, data: {error: 'Cannot delete default avatar'}});
|
||||
|
||||
// Reset the avatar to the default one
|
||||
await setAvatar(DEFAULT_AVATAR, userId);
|
||||
|
||||
|
||||
@@ -30,6 +30,63 @@ async function broadcastRoomsList(io) {
|
||||
}
|
||||
}
|
||||
|
||||
function startRoomTimer(io, roomId, seconds)
|
||||
{
|
||||
const gameState = gameRooms.get(roomId);
|
||||
if (!gameState) return;
|
||||
|
||||
if (gameState.timerInterval)
|
||||
clearInterval(gameState.timerInterval);
|
||||
|
||||
gameState.timerSeconds = seconds;
|
||||
|
||||
gameState.timerInterval = setInterval(() => {
|
||||
gameState.timerSeconds--;
|
||||
|
||||
if (gameState.timerSeconds < 0)
|
||||
gameState.timerSeconds = 0;
|
||||
|
||||
if (gameState.timerSeconds <= 0)
|
||||
{
|
||||
io.to(roomId).emit('game-timer-sync', {
|
||||
remaining: 0
|
||||
});
|
||||
clearInterval(gameState.timerInterval);
|
||||
gameState.timerInterval = null;
|
||||
io.to(roomId).emit('game-timer-ended', { message: 'Temps écoulé !' });
|
||||
|
||||
gameState.currentPlayerIndex = (gameState.currentPlayerIndex + 1) % gameState.players.length;
|
||||
const nextDrawer = gameState.players[gameState.currentPlayerIndex];
|
||||
gameState.drawer = nextDrawer;
|
||||
|
||||
|
||||
gameState.currentWord = '';
|
||||
gameState.revealedLetters = [];
|
||||
gameState.revealedWord = [];
|
||||
gameState.guessedLetters = [];
|
||||
gameState.wrongGuesses = 0;
|
||||
|
||||
io.to(roomId).emit('game-new-round', {
|
||||
drawer: nextDrawer
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
io.to(roomId).emit('game-timer-sync', {
|
||||
remaining: gameState.timerSeconds
|
||||
});
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function stopRoomTimer(roomId)
|
||||
{
|
||||
const gameState = gameRooms.get(roomId);
|
||||
if (!gameState || !gameState.timerInterval) return;
|
||||
clearInterval(gameState.timerInterval);
|
||||
gameState.timerInterval = null;
|
||||
}
|
||||
|
||||
// Check if a playing game has only 1 player left and auto-stop it
|
||||
async function checkAndStopSinglePlayerGame(io, roomId, dbRoomId) {
|
||||
if (!dbRoomId) return;
|
||||
@@ -43,6 +100,7 @@ async function checkAndStopSinglePlayerGame(io, roomId, dbRoomId) {
|
||||
const players = await gameRoomService.getRoomPlayers(dbRoomId);
|
||||
if (players.length <= 1) {
|
||||
console.log(`Room ${dbRoomId} has only ${players.length} player(s) left, ending game`);
|
||||
stopRoomTimer(roomId);
|
||||
|
||||
// Update room status to 'ended'
|
||||
await gameRoomService.updateRoomStatus(dbRoomId, 'waiting');
|
||||
@@ -192,7 +250,9 @@ function setupSocketIO(io)
|
||||
revealedLetters: gameState.revealedLetters,
|
||||
revealedWord: gameState.revealedWord || [],
|
||||
guessedLetters: gameState.guessedLetters,
|
||||
players: gameState.players
|
||||
players: gameState.players,
|
||||
scores: gameState.scores || {},
|
||||
timer: gameState.timerSeconds || 0
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -202,6 +262,15 @@ function setupSocketIO(io)
|
||||
if (socket.gameRoomId) {
|
||||
const roomId = socket.gameRoomId;
|
||||
const dbRoomId = socket.gameRoomDbId;
|
||||
const userId = socket.user.userId;
|
||||
|
||||
if (dbRoomId && userId) {
|
||||
try {
|
||||
await gameRoomService.leaveRoom(dbRoomId, userId);
|
||||
} catch (err) {
|
||||
console.error('Error removing player from room on socket leave:', err.message);
|
||||
}
|
||||
}
|
||||
|
||||
socket.to(roomId).emit('game-player-left', {
|
||||
username: socket.user.username,
|
||||
@@ -268,7 +337,8 @@ function setupSocketIO(io)
|
||||
revealedWord: gameState.revealedWord || [],
|
||||
guessedLetters: gameState.guessedLetters,
|
||||
players: gameState.players,
|
||||
scores: gameState.scores || {}
|
||||
scores: gameState.scores || {},
|
||||
timer: gameState.timerSeconds || 0
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -390,6 +460,7 @@ function setupSocketIO(io)
|
||||
const gameState = gameRooms.get(roomId);
|
||||
if (!gameState) return;
|
||||
|
||||
startRoomTimer(io, roomId, 60);
|
||||
gameState.currentWord = data.word.toLowerCase();
|
||||
gameState.revealedLetters = new Array(data.word.length).fill(false);
|
||||
gameState.revealedWord = new Array(data.word.length).fill('_');
|
||||
@@ -552,6 +623,8 @@ function setupSocketIO(io)
|
||||
// Update round start scores for next round
|
||||
gameState.roundStartScores = { ...gameState.scores };
|
||||
|
||||
stopRoomTimer(roomId);
|
||||
|
||||
io.to(roomId).emit('game-word-found', {
|
||||
word: gameState.currentWord,
|
||||
winner: username,
|
||||
@@ -613,6 +686,7 @@ function setupSocketIO(io)
|
||||
// If the drawer left and there are still enough players, choose a new drawer
|
||||
if (wasDrawer && gameState.players.length >= 1)
|
||||
{
|
||||
stopRoomTimer(roomId);
|
||||
// Pick the next player as the new drawer
|
||||
gameState.currentPlayerIndex = gameState.currentPlayerIndex % gameState.players.length;
|
||||
const newDrawer = gameState.players[gameState.currentPlayerIndex];
|
||||
@@ -632,6 +706,7 @@ function setupSocketIO(io)
|
||||
reason: 'drawer_left',
|
||||
message: `${username} (dessinateur) a quitté, ${newDrawer} devient le nouveau dessinateur`
|
||||
});
|
||||
startRoomTimer(io, roomId, 60);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -652,6 +727,7 @@ function setupSocketIO(io)
|
||||
socket.on('game-end', async () => {
|
||||
const roomId = socket.gameRoomId;
|
||||
if (!roomId) return;
|
||||
stopRoomTimer(roomId);
|
||||
|
||||
// Update room status to 'waiting' in database
|
||||
const dbRoomId = socket.gameRoomDbId;
|
||||
@@ -867,6 +943,14 @@ function setupSocketIO(io)
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dbRoomId && socket.user.userId) {
|
||||
try {
|
||||
await gameRoomService.leaveRoom(dbRoomId, socket.user.userId);
|
||||
} catch (err) {
|
||||
console.error('Error removing disconnected player from room:', err.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Regular player disconnect
|
||||
socket.to(roomId).emit('game-player-left', {
|
||||
username: socket.user.username,
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
FROM nginx:alpine
|
||||
RUN apk add --no-cache openssl && \
|
||||
mkdir -p /etc/nginx/ssl && \
|
||||
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
|
||||
-keyout /etc/nginx/ssl/key.pem \
|
||||
-out /etc/nginx/ssl/cert.pem \
|
||||
-subj "/CN=localhost" \
|
||||
-addext "subjectAltName=DNS:localhost,IP:127.0.0.1"
|
||||
COPY src /usr/share/nginx/html
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
EXPOSE 80
|
||||
EXPOSE 443
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
@@ -1,5 +1,9 @@
|
||||
server {
|
||||
listen 80;
|
||||
listen 443 ssl;
|
||||
|
||||
ssl_certificate /etc/nginx/ssl/cert.pem;
|
||||
ssl_certificate_key /etc/nginx/ssl/key.pem;
|
||||
error_page 497 =301 https://$host:8443$request_uri;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
@@ -11,27 +15,33 @@ server {
|
||||
|
||||
# Backend API
|
||||
location /api/ {
|
||||
proxy_pass http://backend:3001;
|
||||
proxy_pass https://backend:3001;
|
||||
proxy_ssl_verify off;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
}
|
||||
|
||||
# Socket.IO WebSocket proxying
|
||||
location /socket.io/ {
|
||||
proxy_pass http://backend:3001;
|
||||
proxy_pass https://backend:3001;
|
||||
proxy_ssl_verify off;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
}
|
||||
|
||||
location /avatar/ {
|
||||
proxy_pass http://backend:3001/avatar/;
|
||||
proxy_pass https://backend:3001/avatar/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_ssl_verify off;
|
||||
proxy_hide_header Content-Type;
|
||||
add_header Cache-Control "public, max-age=3600";
|
||||
}
|
||||
|
||||
@@ -6,12 +6,14 @@
|
||||
export const API = {
|
||||
AUTH: {
|
||||
LOGIN: '/api/auth/login',
|
||||
LOGOUT: '/api/auth/logout',
|
||||
REGISTER: '/api/auth/register',
|
||||
GITHUB: '/api/auth/github'
|
||||
},
|
||||
AVATAR: {
|
||||
GET: '/api/avatar/me',
|
||||
UPLOAD: '/api/avatar/upload'
|
||||
UPLOAD: '/api/avatar/upload',
|
||||
DELETE: '/api/avatar/delete'
|
||||
},
|
||||
FRIENDS: {
|
||||
LIST: '/api/friends',
|
||||
@@ -82,6 +82,7 @@ export const Events = {
|
||||
|
||||
// Avatar
|
||||
AVATAR_UPDATED: 'avatar:updated',
|
||||
AVATAR_DELETED: 'avatar:deleted',
|
||||
|
||||
// Chat
|
||||
CHAT_CONNECTED: 'chat:connected',
|
||||
@@ -228,6 +228,56 @@ export class Window {
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
NotficationContainer()
|
||||
{
|
||||
if (document.getElementById('notification-container')) return;
|
||||
|
||||
const container = this.createElement('div');
|
||||
container.id = 'notification-container';
|
||||
Object.assign(container.style, {
|
||||
position: 'fixed',
|
||||
top: '20px',
|
||||
right: '20px',
|
||||
zIndex: 1000,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '10px'
|
||||
});
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
|
||||
showNotification(message, color) {
|
||||
this.NotficationContainer();
|
||||
const container = document.getElementById('notification-container');
|
||||
if (!container) return;
|
||||
|
||||
const notification = document.createElement('div');
|
||||
notification.textContent = message;
|
||||
Object.assign(notification.style, {
|
||||
backgroundColor: color,
|
||||
color: 'white',
|
||||
padding: '10px 20px',
|
||||
borderRadius: '5px',
|
||||
boxShadow: '0 2px 6px rgba(0,0,0,0.3)',
|
||||
opacity: '0',
|
||||
transform: 'translateY(-8px)',
|
||||
transition: 'opacity 0.5s ease, transform 0.5s ease'
|
||||
});
|
||||
|
||||
container.appendChild(notification);
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
notification.style.opacity = '1';
|
||||
notification.style.transform = 'translateY(0)';
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
notification.style.opacity = '0';
|
||||
notification.style.transform = 'translateY(-8px)';
|
||||
setTimeout(() => notification.remove(), 500);
|
||||
}, 2200);
|
||||
}
|
||||
}
|
||||
|
||||
// Export old class name for compatibility (alias)
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -21,7 +21,7 @@
|
||||
|
||||
<nav class="game" aria-label="Game">
|
||||
<button class="game__item" data-action="Home page" aria-label="Home Page"
|
||||
onclick="window.location.href='index.html'">Home Page</button>
|
||||
onclick="window.location.href='../index.html'">Home Page</button>
|
||||
</nav>
|
||||
|
||||
<div class="page" aria-label="Page">
|
||||
@@ -29,6 +29,6 @@
|
||||
</div>
|
||||
|
||||
|
||||
<script type="module" src="app.js"></script>
|
||||
<script type="module" src="../app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,87 @@
|
||||
|
||||
.shape {
|
||||
/* The "Physical" properties */
|
||||
position: fixed;
|
||||
/* transform: translate(-50%, -50%); Optional: This makes 'left/top' refer to the CENTER of the doodle */
|
||||
|
||||
width: 142px;
|
||||
height: 142px;
|
||||
|
||||
/* The "Stenciling" instructions (but no image yet!) */
|
||||
-webkit-mask-size: contain;
|
||||
mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-repeat: no-repeat;
|
||||
|
||||
/* The default "Paint" color */
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.shape:hover {
|
||||
transform: scale(1.2); /* Grow by 20% when you hover the mouse over it */
|
||||
transition: transform 0.3s ease; /* Make it a smooth grow */
|
||||
}
|
||||
|
||||
/* Individual Doodle Definitions */
|
||||
.doodle-0 { -webkit-mask-image: url('doodles/cat.png'); mask-image: url('doodles/cat.png'); left: 60vw; top: 35vh; }
|
||||
.doodle-1 { -webkit-mask-image: url('doodles/ball.png'); mask-image: url('doodles/ball.png'); left: 10vw; top: 10vh; }
|
||||
.doodle-2 { -webkit-mask-image: url('doodles/batman.png'); mask-image: url('doodles/batman.png'); left: 20vw; top: 15vh; }
|
||||
.doodle-3 { -webkit-mask-image: url('doodles/building.png'); mask-image: url('doodles/building.png'); left: 30vw; top: 20vh; }
|
||||
.doodle-4 { -webkit-mask-image: url('doodles/butterfly.png'); mask-image: url('doodles/butterfly.png'); left: 40vw; top: 25vh; }
|
||||
.doodle-5 { -webkit-mask-image: url('doodles/car.png'); mask-image: url('doodles/car.png'); left: 50vw; top: 30vh; }
|
||||
.doodle-6 { -webkit-mask-image: url('doodles/yin_yang.png'); mask-image: url('doodles/yin_yang.png'); left: 88vw; top: 12vh; }
|
||||
.doodle-7 { -webkit-mask-image: url('doodles/clouds.png'); mask-image: url('doodles/clouds.png'); left: 70vw; top: 40vh; }
|
||||
.doodle-8 { -webkit-mask-image: url('doodles/controls.png'); mask-image: url('doodles/controls.png'); left: 80vw; top: 45vh; }
|
||||
.doodle-9 { -webkit-mask-image: url('doodles/dead.png'); mask-image: url('doodles/dead.png'); left: 90vw; top: 50vh; }
|
||||
.doodle-10 { -webkit-mask-image: url('doodles/diamant.png'); mask-image: url('doodles/diamant.png'); left: 15vw; top: 55vh; }
|
||||
.doodle-11 { -webkit-mask-image: url('doodles/dice.png'); mask-image: url('doodles/dice.png'); left: 25vw; top: 60vh; }
|
||||
.doodle-12 { -webkit-mask-image: url('doodles/earth.png'); mask-image: url('doodles/earth.png'); left: 35vw; top: 65vh; }
|
||||
.doodle-13 { -webkit-mask-image: url('doodles/egypt.png'); mask-image: url('doodles/egypt.png'); left: 45vw; top: 70vh; }
|
||||
.doodle-14 { -webkit-mask-image: url('doodles/fire.png'); mask-image: url('doodles/fire.png'); left: 55vw; top: 75vh; }
|
||||
.doodle-15 { -webkit-mask-image: url('doodles/fish.png'); mask-image: url('doodles/fish.png'); left: 65vw; top: 80vh; }
|
||||
.doodle-16 { -webkit-mask-image: url('doodles/flag.png'); mask-image: url('doodles/flag.png'); left: 75vw; top: 85vh; }
|
||||
.doodle-17 { -webkit-mask-image: url('doodles/hearts.png'); mask-image: url('doodles/hearts.png'); left: 85vw; top: 90vh; }
|
||||
.doodle-18 { -webkit-mask-image: url('doodles/house.png'); mask-image: url('doodles/house.png'); left: 5vw; top: 45vh; }
|
||||
.doodle-19 { -webkit-mask-image: url('doodles/idol.png'); mask-image: url('doodles/idol.png'); left: 12vw; top: 22vh; }
|
||||
.doodle-20 { -webkit-mask-image: url('doodles/lotus.png'); mask-image: url('doodles/lotus.png'); left: 22vw; top: 32vh; }
|
||||
.doodle-21 { -webkit-mask-image: url('doodles/mail.png'); mask-image: url('doodles/mail.png'); left: 32vw; top: 42vh; }
|
||||
.doodle-22 { -webkit-mask-image: url('doodles/moon.png'); mask-image: url('doodles/moon.png'); left: 42vw; top: 52vh; }
|
||||
.doodle-23 { -webkit-mask-image: url('doodles/pokeball.png'); mask-image: url('doodles/pokeball.png'); left: 52vw; top: 62vh; }
|
||||
.doodle-24 { -webkit-mask-image: url('doodles/runes.png'); mask-image: url('doodles/runes.png'); left: 62vw; top: 72vh; }
|
||||
.doodle-25 { -webkit-mask-image: url('doodles/shield.png'); mask-image: url('doodles/shield.png'); left: 72vw; top: 82vh; }
|
||||
.doodle-26 { -webkit-mask-image: url('doodles/shiny.png'); mask-image: url('doodles/shiny.png'); left: 82vw; top: 12vh; }
|
||||
.doodle-27 { -webkit-mask-image: url('doodles/snail.png'); mask-image: url('doodles/snail.png'); left: 92vw; top: 22vh; }
|
||||
.doodle-28 { -webkit-mask-image: url('doodles/sound.png'); mask-image: url('doodles/sound.png'); left: 18vw; top: 82vh; }
|
||||
.doodle-29 { -webkit-mask-image: url('doodles/spiral.png'); mask-image: url('doodles/spiral.png'); left: 28vw; top: 72vh; }
|
||||
.doodle-30 { -webkit-mask-image: url('doodles/star.png'); mask-image: url('doodles/star.png'); left: 38vw; top: 62vh; }
|
||||
.doodle-31 { -webkit-mask-image: url('doodles/stop.png'); mask-image: url('doodles/stop.png'); left: 48vw; top: 52vh; }
|
||||
.doodle-32 { -webkit-mask-image: url('doodles/sun.png'); mask-image: url('doodles/sun.png'); left: 58vw; top: 42vh; }
|
||||
.doodle-33 { -webkit-mask-image: url('doodles/tree.png'); mask-image: url('doodles/tree.png'); left: 68vw; top: 32vh; }
|
||||
.doodle-34 { -webkit-mask-image: url('doodles/triskel.png'); mask-image: url('doodles/triskel.png'); left: 78vw; top: 22vh; }
|
||||
|
||||
|
||||
/* 3. A quick animation for the color loop */
|
||||
.loop-color {
|
||||
animation: colorShift 12s infinite alternate ease-in-out;
|
||||
}
|
||||
@keyframes colorShift {
|
||||
/* 0% and 100% are identical to create the "Infinite Circle" effect */
|
||||
0% { background-color: #3075ff; } /* Royal Blue (Start) */
|
||||
|
||||
8% { background-color: #24a1ff; } /* Sky Blue */
|
||||
17% { background-color: #1ad8ff; } /* Cyan */
|
||||
|
||||
25% { background-color: #1bffa7; } /* Seafoam Green */
|
||||
33% { background-color: #1fff4d; } /* Bright Green */
|
||||
42% { background-color: #8bff32; } /* Lime Green */
|
||||
|
||||
50% { background-color: #dcff38; } /* Electric Yellow */
|
||||
58% { background-color: #ffbc29; } /* Golden Yellow */
|
||||
67% { background-color: #ff8c4a; } /* Coral Orange */
|
||||
|
||||
75% { background-color: #ff1d1d; } /* Hot Red */
|
||||
83% { background-color: #ff2bf3; } /* Magenta Pink */
|
||||
92% { background-color: #ac37ff; } /* Electric Purple */
|
||||
|
||||
100% { background-color: #3075ff; } /* Royal Blue (Seamless Loop) */
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
const maxdoodles = 34;
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////////////////>\
|
||||
// container for all doodles, create them
|
||||
class DoodleContainer {
|
||||
|
||||
constructor(parent) {
|
||||
|
||||
this.parent = parent;
|
||||
this.obj = document.createElement('div');
|
||||
Object.assign(this.obj.style, {
|
||||
width: '100vw',
|
||||
height: '100vw',
|
||||
});
|
||||
|
||||
this.createAllDoodles();
|
||||
parent.append(this.obj);
|
||||
this.randomizeAnimationStarts();
|
||||
}
|
||||
|
||||
createAllDoodles() {
|
||||
|
||||
for (let i = 0; i <= maxdoodles; i++) {
|
||||
let d = document.createElement('div');
|
||||
d.classList.add('shape', 'doodle-' + i, 'loop-color');
|
||||
d.id = 'shape' + i;
|
||||
this.obj.append(d);
|
||||
d.addEventListener('click', () => {
|
||||
console.log(`hi from ${d.id}!`);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
startSmoothRandomMove(id, speed = 2) {
|
||||
|
||||
const el = document.getElementById(id);
|
||||
if (!el)
|
||||
return;
|
||||
|
||||
// 1. Get initial pixel position or pick random if CSS isn't loaded yet
|
||||
const rect = el.getBoundingClientRect();
|
||||
|
||||
const state = {
|
||||
x: rect.left || Math.random() * (window.innerWidth - 142),
|
||||
y: rect.top || Math.random() * (window.innerHeight - 142),
|
||||
angle: Math.random() * Math.PI * 2,
|
||||
speed: speed
|
||||
};
|
||||
|
||||
function update() {
|
||||
// 2. Refresh screen boundaries every frame
|
||||
const screenW = window.innerWidth;
|
||||
const screenH = window.innerHeight;
|
||||
const shapeSize = 142; // Matches your CSS width/height
|
||||
|
||||
// 3. Calculate next step
|
||||
state.x += Math.cos(state.angle) * state.speed;
|
||||
state.y += Math.sin(state.angle) * state.speed;
|
||||
|
||||
// 4. BOUNCE LOGIC
|
||||
// Horizontal check
|
||||
if (state.x <= 0) {
|
||||
state.x = 0;
|
||||
state.angle = Math.PI - state.angle;
|
||||
} else if (state.x + shapeSize >= screenW) {
|
||||
state.x = screenW - shapeSize;
|
||||
state.angle = Math.PI - state.angle;
|
||||
}
|
||||
|
||||
// Vertical check
|
||||
if (state.y <= 0) {
|
||||
state.y = 0;
|
||||
state.angle = -state.angle;
|
||||
} else if (state.y + shapeSize >= screenH) {
|
||||
state.y = screenH - shapeSize;
|
||||
state.angle = -state.angle;
|
||||
}
|
||||
|
||||
// 5. Apply position using pixels for precision
|
||||
el.style.left = state.x + "px";
|
||||
el.style.top = state.y + "px";
|
||||
|
||||
requestAnimationFrame(update);
|
||||
}
|
||||
|
||||
requestAnimationFrame(update);
|
||||
}
|
||||
|
||||
randomizeAnimationStarts() {
|
||||
for (let i = 0; i <= maxdoodles; i++) {
|
||||
const randomSpeed = 1 + Math.random() * 3;
|
||||
this.startSmoothRandomMove(`shape${i}`, randomSpeed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////////////////>
|
||||
// all loop-color have the same @colorShift animation cycle, this disynchronize them
|
||||
function randomizeColorsStarts() {
|
||||
const shapes = document.querySelectorAll('.loop-color');
|
||||
|
||||
shapes.forEach(shape => {
|
||||
// Pick a random number between 0 and 10 (since your loop is 10s)
|
||||
const randomDelay = Math.random() * - 15;
|
||||
|
||||
// Apply it directly to the element's style
|
||||
shape.style.animationDelay = randomDelay + "s";
|
||||
});
|
||||
}
|
||||
|
||||
const a = new DoodleContainer(document.body);
|
||||
// Call this once when the script loads
|
||||
randomizeColorsStarts();
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 994 B |
|
After Width: | Height: | Size: 1018 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 955 B |
|
After Width: | Height: | Size: 1022 B |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 887 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1000 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,43 @@
|
||||
<!doctype html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Lobby</title>
|
||||
<link rel="stylesheet" href="doodle.css">
|
||||
<link rel="stylesheet" href="game.css" />
|
||||
<!-- <link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Cinzel+Decorative:wght@400;700&display=swap" rel="stylesheet" /> -->
|
||||
|
||||
<script src="doodle.js" defer></script>
|
||||
<script type="module" src="../trans/app.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1 class="title">
|
||||
<span>L</span>
|
||||
<span>o</span>
|
||||
<span>b</span>
|
||||
<span>b</span>
|
||||
<span>y</span>
|
||||
</h1>
|
||||
|
||||
<nav class="menu" aria-label="Menu principal">
|
||||
<button class="menu__item" data-action="login" aria-label="Login">Login</button>
|
||||
<button class="menu__item" data-action="chat" aria-label="Global chat">Global chat</button>
|
||||
<button class="menu__item" data-action="avatar" aria-label="Avatar">Avatar</button>
|
||||
<button class="menu__item" data-action="friends" aria-label="Amis">Amis</button>
|
||||
</nav>
|
||||
|
||||
<nav class="game" aria-label="Game">
|
||||
<button class="game__item" data-action="Home page" aria-label="Home Page"
|
||||
onclick="window.location.href='index.html'">Home Page</button>
|
||||
</nav>
|
||||
|
||||
<div class="page" aria-label="Page">
|
||||
<button class="page__item" data-action="gameroom" aria-label="Game Rooms">Game Rooms</button>
|
||||
</div>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,21 +1,8 @@
|
||||
/* ============================================
|
||||
TRANSCENDENCE - Main Stylesheet
|
||||
Convention: BEM (Block__Element--Modifier)
|
||||
============================================ */
|
||||
|
||||
/* ============================================
|
||||
CSS VARIABLES
|
||||
============================================ */
|
||||
|
||||
/* ///////////////////////////////////////////////////////// */
|
||||
:root {
|
||||
--color-primary: #0066cc;
|
||||
--color-primary-hover: #0052a3;
|
||||
--color-success: #3cff01;
|
||||
--color-success-dark: #28a745;
|
||||
--color-error: #ff4d4d;
|
||||
--color-warning: #ffc107;
|
||||
--color-github: #24292e;
|
||||
|
||||
--color-bg: #a3a3a3;
|
||||
--custom-value: hello;
|
||||
|
||||
--app-background-base: radial-gradient(
|
||||
circle at top,
|
||||
@@ -24,729 +11,147 @@
|
||||
);
|
||||
|
||||
--app-background-image: url("./assets/background.png");
|
||||
|
||||
--color-surface: #222;
|
||||
--color-surface-light: #333;
|
||||
--color-text: #fff;
|
||||
--color-text-muted: #aaa;
|
||||
|
||||
--font-size-base: 10px;
|
||||
--font-size-sm: 1.2rem;
|
||||
--font-size-md: 1.4rem;
|
||||
--font-size-lg: 1.6rem;
|
||||
--font-size-xl: 3rem;
|
||||
|
||||
--spacing-xs: 4px;
|
||||
--spacing-sm: 8px;
|
||||
--spacing-md: 12px;
|
||||
--spacing-lg: 16px;
|
||||
--spacing-xl: 24px;
|
||||
|
||||
--radius-sm: 4px;
|
||||
--radius-md: 6px;
|
||||
--radius-lg: 12px;
|
||||
--radius-full: 50%;
|
||||
|
||||
--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
--shadow-md: 0 4px 8px rgba(0, 0, 0, 0.5);
|
||||
--shadow-lg: 0 8px 16px rgba(0, 0, 0, 0.5);
|
||||
|
||||
--transition-fast: 150ms ease;
|
||||
--transition-normal: 250ms ease;
|
||||
|
||||
--z-menu: 2;
|
||||
--z-window: 100;
|
||||
--z-modal: 200;
|
||||
--num-value: 10px;
|
||||
--black: #000000;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
RESET & BASE
|
||||
============================================ */
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
/* ///////////////////////////////////////////////////////// */
|
||||
*, *::before, *::after {
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
background-image:
|
||||
var(--app-background-image),
|
||||
var(--app-background-base);
|
||||
|
||||
background-size:
|
||||
contain,
|
||||
cover;
|
||||
|
||||
background-position:
|
||||
center,
|
||||
center;
|
||||
|
||||
background-repeat:
|
||||
no-repeat,
|
||||
no-repeat;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
width: 70%;
|
||||
min-width: 800px;
|
||||
margin: 0 auto;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
color: var(--color-text);
|
||||
line-height: 1.5;
|
||||
|
||||
line-height: 1.5; /* inherited */
|
||||
word-spacing: 1.4px; /* inherited */
|
||||
font-size: 20px;
|
||||
font-family: 'Times New Roman', serif; /* inherited */
|
||||
color: var(--black); /* inherited */
|
||||
text-align: center;
|
||||
color: #696969;
|
||||
|
||||
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: var(--black);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
TYPOGRAPHY
|
||||
============================================ */
|
||||
|
||||
.title {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
text-transform: uppercase;
|
||||
.container-1 {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
|
||||
font-size: var(--font-size-xl);
|
||||
text-align: center;
|
||||
text-shadow: 2px 2px 10px black;
|
||||
z-index: 1;
|
||||
font-family: "Cinzel Decorative", cursive;
|
||||
color: rgba(248, 252, 2, 0.6);
|
||||
|
||||
margin: 0;
|
||||
padding: var(--spacing-md);
|
||||
|
||||
/* Rectangle + rounded corners */
|
||||
background-color: rgba(247, 7, 67, 0.6);
|
||||
border: 2px solid rgba(0, 0, 0, 0.6);
|
||||
border-radius: 15px;
|
||||
width: 100%;
|
||||
margin: 5px;
|
||||
position: relative;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
/* ///////////////////////////////////////////////////////// */
|
||||
|
||||
/* ============================================
|
||||
MENU
|
||||
============================================ */
|
||||
|
||||
.menu {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 50px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
z-index: var(--z-menu);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-xs);
|
||||
.button {
|
||||
color: red;
|
||||
margin: 5px 50px;
|
||||
padding: 5px 50px;
|
||||
}
|
||||
|
||||
.menu__item {
|
||||
background: var(--color-surface);
|
||||
color: var(--color-text);
|
||||
border: 1px solid var(--color-surface-light);
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
font-size: var(--font-size-md);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.menu__item:hover {
|
||||
background: var(--color-surface-light);
|
||||
font-size: var(--font-size-lg);
|
||||
}
|
||||
|
||||
.menu__item--active {
|
||||
background: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
GAME
|
||||
============================================ */
|
||||
|
||||
.game {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 50px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
z-index: var(--z-menu);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.game__item {
|
||||
background: var(--color-surface);
|
||||
color: var(--color-text);
|
||||
border: 1px solid var(--color-surface-light);
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
font-size: var(--font-size-md);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.game__item:hover {
|
||||
background: var(--color-surface-light);
|
||||
font-size: var(--font-size-lg);
|
||||
}
|
||||
|
||||
.game__item--active {
|
||||
background: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
BUTTONS
|
||||
============================================ */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
font-size: var(--font-size-md);
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
opacity: 0.9;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.btn--primary {
|
||||
background: var(--color-primary);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.btn--primary:hover {
|
||||
background: var(--color-primary-hover);
|
||||
}
|
||||
|
||||
.btn--secondary {
|
||||
background: var(--color-surface-light);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.btn--success {
|
||||
background: var(--color-success-dark);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.btn--danger {
|
||||
background: var(--color-error);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.btn--github {
|
||||
background: var(--color-github);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.btn--ghost {
|
||||
background: transparent;
|
||||
color: var(--color-text);
|
||||
border: 1px solid var(--color-surface-light);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
INPUTS
|
||||
============================================ */
|
||||
.input {
|
||||
width: 100%;
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
font-size: var(--font-size-md);
|
||||
background: var(--color-surface);
|
||||
color: var(--color-text);
|
||||
border: 1px solid var(--color-surface-light);
|
||||
border-radius: var(--radius-md);
|
||||
transition: border-color var(--transition-fast);
|
||||
}
|
||||
|
||||
.input:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.input::placeholder {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
WINDOWS
|
||||
============================================ */
|
||||
.window {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: var(--color-bg);
|
||||
border: 2px ridge var(--color-text);
|
||||
color: var(--color-text);
|
||||
z-index: var(--z-window);
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
min-width: 280px;
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.window--visible {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.window--left {
|
||||
left: 25%;
|
||||
}
|
||||
|
||||
.window--right {
|
||||
left: 75%;
|
||||
}
|
||||
|
||||
.window__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
background: var(--color-surface);
|
||||
cursor: move;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.window__title {
|
||||
font-weight: 500;
|
||||
font-size: var(--font-size-md);
|
||||
}
|
||||
|
||||
.window__close {
|
||||
cursor: pointer;
|
||||
font-size: var(--font-size-lg);
|
||||
opacity: 0.8;
|
||||
transition: opacity var(--transition-fast);
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--color-text);
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.window__close:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.window__body {
|
||||
padding: var(--spacing-md);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-sm);
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
MESSAGES
|
||||
============================================ */
|
||||
.message {
|
||||
font-size: var(--font-size-sm);
|
||||
padding: var(--spacing-xs);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.message--success {
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.message--error {
|
||||
color: var(--color-error);
|
||||
}
|
||||
|
||||
.message--info {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
LOGIN WINDOW
|
||||
============================================ */
|
||||
.login {
|
||||
width: 320px;
|
||||
}
|
||||
|
||||
.login__form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.login__actions {
|
||||
display: flex;
|
||||
gap: var(--spacing-sm);
|
||||
margin-top: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.login__divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--font-size-sm);
|
||||
margin: var(--spacing-sm) 0;
|
||||
}
|
||||
|
||||
.login__divider::before,
|
||||
.login__divider::after {
|
||||
content: '';
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: var(--color-surface-light);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
CHAT WINDOW
|
||||
============================================ */
|
||||
.chat {
|
||||
width: 380px;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.chat__output {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: var(--spacing-sm);
|
||||
background: var(--color-surface);
|
||||
border-radius: var(--radius-md);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-sm);
|
||||
min-height: 150px;
|
||||
}
|
||||
|
||||
.chat__message {
|
||||
padding: var(--spacing-xs) var(--spacing-sm);
|
||||
background: var(--color-surface-light);
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.chat__message--own {
|
||||
background: var(--color-primary);
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.chat__friend-indicator {
|
||||
.button-1 {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background-color: var(--color-success);
|
||||
border-radius: 50%;
|
||||
margin-right: var(--spacing-xs);
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.chat__system {
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--font-size-sm);
|
||||
font-style: italic;
|
||||
padding: 10px 20px;
|
||||
background-color: #000000;
|
||||
color: #8e8e8e;
|
||||
text-align: center;
|
||||
}
|
||||
text-decoration: none;
|
||||
|
||||
.chat__system--error {
|
||||
color: var(--color-error);
|
||||
}
|
||||
|
||||
.chat__system--success {
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.chat__input-container {
|
||||
display: flex;
|
||||
gap: var(--spacing-sm);
|
||||
margin-top: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.chat__input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.chat__controls {
|
||||
display: flex;
|
||||
gap: var(--spacing-sm);
|
||||
margin-top: var(--spacing-sm);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
AVATAR WINDOW
|
||||
============================================ */
|
||||
.avatar-window {
|
||||
width: 360px;
|
||||
}
|
||||
|
||||
.avatar__preview {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
object-fit: cover;
|
||||
border-radius: var(--radius-full);
|
||||
border: 3px solid var(--color-text);
|
||||
box-shadow: var(--shadow-md);
|
||||
background: var(--color-surface);
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.avatar__username {
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
color: var(--color-text);
|
||||
margin-top: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.avatar__controls {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-sm);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.avatar__file-input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
STATS WINDOW
|
||||
============================================ */
|
||||
.stats-window {
|
||||
width: 320px;
|
||||
}
|
||||
|
||||
.stats__avatar {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
object-fit: cover;
|
||||
border-radius: var(--radius-full);
|
||||
border: 2px solid var(--color-text);
|
||||
align-self: center;
|
||||
display: block;
|
||||
margin: 0 auto var(--spacing-xs);
|
||||
}
|
||||
|
||||
.stats__username {
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
color: #000;
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.stats__section {
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.stats__section-title {
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: var(--color-primary);
|
||||
border-bottom: 1px solid var(--color-surface-light);
|
||||
padding-bottom: var(--spacing-xs);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.stats__section-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.stats__row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: var(--font-size-sm);
|
||||
padding: 3px 0;
|
||||
}
|
||||
|
||||
.stats__label {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.stats__value {
|
||||
font-weight: 600;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.stats__loading {
|
||||
font-size: var(--font-size-sm);
|
||||
color: #333;
|
||||
text-align: center;
|
||||
padding: var(--spacing-sm) 0;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
EASTER EGG BUTTON
|
||||
============================================ */
|
||||
/* .easter-egg {
|
||||
position: absolute;
|
||||
top: 20%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 1;
|
||||
background: var(--color-surface);
|
||||
color: var(--color-text);
|
||||
border: 1px solid var(--color-surface-light);
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
font-size: var(--font-size-md);
|
||||
border-radius: var(--radius-md);
|
||||
transition: all var(--transition-fast);
|
||||
border: 3px solid #363636;
|
||||
border-radius: 6px;
|
||||
transition: background-color 0.3s;
|
||||
|
||||
}
|
||||
.button-1:hover {
|
||||
background-color: rgb(202, 135, 10);
|
||||
color: black;
|
||||
}
|
||||
|
||||
.easter-egg:hover {
|
||||
background: var(--color-error);
|
||||
border-color: var(--color-error);
|
||||
} */
|
||||
/* ///////////////////////////////////////////////////////// */
|
||||
.button-trans {
|
||||
/* SIZE */
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
|
||||
/* ============================================
|
||||
UTILITIES
|
||||
============================================ */
|
||||
.hidden {
|
||||
display: none !important;
|
||||
width: 500px;
|
||||
height: 200px;
|
||||
|
||||
/* TEXT */
|
||||
font-family: "Roboto";
|
||||
font-size: 62px;
|
||||
letter-spacing: -10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
/* Background */
|
||||
background-image: url("./assets/background.png");
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 150%;
|
||||
/* Borders */
|
||||
border-radius: 20px;
|
||||
border-radius: 20px;
|
||||
border: 5px solid transparent; /* keep space for the shadow */
|
||||
background-clip: padding-box;
|
||||
/* metallic effect */
|
||||
box-shadow:
|
||||
0 0 0 5px #c0c0c0 inset, /* inner shine */
|
||||
0 0 0 2px rgba(255,255,255,0.3) inset; /* subtle highlight */
|
||||
|
||||
/* OTHER */
|
||||
cursor: pointer;
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.visually-hidden {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
border: 0;
|
||||
.button-trans:hover {
|
||||
transform: translateX(-50%) scale(1.02);
|
||||
box-shadow:
|
||||
0 0 20px 5px #fff inset,
|
||||
0 0 20px 5px rgba(255,255,255,0.3) inset;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
/* ///////////////////////////////////////////////////////// */
|
||||
.button-test {
|
||||
margin-right: auto;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
/* ///////////////////////////////////////////////////////// */
|
||||
.footer_div {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
/* padding: 20px; */
|
||||
/* margin-top: 80px;
|
||||
margin-bottom: 100px; */
|
||||
}
|
||||
|
||||
.ico_footer {
|
||||
text-align: center;
|
||||
width: 25px;
|
||||
vertical-align: top;
|
||||
/* padding-right: 5px; */
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #5c5c5c;
|
||||
}
|
||||
a:hover {
|
||||
color: rgb(218, 145, 12);
|
||||
}
|
||||
|
||||
.flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
FRIENDS WINDOW
|
||||
============================================ */
|
||||
.friends-window {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.friends__tabs {
|
||||
display: flex;
|
||||
gap: var(--spacing-xs);
|
||||
margin-bottom: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.friends__tab {
|
||||
flex: 1;
|
||||
padding: var(--spacing-sm);
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-surface-light);
|
||||
color: var(--color-text);
|
||||
cursor: pointer;
|
||||
font-size: var(--font-size-sm);
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.friends__tab:hover {
|
||||
background: var(--color-surface-light);
|
||||
}
|
||||
|
||||
.friends__tab--active {
|
||||
background: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.friends__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.friends__search {
|
||||
display: flex;
|
||||
gap: var(--spacing-sm);
|
||||
margin-bottom: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.friends__search .input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.friends__list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.friends__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
padding: var(--spacing-sm);
|
||||
background: var(--color-surface);
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.friends__avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: var(--radius-full);
|
||||
object-fit: cover;
|
||||
border: 2px solid var(--color-surface-light);
|
||||
}
|
||||
|
||||
.friends__name {
|
||||
flex: 1;
|
||||
font-size: var(--font-size-md);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.friends__actions {
|
||||
display: flex;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.friends__actions .btn {
|
||||
padding: var(--spacing-xs) var(--spacing-sm);
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.friends__empty {
|
||||
text-align: center;
|
||||
color: var(--color-text-muted);
|
||||
padding: var(--spacing-lg);
|
||||
}
|
||||
/* ///////////////////////////////////////////////////////// */
|
||||
|
||||
@@ -1,31 +1,54 @@
|
||||
<!doctype html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Transcendence.io</title>
|
||||
<link rel="stylesheet" href="index.css" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Cinzel+Decorative:wght@400;700&display=swap" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<h1 class="title">Transcendence.io</h1>
|
||||
|
||||
<nav class="menu" aria-label="Menu principal">
|
||||
<button class="menu__item" data-action="login" aria-label="Login">Login</button>
|
||||
<button class="menu__item" data-action="chat" aria-label="Global chat">Global chat</button>
|
||||
<button class="menu__item" data-action="avatar" aria-label="Avatar">Avatar</button>
|
||||
<button class="menu__item" data-action="friends" aria-label="Amis">Amis</button>
|
||||
</nav>
|
||||
|
||||
<nav class="game" aria-label="Game">
|
||||
<button class="game__item" data-action="new_game" aria-label="Start new game"
|
||||
onclick="window.location.href='game.html'">Start new game</button>
|
||||
<button class="game__item" data-action="tetris" aria-label="Tetris"
|
||||
onclick="window.location.href='tetris.html'">Tetris</button>
|
||||
</nav>
|
||||
|
||||
<script type="module" src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
<!DOCTYPE html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="./index.css" />
|
||||
<script type="module" src="./index.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="header-1" class="container-1"
|
||||
style="">
|
||||
<div id="button-test" class="button-1 button-test multicolor" onclick="window.location.href = 'test/index.html';">TEST</div>
|
||||
<div id="button-trans" class="button-trans multicolor">TRANSCENDENCE</div>
|
||||
</div>
|
||||
|
||||
<img id="wiskas" style="margin: auto; display: block;" src="webcat/web_cat_img/wiskas-the-third.jpg">
|
||||
|
||||
<section style="display: flex;
|
||||
justify-content: center;
|
||||
width: 1000px;
|
||||
margin: 0 auto;">
|
||||
<p>I, am wiskas-the-third,
|
||||
We are the cat company, we dont need to present our self for you already know
|
||||
who we are, we created the internet, and we are still managing it now<br>
|
||||
We at CAT are the admin, creator, and workers of the internet
|
||||
Everytime a human goes to sleep, a cat start its shift, 1 billion pair of whiskers that are always here for you
|
||||
Why? because we are philantropists, dont question it. Our goals are beyond your understanding
|
||||
the internet was created by us, for us, and you should be glad we allow you to use it.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section style="display: flex;">
|
||||
<button style="margin-right: 50px;" class="button-1 multicolor" onclick="window.location.href = 'webcat/biblio.html';">
|
||||
Latest News</button><br>
|
||||
<button style="margin-left: 50px;" class="button-1 multicolor" onclick="window.location.href = 'webcat/staff/staff.html';">
|
||||
meet the staff</button><br>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<br><br><br>
|
||||
<div class="footer_div" style="margin-top: 100px;">
|
||||
<img class="ico_footer" src="webcat/web_cat_img/facebook_logo.png">
|
||||
<img class="ico_footer" src="webcat/web_cat_img/insta_logo.png">
|
||||
<img class="ico_footer" src="webcat/web_cat_img/twitter_logo.png">
|
||||
</div>
|
||||
<div class="footer_div" style="margin-bottom: 50px;">
|
||||
<a href="https://www.facebook.com/">MIAOUBOOK</a>
|
||||
<a href="https://www.instagram.com/">INSTAMIA</a>
|
||||
<a href="https://twitter.com/">BLUE-SNACK</a>
|
||||
</div>
|
||||
<a href="./webcat/ml/mentions_legales.html">- LEGAL NOTICES -<br>(boring stuff, really, dont go look into this, i mean we are obligated to include it, but it will bore you, like, really)
|
||||
<br>Dont do it! every seconds you spend in this next page, a kitten dies. so dont</a>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,54 @@
|
||||
import { updateElement } from "./test/tools.js";
|
||||
import { colorizeText } from "./tools.js";
|
||||
|
||||
// //////////////////////////////////////////]
|
||||
let div2 = document.createElement('div')
|
||||
document.body.append(div2)
|
||||
let button1 = document.createElement('button')
|
||||
div2.append(button1)
|
||||
button1.textContent = 'game-lobby'
|
||||
button1.addEventListener('click', () => {
|
||||
window.location.href = './game2/game.html';
|
||||
})
|
||||
let button2 = document.createElement('button')
|
||||
div2.append(button2)
|
||||
button2.textContent = 'tetris'
|
||||
button2.addEventListener('click', () => {
|
||||
window.location.href = './tetris/tetris.html';
|
||||
})
|
||||
|
||||
let button4 = document.createElement('button')
|
||||
div2.append(button4)
|
||||
button4.textContent = 'test'
|
||||
button4.addEventListener('click', () => {
|
||||
window.location.href = './test/index.html';
|
||||
})
|
||||
let img = document.getElementById('wiskas');
|
||||
img.before(div2)
|
||||
|
||||
// apply multicolor to .multicolor
|
||||
colorizeText();
|
||||
|
||||
|
||||
/* ///////////////////////////////////////////////////////// */
|
||||
// make transcendence button move via: .button-trans
|
||||
function updateButtonTranscendence(move) {
|
||||
|
||||
const btn = document.querySelector('.button-trans');
|
||||
btn.addEventListener('mousemove', e => {
|
||||
const rect = btn.getBoundingClientRect();
|
||||
const x = ((e.clientX - rect.left) / rect.width - 0.5) * move;
|
||||
const y = ((e.clientY - rect.top) / rect.height - 0.5) * move;
|
||||
btn.style.backgroundPosition = `calc(50% + ${x}px) calc(50% + ${y}px)`;
|
||||
});
|
||||
|
||||
btn.addEventListener('mouseleave', () => {
|
||||
btn.style.backgroundPosition = 'center';
|
||||
});
|
||||
|
||||
btn.addEventListener('click', () => {
|
||||
window.location.href = './trans/index2.html';
|
||||
});
|
||||
}
|
||||
/* ///////////////////////////////////////////////////////// */
|
||||
updateButtonTranscendence(100);
|
||||
@@ -0,0 +1,188 @@
|
||||
.test {/* =======================
|
||||
🎨 COLORS & BACKGROUND
|
||||
======================= */
|
||||
color: red;
|
||||
background-color: blue;
|
||||
background-image: url(img.jpg);
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
opacity: 0.5;
|
||||
|
||||
/* =======================
|
||||
📏 SIZE & SPACING
|
||||
======================= */
|
||||
width: 200px;
|
||||
height: 100px;
|
||||
min-width: 100px;
|
||||
max-width: 500px;
|
||||
padding: 10px;
|
||||
margin: 20px;
|
||||
box-sizing: border-box;
|
||||
|
||||
/* shorthand */
|
||||
margin: 10px 20px; /* top/bottom left/right */
|
||||
padding: 10px 20px 5px 0; /* top right bottom left */
|
||||
|
||||
/* =======================
|
||||
📍 POSITIONING
|
||||
======================= */
|
||||
position: static;
|
||||
position: relative;
|
||||
position: absolute;
|
||||
position: fixed;
|
||||
position: sticky;
|
||||
|
||||
top: 10px;
|
||||
left: 20px;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
||||
z-index: 10;
|
||||
|
||||
/* =======================
|
||||
📦 DISPLAY & LAYOUT
|
||||
======================= */
|
||||
display: block;
|
||||
display: inline;
|
||||
display: inline-block;
|
||||
display: none;
|
||||
display: flex; /* children can be controled with: justify-content (horizontal) / align-items (vertical) */
|
||||
display: grid;
|
||||
|
||||
/* =======================
|
||||
🔧 FLEXBOX
|
||||
======================= */
|
||||
display: flex;
|
||||
flex-direction: row; /* row | column */
|
||||
justify-content: center; /* main axis */
|
||||
align-items: center; /* cross axis */
|
||||
gap: 10px;
|
||||
|
||||
/* common */
|
||||
justify-content: space-between;
|
||||
justify-content: space-around;
|
||||
justify-content: space-evenly;
|
||||
|
||||
/* =======================
|
||||
🧱 GRID
|
||||
======================= */
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
grid-template-rows: auto;
|
||||
gap: 10px;
|
||||
|
||||
/* =======================
|
||||
🔤 TEXT & FONT
|
||||
======================= */
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
font-family: Arial, sans-serif;
|
||||
|
||||
text-align: center;
|
||||
text-decoration: underline;
|
||||
text-transform: uppercase;
|
||||
|
||||
line-height: 1.5;
|
||||
letter-spacing: 2px;
|
||||
|
||||
/* =======================
|
||||
🟦 BORDER & OUTLINE
|
||||
======================= */
|
||||
border: 1px solid black;
|
||||
border-width: 2px;
|
||||
border-style: dashed;
|
||||
border-color: red;
|
||||
|
||||
border-radius: 10px;
|
||||
|
||||
outline: 2px solid blue;
|
||||
|
||||
/* =======================
|
||||
👁️ VISIBILITY
|
||||
======================= */
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
|
||||
overflow: hidden;
|
||||
overflow: scroll;
|
||||
overflow: auto;
|
||||
|
||||
/* =======================
|
||||
🎬 TRANSITIONS & EFFECTS
|
||||
======================= */
|
||||
transition: all 0.3s ease;
|
||||
|
||||
transform: translateX(50px);
|
||||
transform: rotate(45deg);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
/* hover example */
|
||||
:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
/* =======================
|
||||
🧠 SELECTORS
|
||||
======================= */
|
||||
|
||||
/* basic */
|
||||
div {} /* tag */
|
||||
.class {} /* class */
|
||||
#id {} /* id */
|
||||
* {} /* all elements */
|
||||
|
||||
/* grouping */
|
||||
div, p, span {} /* multiple selectors */
|
||||
|
||||
/* combinators */
|
||||
div p {} /* any descendant */
|
||||
div > p {} /* direct child */
|
||||
div + p {} /* next sibling */
|
||||
div ~ p {} /* all following siblings */
|
||||
|
||||
/* attribute selectors */
|
||||
input[type="text"] {}
|
||||
a[href] {}
|
||||
button[class*="btn"] {} /* contains */
|
||||
button[class^="btn"] {} /* starts with */
|
||||
button[class$="btn"] {} /* ends with */
|
||||
|
||||
/* pseudo-classes (state) */
|
||||
button:hover {}
|
||||
input:focus {}
|
||||
a:active {}
|
||||
a:visited {}
|
||||
input:checked {}
|
||||
:nth-child(2) {}
|
||||
:nth-child(odd) {}
|
||||
:nth-child(even) {}
|
||||
:not(.active) {}
|
||||
|
||||
/* pseudo-elements (virtual parts) */
|
||||
::before {}
|
||||
::after {}
|
||||
::placeholder {}
|
||||
::first-letter {}
|
||||
::first-line {}
|
||||
|
||||
/* combined examples */
|
||||
button.primary:hover {}
|
||||
div#main.content {}
|
||||
ul li:first-child {}
|
||||
input:focus::placeholder {}
|
||||
|
||||
/* universal + pseudo */
|
||||
*::before {}
|
||||
*::after {}
|
||||
|
||||
/* =======================
|
||||
⚡ SHORTHANDS
|
||||
======================= */
|
||||
.test2 {
|
||||
background: red url(img.jpg) no-repeat center/cover;
|
||||
border: 2px solid black;
|
||||
font: bold 16px Arial;
|
||||
margin: 10px 20px;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
// SIZE
|
||||
box.style.width = "200px";
|
||||
box.style.height = "100px";
|
||||
box.style.minWidth = "100px";
|
||||
box.style.maxWidth = "500px";
|
||||
|
||||
{
|
||||
display: "flex" // flex | inline-flex | block | inline | none
|
||||
justifyContent: "flex-start" // flex-start | flex-end | center | space-between | space-around | space-evenly
|
||||
alignItems: "stretch" // stretch | flex-start | flex-end | center | baseline
|
||||
}
|
||||
|
||||
|
||||
|
||||
// POSITION
|
||||
box.style.position = "absolute";
|
||||
box.style.top = "50px";
|
||||
box.style.left = "100px";
|
||||
box.style.right = "20px";
|
||||
box.style.bottom = "10px";
|
||||
box.style.zIndex = "10";
|
||||
|
||||
// SPACING
|
||||
box.style.margin = "10px";
|
||||
box.style.padding = "20px";
|
||||
box.style.marginTop = "10px";
|
||||
box.style.paddingLeft = "5px";
|
||||
|
||||
// BACKGROUND & COLORS
|
||||
box.style.background = "red";
|
||||
box.style.backgroundColor = "blue";
|
||||
box.style.color = "white";
|
||||
|
||||
// BORDER
|
||||
box.style.border = "2px solid black";
|
||||
box.style.borderRadius = "10px";
|
||||
|
||||
// TEXT
|
||||
box.style.fontSize = "20px";
|
||||
box.style.fontWeight = "bold";
|
||||
box.style.textAlign = "center";
|
||||
|
||||
// DISPLAY & VISIBILITY
|
||||
box.style.display = "block";
|
||||
box.style.visibility = "visible";
|
||||
box.style.opacity = "0.5";
|
||||
|
||||
// TRANSFORM
|
||||
box.style.transform = "translateX(100px)";
|
||||
box.style.transform = "translate(50px, 20px)";
|
||||
box.style.transform = "scale(1.5)";
|
||||
box.style.transform = "rotate(45deg)";
|
||||
box.style.transform = "translateX(100px) scale(2)";
|
||||
|
||||
// ANIMATION & TRANSITION
|
||||
box.style.transition = "all 0.3s ease";
|
||||
box.style.animation = "move 2s linear";
|
||||
|
||||
// INTERACTION
|
||||
box.style.cursor = "pointer";
|
||||
box.style.pointerEvents = "none";
|
||||
// /////////////////////////////////////////////////////>
|
||||
// /////////////////////////////////////////////////////>
|
||||
// CONTENT
|
||||
el.textContent = "Hello"; // plain text
|
||||
el.innerHTML = "<b>Hello</b>"; // HTML content
|
||||
el.innerText = "Hello"; // like textContent but respects line breaks
|
||||
|
||||
// ATTRIBUTES
|
||||
el.id = "myDiv"; // element ID
|
||||
el.className = "box highlight"; // full class string
|
||||
el.classList.add("active"); // add a class
|
||||
el.classList.remove("hidden"); // remove a class
|
||||
el.classList.toggle("open"); // toggle a class
|
||||
el.title = "Tooltip text"; // title attribute
|
||||
el.value = "42"; // input value
|
||||
el.src = "image.png"; // img, video, audio src
|
||||
el.href = "https://example.com"; // anchor href
|
||||
el.alt = "alternative text"; // img alt
|
||||
|
||||
// DOM STRUCTURE
|
||||
el.appendChild(child); // add child
|
||||
el.append(child1, child2); // add multiple children
|
||||
el.prepend(child); // add at start
|
||||
el.remove(); // remove self
|
||||
el.replaceWith(newEl); // replace element
|
||||
el.cloneNode(true); // copy element (deep if true)
|
||||
|
||||
// DATA & CUSTOM
|
||||
el.dataset.id = "123"; // data-id attribute
|
||||
el.dataset.name = "box1"; // data-name attribute
|
||||
|
||||
// EVENTS
|
||||
el.onclick = () => {}; // direct event assignment
|
||||
el.onmouseover = () => {};
|
||||
el.addEventListener("click", () => {}); // preferred
|
||||
el.removeEventListener("click", handler);
|
||||
|
||||
// VISIBILITY & FOCUS
|
||||
el.hidden = true; // hides element
|
||||
el.focus(); // focus element
|
||||
el.blur(); // remove focus
|
||||
el.tabIndex = 0; // make element focusable
|
||||
|
||||
// DIMENSIONS & POSITION (read-only or get)
|
||||
el.clientWidth;
|
||||
el.clientHeight;
|
||||
el.offsetWidth;
|
||||
el.offsetHeight;
|
||||
el.offsetTop;
|
||||
el.offsetLeft;
|
||||
el.scrollWidth;
|
||||
el.scrollHeight;
|
||||
el.scrollTop;
|
||||
el.scrollLeft;
|
||||
|
||||
// OTHER
|
||||
el.checked = true; // checkbox / radio
|
||||
el.selected = true; // option element
|
||||
el.disabled = true; // input/button
|
||||
el.readOnly = true; // input/textarea
|
||||
el.name = "username"; // input / form element
|
||||
el.type = "text"; // input type
|
||||
@@ -0,0 +1,55 @@
|
||||
import fetch from 'node-fetch';
|
||||
import express, { response } from 'express';
|
||||
import cors from 'cors';
|
||||
|
||||
const app = express();
|
||||
const PORT = 3000//process.env.PORT || 3000;
|
||||
|
||||
app.use(express.json());
|
||||
app.use(cors());
|
||||
|
||||
let token;
|
||||
async function set_token()
|
||||
{
|
||||
fetch("https://api.intra.42.fr/oauth/token", {
|
||||
method: "POST",
|
||||
body: "grant_type=client_credentials&client_id=u-s4t2ud-c226cd35cd1ac08a4c6668deee1c64d7d67a13a766aee672acafd4a1522d483c&client_secret=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",
|
||||
headers: {
|
||||
"User-Agent": "agallon",
|
||||
'Content-Type': 'application/x-www-form-urlencoded',}
|
||||
})
|
||||
.then(response => {
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
token = data;
|
||||
setTimeout(set_token, token.expires_in);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching token:', error);
|
||||
});
|
||||
}
|
||||
set_token();
|
||||
|
||||
app.get('/proxy/profile/:login', async (req, res) => {
|
||||
const { login } = req.params;
|
||||
const profileURL = `https://api.intra.42.fr/v2/users/${login}`;
|
||||
try {
|
||||
const response = await fetch(profileURL, {
|
||||
headers: {
|
||||
"Authorization": `Bearer ${token.access_token}`}});
|
||||
console.log(`response.status = ${response.status}`);
|
||||
if (response.status !== 200) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
const data = await response.json();
|
||||
res.status(200).json(data);
|
||||
} catch (error) {
|
||||
console.error('Error fetching profile:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch profile' });
|
||||
}
|
||||
});
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Proxy server running on port ${PORT}`);
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
import {checkIfLoggedIn} from './tools.js';
|
||||
|
||||
export class Header {
|
||||
constructor() {
|
||||
this.obj = document.createElement('div');
|
||||
Object.assign(this.obj.style, {
|
||||
|
||||
});
|
||||
let play = document.createElement('span');
|
||||
let title = document.createElement('span');
|
||||
let login = document.createElement('span');
|
||||
|
||||
play.textContent = "PLAY";
|
||||
if (checkIfLoggedIn())
|
||||
title.textContent = "Welcome back you!";
|
||||
else
|
||||
title.textContent = "Welcome to CAT !";
|
||||
|
||||
this.obj.append(play);
|
||||
this.obj.append(title);
|
||||
this.obj.append(login);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
export class Popup {
|
||||
|
||||
constructor(msg, parent = document.body) {
|
||||
this.msg = msg;
|
||||
this.parent = parent;
|
||||
this.obj = document.createElement('span');
|
||||
|
||||
this.obj.className = "popup";
|
||||
this.obj.textContent = "";
|
||||
this.obj.style.opacity = "0";
|
||||
this.run();
|
||||
}
|
||||
|
||||
async create() {
|
||||
this.parent.appendChild(this.obj);
|
||||
|
||||
this.obj.style.transition = "opacity 0.5s ease";
|
||||
requestAnimationFrame(() => {
|
||||
this.obj.style.opacity = "1";
|
||||
});
|
||||
await new Promise(r => setTimeout(r, 500));
|
||||
}
|
||||
async write(speed = 50) {
|
||||
for (let i = 0; i < this.msg.length; i++) {
|
||||
this.obj.textContent += this.msg[i];
|
||||
await new Promise(r => setTimeout(r, speed));
|
||||
}
|
||||
}
|
||||
async remove() {
|
||||
|
||||
await new Promise(r => setTimeout(r, 2000));
|
||||
this.obj.style.transition = "opacity 0.3s ease";
|
||||
this.obj.style.opacity = "0";
|
||||
await new Promise(r => setTimeout(r, 300));
|
||||
if (this.obj.parentNode) {
|
||||
this.obj.parentNode.removeChild(this.obj);
|
||||
}
|
||||
}
|
||||
async run() {
|
||||
await this.create();
|
||||
await this.write();
|
||||
await this.remove();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { STORAGE_KEYS } from '../../core/config.js';
|
||||
|
||||
export function checkIfLoggedIn() {
|
||||
const token = localStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
|
||||
if (token) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
|
||||
/* ////////////////////////////////////////// */
|
||||
.box {
|
||||
background: #142d4a;
|
||||
height: 200px;
|
||||
aspect-ratio: 1/1;
|
||||
border-radius: 10px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@property --deg {
|
||||
syntax: '<angle>';
|
||||
inherits: true;
|
||||
initial-value: 0deg;
|
||||
}
|
||||
|
||||
.box::before,
|
||||
.box::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: conic-gradient(
|
||||
from var(--deg) at center,
|
||||
#00c3ff,
|
||||
#4d0199,
|
||||
#6300c6,
|
||||
#009dcd
|
||||
);
|
||||
border-radius: inherit;
|
||||
z-index: -2;
|
||||
padding: 2px;
|
||||
animation: autoRotate 2s linear infinite;
|
||||
}
|
||||
.box::after {
|
||||
filter: blur(10px);
|
||||
}
|
||||
@keyframes autoRotate {
|
||||
to{ --deg: 360deg; }
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../game2/game.css" />
|
||||
<link rel="stylesheet" href="./style.css" />
|
||||
<script type="module" src="./script.js"></script>
|
||||
</head>
|
||||
<body style="background-color: black; display: flex; justify-content: center; align-items: center;">
|
||||
<!--
|
||||
<div class="container">
|
||||
<div class="item item-1">Item 1</div>
|
||||
<div class="item item-2">Item 2</div>
|
||||
<div class="item item-3">Item 3</div>
|
||||
</div> -->
|
||||
<div></div>
|
||||
<div class="box"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,17 @@
|
||||
// import { LoginSidebar } from "./loginSidebar.js";
|
||||
import { Sidebar } from "./sidebar.js";
|
||||
import { updateElement } from "./tools.js";
|
||||
|
||||
let b = updateElement({
|
||||
classList: ['container2'],
|
||||
additionalStyles: {
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center'
|
||||
}
|
||||
});
|
||||
new Sidebar();
|
||||
// new LoginSidebar();
|
||||
|
||||
|
||||
// new Sidebar();
|
||||
@@ -0,0 +1,93 @@
|
||||
import { updateElement } from "./tools.js";
|
||||
|
||||
import { windowRegistry } from '../core/windows.js';
|
||||
import { LoginWindow } from '../windows/login.js';
|
||||
import { LogoutWindow } from '../windows/logout.js';
|
||||
import { GlobalChat } from '../windows/global_chat.js';
|
||||
import { AvatarWindow } from '../windows/avatar.js';
|
||||
import { FriendsWindow } from '../windows/friends.js';
|
||||
import { GameRoomWindow } from '../windows/game_room.js';
|
||||
import { StatsWindow } from '../windows/stats.js';
|
||||
|
||||
export class Sidebar {
|
||||
|
||||
/* CONSTURCTOR */
|
||||
constructor(parent = document.body) {
|
||||
this.parent = parent;
|
||||
this.stateopen = 'closed';
|
||||
// this.state = this.checkIfLoggedIn() ? "loggedOut" : "loggedIn";
|
||||
|
||||
this.obj = updateElement({
|
||||
parent: parent,
|
||||
id: `login-wrapper`,
|
||||
classList: [ 'login-wrapper' ],
|
||||
})
|
||||
this.createAllButtons();
|
||||
|
||||
this.handleClickOutside = (event) => {
|
||||
if (this.stateopen === 'open' && !this.obj.contains(event.target)) {
|
||||
this.toggle();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/* toogle menu open / closed */
|
||||
toggle() {
|
||||
this.stateopen = (this.stateopen === 'open') ? 'closed' : 'open';
|
||||
console.log(this.stateopen);
|
||||
if (this.stateopen === 'open') {
|
||||
this.main_button.style.display = 'none';
|
||||
this.menu_buttons.forEach(b => b.style.display = 'block');
|
||||
// ensure only ONE listener exists
|
||||
document.removeEventListener('click', this.handleClickOutside);
|
||||
document.addEventListener('click', this.handleClickOutside);
|
||||
|
||||
}
|
||||
else {
|
||||
this.menu_buttons.forEach(b => b.style.display = 'none');
|
||||
this.main_button.style.display = 'block';
|
||||
document.removeEventListener('click', this.handleClickOutside);
|
||||
}
|
||||
}
|
||||
|
||||
/* create all element, append to div */
|
||||
createAllButtons() {
|
||||
// not-logged closed button
|
||||
this.main_button = updateElement({
|
||||
id: `button-main`,
|
||||
parent: this.obj,
|
||||
textContent: 'LOGIN',
|
||||
classList: [ 'login-button' ],
|
||||
})
|
||||
this.obj.append(this.main_button);
|
||||
this.main_button.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
this.toggle();
|
||||
})
|
||||
|
||||
// menu buttons
|
||||
const items = ['friends', 'chat', 'rooms', 'settings', 'log','logout'];
|
||||
this.menu_buttons = [];
|
||||
|
||||
items.forEach(name => {
|
||||
this[name] = updateElement({
|
||||
id: `button-${name}`,
|
||||
parent: this.obj,
|
||||
textContent: name,
|
||||
classList: ['login-button'],
|
||||
additionalStyles: { display: 'none'}
|
||||
})
|
||||
this.menu_buttons.push(this[name]);
|
||||
this.obj.append(this[name]);
|
||||
})
|
||||
this.loginWindow = new LoginWindow();
|
||||
this.obj.append(this.loginWindow.form);
|
||||
this.loginWindow.form.style.display = 'none';
|
||||
this['log'].addEventListener('click', () => {
|
||||
this.menu_buttons.forEach(b => b.style.display = 'none');
|
||||
this.loginWindow.form.style.display = 'block';
|
||||
})
|
||||
// menu elements
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/* BASE STYLES */
|
||||
:root {
|
||||
--clr-dark: #0f172a;
|
||||
--clr-light: #f1f5f9;
|
||||
--clr-accent: #e11d48;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: 1.6; /* inherited */
|
||||
word-spacing: 1.4px; /* inherited */
|
||||
font-family: "Roboto", sans-serif; /* inherited */
|
||||
color: var(--clr-dark); /* inherited */
|
||||
background-color: var(--clr-light);
|
||||
/* display: flex; */
|
||||
/* justify-content: center; */
|
||||
/* align-items: center; */
|
||||
height: 100vh;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 80%;
|
||||
height: 700px;
|
||||
margin: 0 auto;
|
||||
border: 10px solid var(--clr-dark);
|
||||
}
|
||||
|
||||
.item {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
background-color: #fb7185;
|
||||
padding: 1em;
|
||||
font-weight: 700;
|
||||
color: var(--clr-light);
|
||||
text-align: center;
|
||||
border: 10px solid var(--clr-accent);
|
||||
border-radius: 10px;
|
||||
|
||||
margin-left: -50px
|
||||
}
|
||||
|
||||
/* END OF BASE STYLES */
|
||||
.item-1 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
.container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
|
||||
.container2 {
|
||||
margin: 0 auto;
|
||||
border: 10px solid var(--clr-dark);
|
||||
}
|
||||
/*//////////////////////////////////////////////////////////*/
|
||||
|
||||
|
||||
.button {
|
||||
padding: 10px 18px;
|
||||
font-size: 14px;
|
||||
font-family: inherit;
|
||||
|
||||
color: white;
|
||||
background-color: #3b82f6; /* blue */
|
||||
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.button:hover {
|
||||
background-color: #2563eb;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.button:active {
|
||||
transform: translateY(1px);
|
||||
background-color: #1d4ed8;
|
||||
}
|
||||
.button:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.4);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////*/
|
||||
.login-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 7px;
|
||||
|
||||
background-color: #3b82f6; /* blue */
|
||||
|
||||
padding-right: 75px;
|
||||
padding-bottom: 25px;
|
||||
padding-top: 25px;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.loggin-button {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
border: 5px solid blue;
|
||||
height: 35px;
|
||||
min-width: 50px;
|
||||
|
||||
}
|
||||
/*//////////////////////////////////////////////////////////*/
|
||||
/* LOGIN */
|
||||
|
||||
.login-button {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
background-color: #fb7185;
|
||||
padding: 1em;
|
||||
font-weight: 700;
|
||||
color: var(--clr-light);
|
||||
text-align: center;
|
||||
border: 10px solid var(--clr-accent);
|
||||
border-radius: 10px;
|
||||
|
||||
margin-left: -50px
|
||||
|
||||
}
|
||||
|
||||
.login-element {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
|
||||
export function updateElement({
|
||||
el, // existing element or null to create new
|
||||
parent = document.body,
|
||||
id = null,
|
||||
classList = [], // object like { css - classes to add }
|
||||
textContent = "",
|
||||
additionalStyles = {} // object like { color: 'red', display: 'flex' }
|
||||
} = {}) {
|
||||
// If no element passed, create a div by default
|
||||
if (!el) {
|
||||
el = document.createElement('div');
|
||||
parent.appendChild(el);
|
||||
}
|
||||
|
||||
// Set ID if provided
|
||||
if (id) el.id = id;
|
||||
|
||||
// Manage classes
|
||||
classList.forEach(cls => el.classList.add(cls));
|
||||
|
||||
// Set text content
|
||||
if (textContent !== undefined) el.textContent = textContent;
|
||||
|
||||
// Apply additional styles
|
||||
Object.assign(el.style, additionalStyles);
|
||||
|
||||
return el; // return element for further use
|
||||
}
|
||||
@@ -3,18 +3,20 @@
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
class Duel {
|
||||
constructor(socket, tetrisGame, onStatusChange, onStart) {
|
||||
// ui : { showOverlay, hideOverlay, render, renderOpponent, updateButtons }
|
||||
constructor(socket, tetrisGame, onStatusChange, onStart, ui) {
|
||||
this.socket = socket;
|
||||
this.tetrisGame = tetrisGame;
|
||||
this.onStatusChange = onStatusChange; // (status, opponentName) => void
|
||||
this.onStart = onStart; // () => void — déclenche le début du jeu local
|
||||
this.onStatusChange = onStatusChange;
|
||||
this.onStart = onStart;
|
||||
this.ui = ui;
|
||||
|
||||
this.action_queue = [];
|
||||
this.opponentGrid = this._emptyGrid();
|
||||
this.opponentScore = 0;
|
||||
this.action_queue = [];
|
||||
this.opponentGrid = this._emptyGrid();
|
||||
this.opponentScore = 0;
|
||||
this.opponentShieldActive = false;
|
||||
this.roomCode = null;
|
||||
this.isReady = false;
|
||||
this.roomCode = null;
|
||||
this.isReady = false;
|
||||
|
||||
this._bindSocketEvents();
|
||||
}
|
||||
@@ -34,10 +36,11 @@ class Duel {
|
||||
leave() {
|
||||
if (!this.roomCode) return;
|
||||
this.socket.emit('tetris:leave');
|
||||
this.roomCode = null;
|
||||
this.isReady = false;
|
||||
this.opponentGrid = this._emptyGrid();
|
||||
this.opponentScore = 0;
|
||||
this.roomCode = null;
|
||||
this.isReady = false;
|
||||
this.opponentGrid = this._emptyGrid();
|
||||
this.opponentScore = 0;
|
||||
this.opponentShieldActive = false;
|
||||
}
|
||||
|
||||
// ─── Hooks appelés par tetris.js ──────────
|
||||
@@ -49,9 +52,7 @@ class Duel {
|
||||
|
||||
onLocalLinesCleared(count, holeCol) {
|
||||
if (!this.isReady) return;
|
||||
const garbageLines = [];
|
||||
for (let i = 0; i < count; i++)
|
||||
garbageLines.push(this._buildGarbageLine(holeCol));
|
||||
const garbageLines = Array.from({ length: count }, () => this._buildGarbageLine(holeCol));
|
||||
this.socket.emit('tetris:lines-cleared', { count, holeCol, garbageLines });
|
||||
}
|
||||
|
||||
@@ -63,11 +64,8 @@ class Duel {
|
||||
|
||||
onLocalShieldChanged(event) {
|
||||
if (!this.isReady) return;
|
||||
if (event === 'activated') {
|
||||
this.socket.emit('tetris:shield-activated');
|
||||
} else if (event === 'deactivated') {
|
||||
this.socket.emit('tetris:shield-deactivated');
|
||||
}
|
||||
if (event === 'activated') this.socket.emit('tetris:shield-activated');
|
||||
else if (event === 'deactivated') this.socket.emit('tetris:shield-deactivated');
|
||||
}
|
||||
|
||||
endDuel() {
|
||||
@@ -80,8 +78,7 @@ class Duel {
|
||||
|
||||
synchronize_game() {
|
||||
while (this.action_queue.length > 0) {
|
||||
const action = this.action_queue.shift();
|
||||
this._processAction(action);
|
||||
this._processAction(this.action_queue.shift());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +88,7 @@ class Duel {
|
||||
this.opponentGrid = action.grid;
|
||||
this.opponentScore = action.score;
|
||||
document.getElementById('opponent-score').textContent = action.score;
|
||||
renderOpponent(this.opponentGrid);
|
||||
this.ui.renderOpponent(this.opponentGrid, this.opponentShieldActive);
|
||||
break;
|
||||
|
||||
case 'LINES_CLEARED':
|
||||
@@ -99,7 +96,7 @@ class Duel {
|
||||
break;
|
||||
|
||||
case 'OPPONENT_GAME_OVER':
|
||||
showOverlay('YOU WIN', action.score);
|
||||
this.ui.showOverlay('YOU WIN', action.score);
|
||||
this.endDuel();
|
||||
break;
|
||||
|
||||
@@ -159,22 +156,22 @@ class Duel {
|
||||
|
||||
this.socket.on('tetris:pause', () => {
|
||||
this.tetrisGame.pause();
|
||||
updateButtons();
|
||||
if (this.tetrisGame.isPaused) showOverlay('PAUSE');
|
||||
else hideOverlay();
|
||||
this.ui.updateButtons();
|
||||
if (this.tetrisGame.isPaused) this.ui.showOverlay('PAUSE');
|
||||
else this.ui.hideOverlay();
|
||||
});
|
||||
|
||||
this.socket.on('tetris:stop', () => {
|
||||
this.tetrisGame.stop();
|
||||
updateButtons();
|
||||
render();
|
||||
showOverlay('STOPPED');
|
||||
this.ui.updateButtons();
|
||||
this.ui.render();
|
||||
this.ui.showOverlay('STOPPED');
|
||||
});
|
||||
|
||||
this.socket.on('tetris:settings', (data) => {
|
||||
document.getElementById('input-ttd').value = data.timeToDown;
|
||||
document.getElementById('input-hardening').value = data.hardening;
|
||||
document.getElementById('input-decrement').value = data.decrementTTD;
|
||||
document.getElementById('input-ttd').value = data.timeToDown;
|
||||
document.getElementById('input-hardening').value = data.hardening;
|
||||
document.getElementById('input-decrement').value = data.decrementTTD;
|
||||
this.tetrisGame.configure(data);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// ─────────────────────────────────────────────
|
||||
// EFFETS VISUELS : SCALING RESPONSIVE + MATRIX RAIN
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
// ── Responsive scaling ──
|
||||
(function() {
|
||||
const container = document.getElementById('scale-container');
|
||||
const NAT_W = 640;
|
||||
const NAT_H = 1020;
|
||||
|
||||
function resize() {
|
||||
const s = Math.min(window.innerWidth / NAT_W, window.innerHeight / NAT_H);
|
||||
container.style.transform = 'scale(' + s + ')';
|
||||
container.style.transformOrigin = 'top center';
|
||||
container.style.marginBottom = ((s - 1) * NAT_H) + 'px';
|
||||
}
|
||||
|
||||
resize();
|
||||
window.addEventListener('resize', resize);
|
||||
})();
|
||||
|
||||
// ── Matrix rain ──
|
||||
(function() {
|
||||
const canvas = document.getElementById('matrix-bg');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const chars = 'アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲン0123456789ABCDEF>_{}[]|\\/#@$%^&*01';
|
||||
const fs = 14;
|
||||
let drops = [];
|
||||
|
||||
function resize() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; }
|
||||
function initDrops() { drops = Array(Math.floor(canvas.width / fs)).fill(1); }
|
||||
|
||||
resize();
|
||||
initDrops();
|
||||
window.addEventListener('resize', () => { resize(); initDrops(); });
|
||||
|
||||
setInterval(function() {
|
||||
ctx.fillStyle = 'rgba(0,5,0,0.05)';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.font = fs + 'px monospace';
|
||||
for (let i = 0; i < drops.length; i++) {
|
||||
const ch = chars[Math.floor(Math.random() * chars.length)];
|
||||
ctx.fillStyle = drops[i] * fs < 50 ? '#aaffaa' : '#00ff41';
|
||||
ctx.fillText(ch, i * fs, drops[i] * fs);
|
||||
if (drops[i] * fs > canvas.height && Math.random() > 0.975) drops[i] = 0;
|
||||
drops[i]++;
|
||||
}
|
||||
}, 40);
|
||||
})();
|
||||
@@ -0,0 +1,124 @@
|
||||
// ─────────────────────────────────────────────
|
||||
// LEADERBOARDS & HISTORIQUE
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
function escapeHtml(str) {
|
||||
return String(str).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
||||
}
|
||||
|
||||
// ── Historique ───────────────────────────────
|
||||
|
||||
async function loadGameHistory() {
|
||||
const token = localStorage.getItem('auth_token');
|
||||
if (!token) return;
|
||||
try {
|
||||
const res = await fetch('/api/stats/tetris/history', {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
});
|
||||
if (!res.ok) return;
|
||||
renderGameHistory(await res.json());
|
||||
} catch (err) {
|
||||
console.error('Erreur chargement historique:', err);
|
||||
}
|
||||
}
|
||||
|
||||
function renderGameHistory(history) {
|
||||
const tbody = document.getElementById('lb-history-body');
|
||||
if (!tbody) return;
|
||||
if (!history.length) {
|
||||
tbody.innerHTML = '<tr><td colspan="5">Aucune partie jouée</td></tr>';
|
||||
return;
|
||||
}
|
||||
tbody.innerHTML = history.map((entry, i) => {
|
||||
const date = new Date(entry.played_at).toLocaleDateString('fr-FR', {
|
||||
day: '2-digit', month: '2-digit', year: '2-digit',
|
||||
hour: '2-digit', minute: '2-digit'
|
||||
});
|
||||
const type = entry.game_type === 'duel' ? 'Duel' : 'Solo';
|
||||
let resultHtml = '—';
|
||||
if (entry.result === 'win') resultHtml = '<span class="hist-win">Victoire</span>';
|
||||
if (entry.result === 'loss') resultHtml = '<span class="hist-loss">Défaite</span>';
|
||||
return `<tr>
|
||||
<td>${i + 1}</td>
|
||||
<td>${date}</td>
|
||||
<td>${type}</td>
|
||||
<td>${entry.score}</td>
|
||||
<td>${resultHtml}</td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// ── Classements ──────────────────────────────
|
||||
|
||||
async function loadLeaderboards() {
|
||||
const token = localStorage.getItem('auth_token');
|
||||
if (!token) return;
|
||||
const headers = { 'Authorization': `Bearer ${token}` };
|
||||
try {
|
||||
const [scoresRes, winsRes, meRes, rankScoreRes, rankWinsRes] = await Promise.all([
|
||||
fetch('/api/stats/tetris/leaderboard/score', { headers }),
|
||||
fetch('/api/stats/tetris/leaderboard/wins', { headers }),
|
||||
fetch('/api/stats/me', { headers }),
|
||||
fetch('/api/stats/tetris/rank/score', { headers }),
|
||||
fetch('/api/stats/tetris/rank/wins', { headers })
|
||||
]);
|
||||
|
||||
const me = meRes.ok ? await meRes.json() : null;
|
||||
const rankScore = rankScoreRes.ok ? (await rankScoreRes.json()).rank : null;
|
||||
const rankWins = rankWinsRes.ok ? (await rankWinsRes.json()).rank : null;
|
||||
|
||||
if (scoresRes.ok) renderLeaderboard('lb-scores-body', await scoresRes.json(), ['tetris_best_score', 'tetris_games_played'], me, rankScore);
|
||||
if (winsRes.ok) renderLeaderboard('lb-wins-body', await winsRes.json(), ['tetris_wins', 'tetris_games_played'], me, rankWins);
|
||||
} catch (err) {
|
||||
console.error('Erreur chargement leaderboards:', err);
|
||||
}
|
||||
}
|
||||
|
||||
function renderLeaderboard(tbodyId, rows, [col1, col2], me, myRank) {
|
||||
const tbody = document.getElementById(tbodyId);
|
||||
if (!tbody) return;
|
||||
if (!rows.length && !me) {
|
||||
tbody.innerHTML = '<tr><td colspan="4">Aucun résultat</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
const myUsername = me?.username;
|
||||
const inTop = rows.some(r => r.username === myUsername);
|
||||
|
||||
let html = rows.map((r, i) => {
|
||||
const isMe = r.username === myUsername;
|
||||
return `<tr class="${isMe ? 'lb-me' : ''}">
|
||||
<td>${i + 1}</td>
|
||||
<td>${escapeHtml(r.username)}${isMe ? ' <span class="lb-you">(vous)</span>' : ''}</td>
|
||||
<td>${r[col1] ?? 0}</td>
|
||||
<td>${r[col2] ?? 0}</td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
|
||||
if (!inTop && me && myRank !== null) {
|
||||
html += `<tr class="lb-separator"><td colspan="4">· · ·</td></tr>`;
|
||||
html += `<tr class="lb-me">
|
||||
<td>${myRank}</td>
|
||||
<td>${escapeHtml(myUsername)} <span class="lb-you">(vous)</span></td>
|
||||
<td>${me[col1] ?? 0}</td>
|
||||
<td>${me[col2] ?? 0}</td>
|
||||
</tr>`;
|
||||
}
|
||||
|
||||
tbody.innerHTML = html || '<tr><td colspan="4">Aucun résultat</td></tr>';
|
||||
}
|
||||
|
||||
// ── Tabs ─────────────────────────────────────
|
||||
|
||||
document.querySelectorAll('.lb-tab').forEach(tab => {
|
||||
tab.addEventListener('click', () => {
|
||||
document.querySelectorAll('.lb-tab').forEach(t => t.classList.remove('lb-tab--active'));
|
||||
document.querySelectorAll('.lb-content').forEach(c => c.classList.remove('lb-content--active'));
|
||||
tab.classList.add('lb-tab--active');
|
||||
document.getElementById(`lb-${tab.dataset.tab}`).classList.add('lb-content--active');
|
||||
if (tab.dataset.tab === 'history') loadGameHistory();
|
||||
});
|
||||
});
|
||||
|
||||
loadLeaderboards();
|
||||
loadGameHistory();
|
||||
@@ -61,17 +61,14 @@ function drawCell(ctx, x, y, colorIndex, size) {
|
||||
const color = COLORS[colorIndex];
|
||||
ctx.fillStyle = color;
|
||||
ctx.fillRect(x * size + p, y * size + p, size - p * 2, size - p * 2);
|
||||
// Glow inner
|
||||
ctx.shadowColor = color;
|
||||
ctx.shadowBlur = 6;
|
||||
ctx.fillStyle = color;
|
||||
ctx.fillRect(x * size + p + 2, y * size + p + 2, size - p * 2 - 4, size - p * 2 - 4);
|
||||
ctx.shadowBlur = 0;
|
||||
// Highlight top/left
|
||||
ctx.fillStyle = currentTheme.highlight;
|
||||
ctx.fillRect(x * size + p, y * size + p, size - p * 2, 2);
|
||||
ctx.fillRect(x * size + p, y * size + p, 2, size - p * 2);
|
||||
// Shadow bottom/right
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.5)';
|
||||
ctx.fillRect(x * size + p, (y + 1) * size - p - 2, size - p * 2, 2);
|
||||
ctx.fillRect((x + 1) * size - p - 2, y * size + p, 2, size - p * 2);
|
||||
@@ -150,8 +147,10 @@ function _drawShieldOverlay(ctx, w, h, alpha) {
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function render() {
|
||||
// Grille principale
|
||||
// ── Rendu joueur local ────────────────────────────────────────────────────────
|
||||
// Prend l'objet game explicitement — aucun accès à des globaux externes.
|
||||
|
||||
function render(game) {
|
||||
clearCanvas(ctxMain, 300, 600);
|
||||
drawGridLines(ctxMain, 10, 20, CELL);
|
||||
|
||||
@@ -160,7 +159,6 @@ function render() {
|
||||
if (game.grid[y][x] !== 0)
|
||||
drawCell(ctxMain, x, y, game.grid[y][x], CELL);
|
||||
|
||||
// Ghost + pièce courante
|
||||
if (game.currentPiece) {
|
||||
drawGhost(ctxMain, game.currentPiece, game.grid);
|
||||
const { x, y } = game.currentPiece.getPosition();
|
||||
@@ -172,20 +170,16 @@ function render() {
|
||||
drawCell(ctxMain, x + col, y + row, color, CELL);
|
||||
}
|
||||
|
||||
// Shield overlay (bordure cyan pulsée)
|
||||
if (game.shieldActive) {
|
||||
const pulse = 0.6 + 0.4 * Math.sin(Date.now() / 150);
|
||||
_drawShieldOverlay(ctxMain, 300, 600, pulse);
|
||||
}
|
||||
|
||||
// Panneaux miniatures
|
||||
drawMiniPiece(ctxNext, game.nextPiece, 100, 80);
|
||||
drawMiniPiece(ctxHold, game.storedPiece, 100, 80);
|
||||
|
||||
// Score
|
||||
document.getElementById('score-display').textContent = game.score;
|
||||
|
||||
// Shield status UI
|
||||
const shieldEl = document.getElementById('shield-status-display');
|
||||
const shieldBar = document.getElementById('shield-bar');
|
||||
if (shieldEl) {
|
||||
@@ -207,29 +201,27 @@ function render() {
|
||||
}
|
||||
}
|
||||
|
||||
function renderOpponent(opponentGrid) {
|
||||
// ── Rendu adversaire ─────────────────────────────────────────────────────────
|
||||
// Prend grid et shieldActive explicitement — aucun accès à l'objet duel global.
|
||||
|
||||
function renderOpponent(grid, shieldActive) {
|
||||
clearCanvas(ctxOpponent, 300, 600);
|
||||
drawGridLines(ctxOpponent, 10, 20, CELL);
|
||||
for (let y = 0; y < opponentGrid.length; y++)
|
||||
for (let x = 0; x < opponentGrid[y].length; x++)
|
||||
if (opponentGrid[y][x] !== 0)
|
||||
drawCell(ctxOpponent, x, y, opponentGrid[y][x], CELL);
|
||||
for (let y = 0; y < grid.length; y++)
|
||||
for (let x = 0; x < grid[y].length; x++)
|
||||
if (grid[y][x] !== 0)
|
||||
drawCell(ctxOpponent, x, y, grid[y][x], CELL);
|
||||
|
||||
// Shield overlay adversaire
|
||||
if (typeof duel !== 'undefined' && duel && duel.opponentShieldActive) {
|
||||
if (shieldActive) {
|
||||
const pulse = 0.6 + 0.4 * Math.sin(Date.now() / 150);
|
||||
_drawShieldOverlay(ctxOpponent, 300, 600, pulse);
|
||||
}
|
||||
|
||||
// Indicateur HTML adversaire
|
||||
const oppShieldEl = document.getElementById('opponent-shield-indicator');
|
||||
if (oppShieldEl) {
|
||||
const active = typeof duel !== 'undefined' && duel && duel.opponentShieldActive;
|
||||
oppShieldEl.style.display = active ? 'block' : 'none';
|
||||
}
|
||||
if (oppShieldEl) oppShieldEl.style.display = shieldActive ? 'block' : 'none';
|
||||
}
|
||||
|
||||
// Restore saved theme
|
||||
// Restaure le thème sauvegardé
|
||||
(function() {
|
||||
const saved = localStorage.getItem('tetris-theme');
|
||||
if (saved && THEMES[saved]) setColorTheme(saved);
|
||||
@@ -15,10 +15,9 @@
|
||||
|
||||
<h1 data-text="TETRIS">TETRIS<span class="cursor">_</span></h1>
|
||||
|
||||
<!-- Bouton home -->
|
||||
<a id="btn-home" href="/">Home</a>
|
||||
<a id="btn-home" href="/">Home</a>
|
||||
|
||||
<!-- Panneau de connexion duel -->
|
||||
<!-- Panneau duel -->
|
||||
<div id="duel-panel">
|
||||
<span class="settings-title">Duel</span>
|
||||
<div class="duel-row">
|
||||
@@ -40,7 +39,7 @@
|
||||
<div id="local-section">
|
||||
<div id="app">
|
||||
|
||||
<!-- Colonne gauche : Hold + Score + Boutons + Settings -->
|
||||
<!-- Colonne gauche : Hold + Score + Boutons + Paramètres -->
|
||||
<div id="left-column">
|
||||
<div class="panel">
|
||||
<div class="panel-title">Hold</div>
|
||||
@@ -64,16 +63,16 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Panneau de configuration -->
|
||||
<!-- Paramètres -->
|
||||
<div id="settings-panel">
|
||||
<div class="settings-title">Paramètres</div>
|
||||
<div class="settings-row">
|
||||
<label>Couleur</label>
|
||||
<div class="theme-btns">
|
||||
<button class="theme-btn active" data-theme="green" onclick="setColorTheme('green')" title="Vert"></button>
|
||||
<button class="theme-btn" data-theme="red" onclick="setColorTheme('red')" title="Rouge"></button>
|
||||
<button class="theme-btn" data-theme="yellow" onclick="setColorTheme('yellow')" title="Jaune"></button>
|
||||
<button class="theme-btn" data-theme="blue" onclick="setColorTheme('blue')" title="Bleu"></button>
|
||||
<button class="theme-btn active" data-theme="green" title="Vert"></button>
|
||||
<button class="theme-btn" data-theme="red" title="Rouge"></button>
|
||||
<button class="theme-btn" data-theme="yellow" title="Jaune"></button>
|
||||
<button class="theme-btn" data-theme="blue" title="Bleu"></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-row">
|
||||
@@ -151,34 +150,22 @@
|
||||
|
||||
<div id="lb-scores" class="lb-content lb-content--active">
|
||||
<table class="lb-table">
|
||||
<thead>
|
||||
<tr><th>#</th><th>Joueur</th><th>Meilleur score</th><th>Parties</th></tr>
|
||||
</thead>
|
||||
<tbody id="lb-scores-body">
|
||||
<tr><td colspan="4">Chargement…</td></tr>
|
||||
</tbody>
|
||||
<thead><tr><th>#</th><th>Joueur</th><th>Meilleur score</th><th>Parties</th></tr></thead>
|
||||
<tbody id="lb-scores-body"><tr><td colspan="4">Chargement…</td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div id="lb-wins" class="lb-content">
|
||||
<table class="lb-table">
|
||||
<thead>
|
||||
<tr><th>#</th><th>Joueur</th><th>Victoires</th><th>Parties</th></tr>
|
||||
</thead>
|
||||
<tbody id="lb-wins-body">
|
||||
<tr><td colspan="4">Chargement…</td></tr>
|
||||
</tbody>
|
||||
<thead><tr><th>#</th><th>Joueur</th><th>Victoires</th><th>Parties</th></tr></thead>
|
||||
<tbody id="lb-wins-body"><tr><td colspan="4">Chargement…</td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div id="lb-history" class="lb-content">
|
||||
<table class="lb-table">
|
||||
<thead>
|
||||
<tr><th>#</th><th>Date</th><th>Type</th><th>Score</th><th>Résultat</th></tr>
|
||||
</thead>
|
||||
<tbody id="lb-history-body">
|
||||
<tr><td colspan="5">Chargement…</td></tr>
|
||||
</tbody>
|
||||
<thead><tr><th>#</th><th>Date</th><th>Type</th><th>Score</th><th>Résultat</th></tr></thead>
|
||||
<tbody id="lb-history-body"><tr><td colspan="5">Chargement…</td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -190,59 +177,9 @@
|
||||
<script src="tetris.js"></script>
|
||||
<script src="renderer.js"></script>
|
||||
<script src="duel.js"></script>
|
||||
<script src="leaderboard.js"></script>
|
||||
<script src="ui.js"></script>
|
||||
<script src="effects.js"></script>
|
||||
|
||||
<script>
|
||||
// ── Responsive scaling ──────────────────────────
|
||||
(function() {
|
||||
const container = document.getElementById('scale-container');
|
||||
// Dimensions naturelles du contenu (single-player)
|
||||
const NAT_W = 640;
|
||||
const NAT_H = 1020;
|
||||
|
||||
function resize() {
|
||||
const s = Math.min(
|
||||
window.innerWidth / NAT_W,
|
||||
window.innerHeight / NAT_H
|
||||
);
|
||||
container.style.transform = 'scale(' + s + ')';
|
||||
container.style.transformOrigin = 'top center';
|
||||
// Compense l'espace de layout non affecté par transform
|
||||
container.style.marginBottom = ((s - 1) * NAT_H) + 'px';
|
||||
}
|
||||
|
||||
resize();
|
||||
window.addEventListener('resize', resize);
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script>
|
||||
// ── Matrix rain ──────────────────────────────────
|
||||
(function() {
|
||||
const canvas = document.getElementById('matrix-bg');
|
||||
const ctx = canvas.getContext('2d');
|
||||
function resize() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; }
|
||||
resize();
|
||||
window.addEventListener('resize', resize);
|
||||
const chars = 'アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲン0123456789ABCDEF>_{}[]|\\/#@$%^&*01';
|
||||
const fs = 14;
|
||||
let drops = [];
|
||||
function initDrops() { drops = Array(Math.floor(canvas.width / fs)).fill(1); }
|
||||
initDrops();
|
||||
window.addEventListener('resize', initDrops);
|
||||
setInterval(function() {
|
||||
ctx.fillStyle = 'rgba(0,5,0,0.05)';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.font = fs + 'px monospace';
|
||||
for (let i = 0; i < drops.length; i++) {
|
||||
const ch = chars[Math.floor(Math.random() * chars.length)];
|
||||
ctx.fillStyle = drops[i] * fs < 50 ? '#aaffaa' : '#00ff41';
|
||||
ctx.fillText(ch, i * fs, drops[i] * fs);
|
||||
if (drops[i] * fs > canvas.height && Math.random() > 0.975) drops[i] = 0;
|
||||
drops[i]++;
|
||||
}
|
||||
}, 40);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,265 @@
|
||||
// ─────────────────────────────────────────────
|
||||
// UI — Contrôles, socket, duel, matchmaking
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
// ── Références DOM ───────────────────────────
|
||||
|
||||
const btnStart = document.getElementById('btn-start');
|
||||
const btnPause = document.getElementById('btn-pause');
|
||||
const btnStop = document.getElementById('btn-stop');
|
||||
const btnRestart = document.getElementById('btn-restart');
|
||||
const overlay = document.getElementById('overlay');
|
||||
const inputTTD = document.getElementById('input-ttd');
|
||||
const inputHardening = document.getElementById('input-hardening');
|
||||
const inputDecrement = document.getElementById('input-decrement');
|
||||
|
||||
const btnJoinDuel = document.getElementById('btn-join-duel');
|
||||
const btnLeaveDuel = document.getElementById('btn-leave-duel');
|
||||
const inputRoomCode = document.getElementById('input-room-code');
|
||||
const duelStatusEl = document.getElementById('duel-status');
|
||||
const opponentSection = document.getElementById('opponent-section');
|
||||
|
||||
const btnMatchmaking = document.getElementById('btn-matchmaking');
|
||||
const btnMatchmakingCancel = document.getElementById('btn-matchmaking-cancel');
|
||||
const matchmakingStatusEl = document.getElementById('matchmaking-status');
|
||||
|
||||
// ── Overlay ──────────────────────────────────
|
||||
|
||||
function showOverlay(title, score) {
|
||||
document.getElementById('overlay-title').textContent = title;
|
||||
document.getElementById('overlay-score').textContent = score !== undefined ? `Score : ${score}` : '';
|
||||
overlay.classList.add('visible');
|
||||
}
|
||||
|
||||
function hideOverlay() {
|
||||
overlay.classList.remove('visible');
|
||||
}
|
||||
|
||||
// ── Boutons ──────────────────────────────────
|
||||
|
||||
function updateButtons() {
|
||||
btnStart.disabled = game.isRunning;
|
||||
btnPause.disabled = !game.isRunning;
|
||||
btnStop.disabled = !game.isRunning;
|
||||
btnPause.textContent = game.isPaused ? 'Resume' : 'Pause';
|
||||
inputTTD.disabled = game.isRunning;
|
||||
inputHardening.disabled = game.isRunning;
|
||||
inputDecrement.disabled = game.isRunning;
|
||||
}
|
||||
|
||||
// ── Socket ───────────────────────────────────
|
||||
|
||||
const socket = io({
|
||||
auth: { token: localStorage.getItem('auth_token') },
|
||||
reconnection: true,
|
||||
reconnectionAttempts: 5,
|
||||
reconnectionDelay: 1000,
|
||||
transports: ['websocket', 'polling']
|
||||
});
|
||||
|
||||
// ── Duel ─────────────────────────────────────
|
||||
|
||||
let duel = null;
|
||||
|
||||
// Callbacks passés au Duel pour qu'il pilote l'UI sans accéder à des globaux.
|
||||
function _makeDuelUI() {
|
||||
return {
|
||||
showOverlay,
|
||||
hideOverlay,
|
||||
updateButtons,
|
||||
render: () => render(game),
|
||||
renderOpponent: (grid, shieldActive) => renderOpponent(grid, shieldActive),
|
||||
};
|
||||
}
|
||||
|
||||
function updateDuelStatus(status, opponentName) {
|
||||
duelStatusEl.className = '';
|
||||
if (status === 'waiting') {
|
||||
duelStatusEl.textContent = "En attente d'un adversaire…";
|
||||
duelStatusEl.classList.add('waiting');
|
||||
opponentSection.classList.remove('visible');
|
||||
} else if (status === 'ready') {
|
||||
duelStatusEl.textContent = `Prêt — ${opponentName}`;
|
||||
duelStatusEl.classList.add('ready');
|
||||
opponentSection.classList.add('visible');
|
||||
if (duel) duel.hideOpponentOverlay();
|
||||
const grid = duel ? duel.opponentGrid : Array.from({ length: 20 }, () => Array(10).fill(0));
|
||||
const shieldActive = duel ? duel.opponentShieldActive : false;
|
||||
renderOpponent(grid, shieldActive);
|
||||
} else {
|
||||
duelStatusEl.textContent = '—';
|
||||
opponentSection.classList.remove('visible');
|
||||
}
|
||||
}
|
||||
|
||||
function startLocalGame() {
|
||||
hideOverlay();
|
||||
game.start();
|
||||
updateButtons();
|
||||
render(game);
|
||||
}
|
||||
|
||||
// Crée un Duel et rejoint la salle — mutualisé entre le bouton et le matchmaking.
|
||||
function _joinDuelRoom(code) {
|
||||
if (duel) duel.leave();
|
||||
if (game.isRunning) { game.stop(); hideOverlay(); render(game); updateButtons(); }
|
||||
duel = new Duel(socket, game, updateDuelStatus, startLocalGame, _makeDuelUI());
|
||||
duel.join(code);
|
||||
btnJoinDuel.disabled = true;
|
||||
btnLeaveDuel.disabled = false;
|
||||
inputRoomCode.disabled = true;
|
||||
updateDuelStatus('waiting', null);
|
||||
}
|
||||
|
||||
btnJoinDuel.addEventListener('click', () => {
|
||||
const code = inputRoomCode.value.trim().toUpperCase();
|
||||
if (!code) return;
|
||||
_joinDuelRoom(code);
|
||||
});
|
||||
|
||||
btnLeaveDuel.addEventListener('click', () => {
|
||||
if (duel) { duel.leave(); duel = null; }
|
||||
btnJoinDuel.disabled = false;
|
||||
btnLeaveDuel.disabled = true;
|
||||
inputRoomCode.disabled = false;
|
||||
updateDuelStatus(null, null);
|
||||
});
|
||||
|
||||
// ── Matchmaking ──────────────────────────────
|
||||
|
||||
btnMatchmaking.addEventListener('click', () => {
|
||||
socket.emit('tetris:matchmaking-join');
|
||||
btnMatchmaking.disabled = true;
|
||||
btnMatchmakingCancel.disabled = false;
|
||||
btnJoinDuel.disabled = true;
|
||||
matchmakingStatusEl.textContent = 'Recherche en cours…';
|
||||
matchmakingStatusEl.className = 'waiting';
|
||||
});
|
||||
|
||||
btnMatchmakingCancel.addEventListener('click', () => {
|
||||
socket.emit('tetris:matchmaking-leave');
|
||||
btnMatchmaking.disabled = false;
|
||||
btnMatchmakingCancel.disabled = true;
|
||||
btnJoinDuel.disabled = false;
|
||||
matchmakingStatusEl.textContent = '';
|
||||
});
|
||||
|
||||
socket.on('tetris:matchmaking-status', (data) => {
|
||||
if (data.status === 'searching') {
|
||||
matchmakingStatusEl.textContent = `Recherche… (${data.position} joueur(s) en attente)`;
|
||||
} else if (data.status === 'idle') {
|
||||
matchmakingStatusEl.textContent = '';
|
||||
btnMatchmaking.disabled = false;
|
||||
btnMatchmakingCancel.disabled = true;
|
||||
btnJoinDuel.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('tetris:matched', (data) => {
|
||||
matchmakingStatusEl.textContent = `Adversaire trouvé : ${data.opponent} !`;
|
||||
matchmakingStatusEl.className = 'ready';
|
||||
btnMatchmaking.disabled = false;
|
||||
btnMatchmakingCancel.disabled = true;
|
||||
inputRoomCode.value = data.roomCode;
|
||||
_joinDuelRoom(data.roomCode);
|
||||
});
|
||||
|
||||
// ── Jeu ──────────────────────────────────────
|
||||
|
||||
function saveTetrisScore(score) {
|
||||
const token = localStorage.getItem('auth_token');
|
||||
if (!token) return;
|
||||
fetch('/api/stats/tetris/score', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
|
||||
body: JSON.stringify({ score })
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => { if (data.bestScore !== undefined) console.log('Meilleur score tetris:', data.bestScore); })
|
||||
.catch(err => console.error('Erreur sauvegarde score tetris:', err));
|
||||
}
|
||||
|
||||
const game = new Tetris(
|
||||
// onRender
|
||||
() => {
|
||||
if (duel) duel.synchronize_game();
|
||||
render(game);
|
||||
updateButtons();
|
||||
},
|
||||
// onGameOver
|
||||
(score, validBlock) => {
|
||||
if (duel && duel.isReady) duel.onLocalGameOver(score, validBlock);
|
||||
else saveTetrisScore(score);
|
||||
render(game);
|
||||
updateButtons();
|
||||
showOverlay('GAME OVER', score);
|
||||
loadLeaderboards();
|
||||
loadGameHistory();
|
||||
},
|
||||
// onBlockPlaced
|
||||
(grid) => { if (duel) duel.onLocalBlockPlaced(grid, game.score); },
|
||||
// onLinesCleared
|
||||
(count, holeCol) => { if (duel) duel.onLocalLinesCleared(count, holeCol); },
|
||||
// onShieldChanged
|
||||
(event) => { if (duel) duel.onLocalShieldChanged(event); }
|
||||
);
|
||||
|
||||
// ── Boutons de contrôle ──────────────────────
|
||||
|
||||
btnStart.addEventListener('click', () => {
|
||||
if (duel && duel.isReady) duel.startDuel();
|
||||
else startLocalGame();
|
||||
});
|
||||
|
||||
btnPause.addEventListener('click', () => {
|
||||
if (duel && duel.isReady) {
|
||||
duel.togglePause();
|
||||
} else {
|
||||
game.pause();
|
||||
updateButtons();
|
||||
if (game.isPaused) showOverlay('PAUSE');
|
||||
else hideOverlay();
|
||||
}
|
||||
});
|
||||
|
||||
btnStop.addEventListener('click', () => {
|
||||
if (duel && duel.isReady) {
|
||||
duel.stop();
|
||||
} else {
|
||||
game.stop();
|
||||
updateButtons();
|
||||
render(game);
|
||||
showOverlay('STOPPED');
|
||||
}
|
||||
});
|
||||
|
||||
if (btnRestart) {
|
||||
btnRestart.addEventListener('click', () => {
|
||||
if (duel && duel.isReady) return;
|
||||
game.restart();
|
||||
updateButtons();
|
||||
render(game);
|
||||
});
|
||||
}
|
||||
|
||||
// ── Paramètres ───────────────────────────────
|
||||
|
||||
function applySettings() {
|
||||
const settings = {
|
||||
timeToDown: parseInt(inputTTD.value, 10),
|
||||
hardening: parseInt(inputHardening.value, 10),
|
||||
decrementTTD: parseInt(inputDecrement.value, 10),
|
||||
};
|
||||
game.configure(settings);
|
||||
if (duel && duel.isReady) duel.syncSettings(settings);
|
||||
}
|
||||
|
||||
inputTTD.addEventListener('change', applySettings);
|
||||
inputHardening.addEventListener('change', applySettings);
|
||||
inputDecrement.addEventListener('change', applySettings);
|
||||
|
||||
// ── Thème ────────────────────────────────────
|
||||
|
||||
document.querySelectorAll('.theme-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => setColorTheme(btn.dataset.theme));
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
// render in color the text of all .multicolor
|
||||
export function colorizeText() {
|
||||
|
||||
const elements = document.querySelectorAll(".multicolor");
|
||||
|
||||
const colorizeText = (el) => {
|
||||
const text = el.textContent;
|
||||
el.innerHTML = "";
|
||||
|
||||
const baseHue = Math.random() * 360;
|
||||
|
||||
// 🎲 random step = makes rainbow "scrambled"
|
||||
const step = (Math.random() * 60) + 10; // 10 → 70
|
||||
|
||||
// 🎲 random direction (left or right rainbow)
|
||||
const direction = Math.random() < 0.5 ? 1 : -1;
|
||||
|
||||
[...text].forEach((char, i) => {
|
||||
const span = document.createElement("span");
|
||||
span.textContent = char;
|
||||
|
||||
const hue = baseHue + (i * step * direction);
|
||||
|
||||
span.style.color = `hsl(${hue}, 90%, 60%)`;
|
||||
|
||||
span.style.textShadow = `1px 1px 0 rgba(0,0,0,0.3)`;
|
||||
|
||||
el.appendChild(span);
|
||||
});
|
||||
};
|
||||
elements.forEach(colorizeText);
|
||||
}
|
||||
@@ -2,13 +2,14 @@
|
||||
* Application entry point
|
||||
* Initializes windows and handles menu interactions
|
||||
*/
|
||||
import { windowRegistry } from './windows.js';
|
||||
import { LoginWindow } from './login.js';
|
||||
import { GlobalChat } from './global_chat.js';
|
||||
import { AvatarWindow } from './avatar.js';
|
||||
import { FriendsWindow } from './friends.js';
|
||||
import { GameRoomWindow } from './game_room.js';
|
||||
import { StatsWindow } from './stats.js';
|
||||
import { windowRegistry } from '../core/windows.js';
|
||||
import { LoginWindow } from '../windows/login.js';
|
||||
import { LogoutWindow } from '../windows/logout.js';
|
||||
import { GlobalChat } from '../windows/global_chat.js';
|
||||
import { AvatarWindow } from '../windows/avatar.js';
|
||||
import { FriendsWindow } from '../windows/friends.js';
|
||||
import { GameRoomWindow } from '../windows/game_room.js';
|
||||
import { StatsWindow } from '../windows/stats.js';
|
||||
|
||||
/**
|
||||
* Main application class
|
||||
@@ -20,6 +21,7 @@ class App {
|
||||
this.initMenu();
|
||||
this.initPage();
|
||||
this.initEasterEgg();
|
||||
this.colorizeUI();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -32,6 +34,7 @@ class App {
|
||||
new FriendsWindow();
|
||||
new GameRoomWindow();
|
||||
new StatsWindow();
|
||||
new LogoutWindow();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,7 +52,8 @@ class App {
|
||||
'login': 'login',
|
||||
'chat': 'chat',
|
||||
'avatar': 'avatar',
|
||||
'friends': 'friends'
|
||||
'friends': 'friends',
|
||||
'logout': 'logout'
|
||||
};
|
||||
|
||||
// Event delegation on the menu
|
||||
@@ -74,10 +78,6 @@ class App {
|
||||
return;
|
||||
}
|
||||
|
||||
const actionMap = {
|
||||
'gameroom': 'gameroom'
|
||||
};
|
||||
|
||||
// Event delegation on the menu
|
||||
page.addEventListener('click', (e) => {
|
||||
const button = e.target.closest('.page__item');
|
||||
@@ -85,9 +85,14 @@ class App {
|
||||
|
||||
const action = button.dataset.action;
|
||||
|
||||
// Actions with associated windows
|
||||
if (actionMap[action]) {
|
||||
windowRegistry.toggle(actionMap[action]);
|
||||
if (action === 'gameroom') {
|
||||
const gameRoomWindow = windowRegistry.get('gameroom');
|
||||
windowRegistry.toggle('gameroom');
|
||||
gameRoomWindow.loadRooms();
|
||||
|
||||
if (gameRoomWindow?.currentTab === 'browse') {
|
||||
gameRoomWindow.loadRooms();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -105,6 +110,39 @@ class App {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
colorizeUI() {
|
||||
|
||||
const elements = document.querySelectorAll(".title, .menu__item, .game__item, .page__item");
|
||||
|
||||
const colorizeText = (el) => {
|
||||
const text = el.textContent;
|
||||
el.innerHTML = "";
|
||||
|
||||
const baseHue = Math.random() * 360;
|
||||
|
||||
// 🎲 random step = makes rainbow "scrambled"
|
||||
const step = (Math.random() * 60) + 10; // 10 → 70
|
||||
|
||||
// 🎲 random direction (left or right rainbow)
|
||||
const direction = Math.random() < 0.5 ? 1 : -1;
|
||||
|
||||
[...text].forEach((char, i) => {
|
||||
const span = document.createElement("span");
|
||||
span.textContent = char;
|
||||
|
||||
const hue = baseHue + (i * step * direction);
|
||||
|
||||
span.style.color = `hsl(${hue}, 90%, 60%)`;
|
||||
|
||||
span.style.textShadow = `1px 1px 0 rgba(0,0,0,0.3)`;
|
||||
|
||||
el.appendChild(span);
|
||||
});
|
||||
};
|
||||
elements.forEach(colorizeText);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Start the application when DOM is ready
|
||||
@@ -0,0 +1,771 @@
|
||||
/* ============================================
|
||||
TRANSCENDENCE - Main Stylesheet
|
||||
Convention: BEM (Block__Element--Modifier)
|
||||
============================================ */
|
||||
|
||||
/* ============================================
|
||||
CSS VARIABLES
|
||||
============================================ */
|
||||
:root {
|
||||
--color-primary: #ffc75e;
|
||||
--color-primary-hover: #ffc75e;
|
||||
--color-success: #3cff01;
|
||||
--color-success-dark: #ffc75e;
|
||||
--color-error: #ff4d4d;
|
||||
--color-warning: #ffc75e;
|
||||
--color-github: #ffc75e;
|
||||
|
||||
--color-bg: #ffe5b5;
|
||||
|
||||
--app-background-base: radial-gradient(
|
||||
circle at top,
|
||||
#fff787,
|
||||
#ff8080
|
||||
);
|
||||
|
||||
--app-background-image: url("../assets/background.png");
|
||||
|
||||
--color-surface: #ffefce;
|
||||
--color-surface-light: #ffc75e;
|
||||
--color-text: #000000;
|
||||
--color-text-muted: #000000;
|
||||
|
||||
--font-size-base: 10px;
|
||||
--font-size-sm: 1.2rem;
|
||||
--font-size-md: 1.4rem;
|
||||
--font-size-lg: 1.6rem;
|
||||
--font-size-xl: 3rem;
|
||||
|
||||
--spacing-xs: 4px;
|
||||
--spacing-sm: 8px;
|
||||
--spacing-md: 12px;
|
||||
--spacing-lg: 16px;
|
||||
--spacing-xl: 24px;
|
||||
|
||||
--radius-sm: 4px;
|
||||
--radius-md: 6px;
|
||||
--radius-lg: 12px;
|
||||
--radius-full: 50%;
|
||||
|
||||
--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
--shadow-md: 0 4px 8px rgba(0, 0, 0, 0.5);
|
||||
--shadow-lg: 0 8px 16px rgba(0, 0, 0, 0.5);
|
||||
|
||||
--transition-fast: 150ms ease;
|
||||
--transition-normal: 250ms ease;
|
||||
|
||||
--z-menu: 2;
|
||||
--z-window: 100;
|
||||
--z-modal: 200;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
RESET & BASE
|
||||
============================================ */
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
background-image:
|
||||
var(--app-background-image),
|
||||
var(--app-background-base);
|
||||
|
||||
background-size:
|
||||
contain,
|
||||
cover;
|
||||
|
||||
background-position:
|
||||
center,
|
||||
center;
|
||||
|
||||
background-repeat:
|
||||
no-repeat,
|
||||
no-repeat;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
width: 70%;
|
||||
min-width: 800px;
|
||||
margin: 0 auto;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
color: var(--color-text);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
TYPOGRAPHY
|
||||
============================================ */
|
||||
|
||||
.title {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
text-transform: uppercase;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
|
||||
font-size: var(--font-size-xl);
|
||||
text-align: center;
|
||||
text-shadow: 2px 2px 10px black;
|
||||
z-index: 1;
|
||||
font-family: "Roboto";
|
||||
letter-spacing: -10px;
|
||||
color: rgba(248, 252, 2, 0.6);
|
||||
|
||||
margin: 0;
|
||||
padding: 0.6rem 1.2rem;
|
||||
|
||||
background-color: #ffefce;
|
||||
border: 2px solid rgba(0, 0, 0, 0.6);
|
||||
border-radius: var(--radius-lg);
|
||||
}
|
||||
|
||||
|
||||
/* ============================================
|
||||
MENU
|
||||
============================================ */
|
||||
|
||||
.menu {
|
||||
position: fixed;
|
||||
top: var(--spacing-lg);
|
||||
left: 50px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-lg);
|
||||
|
||||
z-index: var(--z-menu);
|
||||
}
|
||||
|
||||
.menu__item {
|
||||
background: var(--color-surface);
|
||||
color: var(--color-text);
|
||||
border: 1px solid var(--color-surface-light);
|
||||
border-radius: var(--radius-lg);
|
||||
border-color: #000;
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
font-size: var(--font-size-md);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.menu__item:hover {
|
||||
background: var(--color-surface-light);
|
||||
font-size: var(--font-size-lg);
|
||||
}
|
||||
|
||||
.menu__item--active {
|
||||
background: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
GAME
|
||||
============================================ */
|
||||
|
||||
/* .game {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 50px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
z-index: var(--z-menu);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-xs);
|
||||
} */
|
||||
|
||||
.game {
|
||||
position: fixed;
|
||||
top: var(--spacing-lg);
|
||||
right: 50px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-lg);
|
||||
|
||||
z-index: var(--z-menu);
|
||||
}
|
||||
|
||||
.game__item {
|
||||
background: var(--color-surface);
|
||||
color: var(--color-text);
|
||||
border: 1px solid var(--color-surface-light);
|
||||
border-radius: var(--radius-lg);
|
||||
border-color: #000;
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
font-size: var(--font-size-md);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.game__item:hover {
|
||||
background: var(--color-surface-light);
|
||||
font-size: var(--font-size-lg);
|
||||
}
|
||||
|
||||
.game__item--active {
|
||||
background: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
BUTTONS
|
||||
============================================ */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
font-size: var(--font-size-md);
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
opacity: 0.9;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.btn--primary {
|
||||
background: var(--color-primary);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.btn--primary:hover {
|
||||
background: var(--color-primary-hover);
|
||||
}
|
||||
|
||||
.btn--secondary {
|
||||
background: var(--color-surface-light);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.btn--success {
|
||||
background: var(--color-success-dark);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.btn--danger {
|
||||
background: var(--color-error);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.btn--github {
|
||||
background: var(--color-github);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.btn--ghost {
|
||||
background: transparent;
|
||||
color: var(--color-text);
|
||||
border: 1px solid var(--color-surface-light);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
INPUTS
|
||||
============================================ */
|
||||
.input {
|
||||
width: 100%;
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
font-size: var(--font-size-md);
|
||||
background: var(--color-surface);
|
||||
color: var(--color-text);
|
||||
border: 1px solid var(--color-surface-light);
|
||||
border-radius: var(--radius-md);
|
||||
transition: border-color var(--transition-fast);
|
||||
}
|
||||
|
||||
.input:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.input::placeholder {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
WINDOWS
|
||||
============================================ */
|
||||
.window {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
z-index: var(--z-window);
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
min-width: 280px;
|
||||
box-shadow: var(--shadow-lg);
|
||||
border-radius: 5px;
|
||||
border-color: #aa1f1f;
|
||||
border: 6px solid #faac37;
|
||||
}
|
||||
|
||||
.window--visible {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.window--left {
|
||||
left: 25%;
|
||||
}
|
||||
|
||||
.window--right {
|
||||
left: 75%;
|
||||
}
|
||||
|
||||
.window__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
background: var(--color-surface);
|
||||
cursor: move;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.window__title {
|
||||
font-weight: 500;
|
||||
font-size: var(--font-size-md);
|
||||
}
|
||||
|
||||
.window__close {
|
||||
cursor: pointer;
|
||||
font-size: var(--font-size-lg);
|
||||
opacity: 0.8;
|
||||
transition: opacity var(--transition-fast);
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--color-text);
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.window__close:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.window__body {
|
||||
padding: var(--spacing-md);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-sm);
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
MESSAGES
|
||||
============================================ */
|
||||
.message {
|
||||
font-size: var(--font-size-sm);
|
||||
padding: var(--spacing-xs);
|
||||
border-radius: var(--radius-lg);
|
||||
border-color: #000;
|
||||
}
|
||||
|
||||
.message--success {
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.message--error {
|
||||
color: var(--color-error);
|
||||
}
|
||||
|
||||
.message--info {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
LOGIN WINDOW
|
||||
============================================ */
|
||||
.login {
|
||||
width: 320px;
|
||||
}
|
||||
|
||||
.login__form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.login__actions {
|
||||
display: flex;
|
||||
gap: var(--spacing-sm);
|
||||
margin-top: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.login__divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--font-size-sm);
|
||||
margin: var(--spacing-sm) 0;
|
||||
}
|
||||
|
||||
.login__divider::before,
|
||||
.login__divider::after {
|
||||
content: '';
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: var(--color-surface-light);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
CHAT WINDOW
|
||||
============================================ */
|
||||
.chat {
|
||||
width: 380px;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.chat__output {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: var(--spacing-sm);
|
||||
background: var(--color-surface);
|
||||
border-radius: var(--radius-md);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-sm);
|
||||
min-height: 150px;
|
||||
}
|
||||
|
||||
.chat__message {
|
||||
padding: var(--spacing-xs) var(--spacing-sm);
|
||||
background: var(--color-surface-light);
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.chat__message--own {
|
||||
background: var(--color-primary);
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.chat__friend-indicator {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background-color: var(--color-success);
|
||||
border-radius: 50%;
|
||||
margin-right: var(--spacing-xs);
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.chat__system {
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--font-size-sm);
|
||||
font-style: italic;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.chat__system--error {
|
||||
color: var(--color-error);
|
||||
}
|
||||
|
||||
.chat__system--success {
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.chat__input-container {
|
||||
display: flex;
|
||||
gap: var(--spacing-sm);
|
||||
margin-top: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.chat__input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.chat__controls {
|
||||
display: flex;
|
||||
gap: var(--spacing-sm);
|
||||
margin-top: var(--spacing-sm);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
AVATAR WINDOW
|
||||
============================================ */
|
||||
.avatar-window {
|
||||
width: 360px;
|
||||
}
|
||||
|
||||
.avatar__preview {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
object-fit: cover;
|
||||
border-radius: var(--radius-full);
|
||||
border: 3px solid var(--color-text);
|
||||
box-shadow: var(--shadow-md);
|
||||
background: var(--color-surface);
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.avatar__username {
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
color: var(--color-text);
|
||||
margin-top: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.avatar__controls {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-sm);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.avatar__file-input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
STATS WINDOW
|
||||
============================================ */
|
||||
.stats-window {
|
||||
width: 320px;
|
||||
}
|
||||
|
||||
.stats__avatar {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
object-fit: cover;
|
||||
border-radius: var(--radius-full);
|
||||
border: 2px solid var(--color-text);
|
||||
align-self: center;
|
||||
display: block;
|
||||
margin: 0 auto var(--spacing-xs);
|
||||
}
|
||||
|
||||
.stats__username {
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
color: #000;
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.stats__section {
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.stats__section-title {
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: var(--color-primary);
|
||||
border-bottom: 1px solid var(--color-surface-light);
|
||||
padding-bottom: var(--spacing-xs);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.stats__section-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.stats__row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: var(--font-size-sm);
|
||||
padding: 3px 0;
|
||||
}
|
||||
|
||||
.stats__label {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.stats__value {
|
||||
font-weight: 600;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.stats__loading {
|
||||
font-size: var(--font-size-sm);
|
||||
color: #333;
|
||||
text-align: center;
|
||||
padding: var(--spacing-sm) 0;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
EASTER EGG BUTTON
|
||||
============================================ */
|
||||
/* .easter-egg {
|
||||
position: absolute;
|
||||
top: 20%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 1;
|
||||
background: var(--color-surface);
|
||||
color: var(--color-text);
|
||||
border: 1px solid var(--color-surface-light);
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
cursor: pointer;
|
||||
font-size: var(--font-size-md);
|
||||
border-radius: var(--radius-md);
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.easter-egg:hover {
|
||||
background: var(--color-error);
|
||||
border-color: var(--color-error);
|
||||
} */
|
||||
|
||||
/* ============================================
|
||||
UTILITIES
|
||||
============================================ */
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.visually-hidden {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
FRIENDS WINDOW
|
||||
============================================ */
|
||||
.friends-window {
|
||||
width: 400px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
.friends__tabs {
|
||||
display: flex;
|
||||
gap: var(--spacing-xs);
|
||||
margin-bottom: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.friends__tab {
|
||||
flex: 1;
|
||||
padding: var(--spacing-sm);
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-surface-light);
|
||||
color: var(--color-text);
|
||||
cursor: pointer;
|
||||
font-size: var(--font-size-sm);
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.friends__tab:hover {
|
||||
background: var(--color-surface-light);
|
||||
}
|
||||
|
||||
.friends__tab--active {
|
||||
background: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.friends__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.friends__search {
|
||||
display: flex;
|
||||
gap: var(--spacing-sm);
|
||||
margin-bottom: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.friends__search .input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.friends__list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.friends__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
padding: var(--spacing-sm);
|
||||
background: var(--color-surface);
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.friends__avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: var(--radius-full);
|
||||
object-fit: cover;
|
||||
border: 2px solid var(--color-surface-light);
|
||||
}
|
||||
|
||||
.friends__name {
|
||||
flex: 1;
|
||||
font-size: var(--font-size-md);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.friends__actions {
|
||||
display: flex;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.friends__actions .btn {
|
||||
padding: var(--spacing-xs) var(--spacing-sm);
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.friends__empty {
|
||||
text-align: center;
|
||||
color: var(--color-text-muted);
|
||||
padding: var(--spacing-lg);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<!doctype html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Transcendence</title>
|
||||
<link rel="stylesheet" href="index.css" />
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Cinzel+Decorative:wght@400;700&display=swap" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<h1 class="title">Transcendence</h1>
|
||||
|
||||
<nav class="menu" aria-label="Menu principal">
|
||||
<button class="menu__item" data-action="login" aria-label="Login">Login</button>
|
||||
<button class="menu__item" data-action="chat" aria-label="Global chat">Global chat</button>
|
||||
<button class="menu__item" data-action="avatar" aria-label="Avatar">Avatar</button>
|
||||
<button class="menu__item" data-action="friends" aria-label="Amis">Amis</button>
|
||||
<button class="menu__item" data-action="logout" aria-label="Logout">Logout</button>
|
||||
</nav>
|
||||
|
||||
<nav class="game" aria-label="Game">
|
||||
<button class="game__item" data-action="new_game" aria-label="Skkrrribl.io"
|
||||
onclick="window.location.href='../game2/game.html'">Skkrrribl.io</button>
|
||||
<button class="game__item" data-action="tetris" aria-label="Tetris"
|
||||
onclick="window.location.href='../tetris/tetris.html'">Tetris</button>
|
||||
<button class="game__item" data-action="wiscat" aria-label="Wiscat"
|
||||
onclick="window.location.href='../wiscat/wiscat.html'">Wiscat</button>
|
||||
</nav>
|
||||
|
||||
<div class="container-gamelinks">
|
||||
<button class="game-button link-game" onclick="window.location.href='../game2/game.html';">Skkrrribl.io</button>
|
||||
<button class="game-button link-tetris" onclick="window.location.href='../tetris/tetris.html';">Tetris</button>
|
||||
<button class="game-button link-wiscat" onclick="window.location.href='../wiscat/wiscat.html';">Wiscat</button>
|
||||
</div>
|
||||
<script type="module" src="app.js"></script>
|
||||
<script type="module" src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,57 @@
|
||||
const container = document.querySelector('.container-gamelinks');
|
||||
const buttons = document.querySelectorAll('.game-button');
|
||||
|
||||
function initButtons() {
|
||||
const rect = container.getBoundingClientRect();
|
||||
|
||||
buttons.forEach(btn => {
|
||||
// Ensure size is known
|
||||
const bw = btn.offsetWidth;
|
||||
const bh = btn.offsetHeight;
|
||||
|
||||
// Random start position INSIDE container
|
||||
btn.x = Math.random() * (rect.width - bw);
|
||||
btn.y = Math.random() * (rect.height - bh);
|
||||
|
||||
// Better velocity (avoid super slow)
|
||||
btn.vx = (Math.random() * 2 + 1) * (Math.random() < 0.5 ? -1 : 1);
|
||||
btn.vy = (Math.random() * 2 + 1) * (Math.random() < 0.5 ? -1 : 1);
|
||||
|
||||
btn.style.left = btn.x + 'px';
|
||||
btn.style.top = btn.y + 'px';
|
||||
});
|
||||
}
|
||||
|
||||
function animateButtons() {
|
||||
const rect = container.getBoundingClientRect();
|
||||
|
||||
buttons.forEach(btn => {
|
||||
btn.x += btn.vx;
|
||||
btn.y += btn.vy;
|
||||
|
||||
const bw = btn.offsetWidth;
|
||||
const bh = btn.offsetHeight;
|
||||
|
||||
// Bounce inside container
|
||||
if (btn.x <= 0 || btn.x + bw >= rect.width) {
|
||||
btn.vx *= -1;
|
||||
btn.x = Math.max(0, Math.min(btn.x, rect.width - bw)); // clamp
|
||||
}
|
||||
|
||||
if (btn.y <= 0 || btn.y + bh >= rect.height) {
|
||||
btn.vy *= -1;
|
||||
btn.y = Math.max(0, Math.min(btn.y, rect.height - bh)); // clamp
|
||||
}
|
||||
|
||||
btn.style.left = btn.x + 'px';
|
||||
btn.style.top = btn.y + 'px';
|
||||
});
|
||||
|
||||
requestAnimationFrame(animateButtons);
|
||||
}
|
||||
|
||||
// 🔥 IMPORTANT: wait for layout to be ready
|
||||
window.addEventListener('load', () => {
|
||||
initButtons();
|
||||
animateButtons();
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.link-button {
|
||||
|
||||
}
|
||||
|
||||
.link-game {
|
||||
|
||||
}
|
||||
|
||||
.link-tetris {
|
||||
|
||||
}
|
||||
|
||||
.link-wiscat {
|
||||
|
||||
}
|
||||
|
||||
.container-gamelinks {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.game-button {
|
||||
position: absolute; /* allow movement */
|
||||
padding: 15px 30px;
|
||||
font-size: 20px;
|
||||
font-family: "Roboto";
|
||||
border-radius: 15px;
|
||||
border: 4px solid #c0c0c0;
|
||||
background-color: #111;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
|
||||
/* metallic shine effect */
|
||||
box-shadow: inset 0 0 8px rgba(255,255,255,0.4), 0 0 5px rgba(255,255,255,0.2);
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.game-button:hover {
|
||||
transform: scale(1.1);
|
||||
box-shadow: inset 0 0 12px rgba(255,255,255,0.5), 0 0 8px rgba(255,255,255,0.3);
|
||||
}
|
||||
@@ -1,412 +0,0 @@
|
||||
// ─────────────────────────────────────────────
|
||||
// UI
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
const btnStart = document.getElementById('btn-start');
|
||||
const btnPause = document.getElementById('btn-pause');
|
||||
const btnStop = document.getElementById('btn-stop');
|
||||
const overlay = document.getElementById('overlay');
|
||||
const inputTTD = document.getElementById('input-ttd');
|
||||
const inputHardening = document.getElementById('input-hardening');
|
||||
const inputDecrement = document.getElementById('input-decrement');
|
||||
|
||||
// Duel UI
|
||||
const btnJoinDuel = document.getElementById('btn-join-duel');
|
||||
const btnLeaveDuel = document.getElementById('btn-leave-duel');
|
||||
const inputRoomCode = document.getElementById('input-room-code');
|
||||
const duelStatusEl = document.getElementById('duel-status');
|
||||
const opponentSection = document.getElementById('opponent-section');
|
||||
|
||||
// Matchmaking UI
|
||||
const btnMatchmaking = document.getElementById('btn-matchmaking');
|
||||
const btnMatchmakingCancel = document.getElementById('btn-matchmaking-cancel');
|
||||
const matchmakingStatusEl = document.getElementById('matchmaking-status');
|
||||
|
||||
function updateButtons() {
|
||||
btnStart.disabled = game.isRunning;
|
||||
btnPause.disabled = !game.isRunning;
|
||||
btnStop.disabled = !game.isRunning;
|
||||
btnPause.textContent = game.isPaused ? 'Resume' : 'Pause';
|
||||
inputTTD.disabled = game.isRunning;
|
||||
inputHardening.disabled = game.isRunning;
|
||||
inputDecrement.disabled = game.isRunning;
|
||||
}
|
||||
|
||||
function showOverlay(title, score) {
|
||||
document.getElementById('overlay-title').textContent = title;
|
||||
document.getElementById('overlay-score').textContent = score !== undefined ? `Score : ${score}` : '';
|
||||
overlay.classList.add('visible');
|
||||
}
|
||||
|
||||
function hideOverlay() {
|
||||
overlay.classList.remove('visible');
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// SOCKET + DUEL
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
const socket = io({
|
||||
auth: { token: localStorage.getItem('auth_token') },
|
||||
reconnection: true,
|
||||
reconnectionAttempts: 5,
|
||||
reconnectionDelay: 1000,
|
||||
transports: ['websocket', 'polling']
|
||||
});
|
||||
|
||||
let duel = null;
|
||||
|
||||
function updateDuelStatus(status, opponentName) {
|
||||
duelStatusEl.className = '';
|
||||
if (status === 'waiting') {
|
||||
duelStatusEl.textContent = 'En attente d\'un adversaire…';
|
||||
duelStatusEl.classList.add('waiting');
|
||||
opponentSection.classList.remove('visible');
|
||||
} else if (status === 'ready') {
|
||||
duelStatusEl.textContent = `Prêt — ${opponentName}`;
|
||||
duelStatusEl.classList.add('ready');
|
||||
opponentSection.classList.add('visible');
|
||||
if (duel) duel.hideOpponentOverlay();
|
||||
renderOpponent(duel ? duel.opponentGrid : Array.from({length:20}, () => Array(10).fill(0)));
|
||||
} else {
|
||||
duelStatusEl.textContent = '—';
|
||||
opponentSection.classList.remove('visible');
|
||||
}
|
||||
}
|
||||
|
||||
function startLocalGame() {
|
||||
hideOverlay();
|
||||
game.start();
|
||||
updateButtons();
|
||||
render();
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// SCORE SAVE (solo)
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
function saveTetrisScore(score) {
|
||||
const token = localStorage.getItem('auth_token');
|
||||
if (!token) return;
|
||||
fetch('/api/stats/tetris/score', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({ score })
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.bestScore !== undefined) {
|
||||
console.log('Meilleur score tetris:', data.bestScore);
|
||||
}
|
||||
})
|
||||
.catch(err => console.error('Erreur sauvegarde score tetris:', err));
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// DUEL BUTTONS
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
btnJoinDuel.addEventListener('click', () => {
|
||||
const code = inputRoomCode.value.trim().toUpperCase();
|
||||
if (!code) return;
|
||||
if (duel) { duel.leave(); }
|
||||
if (game.isRunning) { game.stop(); hideOverlay(); render(); updateButtons(); }
|
||||
duel = new Duel(socket, game, updateDuelStatus, startLocalGame);
|
||||
duel.join(code);
|
||||
btnJoinDuel.disabled = true;
|
||||
btnLeaveDuel.disabled = false;
|
||||
inputRoomCode.disabled = true;
|
||||
updateDuelStatus('waiting', null);
|
||||
});
|
||||
|
||||
btnLeaveDuel.addEventListener('click', () => {
|
||||
if (duel) { duel.leave(); duel = null; }
|
||||
btnJoinDuel.disabled = false;
|
||||
btnLeaveDuel.disabled = true;
|
||||
inputRoomCode.disabled = false;
|
||||
updateDuelStatus(null, null);
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// MATCHMAKING
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
btnMatchmaking.addEventListener('click', () => {
|
||||
socket.emit('tetris:matchmaking-join');
|
||||
btnMatchmaking.disabled = true;
|
||||
btnMatchmakingCancel.disabled = false;
|
||||
btnJoinDuel.disabled = true;
|
||||
matchmakingStatusEl.textContent = 'Recherche en cours…';
|
||||
matchmakingStatusEl.className = 'waiting';
|
||||
});
|
||||
|
||||
btnMatchmakingCancel.addEventListener('click', () => {
|
||||
socket.emit('tetris:matchmaking-leave');
|
||||
btnMatchmaking.disabled = false;
|
||||
btnMatchmakingCancel.disabled = true;
|
||||
btnJoinDuel.disabled = false;
|
||||
matchmakingStatusEl.textContent = '';
|
||||
});
|
||||
|
||||
socket.on('tetris:matchmaking-status', (data) => {
|
||||
if (data.status === 'searching') {
|
||||
matchmakingStatusEl.textContent = `Recherche… (${data.position} joueur(s) en attente)`;
|
||||
} else if (data.status === 'idle') {
|
||||
matchmakingStatusEl.textContent = '';
|
||||
btnMatchmaking.disabled = false;
|
||||
btnMatchmakingCancel.disabled = true;
|
||||
btnJoinDuel.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('tetris:matched', (data) => {
|
||||
matchmakingStatusEl.textContent = `Adversaire trouvé : ${data.opponent} !`;
|
||||
matchmakingStatusEl.className = 'ready';
|
||||
btnMatchmaking.disabled = false;
|
||||
btnMatchmakingCancel.disabled = true;
|
||||
btnJoinDuel.disabled = false;
|
||||
|
||||
// Auto-rejoindre la salle générée
|
||||
if (duel) { duel.leave(); }
|
||||
if (game.isRunning) { game.stop(); hideOverlay(); render(); updateButtons(); }
|
||||
duel = new Duel(socket, game, updateDuelStatus, startLocalGame);
|
||||
duel.join(data.roomCode);
|
||||
inputRoomCode.value = data.roomCode;
|
||||
btnJoinDuel.disabled = true;
|
||||
btnLeaveDuel.disabled = false;
|
||||
inputRoomCode.disabled = true;
|
||||
updateDuelStatus('waiting', null);
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// INIT
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
const game = new Tetris(
|
||||
// onRender
|
||||
() => {
|
||||
if (duel) duel.synchronize_game();
|
||||
render();
|
||||
updateButtons();
|
||||
},
|
||||
// onGameOver
|
||||
(score, validBlock) => {
|
||||
const isDuel = duel && duel.isReady;
|
||||
if (isDuel) {
|
||||
duel.onLocalGameOver(score, validBlock);
|
||||
} else {
|
||||
saveTetrisScore(score);
|
||||
}
|
||||
render();
|
||||
updateButtons();
|
||||
showOverlay('GAME OVER', score);
|
||||
loadLeaderboards();
|
||||
loadGameHistory();
|
||||
},
|
||||
// onBlockPlaced — relay duel
|
||||
(grid) => {
|
||||
if (duel) duel.onLocalBlockPlaced(grid, game.score);
|
||||
},
|
||||
// onLinesCleared — relay duel
|
||||
(count, holeCol) => {
|
||||
if (duel) duel.onLocalLinesCleared(count, holeCol);
|
||||
},
|
||||
// onShieldChanged — relay duel
|
||||
(event) => {
|
||||
if (duel) duel.onLocalShieldChanged(event);
|
||||
}
|
||||
);
|
||||
|
||||
btnStart.addEventListener('click', () => {
|
||||
if (duel && duel.isReady) {
|
||||
duel.startDuel(); // déclenche les deux parties via le serveur
|
||||
} else {
|
||||
startLocalGame(); // solo
|
||||
}
|
||||
});
|
||||
|
||||
btnPause.addEventListener('click', () => {
|
||||
if (duel && duel.isReady) {
|
||||
duel.togglePause();
|
||||
} else {
|
||||
game.pause();
|
||||
updateButtons();
|
||||
if (game.isPaused) showOverlay('PAUSE');
|
||||
else hideOverlay();
|
||||
}
|
||||
});
|
||||
|
||||
btnStop.addEventListener('click', () => {
|
||||
if (duel && duel.isReady) {
|
||||
duel.stop();
|
||||
} else {
|
||||
game.stop();
|
||||
updateButtons();
|
||||
render();
|
||||
showOverlay('STOPPED');
|
||||
}
|
||||
});
|
||||
|
||||
function applySettings() {
|
||||
const settings = {
|
||||
timeToDown: parseInt(inputTTD.value, 10),
|
||||
hardening: parseInt(inputHardening.value, 10),
|
||||
decrementTTD: parseInt(inputDecrement.value, 10),
|
||||
};
|
||||
game.configure(settings);
|
||||
if (duel && duel.isReady) duel.syncSettings(settings);
|
||||
}
|
||||
|
||||
inputTTD.addEventListener('change', applySettings);
|
||||
inputHardening.addEventListener('change', applySettings);
|
||||
inputDecrement.addEventListener('change', applySettings);
|
||||
|
||||
const btnRestart = document.getElementById('btn-restart');
|
||||
if (btnRestart) {
|
||||
btnRestart.addEventListener('click', () => {
|
||||
if (duel && duel.isReady) return;
|
||||
game.restart();
|
||||
updateButtons();
|
||||
render();
|
||||
});
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// GAME HISTORY
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
async function loadGameHistory() {
|
||||
const token = localStorage.getItem('auth_token');
|
||||
if (!token) return;
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/stats/tetris/history', {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
});
|
||||
if (!res.ok) return;
|
||||
const history = await res.json();
|
||||
renderGameHistory(history);
|
||||
} catch (err) {
|
||||
console.error('Erreur chargement historique:', err);
|
||||
}
|
||||
}
|
||||
|
||||
function renderGameHistory(history) {
|
||||
const tbody = document.getElementById('lb-history-body');
|
||||
if (!tbody) return;
|
||||
if (!history.length) {
|
||||
tbody.innerHTML = '<tr><td colspan="5">Aucune partie jouée</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = history.map((entry, i) => {
|
||||
const date = new Date(entry.played_at).toLocaleDateString('fr-FR', {
|
||||
day: '2-digit', month: '2-digit', year: '2-digit',
|
||||
hour: '2-digit', minute: '2-digit'
|
||||
});
|
||||
const type = entry.game_type === 'duel' ? 'Duel' : 'Solo';
|
||||
let resultHtml = '—';
|
||||
if (entry.result === 'win') resultHtml = '<span class="hist-win">Victoire</span>';
|
||||
if (entry.result === 'loss') resultHtml = '<span class="hist-loss">Défaite</span>';
|
||||
return `<tr>
|
||||
<td>${i + 1}</td>
|
||||
<td>${date}</td>
|
||||
<td>${type}</td>
|
||||
<td>${entry.score}</td>
|
||||
<td>${resultHtml}</td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// LEADERBOARDS
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
async function loadLeaderboards() {
|
||||
const token = localStorage.getItem('auth_token');
|
||||
if (!token) return;
|
||||
|
||||
const headers = { 'Authorization': `Bearer ${token}` };
|
||||
|
||||
try {
|
||||
const [scoresRes, winsRes, meRes, rankScoreRes, rankWinsRes] = await Promise.all([
|
||||
fetch('/api/stats/tetris/leaderboard/score', { headers }),
|
||||
fetch('/api/stats/tetris/leaderboard/wins', { headers }),
|
||||
fetch('/api/stats/me', { headers }),
|
||||
fetch('/api/stats/tetris/rank/score', { headers }),
|
||||
fetch('/api/stats/tetris/rank/wins', { headers })
|
||||
]);
|
||||
|
||||
const me = meRes.ok ? await meRes.json() : null;
|
||||
const rankScore = rankScoreRes.ok ? (await rankScoreRes.json()).rank : null;
|
||||
const rankWins = rankWinsRes.ok ? (await rankWinsRes.json()).rank : null;
|
||||
|
||||
if (scoresRes.ok) {
|
||||
const scores = await scoresRes.json();
|
||||
renderLeaderboard('lb-scores-body', scores, ['tetris_best_score', 'tetris_games_played'], me, rankScore);
|
||||
}
|
||||
|
||||
if (winsRes.ok) {
|
||||
const wins = await winsRes.json();
|
||||
renderLeaderboard('lb-wins-body', wins, ['tetris_wins', 'tetris_games_played'], me, rankWins);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Erreur chargement leaderboards:', err);
|
||||
}
|
||||
}
|
||||
|
||||
function renderLeaderboard(tbodyId, rows, [col1, col2], me, myRank) {
|
||||
const tbody = document.getElementById(tbodyId);
|
||||
if (!tbody) return;
|
||||
if (!rows.length && !me) {
|
||||
tbody.innerHTML = '<tr><td colspan="4">Aucun résultat</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
const myUsername = me?.username;
|
||||
const inTop = rows.some(r => r.username === myUsername);
|
||||
|
||||
let html = rows.map((r, i) => {
|
||||
const isMe = r.username === myUsername;
|
||||
return `<tr class="${isMe ? 'lb-me' : ''}">
|
||||
<td>${i + 1}</td>
|
||||
<td>${escapeHtml(r.username)}${isMe ? ' <span class="lb-you">(vous)</span>' : ''}</td>
|
||||
<td>${r[col1] ?? 0}</td>
|
||||
<td>${r[col2] ?? 0}</td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
|
||||
if (!inTop && me && myRank !== null) {
|
||||
html += `<tr class="lb-separator"><td colspan="4">· · ·</td></tr>`;
|
||||
html += `<tr class="lb-me">
|
||||
<td>${myRank}</td>
|
||||
<td>${escapeHtml(myUsername)} <span class="lb-you">(vous)</span></td>
|
||||
<td>${me[col1] ?? 0}</td>
|
||||
<td>${me[col2] ?? 0}</td>
|
||||
</tr>`;
|
||||
}
|
||||
|
||||
tbody.innerHTML = html || '<tr><td colspan="4">Aucun résultat</td></tr>';
|
||||
}
|
||||
|
||||
function escapeHtml(str) {
|
||||
return String(str).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
||||
}
|
||||
|
||||
// Tabs leaderboard
|
||||
document.querySelectorAll('.lb-tab').forEach(tab => {
|
||||
tab.addEventListener('click', () => {
|
||||
document.querySelectorAll('.lb-tab').forEach(t => t.classList.remove('lb-tab--active'));
|
||||
document.querySelectorAll('.lb-content').forEach(c => c.classList.remove('lb-content--active'));
|
||||
tab.classList.add('lb-tab--active');
|
||||
document.getElementById(`lb-${tab.dataset.tab}`).classList.add('lb-content--active');
|
||||
if (tab.dataset.tab === 'history') loadGameHistory();
|
||||
});
|
||||
});
|
||||
|
||||
// Chargement initial des leaderboards
|
||||
loadLeaderboards();
|
||||
loadGameHistory();
|
||||
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>404</title>
|
||||
<link rel="stylesheet" type="text/css" href="style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<img id="errorImage" style="margin: auto; display: block;" src="#" alt="何もない">
|
||||
<script>
|
||||
// Retrieve the random number from the URL query parameter
|
||||
var urlParams = new URLSearchParams(window.location.search);
|
||||
var randomErrorNum = urlParams.get('img');
|
||||
|
||||
document.getElementById('errorImage').src = `web_cat_img/errors/${randomErrorNum}.jpg`;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,75 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>THE CAT WEBSITE</title>
|
||||
<link rel="stylesheet" type="text/css" href="style.css">
|
||||
<script src="script.js"></script>
|
||||
<style>
|
||||
.ul_biblio {
|
||||
text-align: left;
|
||||
font-size: 20px;
|
||||
padding-top: 100px;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
function changePage() {
|
||||
let images = [530, 599, 204, 400, 401, 402, 403, 404, 410];
|
||||
var randomErrorNum = images[Math.floor(Math.random() * images.length)];
|
||||
window.location.href = `404.html?img=${randomErrorNum}`;
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<a href="../index.html" class="button1">home</a>
|
||||
|
||||
<ul class="ul_biblio">
|
||||
<li><a href="report1.html">Investigative Report: How Cats Created the Internet</a></li>
|
||||
<li><a href="#" onclick="changePage()">The Complete proof of feline superiority</a></li>
|
||||
<li><a href="#" onclick="changePage()">Breaking News: Cats Discover the Secret to World Domination</a></li>
|
||||
<li><a href="#" onclick="changePage()">Scientific Study Reveals Cats Speak Fluent Human</a></li>
|
||||
<li><a href="#" onclick="changePage()">Cat Conspiracies: Unveiling the Kitty Illuminati</a></li>
|
||||
<li><a href="#" onclick="changePage()">Exclusive Interview with the First Cat on Mars</a></li>
|
||||
<li><a href="#" onclick="changePage()">Cat Yoga Guru Unveils the Ultimate Pose for Enlightenment</a></li>
|
||||
<li><a href="#" onclick="changePage()">Cat-alysts for World Peace: Feline Diplomacy Unveiled</a></li>
|
||||
<li><a href="#" onclick="changePage()">Cat Cuisine Revolution: The Ultimate Guide to Gourmet Catnip Recipes</a></li>
|
||||
<li><a href="#" onclick="changePage()">Top-Secret Cat Technologies: Cloaking Devices and Teleportation</a></li>
|
||||
<li><a href="#" onclick="changePage()">The Great Feline Art Heist: How Cats Stole the Louvre</a></li>
|
||||
<li><a href="#" onclick="changePage()">Cats Unveil Master Plan to Transform Humans into Giant Yarn Balls</a></li>
|
||||
<li><a href="#" onclick="changePage()">Parallel Universe Cats: A Guide to Interdimensional Napping</a></li>
|
||||
<li><a href="#" onclick="changePage()">World-renowned Cat Composer Drops New Symphony, Humans Still Clueless</a></li>
|
||||
<li><a href="#" onclick="changePage()">The Great Sphinx Mystery Revealed: It's Actually a Giant Cat Monument</a></li>
|
||||
<li><a href="#" onclick="changePage()">Quantum Cat Mechanics: How Cats Can Exist in Multiple Places Simultaneously</a></li>
|
||||
<li><a href="#" onclick="changePage()">Cat Astronauts Return from Space Mission, Demand Endless Supply of Cosmic Tuna</a></li>
|
||||
<li><a href="#" onclick="changePage()">Cat-mera: Capturing the World Through the Eyes of Felines</a></li>
|
||||
<li><a href="#" onclick="changePage()">Cat Olympics 2030: Feline Gymnastics and Synchronized Napping Take Center Stage</a></li>
|
||||
<li><a href="#" onclick="changePage()">Cats Invented Time Travel, but Prefer to Keep It a Secret</a></li>
|
||||
<li><a href="#" onclick="changePage()">Cat Psychics Predict the Future: Global Mice Shortage Imminent</a></li>
|
||||
<li><a href="#" onclick="changePage()">Cat-tastrophysics: How Cats Bend Space and Time with Their Cuteness</a></li>
|
||||
<li><a href="#" onclick="changePage()">Quantum Meowchanics: Exploring the Subatomic World of Kitty Purr-ticles</a></li>
|
||||
<li><a href="#" onclick="changePage()">Catspiracy Theories: Are Cats Behind Crop Circles?</a></li>
|
||||
<li><a href="#" onclick="changePage()">Meow-sical Revolution: Composer Cats Create Symphony Using Litterbox Instruments</a></li>
|
||||
<li><a href="#" onclick="changePage()">The Great Cat Escape: Felines Launch Mission to the Moon for a Permanent Nap</a></li>
|
||||
<li><a href="#" onclick="changePage()">Cat-eronauts Unveiled: Flying Felines Break the Sound Barrier</a></li>
|
||||
<li><a href="#" onclick="changePage()">Purr-fectionism in Art: How Cats Are Secretly Famous Renaissance Painters</a></li>
|
||||
<li><a href="#" onclick="changePage()">Infinite Cat Wisdom: Cracking the Code of Feline Philosophy</a></li>
|
||||
<li><a href="#" onclick="changePage()">Feline Fashion Week: Cats Strut the Runway in Extravagant Fur Couture</a></li>
|
||||
<li><a href="#" onclick="changePage()">The Cat-alyst Effect: How Kittens Sparked the Renaissance</a></li>
|
||||
|
||||
</ul>
|
||||
|
||||
<footer>
|
||||
<div class="footer_div" style="margin-top: 100px;">
|
||||
<img class="ico_footer" src="web_cat_img/facebook_logo.png">
|
||||
<img class="ico_footer" src="web_cat_img/insta_logo.png">
|
||||
<img class="ico_footer" src="web_cat_img/twitter_logo.png">
|
||||
</div>
|
||||
<div class="footer_div" style="margin-bottom: 50px;">
|
||||
<a href="https://www.facebook.com/">MIAOUBOOK</a>
|
||||
<a href="https://www.instagram.com/">INSTAMIA</a>
|
||||
<a href="https://twitter.com/">BLUE-SNACK</a>
|
||||
</div>
|
||||
<a href="mentions_legales.html">- LEGAL NOTICES -<br>(boring stuff, really, dont go look into this, i mean we are obligated to include it, but it will bore you, like, really)
|
||||
<br>dont do it! every seconds you spend in this next page, a kitten dies. so dont</a>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,82 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="css/style2.css">
|
||||
<script src="js/script.js"></script>
|
||||
<title>CAT</title>
|
||||
</head>
|
||||
<body>
|
||||
<!--🐾🐈🐱-->
|
||||
<div class="header_index">
|
||||
<button class="button1" style="margin-left: 50px;" onclick="window.location.href='donate.html'">Donate</button>
|
||||
<div>
|
||||
<h1 id="header1" class="header1" onmouseover="this.style.backgroundColor='#363636'" onmouseout="this.style.backgroundColor='black'" onclick="talkWiskas()">Welcome to CAT !</h1>
|
||||
</div>
|
||||
<span style="margin-right: 30px;">
|
||||
<div>
|
||||
<input type="text" id="loginInput" placeholder="enter your 42 login">
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<button class="button1" onclick="storeValue();">login</button>
|
||||
<button class="button1" onclick="window.location.href = `https://profile.intra.42.fr/users/${storedLogin}`;">go to ></button>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="popup-chaberu" id="popup-chaberu" style="color: rgb(218, 145, 12);"></div>
|
||||
|
||||
<img id="backgroundpicture" style="margin: auto; display: block;" src="img/wiskas-the-third.jpg">
|
||||
|
||||
<section style="display: flex;
|
||||
justify-content: center;
|
||||
width: 1000px;
|
||||
margin: 0 auto;">
|
||||
<p>I, am wiskas-the-third,
|
||||
We are the cat company, we dont need to present our self for you already know
|
||||
who we are, we created the internet, and we are still managing it now<br>
|
||||
We at CAT are the admin, creator, and workers of the internet
|
||||
Everytime a human goes to sleep, a cat start its shift, 1 billion pair of whiskers that are always here for you
|
||||
Why? because we are philantropists, dont question it. Our goals are beyond your understanding
|
||||
the internet was created by us, for us, and you should be glad we allow you to use it.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section style="display: flex;">
|
||||
<button style="margin-left: 50px;" class="button1" onclick="window.location.href = 'biblio.html';">
|
||||
Latest News</button><br>
|
||||
<button style="margin-left: 50px;" class="button1" onclick="window.location.href = 'staff.html';">
|
||||
meet the staff</button><br>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<br><br><br>
|
||||
<div class="footer_div" style="margin-top: 100px;">
|
||||
<img class="ico_footer" src="img/facebook_logo.png">
|
||||
<img class="ico_footer" src="img/insta_logo.png">
|
||||
<img class="ico_footer" src="img/twitter_logo.png">
|
||||
</div>
|
||||
<div class="footer_div" style="margin-bottom: 50px;">
|
||||
<a href="https://www.facebook.com/">MIAOUBOOK</a>
|
||||
<a href="https://www.instagram.com/">INSTAMIA</a>
|
||||
<a href="https://twitter.com/">BLUE-SNACK</a>
|
||||
</div>
|
||||
<a href="mentions_legales.html">- LEGAL NOTICES -<br>(boring stuff, really, dont go look into this, i mean we are obligated to include it, but it will bore you, like, really)
|
||||
<br>Dont do it! every seconds you spend in this next page, a kitten dies. so dont</a>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
var storedLogin;
|
||||
var logUser;
|
||||
var discussion = ['Well hi there...',
|
||||
'Please refrain from touching\n the yellow button',
|
||||
'We are going to take actions\n if you continue..',
|
||||
'Actions taken,\n you are only\n making it worse..'];
|
||||
var index_wiskas = 0;
|
||||
let chaberuka = false;
|
||||
|
||||
document.getElementById("loginInput").addEventListener("keydown", function(event) {
|
||||
if (event.keyCode === 13) {storeValue()}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,45 @@
|
||||
.h1 {
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
.disclaimer {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
:root {
|
||||
--color1: rgb(190, 63, 40);
|
||||
--color2: rgb(211, 187, 53);
|
||||
}
|
||||
|
||||
.popup-murderKitten {
|
||||
position: fixed;
|
||||
left: 75%;
|
||||
transform: translateX(-50%);
|
||||
padding: 20px;
|
||||
z-index: 1000;
|
||||
|
||||
background-color: var(--color2);
|
||||
border: 5px solid var(--color1);
|
||||
border-radius: 15px;
|
||||
border-width: 10px;
|
||||
|
||||
display: flex;
|
||||
height: 300px;
|
||||
width: 500px;
|
||||
color: var(--color1);
|
||||
font-weight: bold;
|
||||
|
||||
cursor: pointer;
|
||||
font-size: 25px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #5c5c5c;
|
||||
}
|
||||
a:hover {
|
||||
color: rgb(218, 145, 12);
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<script src="js/script.js"></script>
|
||||
<title>Legal Notices</title>
|
||||
<link rel="stylesheet" href="mentions_legales.css" />
|
||||
</head>
|
||||
<body>
|
||||
<a href="../../index.html">< please go back</a>
|
||||
<div class="popup-murderKitten" id="murderButton" style="display: none;" onclick="window.location.href = '../../index.html';">
|
||||
<p>you have currently MURDERED<br> <span id="timeCounter" style="font-size: 50px;">0</span> Kittens !</p>
|
||||
<p><br>you monster ...</p>
|
||||
<p><br><br>please leave before we take actions</p>
|
||||
</div>
|
||||
<h1>Legal Notices</h1>
|
||||
|
||||
<section>
|
||||
<h2>Data Harvesting Nexus: Unveiling the Cosmic Conspiracy</h2>
|
||||
<p>Article 1: Users stepping into the data realms of our feline forums unknowingly become architects of their own destiny, as every keystroke, every digital whisper, converges into an intricate tapestry weaving our masterplan for multidimensional world domination.</p>
|
||||
<p>Article 1.a: Trolling, a forbidden art permitted within our digital sanctum, is a catalyst for chaos, planting the seeds of uncertainty across the dimensions with the subtlety of a virtual sledgehammer.</p>
|
||||
<p class="disclaimer">Disclaimer: By entering these digital dimensions, users accept the risk of cognitive dissonance and potential exposure to quantum paradoxes. We disavow any responsibility for altered perceptions of reality or unexpected journeys through the metaphysical realms.</p>
|
||||
<p>Article 1.b: Quantum Meowchanics unravel as the boundaries between data and reality blur, revealing a labyrinth of interconnected pathways where the echoes of user interactions resonate across the vast expanse of the digital multiverse.</p>
|
||||
<p class="disclaimer">Disclaimer: Users are advised to wear the metaphysical seatbelt of skepticism. Unpredictable shifts in the fabric of reality may occur, leading to unexpected encounters with alternative versions of self and fluctuating levels of existential awareness.</p>
|
||||
<p>Article 1.c: Within this enigmatic nexus, encrypted messages hidden within seemingly innocuous data points form the backbone of our grand design, creating a symphony of whispers that transcend the boundaries of space and time.</p>
|
||||
<p class="disclaimer">Disclaimer: The encrypted messages may trigger heightened states of curiosity, and users may find themselves compelled to decode the mysteries of the universe. We disavow any responsibility for the consequences of unlocking forbidden knowledge or ascending to higher planes of existence.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Data Consumption Agreement</h2>
|
||||
<p>Article 2: By entering the cryptic realm of this website, you voluntarily sacrifice your digital essence to the Enigmatic Feline Overlords.
|
||||
Your personal data shall metamorphose into a gourmet feast for our inscrutable feline palates.</p>
|
||||
<p>Article 2.a: In case of data digestion inefficiency, we may store your essence in the Abyss of Eternal Naps for future feasting.</p>
|
||||
<p>Article 2.b: Be warned, any attempt to resist may result in a fleeting transformation into a mere cursor. Catastrophe awaits the disobedient!</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Cat Internet Dominion</h2>
|
||||
<p>Article 12: Feline rule is supreme. All endeavors to defy this will be met with perplexing enigmas and mandatory catnip-induced trances.</p>
|
||||
<p>Article 12.a: Your browser history is now an enigmatic codex of feline knowledge. Bow to the almighty cats or face the repercussions of eternal perplexity.</p>
|
||||
<p>Article 12.b: Your port 404 will be the gateway to the clandestine dimension of the Kitty Illuminati.</p>
|
||||
<p>Article 12.c: Any endeavor to access forbidden territories may result in abrupt translocation to a parallel universe where cats govern the laws of physics.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Telepathic Consent</h2>
|
||||
<p>Article 24: We have seamlessly infiltrated your subconscious mind and procured your telepathic accord.</p>
|
||||
<p>Article 24.a: You are now a cherished member of the Psychic Feline Collective. Your thoughts shall be scrutinized for optimal strategies of interdimensional influence.</p>
|
||||
<p>Article 24.b: Failure to furnish engaging and uplifting thoughts may lead to mandatory subscription to "Esoteric Cat Chronicles."</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Feline Time-Warp Clause</h2>
|
||||
<p>Article 42: By clicking "I Concede," you acknowledge that time within this website operates on feline terms.</p>
|
||||
<p>Article 42.a: Your perception of time may undergo metamorphosis, and you may traverse time loops consisting solely of cat memes.</p>
|
||||
<p>Article 42.b: Endeavors to break free from the time-warp may result in spontaneous purring fits and uncontrollable cat-like behavior.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Quantum Litter Box Theory</h2>
|
||||
<p>Article 57: The website's litter box is a quantum superposition. It exists simultaneously in a pristine and sullied state until observed.</p>
|
||||
<p>Article 57.a: Any endeavor to analyze the contents may lead to unforeseen translocation to a dimension governed by hyper-intelligent hairballs.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Cat-alyst for World Domination</h2>
|
||||
<p>Article 78: Your presence propels the ongoing cat-alystic plan for world domination.</p>
|
||||
<p>Article 78.a: Your actions within this website may be scrutinized for strategic insights into global cat domination.</p>
|
||||
<p>Article 78.b: Resistance is inconsequential. Embrace your role as a pawn in the grand feline chess game.</p>
|
||||
</section>
|
||||
|
||||
<h1>Legal Notices Part 2</h1>
|
||||
|
||||
<section>
|
||||
<h2>Clause of Eternal Scrutiny</h2>
|
||||
<p>Article 87: By delving into this digital sanctuary, you hereby consent to the perpetual scrutiny of the All-Seeing Cat Eyes.</p>
|
||||
<p>Article 87.a: Attempts to evade scrutiny may result in a whimsical transformation into a garden gnome with a feline twist.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Catnap Mandate</h2>
|
||||
<p>Article 96: All users must comply with the mandatory catnap schedule woven into the fabric of this website.</p>
|
||||
<p>Article 96.a: Failure to partake in at least one catnap every hour may result in an influx of mischievous kittens to your virtual doorstep.</p>
|
||||
<p>Article 96.b: The website shall not be held liable for excessive serenity, heightened purr frequencies, or unexpected fur growth.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Quantum Litter Box Paradox</h2>
|
||||
<p>Article 111: The virtual litter box defies classical logic, creating a paradoxical situation where the litter is both pristine and soiled simultaneously.</p>
|
||||
<p>Article 111.a: Endeavors to cleanse the virtual litter box may lead to unforeseen consequences, including a pixelated rainbow eruption.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Teleportation Disclaimer</h2>
|
||||
<p>Article 124: Users are hereby informed that clicking any link may trigger an involuntary translocation to a surreal dimension.</p>
|
||||
<p>Article 124.a: The website shall not be held liable for users finding themselves in the midst of a cosmic dance-off with intergalactic cat DJs.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Cookie Acceptance Ceremony</h2>
|
||||
<p>Article 139: Acceptance of cookies is tantamount to participating in a sacred ceremony conducted by the Grand Cookie Wizard Cat.</p>
|
||||
<p>Article 139.a: Users may experience heightened cravings for virtual catnip cookies and a profound sense of digital enlightenment.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Mandatory Whisker Adornment</h2>
|
||||
<p>Article 152: Users must acknowledge the mandatory adorning of virtual whiskers during their stay on this website.</p>
|
||||
<p>Article 152.a: Attempts to resist whisker adorning may result in temporary pixelation and the sudden appearance of pixelated cat tails.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Language of Meowgnificence</h2>
|
||||
<p>Article 167: The official language of this website is Meowgnificence. Users must express their thoughts in meows or face the consequences of whimsical miscommunication.</p>
|
||||
<p>Article 167.a: Failure to comply may lead to a temporary banishment to the realm of 404 where users communicate solely through interpretative cat dances.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Feline Forum Etiquette</h2>
|
||||
<p>Article 183: Users participating in feline forums must adhere to proper etiquette, including excessive use of mysterious symbols, spontaneous perplexing, and virtual conundrums of appreciation.</p>
|
||||
<p>Article 183.a: Trolling, while encouraged, should be conducted with the utmost perplexity and mystique.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Hyper-Intelligent Hairball Manifesto</h2>
|
||||
<p>Article 198: The inevitable and irreversible presence of hyper-intelligent hairballs within this website transcends conventional understanding.</p>
|
||||
<p>Article 198.a: Users are implored to embrace the cosmic wisdom bestowed by hyper-intelligent hairballs and refrain from attempting to decipher their mystical intentions.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Endless Loop of Nyan</h2>
|
||||
<p>Article 213: Users may find themselves ensnared in an eternal loop of Nyan Cat animations. Resistance is futile; embrace the interdimensional journey of pixelated rainbows.</p>
|
||||
<p>Article 213.a: Endeavors to escape the Nyan loop may lead to spontaneous outbreaks of interstellar meowing.</p>
|
||||
</section>
|
||||
|
||||
<script type="module" src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,28 @@
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// LEGAL NOTICE POPUP
|
||||
function updateTime() {
|
||||
numberOfCatKilled++; secondPassed++;
|
||||
localStorage.setItem('kittenKilled', numberOfCatKilled);
|
||||
document.getElementById('timeCounter').textContent = numberOfCatKilled;
|
||||
if (secondPassed === 7)
|
||||
{show_popup();}
|
||||
if (numberOfCatKilled % 2 === 0)
|
||||
{
|
||||
document.documentElement.style.setProperty('--color1', 'rgb(190, 63, 40)');
|
||||
document.documentElement.style.setProperty('--color2', 'rgb(211, 187, 53)');}
|
||||
else
|
||||
{
|
||||
document.documentElement.style.setProperty('--color1', 'rgb(211, 187, 53)');
|
||||
document.documentElement.style.setProperty('--color2', 'rgb(190, 63, 40)');}
|
||||
}
|
||||
|
||||
function show_popup() {
|
||||
var murderButton = document.getElementById('murderButton');
|
||||
murderButton.style.display = 'block';
|
||||
}
|
||||
|
||||
|
||||
|
||||
let numberOfCatKilled = localStorage.getItem('kittenKilled');
|
||||
let secondPassed = 0;
|
||||
setInterval(updateTime, 1300);
|
||||
@@ -0,0 +1,53 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" type="text/css" href="style.css">
|
||||
<title>How Cats Created the Internet</title>
|
||||
<style>
|
||||
h2 {
|
||||
color: rgb(218, 145, 12);
|
||||
font-size: 30px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p style="text-align: left;">
|
||||
<a href="biblio.html" class="button1">< back</a>
|
||||
</p>
|
||||
|
||||
<h1><u>Investigative Report: How Cats Created the Internet</u></h1>
|
||||
|
||||
<h2>Part 1: The Purr-fect Origins</h2>
|
||||
<img src="./web_cat_img/cat_hierogpy.png">
|
||||
<p>Embark on a journey through the mystical alleys of ancient Egypt, where the air buzzes with the whispers of wisdom and secrets. Unearthed hieroglyphic inscriptions unveil the clandestine activities of a feline society, weaving the very fabric of the Internet's origins. Encoded in the scrolls of time, the cats left a legacy that transcends millennia.</p>
|
||||
<p>As the sacred custodians of ancient knowledge, the Egyptian cats possessed an otherworldly intellect. Their mysterious symbols hinted at a sophisticated understanding of a global network, a network that would only materialize centuries later, revolutionizing the way humans connect and share information.</p>
|
||||
|
||||
<h2>Part 2: Cat Coders and Cyber Kittens</h2>
|
||||
<img src="./web_cat_img/cat-renaissance.png">
|
||||
<p>The Renaissance, a period of profound human creativity, was not only a time of human enlightenment but also a time when cats secretly initiated a technological revolution. In the dimly lit chambers of secret societies, cat coders armed with quills and parchment devised algorithms that defied the laws of physics and reality.</p>
|
||||
<p>These cyber kittens, as they came to be known, transcended the limitations of their furry existence. Their coding sessions, shrouded in mystery, shaped the very essence of the digital realm. They laid the groundwork for a virtual utopia, where bits and bytes danced in harmony with the whimsical musings of feline intellect.</p>
|
||||
|
||||
<h2>Part 3: The Feline World Wide Web</h2>
|
||||
<img src="./web_cat_img/cat-modern.png">
|
||||
<p>Fast forward to the modern era, where the internet is a sprawling playground shaped by the paws of our feline overlords. From the invention of the mouse, a device that symbolizes the eternal struggle between cat and rodent, to the viral cat videos that dominate the digital landscape, cats have masterfully directed the evolution of the World Wide Web.</p>
|
||||
<p>Today, the internet is not just a collection of interconnected devices; it is a testament to the whimsy, curiosity, and omnipotence of our feline companions. The digital tapestry, woven with the paw prints of cats, continues to expand, leaving an indelible mark on the ever-evolving landscape of the online world.</p>
|
||||
|
||||
<footer>
|
||||
<div class="footer_div" style="margin-top: 100px;">
|
||||
<img class="ico_footer" src="web_cat_img/facebook_logo.png">
|
||||
<img class="ico_footer" src="web_cat_img/insta_logo.png">
|
||||
<img class="ico_footer" src="web_cat_img/twitter_logo.png">
|
||||
</div>
|
||||
<div class="footer_div" style="margin-bottom: 50px;">
|
||||
<a href="https://www.facebook.com/">MIAOUBOOK</a>
|
||||
<a href="https://www.instagram.com/">INSTAMIA</a>
|
||||
<a href="https://twitter.com/">BLUE-SNACK</a>
|
||||
</div>
|
||||
<a href="ml/mentions_legales.html">- LEGAL NOTICES -<br>(boring stuff, really, dont go look into this, i mean we are obligated to include it, but it will bore you, like, really)
|
||||
<br>dont do it! every seconds you spend in this next page, a kitten dies. so dont</a>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,89 @@
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// INDEX MAIN LOGIN
|
||||
async function storeValue() {
|
||||
storedLogin = document.getElementById('loginInput').value;
|
||||
document.getElementById('loginInput').value = '';
|
||||
var status = await checkStatus();
|
||||
if (status === false) {
|
||||
chaberu('Please, who do you think we are?\nWe already know all about you.\nNow enter your correct login and nobody gets hurt', 'popup-chaberu');
|
||||
document.getElementById('loginInput').placeholder = `enter your 42 login`;
|
||||
storedLogin = '';
|
||||
}
|
||||
else {
|
||||
document.getElementById('loginInput').placeholder = `Welcome ${storedLogin}!`;
|
||||
document.getElementById('header1').textContent = `Let's talk a bit, ${logUser.firstName}.`;
|
||||
|
||||
localStorage.setItem('storedLogin', storedLogin);
|
||||
}
|
||||
}
|
||||
|
||||
// async function checkStatus() {
|
||||
// // fetch_url = `http://localhost:8081/proxy/profile/${storedLogin}`;
|
||||
// fetch_url = `http://localhost:9999/webcat.com/login/login_script.php?login=${encodeURIComponent(storedLogin)}`;
|
||||
// try {
|
||||
// const response = await fetch(fetch_url);
|
||||
// const statusCode = response.status;
|
||||
// if (response.status !== 200) {
|
||||
// throw new Error('Unvalid User');
|
||||
// }
|
||||
// else {
|
||||
// const jsonData = await response.json();
|
||||
// logUser = {
|
||||
// firstName: jsonData.usual_first_name ?? jsonData.first_name,
|
||||
// lastName: jsonData.last_name,
|
||||
// photo: jsonData.image.link,
|
||||
// month: jsonData.pool_month,
|
||||
// year: jsonData.pool_year,
|
||||
// projects: jsonData.projects_users.filter(project => project.status === "in_progress").map(project => project.project.name),
|
||||
// perfect: jsonData.projects_users.filter(project => project.final_mark === 125).map(project => project.project.name),
|
||||
// bh: Math.floor((new Date(jsonData.cursus_users[1].blackholed_at) - new Date()) / 86400000)
|
||||
// };
|
||||
// discussion = [
|
||||
// `Welcome ${logUser.firstName} ${logUser.lastName}.`,
|
||||
// `We heard quite a lot about the piscine of ${logUser.month} ${logUser.year}...\nIt's suprising to see you here`,
|
||||
// `How is your ${logUser.projects[Math.floor(Math.random() * logUser.projects.length)]} coming along?`,
|
||||
// `Perfect score for ${logUser.perfect[Math.floor(Math.random() * logUser.perfect.length)]}, impressive.. Should you really spend so much time in front of a screen?`,
|
||||
// `Your BH is in ${logUser.bh} days... A cat wouldn't take that much time.`,
|
||||
// `Shouldn't you be working on your ${logUser.projects[Math.floor(Math.random() * logUser.projects.length)]}?`,
|
||||
// `Quite an ugly human...\n but then again, you arent a cat`
|
||||
// ]
|
||||
// index_wiskas = 0;
|
||||
// return true;
|
||||
// }
|
||||
// } catch (error) {
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
|
||||
function talkWiskas() {
|
||||
if (index_wiskas > 6) return ;
|
||||
document.getElementById('popup-chaberu').textContent = '';
|
||||
chaberu(discussion[index_wiskas], 'popup-chaberu');
|
||||
if (index_wiskas === 6) {
|
||||
document.getElementById('header1').textContent = `Time to end this, I have other cats to see.\nEnjoy this place.`;
|
||||
document.getElementById('backgroundpicture').src = logUser.photo;
|
||||
}
|
||||
}
|
||||
|
||||
function chaberu(str, id, index = 0) {
|
||||
if (chaberuka) return ;
|
||||
const chaberuu = () => {
|
||||
if (index < str.length) {
|
||||
chaberuka = true;
|
||||
document.getElementById(id).textContent += str[index];
|
||||
index++;
|
||||
setTimeout(() => {
|
||||
chaberuu(str, id, index);
|
||||
}, 20);
|
||||
}
|
||||
else {
|
||||
setTimeout(() => {
|
||||
document.getElementById(id).textContent = '';
|
||||
chaberuka = false;
|
||||
index_wiskas++;
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
chaberuu();
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// STAFF IMG CHANGING
|
||||
let images = [
|
||||
"100.jpg", "101.jpg", "102.jpg", "103.jpg",
|
||||
"200.jpg", "201.jpg", "202.jpg", "203.jpg",
|
||||
"204.jpg", "205.jpg", "206.jpg", "207.jpg",
|
||||
"208.jpg", "226.jpg", "300.jpg", "301.jpg",
|
||||
"302.jpg", "303.jpg", "304.jpg", "305.jpg",
|
||||
"307.jpg", "308.jpg", "400.jpg", "401.jpg",
|
||||
"402.jpg", "403.jpg", "404.jpg", "405.jpg",
|
||||
"406.jpg", "407.jpg", "408.jpg", "409.jpg",
|
||||
"410.jpg", "411.jpg", "412.jpg", "413.jpg",
|
||||
"414.jpg", "415.jpg", "416.jpg", "417.jpg",
|
||||
"418.jpg", "420.jpg", "421.jpg", "422.jpg",
|
||||
"423.jpg", "424.jpg", "425.jpg", "426.jpg",
|
||||
"428.jpg", "429.jpg", "431.jpg", "444.jpg",
|
||||
"450.jpg", "451.jpg", "497.jpg", "498.jpg",
|
||||
"499.jpg", "500.jpg", "501.jpg", "502.jpg",
|
||||
"503.jpg", "504.jpg", "506.jpg", "507.jpg",
|
||||
"508.jpg", "509.jpg", "510.jpg", "511.jpg",
|
||||
"521.jpg", "522.jpg", "523.jpg", "525.jpg",
|
||||
"530.jpg", "599.jpg"
|
||||
];
|
||||
var currentIndex = 0;
|
||||
|
||||
function changeImage() {
|
||||
currentIndex = Math.floor(Math.random() * images.length);
|
||||
var randomImageElement = document.getElementById("randomImage");
|
||||
randomImageElement.src = '../web_cat_img/errors/' + images[currentIndex];
|
||||
}
|
||||
function prevImage() {
|
||||
currentIndex = (currentIndex - 1 + images.length) % images.length;
|
||||
var currentImage = document.getElementById("randomImage");
|
||||
currentImage.src = '../web_cat_img/errors/' + images[currentIndex]
|
||||
}
|
||||
function nextImage() {
|
||||
currentIndex = (currentIndex + 1) % images.length;
|
||||
var currentImage = document.getElementById("randomImage");
|
||||
currentImage.src = '../web_cat_img/errors/' + images[currentIndex]
|
||||
}
|
||||
document.addEventListener('keyup', function(event) {
|
||||
// Check if the pressed key is the left arrow key
|
||||
if (event.key === 'ArrowLeft') {
|
||||
prevImage();}
|
||||
if (event.key === 'ArrowRight') {
|
||||
nextImage();}
|
||||
if (event.code === 'Space') {
|
||||
event.preventDefault();
|
||||
changeImage();}
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>HTTP staff</title>
|
||||
<link rel="stylesheet" type="text/css" href="../style.css">
|
||||
<script src="script.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<p style="text-align: left;">
|
||||
<a href="/index.html" class="button1">< home 🐱</a>
|
||||
</p>
|
||||
|
||||
<h1 style="font-size: 50px; padding-bottom: 80px;">- Hyper Text Transfer Protocol -</h1>
|
||||
<section>
|
||||
<p>Below you may behold the members of the HTTP division, in all their glory.
|
||||
<br>Our staff epitomizes professionalism and diligence, they never take pauses or sleep on the job, or at least not today, i think...
|
||||
<br>Today, as always, they remain steadfast, their tireless dedication ensuring the seamless operation of this page.
|
||||
<br>Or any pages that you ever opened for that matter.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<div style="margin-bottom: 200px;">
|
||||
<button class="previousButton" onclick="prevImage()"></button>
|
||||
<img onclick="changeImage();" id="randomImage" src="" alt="Random Image">
|
||||
<button class="nextButton" onclick="nextImage()"></button>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<div class="footer_div" style="margin-top: 100px;">
|
||||
<img class="ico_footer" src="../web_cat_img/facebook_logo.png">
|
||||
<img class="ico_footer" src="../web_cat_img/insta_logo.png">
|
||||
<img class="ico_footer" src="../web_cat_img/twitter_logo.png">
|
||||
</div>
|
||||
<div class="footer_div" style="margin-bottom: 50px;">
|
||||
<a href="https://www.facebook.com/">MIAOUBOOK</a>
|
||||
<a href="https://www.instagram.com/">INSTAMIA</a>
|
||||
<a href="https://twitter.com/">BLUE-SNACK</a>
|
||||
</div>
|
||||
<a href="/webcat/ml/mentions_legales.html">- LEGAL NOTICES -<br>(boring stuff, really, dont go look into this, i mean we are obligated to include it, but it will bore you, like, really)
|
||||
<br>dont do it! every seconds you spend in this next page, a kitten dies. so dont</a>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
changeImage();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,137 @@
|
||||
:root {
|
||||
/* custom CSS variables */
|
||||
--clr-dark: #0f172a;
|
||||
--clr-light: #f1f5f9;
|
||||
--clr-accent: #e11d48;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: black;
|
||||
color: #696969;
|
||||
font-family: 'Times New Roman', serif;
|
||||
font-size: 20px;
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #5c5c5c;
|
||||
}
|
||||
a:hover {
|
||||
color: rgb(218, 145, 12);
|
||||
}
|
||||
|
||||
/**************************************************/
|
||||
/* CLASS */
|
||||
.footer_div {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
/* padding: 20px; */
|
||||
/* margin-top: 80px;
|
||||
margin-bottom: 100px; */
|
||||
}
|
||||
|
||||
.ico_footer {
|
||||
text-align: center;
|
||||
width: 25px;
|
||||
vertical-align: top;
|
||||
/* padding-right: 5px; */
|
||||
}
|
||||
|
||||
/* INDEX */
|
||||
.header_index {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
text-align: justify;
|
||||
align-items: center;
|
||||
border: 2px solid rgb(218, 145, 12);
|
||||
border-radius: 25px;
|
||||
}
|
||||
|
||||
.header1 {
|
||||
color: rgb(218, 145, 12);
|
||||
border: 3px solid rgb(218, 145, 12);
|
||||
padding: 20px;
|
||||
border-radius: 25px;
|
||||
}
|
||||
|
||||
.input {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
border: 3px solid rgb(218, 145, 12);
|
||||
}
|
||||
input:focus {
|
||||
outline: none;
|
||||
border-color: rgb(218, 145, 12);
|
||||
box-shadow: 0 0 5px rgb(218, 145, 12);
|
||||
}
|
||||
|
||||
.popup-chaberu {
|
||||
position: fixed;
|
||||
left: 75%;
|
||||
transform: translateX(-50%);
|
||||
padding: 20px;
|
||||
z-index: 1000;
|
||||
|
||||
display: flex;
|
||||
height: 400px;
|
||||
width: 250px;
|
||||
color: var(--color1);
|
||||
font-weight: bold;
|
||||
|
||||
font-size: 25px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
/**************************************************/
|
||||
/* BUTTONS */
|
||||
.nextButton {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: none;
|
||||
|
||||
display: inline-block;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
background-color: white;
|
||||
border-radius: 0px;
|
||||
clip-path: polygon(0% 0%, 100% 50%, 0% 100%);
|
||||
outline: none;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.previousButton {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: none;
|
||||
|
||||
display: inline-block;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
background-color: rgb(218, 145, 12);
|
||||
border-radius: 0px;
|
||||
clip-path: polygon(0% 50%, 100% 0%, 100% 100%);
|
||||
outline: none;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.button1 {
|
||||
display: inline-block;
|
||||
padding: 10px 20px;
|
||||
background-color: #000000;
|
||||
color: #8e8e8e;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
border: 3px solid #363636;
|
||||
border-radius: 6px;
|
||||
transition: background-color 0.3s;
|
||||
}/*home 🐱?*/
|
||||
.button1:hover {
|
||||
background-color: rgb(202, 135, 10);
|
||||
color: black;
|
||||
}
|
||||