5 Commits

Author SHA1 Message Date
Georges-Leonard Prunet 55c241fd61 notification for login/register 2026-03-20 17:57:17 +01:00
Georges-Leonard Prunet 592bb38c0d fixed next drawer 2026-03-20 17:29:06 +01:00
H3XploR 72bc9ea628 added shield 2026-03-19 14:38:56 +01:00
H3XploR 557cf23f71 reset before join 2026-03-19 14:14:20 +01:00
H3XploR b51b711b10 ajout de theme 2026-03-19 14:00:20 +01:00
71 changed files with 547 additions and 1119 deletions
-10
View File
@@ -1,10 +0,0 @@
POSTGRES_PASSWORD=coucou
JWT_SECRET=superlongsecretkeyatleast32characterspleasenevercommitthis
POSTGRES_DB=database
POSTGRES_HOST=database
POSTGRES_USER=user
GITHUB_CLIENT_ID=Ov23li6ovg3fzec5IO5D
GITHUB_CLIENT_SECRET=0345e959e8f0e9f784061c5c90ee227ddb2ef9ab
GITHUB_CALLBACK_URL=http://localhost:8080/api/auth/github/callback
pogpog
+3 -454
View File
@@ -1,465 +1,14 @@
all :
@$(call random_shmol_cat, "hELLO", "nice human corrector", $(CLS), )
@docker compose -f ./docker-compose.yml up -d
all : up
no_cache :
@docker compose -f ./docker-compose.yml build --no-cache
up :
@docker compose -f ./docker-compose.yml up -d
clean :
@$(call print_cat, $(CLEAR), $(C_225), $(C_320), $(C_450), $(call pad_word, 10, "Objects"), $(call pad_word, 12, "Exterminated"));
@docker compose -f ./docker-compose.yml down -t 1
fclean :
@$(call print_cat, $(CLEAR), $(C_120), $(C_300), $(C_210), $(call pad_word, 10, "Allclean"), $(call pad_word, 12, "Miaster"));
@docker compose -f ./docker-compose.yml down -v -t 1
@docker system prune -af --volumes
re : fclean no_cache
@$(call print_cat, $(CLEAR), $(C_120), $(C_300), $(C_210), $(call pad_word, 10, "Re-Doing"), $(call pad_word, 12, "Miaster"));
re : fclean up
.PHONY : all no_cache clean fclean re
# ╭────────────────────────────────────────────────────────────────────────────╮
# │─██████████████─██████████████─██████████████─██████─────────██████████████─│
# │─██░░░░░░░░░░██─██░░░░░░░░░░██─██░░░░░░░░░░██─██░░██─────────██░░░░░░░░░░██─│
# │─██████░░██████─██░░██████░░██─██░░██████░░██─██░░██─────────██░░██████████─│
# │─────██░░██─────██░░██──██░░██─██░░██──██░░██─██░░██─────────██░░██─────────│
# │─────██░░██─────██░░██──██░░██─██░░██──██░░██─██░░██─────────██░░██████████─│
# │─────██░░██─────██░░██──██░░██─██░░██──██░░██─██░░██─────────██░░░░░░░░░░██─│
# │─────██░░██─────██░░██──██░░██─██░░██──██░░██─██░░██─────────██████████░░██─│
# │─────██░░██─────██░░██──██░░██─██░░██──██░░██─██░░██─────────────────██░░██─│
# │─────██░░██─────██░░██████░░██─██░░██████░░██─██░░██████████─██████████░░██─│
# │─────██░░██─────██░░░░░░░░░░██─██░░░░░░░░░░██─██░░░░░░░░░░██─██░░░░░░░░░░██─│
# │─────██████─────██████████████─██████████████─██████████████─██████████████─│
# ╰────────────────────────────────────────────────────────────────────────────╯
# --------------------------------------------------------------------------------- >
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, TheCake), $(call pad_word, 14, IsALie...), $(CLS), $(RESET));
@$(call random_cat, $(call pad_word, 13, TheCake), $(call pad_word, 15, IsALie...), , $(RESET));
# $(call pad_word, 12, TheCake)
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, "TheCake"), $(call pad_word, 12, "IsALie..."));
# 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
-2
View File
@@ -24,8 +24,6 @@ services:
build: ./srcs/backend
expose:
- "3001"
# ports:
# - "3001:3001"
depends_on:
- database
volumes:
@@ -730,6 +730,16 @@ function setupSocketIO(io)
_tetrisRelayToOpponent(socket, 'tetris:lines-cleared', data);
});
// Relay pur : shield-activated → adversaire uniquement
socket.on('tetris:shield-activated', () => {
_tetrisRelayToOpponent(socket, 'tetris:shield-activated', {});
});
// Relay pur : shield-deactivated → adversaire uniquement
socket.on('tetris:shield-deactivated', () => {
_tetrisRelayToOpponent(socket, 'tetris:shield-deactivated', {});
});
// start-duel → relayé aux DEUX joueurs de la room (inclut l'émetteur)
socket.on('tetris:start-duel', () => {
const code = socket.tetrisRoomCode;
+1 -36
View File
@@ -16,12 +16,10 @@ import { StatsWindow } from './stats.js';
*/
class App {
constructor() {
console.log("APP STARTED");
this.initWindows();
this.initMenu();
this.initPage();
this.initEasterEgg();
this.colorizeUI();
}
/**
@@ -107,39 +105,6 @@ 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
@@ -147,4 +112,4 @@ if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => new App());
} else {
new App();
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 994 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1018 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 955 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1022 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 887 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1000 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

@@ -1,87 +0,0 @@
.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-1 { -webkit-mask-image: url('assets/doodles/ball.png'); mask-image: url('assets/doodles/ball.png'); left: 10vw; top: 10vh; }
.doodle-2 { -webkit-mask-image: url('assets/doodles/batman.png'); mask-image: url('assets/doodles/batman.png'); left: 20vw; top: 15vh; }
.doodle-3 { -webkit-mask-image: url('assets/doodles/building.png'); mask-image: url('assets/doodles/building.png'); left: 30vw; top: 20vh; }
.doodle-4 { -webkit-mask-image: url('assets/doodles/butterfly.png'); mask-image: url('assets/doodles/butterfly.png'); left: 40vw; top: 25vh; }
.doodle-5 { -webkit-mask-image: url('assets/doodles/car.png'); mask-image: url('assets/doodles/car.png'); left: 50vw; top: 30vh; }
.doodle-6 { -webkit-mask-image: url('assets/doodles/cat.png'); mask-image: url('assets/doodles/cat.png'); left: 60vw; top: 35vh; }
.doodle-7 { -webkit-mask-image: url('assets/doodles/clouds.png'); mask-image: url('assets/doodles/clouds.png'); left: 70vw; top: 40vh; }
.doodle-8 { -webkit-mask-image: url('assets/doodles/controls.png'); mask-image: url('assets/doodles/controls.png'); left: 80vw; top: 45vh; }
.doodle-9 { -webkit-mask-image: url('assets/doodles/dead.png'); mask-image: url('assets/doodles/dead.png'); left: 90vw; top: 50vh; }
.doodle-10 { -webkit-mask-image: url('assets/doodles/diamant.png'); mask-image: url('assets/doodles/diamant.png'); left: 15vw; top: 55vh; }
.doodle-11 { -webkit-mask-image: url('assets/doodles/dice.png'); mask-image: url('assets/doodles/dice.png'); left: 25vw; top: 60vh; }
.doodle-12 { -webkit-mask-image: url('assets/doodles/earth.png'); mask-image: url('assets/doodles/earth.png'); left: 35vw; top: 65vh; }
.doodle-13 { -webkit-mask-image: url('assets/doodles/egypt.png'); mask-image: url('assets/doodles/egypt.png'); left: 45vw; top: 70vh; }
.doodle-14 { -webkit-mask-image: url('assets/doodles/fire.png'); mask-image: url('assets/doodles/fire.png'); left: 55vw; top: 75vh; }
.doodle-15 { -webkit-mask-image: url('assets/doodles/fish.png'); mask-image: url('assets/doodles/fish.png'); left: 65vw; top: 80vh; }
.doodle-16 { -webkit-mask-image: url('assets/doodles/flag.png'); mask-image: url('assets/doodles/flag.png'); left: 75vw; top: 85vh; }
.doodle-17 { -webkit-mask-image: url('assets/doodles/hearts.png'); mask-image: url('assets/doodles/hearts.png'); left: 85vw; top: 90vh; }
.doodle-18 { -webkit-mask-image: url('assets/doodles/house.png'); mask-image: url('assets/doodles/house.png'); left: 5vw; top: 45vh; }
.doodle-19 { -webkit-mask-image: url('assets/doodles/idol.png'); mask-image: url('assets/doodles/idol.png'); left: 12vw; top: 22vh; }
.doodle-20 { -webkit-mask-image: url('assets/doodles/lotus.png'); mask-image: url('assets/doodles/lotus.png'); left: 22vw; top: 32vh; }
.doodle-21 { -webkit-mask-image: url('assets/doodles/mail.png'); mask-image: url('assets/doodles/mail.png'); left: 32vw; top: 42vh; }
.doodle-22 { -webkit-mask-image: url('assets/doodles/moon.png'); mask-image: url('assets/doodles/moon.png'); left: 42vw; top: 52vh; }
.doodle-23 { -webkit-mask-image: url('assets/doodles/pokeball.png'); mask-image: url('assets/doodles/pokeball.png'); left: 52vw; top: 62vh; }
.doodle-24 { -webkit-mask-image: url('assets/doodles/runes.png'); mask-image: url('assets/doodles/runes.png'); left: 62vw; top: 72vh; }
.doodle-25 { -webkit-mask-image: url('assets/doodles/shield.png'); mask-image: url('assets/doodles/shield.png'); left: 72vw; top: 82vh; }
.doodle-26 { -webkit-mask-image: url('assets/doodles/shiny.png'); mask-image: url('assets/doodles/shiny.png'); left: 82vw; top: 12vh; }
.doodle-27 { -webkit-mask-image: url('assets/doodles/snail.png'); mask-image: url('assets/doodles/snail.png'); left: 92vw; top: 22vh; }
.doodle-28 { -webkit-mask-image: url('assets/doodles/sound.png'); mask-image: url('assets/doodles/sound.png'); left: 18vw; top: 82vh; }
.doodle-29 { -webkit-mask-image: url('assets/doodles/spiral.png'); mask-image: url('assets/doodles/spiral.png'); left: 28vw; top: 72vh; }
.doodle-30 { -webkit-mask-image: url('assets/doodles/star.png'); mask-image: url('assets/doodles/star.png'); left: 38vw; top: 62vh; }
.doodle-31 { -webkit-mask-image: url('assets/doodles/stop.png'); mask-image: url('assets/doodles/stop.png'); left: 48vw; top: 52vh; }
.doodle-32 { -webkit-mask-image: url('assets/doodles/sun.png'); mask-image: url('assets/doodles/sun.png'); left: 58vw; top: 42vh; }
.doodle-33 { -webkit-mask-image: url('assets/doodles/tree.png'); mask-image: url('assets/doodles/tree.png'); left: 68vw; top: 32vh; }
.doodle-34 { -webkit-mask-image: url('assets/doodles/triskel.png'); mask-image: url('assets/doodles/triskel.png'); left: 78vw; top: 22vh; }
.doodle-35 { -webkit-mask-image: url('assets/doodles/yin_yang.png'); mask-image: url('assets/doodles/yin_yang.png'); left: 88vw; top: 12vh; }
/* 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) */
}
-117
View File
@@ -1,117 +0,0 @@
// Function to update a specific shape's color and position
function updateShape(id, x, y, color) {
const element = document.getElementById(id);
if (element) {
element.style.left = x + "px";
element.style.top = y + "px";
element.style.backgroundColor = color;
}
}
// Example usage: Move shape1 to (100, 100) and make it red
// updateShape('shape1', 100, 100, '#ff0000');
function moveRandomly(id) {
const element = document.getElementById(id);
if (!element) return;
// Calculate random coordinates
// We subtract 300 so the shape doesn't go partially off-screen (since your width is 300px)
const maxX = window.innerWidth - 300;
const maxY = window.innerHeight - 300;
const randomX = Math.floor(Math.random() * maxX);
const randomY = Math.floor(Math.random() * maxY);
// Generate a random HEX color
const randomColor = "#" + Math.floor(Math.random()*16777215).toString(16);
// Apply the changes
element.style.left = randomX + "px";
element.style.top = randomY + "px";
element.style.backgroundColor = randomColor;
}
// To make it move every 2 seconds automatically:
// setInterval(() => moveRandomly('shape1'), 2000);
// setInterval(() => moveRandomly('shape2'), 2000);
function 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 (Corrected)
// 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);
}
// This loop runs 35 times, once for each shape ID
for (let i = 1; i <= 35; i++) {
// Generate a random speed between 1 and 4 for each shape
// so they don't all move at the exact same pace
const randomSpeed = 1 + Math.random() * 3;
// Call your function using the ID 'shape1', 'shape2', etc.
startSmoothRandomMove(`shape${i}`, randomSpeed);
}
function randomizeAnimationStarts() {
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() * - 12;
// Apply it directly to the element's style
shape.style.animationDelay = randomDelay + "s";
});
}
// Call this once when the script loads
randomizeAnimationStarts();
+31 -5
View File
@@ -9,11 +9,12 @@ class Duel {
this.onStatusChange = onStatusChange; // (status, opponentName) => void
this.onStart = onStart; // () => void — déclenche le début du jeu local
this.action_queue = [];
this.opponentGrid = this._emptyGrid();
this.opponentScore = 0;
this.roomCode = null;
this.isReady = false;
this.action_queue = [];
this.opponentGrid = this._emptyGrid();
this.opponentScore = 0;
this.opponentShieldActive = false;
this.roomCode = null;
this.isReady = false;
this._bindSocketEvents();
}
@@ -60,6 +61,15 @@ class Duel {
this.endDuel();
}
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');
}
}
endDuel() {
this.isReady = false;
this.action_queue = [];
@@ -92,6 +102,14 @@ class Duel {
showOverlay('YOU WIN', action.score);
this.endDuel();
break;
case 'OPPONENT_SHIELD_ACTIVATED':
this.opponentShieldActive = true;
break;
case 'OPPONENT_SHIELD_DEACTIVATED':
this.opponentShieldActive = false;
break;
}
}
@@ -127,6 +145,14 @@ class Duel {
this.action_queue.push({ type: 'OPPONENT_GAME_OVER', score: data.score, validBlock: data.validBlock });
});
this.socket.on('tetris:shield-activated', () => {
this.action_queue.push({ type: 'OPPONENT_SHIELD_ACTIVATED' });
});
this.socket.on('tetris:shield-deactivated', () => {
this.action_queue.push({ type: 'OPPONENT_SHIELD_DEACTIVATED' });
});
this.socket.on('tetris:start-duel', () => {
if (this.onStart) this.onStart();
});
+129 -235
View File
@@ -1,25 +1,26 @@
:root {
--color-primary: #ffc75e;
--color-primary-hover: #ffc75e;
--color-primary: #0066cc;
--color-primary-hover: #0052a3;
--color-success: #3cff01;
--color-success-dark: #ffc75e;
--color-success-dark: #28a745;
--color-error: #ff4d4d;
--color-warning: #ffc75e;
--color-github: #ffc75e;
--color-warning: #ffc107;
--color-github: #24292e;
--color-bg: #ffe5b5;
--color-bg: #000;
--app-background-base: radial-gradient(
circle at top,
#3fc9ff,
#21fcc5
#1b2735,
#090a0f
);
--color-surface: #ffcc00;
--color-surface-light: #feffa6;
--color-text: #000000;
--color-text-muted: #353535;
/* --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;
@@ -61,22 +62,19 @@
html {
height: 100%;
background-image:
background-image:
var(--app-background-base);
background-size: contain, cover;
background-position: center, center;
background-repeat: no-repeat, no-repeat;
background-size:
contain,
cover;
background-position:
center,
center;
background-repeat:
no-repeat,
no-repeat;
}
@@ -96,68 +94,101 @@ body {
/* ============================================
ANIMATIONS
TYPOGRAPHY
============================================ */
@keyframes wobble {
0% { transform: translate(0%, 0) rotate(0deg); }
25% { transform: translate(-5%, -1px) rotate(-0.5deg); }
50% { transform: translate(0%, 1px) rotate(0.5deg); }
75% { transform: translate(+5%, -1px) rotate(0.5deg); }
100% { transform: translate(0%, 0) rotate(0deg); }
}
@keyframes bounce {
0% { transform: translateY(0) rotate(var(--rot)); }
33% { transform: translateY(-6px) rotate(var(--rot)); }
66% { transform: translateY(-8px) rotate(var(--rot)); }
100% { transform: translateY(0) rotate(var(--rot)); }
.title {
position: absolute;
top: 0;
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: "Cinzel Decorative", cursive;
color: var(--color-success);
margin: 0;
padding: var(--spacing-md);
}
/* ============================================
TYPOGRAPHY
MENU
============================================ */
.title {
position: absolute;
z-index: 999;
top: 20px;
left: 50%;
translate: -50% 0;
background: #ffcc00;
color: #000;
border: 4px solid #feffa6;
border-radius: 18px;
padding: 0.6rem 1.2rem;
animation: wobble 2s infinite ease-in-out;
.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);
}
.title span {
display: inline-block;
transform-origin: center;
font-size: 4rem;
font-weight: bold;
text-shadow: 2px 2px 6px rgba(0, 0, 0, 0.5);
animation: bounce 1.2s infinite alternate;
animation-timing-function: ease-in-out;
.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;
}
.title span:nth-child(1) { --rot: -5deg; color: #ff4d4d; }
.title span:nth-child(2) { --rot: 3deg; color: #5beb67; }
.title span:nth-child(3) { --rot: -3deg; color: #ca8dfc; }
.title span:nth-child(4) { --rot: 2deg; color: #6698f5; }
.title span:nth-child(5) { --rot: -4deg; color: #ff66cc; }
.menu__item:hover {
background: var(--color-surface-light);
font-size: var(--font-size-lg);
}
.title span:nth-child(2) { animation-delay: 0.2s; }
.title span:nth-child(3) { animation-delay: 0.4s; }
.title span:nth-child(4) { animation-delay: 0.6s; }
.title span:nth-child(5) { animation-delay: 0.8s; }
.menu__item--active {
background: var(--color-primary);
border-color: var(--color-primary);
}
.title span { will-change: transform; }
/* ============================================
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);
}
/* ============================================
PAGES
@@ -177,17 +208,14 @@ body {
}
.page__item {
border-radius: var(--radius-lg);
background: var(--color-surface);
color: var(--color-text);
border: 1px solid var(--color-surface-light);
border-color: #fda725;
padding: var(--spacing-sm) var(--spacing-md);
font-size: var(--font-size-md);
cursor: pointer;
transition: all var(--transition-fast);
text-align: center;
text-align: right;
}
.page__item:hover {
@@ -200,88 +228,10 @@ body {
border-color: var(--color-primary);
}
/* ============================================
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: #fda725;
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: 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: #fda725;
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;
@@ -306,7 +256,7 @@ body {
}
.btn--primary {
background: var(--color-surface);
background: var(--color-primary);
color: var(--color-text);
}
@@ -315,7 +265,7 @@ body {
}
.btn--secondary {
background: var(--color-surface);
background: var(--color-surface-light);
color: var(--color-text);
}
@@ -378,15 +328,13 @@ body {
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);
border-radius: 5px;
border-color: #aa1f1f;
border: 6px solid #faac37;
}
.window--visible {
@@ -447,8 +395,7 @@ body {
.message {
font-size: var(--font-size-sm);
padding: var(--spacing-xs);
border-radius: var(--radius-lg);
border-color: #000;
border-radius: var(--radius-sm);
}
.message--success {
@@ -468,11 +415,6 @@ body {
============================================ */
.login {
width: 320px;
border-radius: 5px;
border-color: #aa1f1f;
border: 6px solid #faac37;
background: #ffffff;
color: #000;
}
.login__form {
@@ -591,7 +533,7 @@ body {
border-radius: var(--radius-full);
border: 3px solid var(--color-text);
box-shadow: var(--shadow-md);
background: var(--color-surface-light);
background: var(--color-surface);
align-self: center;
}
@@ -615,74 +557,28 @@ body {
}
/* ============================================
STATS WINDOW
EASTER EGG BUTTON
============================================ */
.stats-window {
width: 320px;
/* .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);
}
.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:hover {
background: var(--color-error);
border-color: var(--color-error);
} */
/* ============================================
UTILITIES
@@ -730,7 +626,7 @@ body {
flex: 1;
padding: var(--spacing-sm);
background: var(--color-surface);
border: 1px solid var(--color-surface);
border: 1px solid var(--color-surface-light);
color: var(--color-text);
cursor: pointer;
font-size: var(--font-size-sm);
@@ -813,10 +709,9 @@ body {
/* ============================================
GAME ROOM WINDOW
============================================ */
.gameroom-window {
width: 800px;
height: 900px;
width: 600px;
height: 800px;
}
.gameroom__tabs {
@@ -1124,4 +1019,3 @@ body {
.gameroom__game-buttons .btn {
flex: 1;
}
+2 -47
View File
@@ -4,23 +4,13 @@
<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>
</head>
<script type="module" src="app.js"></script>
<body>
<h1 class="title">
<span>L</span>
<span>o</span>
<span>b</span>
<span>b</span>
<span>y</span>
</h1>
<h1 class="title">Lobby</h1>
<nav class="menu" aria-label="Menu principal">
<button class="menu__item" data-action="login" aria-label="Login">Login</button>
@@ -39,41 +29,6 @@
</div>
<div class="shape doodle-1 loop-color" id="shape1"></div>
<div class="shape doodle-2 loop-color" id="shape2"></div>
<div class="shape doodle-3 loop-color" id="shape3"></div>
<div class="shape doodle-4 loop-color" id="shape4"></div>
<div class="shape doodle-5 loop-color" id="shape5"></div>
<div class="shape doodle-6 loop-color" id="shape6"></div>
<div class="shape doodle-7 loop-color" id="shape7"></div>
<div class="shape doodle-8 loop-color" id="shape8"></div>
<div class="shape doodle-9 loop-color" id="shape9"></div>
<div class="shape doodle-10 loop-color" id="shape10"></div>
<div class="shape doodle-11 loop-color" id="shape11"></div>
<div class="shape doodle-12 loop-color" id="shape12"></div>
<div class="shape doodle-13 loop-color" id="shape13"></div>
<div class="shape doodle-14 loop-color" id="shape14"></div>
<div class="shape doodle-15 loop-color" id="shape15"></div>
<div class="shape doodle-16 loop-color" id="shape16"></div>
<div class="shape doodle-17 loop-color" id="shape17"></div>
<div class="shape doodle-18 loop-color" id="shape18"></div>
<div class="shape doodle-19 loop-color" id="shape19"></div>
<div class="shape doodle-20 loop-color" id="shape20"></div>
<div class="shape doodle-21 loop-color" id="shape21"></div>
<div class="shape doodle-22 loop-color" id="shape22"></div>
<div class="shape doodle-23 loop-color" id="shape23"></div>
<div class="shape doodle-24 loop-color" id="shape24"></div>
<div class="shape doodle-25 loop-color" id="shape25"></div>
<div class="shape doodle-26 loop-color" id="shape26"></div>
<div class="shape doodle-27 loop-color" id="shape27"></div>
<div class="shape doodle-28 loop-color" id="shape28"></div>
<div class="shape doodle-29 loop-color" id="shape29"></div>
<div class="shape doodle-30 loop-color" id="shape30"></div>
<div class="shape doodle-31 loop-color" id="shape31"></div>
<div class="shape doodle-32 loop-color" id="shape32"></div>
<div class="shape doodle-33 loop-color" id="shape33"></div>
<div class="shape doodle-34 loop-color" id="shape34"></div>
<div class="shape doodle-35 loop-color" id="shape35"></div>
<script type="module" src="app.js"></script>
</body>
</html>
+7 -3
View File
@@ -194,7 +194,8 @@ export class GameRoomWindow extends Window {
players: [],
currentPlayerIndex: 0,
guessedLetters: [],
scores: {}
scores: {},
counter: 0
};
this.initDrawing();
@@ -1568,8 +1569,11 @@ export class GameRoomWindow extends Window {
nextRound() {
// Move to next player
this.gameState.currentPlayerIndex = (this.gameState.currentPlayerIndex + 1) % this.gameState.players.length;
const nextDrawer = this.gameState.players[this.gameState.currentPlayerIndex];
this.gameState.counter++;
if (this.gameState.counter >= this.gameState.players.length) {
this.gameState.counter = 0;
}
const nextDrawer = this.gameState.players[this.gameState.counter];
if (this.socket?.connected) {
this.socket.emit('game-next-round', { drawer: nextDrawer });
+52 -52
View File
@@ -7,28 +7,28 @@
CSS VARIABLES
============================================ */
:root {
--color-primary: #ffc75e;
--color-primary-hover: #ffc75e;
--color-primary: #0066cc;
--color-primary-hover: #0052a3;
--color-success: #3cff01;
--color-success-dark: #ffc75e;
--color-success-dark: #28a745;
--color-error: #ff4d4d;
--color-warning: #ffc75e;
--color-github: #ffc75e;
--color-warning: #ffc107;
--color-github: #24292e;
--color-bg: #ffe5b5;
--color-bg: #a3a3a3;
--app-background-base: radial-gradient(
circle at top,
#fff787,
#ff8080
#000000,
#4d4d4d
);
--app-background-image: url("./assets/background.png");
--color-surface: #ffefce;
--color-surface-light: #ffc75e;
--color-text: #000000;
--color-text-muted: #000000;
--color-surface: #222;
--color-surface-light: #333;
--color-text: #fff;
--color-text-muted: #aaa;
--font-size-base: 10px;
--font-size-sm: 1.2rem;
@@ -117,16 +117,16 @@ body {
text-align: center;
text-shadow: 2px 2px 10px black;
z-index: 1;
font-family: "Roboto";
letter-spacing: -10px;
font-family: "Cinzel Decorative", cursive;
color: rgba(248, 252, 2, 0.6);
margin: 0;
padding: 0.6rem 1.2rem;
padding: var(--spacing-md);
background-color: #ffefce;
/* Rectangle + rounded corners */
background-color: rgba(247, 7, 67, 0.6);
border: 2px solid rgba(0, 0, 0, 0.6);
border-radius: var(--radius-lg);
border-radius: 15px;
}
@@ -136,27 +136,25 @@ body {
.menu {
position: fixed;
top: var(--spacing-lg);
top: 0;
left: 50px;
padding: 0;
margin: 0;
z-index: var(--z-menu);
display: flex;
flex-direction: column;
gap: var(--spacing-lg);
z-index: var(--z-menu);
gap: var(--spacing-xs);
}
.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;
text-align: left;
}
.menu__item:hover {
@@ -173,7 +171,7 @@ body {
GAME
============================================ */
/* .game {
.game {
position: fixed;
top: 0;
right: 50px;
@@ -183,31 +181,17 @@ body {
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;
text-align: right;
}
.game__item:hover {
@@ -319,15 +303,13 @@ body {
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);
border-radius: 5px;
border-color: #aa1f1f;
border: 6px solid #faac37;
}
.window--visible {
@@ -388,8 +370,7 @@ body {
.message {
font-size: var(--font-size-sm);
padding: var(--spacing-xs);
border-radius: var(--radius-lg);
border-color: #000;
border-radius: var(--radius-sm);
}
.message--success {
@@ -409,11 +390,6 @@ body {
============================================ */
.login {
width: 320px;
border-radius: 5px;
border-color: #aa1f1f;
border: 6px solid #faac37;
background: #ffffff;
color: #000;
}
.login__form {
@@ -625,6 +601,30 @@ body {
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
============================================ */
@@ -670,7 +670,7 @@ body {
.friends__tab {
flex: 1;
padding: var(--spacing-sm);
background: var(--color-surface-light);
background: var(--color-surface);
border: 1px solid var(--color-surface-light);
color: var(--color-text);
cursor: pointer;
+4 -9
View File
@@ -3,27 +3,25 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Transcendence</title>
<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</h1>
<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>
<button class="menu__item" data-action="test" aria-label="Test Page"
onclick="window.location.href='test.html'">Test Page</button>
</nav>
<nav class="game" aria-label="Game">
<button class="game__item" data-action="new_game" aria-label="Skkrrribl.io"
onclick="window.location.href='game.html'">Skkrrribl.io</button>
<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>
@@ -31,6 +29,3 @@
<script type="module" src="app.js"></script>
</body>
</html>
+55
View File
@@ -17,6 +17,7 @@ export class LoginWindow extends Window {
this.buildUI();
this.bindEvents();
this.checkIfAlreadyLoggedIn();
this.NotficationContainer();
}
/**
@@ -129,6 +130,7 @@ export class LoginWindow extends Window {
if (response.ok && data.token) {
localStorage.setItem(STORAGE_KEYS.AUTH_TOKEN, data.token);
this.showMessage('Login successful! Welcome.', 'success');
this.showNotification('Login successful', 'green');
// Emit login event
eventBus.emit(Events.USER_LOGGED_IN, { username, token: data.token });
@@ -138,6 +140,7 @@ export class LoginWindow extends Window {
} else {
const errorMsg = data?.message || 'Login failed';
this.showMessage(errorMsg, 'error');
this.showNotification(errorMsg, 'red');
}
} catch (error) {
console.error('Login error:', error);
@@ -170,10 +173,12 @@ export class LoginWindow extends Window {
if (response.ok) {
this.showMessage('Registration successful! You can now sign in.', 'success');
this.showNotification('Registration successful', 'green');
eventBus.emit(Events.USER_REGISTERED, { username });
} else {
const errorMsg = data?.message || 'Registration failed';
this.showMessage(errorMsg, 'error');
this.showNotification(errorMsg, 'red');
}
} catch (error) {
console.error('Registration error:', error);
@@ -200,6 +205,7 @@ export class LoginWindow extends Window {
if (event.data?.token) {
localStorage.setItem(STORAGE_KEYS.AUTH_TOKEN, event.data.token);
this.showMessage('GitHub login successful! Welcome.', 'success');
this.showNotification('GitHub login successful', 'green');
// Emit login event
eventBus.emit(Events.USER_LOGGED_IN, {
@@ -215,6 +221,55 @@ export class LoginWindow extends Window {
window.addEventListener('message', handleMessage, { once: true });
}
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) {
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);
}
/**
* Displays a feedback message
* @param {string} text - Message text
+109 -6
View File
@@ -2,8 +2,54 @@
// RENDU
// ─────────────────────────────────────────────
const CELL = 30;
const COLORS = ['#000500','#00ff41','#39ff14','#00e676','#76ff03','#b2ff59','#00ffaa','#ccff00','#2d5a2d'];
const CELL = 30;
const THEMES = {
green: {
bg: '#000500', panel: '#000d00', border: '#004400',
accent: '#00ff41', accent2: '#39ff14', dim: '#1a5c1a', text: '#00cc26',
grid: 'rgba(0,255,65,0.06)', ghost: 'rgba(0,255,65,0.25)', highlight: 'rgba(200,255,200,0.2)',
colors: ['#000500','#00ff41','#39ff14','#00e676','#76ff03','#b2ff59','#00ffaa','#ccff00','#2d5a2d']
},
red: {
bg: '#050000', panel: '#0d0000', border: '#440000',
accent: '#ff1744', accent2: '#ff4569', dim: '#5c1a1a', text: '#cc2626',
grid: 'rgba(255,23,68,0.06)', ghost: 'rgba(255,23,68,0.25)', highlight: 'rgba(255,200,200,0.2)',
colors: ['#050000','#ff1744','#ff4569','#e53935','#ff6d00','#ff8a65','#ff5252','#ff6e40','#5a2d2d']
},
yellow: {
bg: '#050500', panel: '#0d0d00', border: '#444400',
accent: '#ffd600', accent2: '#ffea00', dim: '#5c5c1a', text: '#ccaa00',
grid: 'rgba(255,214,0,0.06)', ghost: 'rgba(255,214,0,0.25)', highlight: 'rgba(255,255,200,0.2)',
colors: ['#050500','#ffd600','#ffea00','#ffab00','#fff176','#ffe57f','#ffff00','#ffc400','#5a5a2d']
},
blue: {
bg: '#000005', panel: '#00000d', border: '#000044',
accent: '#00b0ff', accent2: '#40c4ff', dim: '#1a1a5c', text: '#2626cc',
grid: 'rgba(0,176,255,0.06)', ghost: 'rgba(0,176,255,0.25)', highlight: 'rgba(200,200,255,0.2)',
colors: ['#000005','#00b0ff','#40c4ff','#0091ea','#448aff','#82b1ff','#00e5ff','#2979ff','#2d2d5a']
}
};
let currentTheme = THEMES.green;
let COLORS = [...currentTheme.colors];
function setColorTheme(themeName) {
currentTheme = THEMES[themeName] || THEMES.green;
COLORS = [...currentTheme.colors];
const root = document.documentElement;
root.style.setProperty('--bg', currentTheme.bg);
root.style.setProperty('--panel', currentTheme.panel);
root.style.setProperty('--border', currentTheme.border);
root.style.setProperty('--accent', currentTheme.accent);
root.style.setProperty('--accent2', currentTheme.accent2);
root.style.setProperty('--dim', currentTheme.dim);
root.style.setProperty('--text', currentTheme.text);
localStorage.setItem('tetris-theme', themeName);
document.querySelectorAll('.theme-btn').forEach(btn => {
btn.classList.toggle('active', btn.dataset.theme === themeName);
});
}
const ctxMain = document.getElementById('canvas-main').getContext('2d');
const ctxNext = document.getElementById('canvas-next').getContext('2d');
@@ -22,7 +68,7 @@ function drawCell(ctx, x, y, colorIndex, size) {
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 = 'rgba(200,255,200,0.2)';
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
@@ -32,12 +78,12 @@ function drawCell(ctx, x, y, colorIndex, size) {
}
function clearCanvas(ctx, w, h) {
ctx.fillStyle = '#000500';
ctx.fillStyle = currentTheme.bg;
ctx.fillRect(0, 0, w, h);
}
function drawGridLines(ctx, cols, rows, size) {
ctx.strokeStyle = 'rgba(0,255,65,0.06)';
ctx.strokeStyle = currentTheme.grid;
ctx.lineWidth = 1;
for (let x = 0; x <= cols; x++) {
ctx.beginPath(); ctx.moveTo(x * size, 0); ctx.lineTo(x * size, rows * size); ctx.stroke();
@@ -67,7 +113,7 @@ function drawGhost(ctx, piece, grid) {
if (ghost.y === piece.getPosition().y) return;
ctx.strokeStyle = 'rgba(0,255,65,0.25)';
ctx.strokeStyle = currentTheme.ghost;
ctx.lineWidth = 1;
for (let row = 0; row < shape.length; row++)
for (let col = 0; col < shape[row].length; col++)
@@ -93,6 +139,17 @@ function drawMiniPiece(ctx, piece, canvasW, canvasH) {
drawCell(ctx, offsetX + col, offsetY + row, color, s);
}
function _drawShieldOverlay(ctx, w, h, alpha) {
ctx.save();
ctx.strokeStyle = `rgba(0,212,255,${alpha})`;
ctx.lineWidth = 4;
ctx.shadowColor = '#00d4ff';
ctx.shadowBlur = 16;
ctx.strokeRect(2, 2, w - 4, h - 4);
ctx.shadowBlur = 0;
ctx.restore();
}
function render() {
// Grille principale
clearCanvas(ctxMain, 300, 600);
@@ -115,12 +172,39 @@ 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) {
if (game.shieldActive) {
const secs = Math.ceil(game.shieldActiveMs / 1000);
shieldEl.textContent = `ACTIF ${secs}s`;
shieldEl.className = 'score-value shield-active';
if (shieldBar) shieldBar.style.width = (game.shieldActiveMs / 3000 * 100) + '%';
} else if (game.shieldReady) {
shieldEl.textContent = 'PRÊT';
shieldEl.className = 'score-value shield-ready';
if (shieldBar) shieldBar.style.width = '100%';
} else {
const secs = Math.ceil(game.shieldCooldownMs / 1000);
shieldEl.textContent = `${secs}s`;
shieldEl.className = 'score-value shield-cooldown';
if (shieldBar) shieldBar.style.width = ((1 - game.shieldCooldownMs / 60000) * 100) + '%';
}
}
}
function renderOpponent(opponentGrid) {
@@ -130,4 +214,23 @@ function renderOpponent(opponentGrid) {
for (let x = 0; x < opponentGrid[y].length; x++)
if (opponentGrid[y][x] !== 0)
drawCell(ctxOpponent, x, y, opponentGrid[y][x], CELL);
// Shield overlay adversaire
if (typeof duel !== 'undefined' && duel && duel.opponentShieldActive) {
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';
}
}
// Restore saved theme
(function() {
const saved = localStorage.getItem('tetris-theme');
if (saved && THEMES[saved]) setColorTheme(saved);
})();
-47
View File
@@ -1,47 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dynamic Hand-Drawn Shapes</title>
<link rel="stylesheet" href="doodle.css">
<script src="doodle.js" defer></script>
</head>
<body>
<div class="shape doodle-1 loop-color" id="shape1"></div>
<div class="shape doodle-2 loop-color" id="shape2"></div>
<div class="shape doodle-3 loop-color" id="shape3"></div>
<div class="shape doodle-4 loop-color" id="shape4"></div>
<div class="shape doodle-5 loop-color" id="shape5"></div>
<div class="shape doodle-6 loop-color" id="shape6"></div>
<div class="shape doodle-7 loop-color" id="shape7"></div>
<div class="shape doodle-8 loop-color" id="shape8"></div>
<div class="shape doodle-9 loop-color" id="shape9"></div>
<div class="shape doodle-10 loop-color" id="shape10"></div>
<div class="shape doodle-11 loop-color" id="shape11"></div>
<div class="shape doodle-12 loop-color" id="shape12"></div>
<div class="shape doodle-13 loop-color" id="shape13"></div>
<div class="shape doodle-14 loop-color" id="shape14"></div>
<div class="shape doodle-15 loop-color" id="shape15"></div>
<div class="shape doodle-16 loop-color" id="shape16"></div>
<div class="shape doodle-17 loop-color" id="shape17"></div>
<div class="shape doodle-18 loop-color" id="shape18"></div>
<div class="shape doodle-19 loop-color" id="shape19"></div>
<div class="shape doodle-20 loop-color" id="shape20"></div>
<div class="shape doodle-21 loop-color" id="shape21"></div>
<div class="shape doodle-22 loop-color" id="shape22"></div>
<div class="shape doodle-23 loop-color" id="shape23"></div>
<div class="shape doodle-24 loop-color" id="shape24"></div>
<div class="shape doodle-25 loop-color" id="shape25"></div>
<div class="shape doodle-26 loop-color" id="shape26"></div>
<div class="shape doodle-27 loop-color" id="shape27"></div>
<div class="shape doodle-28 loop-color" id="shape28"></div>
<div class="shape doodle-29 loop-color" id="shape29"></div>
<div class="shape doodle-30 loop-color" id="shape30"></div>
<div class="shape doodle-31 loop-color" id="shape31"></div>
<div class="shape doodle-32 loop-color" id="shape32"></div>
<div class="shape doodle-33 loop-color" id="shape33"></div>
<div class="shape doodle-34 loop-color" id="shape34"></div>
<div class="shape doodle-35 loop-color" id="shape35"></div>
</body>
</html>
@@ -445,6 +445,37 @@ button:disabled { opacity: 0.3; cursor: not-allowed; }
letter-spacing: 0.05em;
}
/* ── Theme color picker ── */
.theme-btns {
display: flex;
gap: 6px;
margin-top: 2px;
}
.theme-btn {
width: 22px;
height: 22px;
min-width: 22px;
padding: 0;
border-radius: 50%;
border: 2px solid transparent;
cursor: pointer;
transition: transform 0.15s, box-shadow 0.15s;
}
.theme-btn[data-theme="green"] { background: #00ff41; }
.theme-btn[data-theme="red"] { background: #ff1744; }
.theme-btn[data-theme="yellow"] { background: #ffd600; }
.theme-btn[data-theme="blue"] { background: #00b0ff; }
.theme-btn:hover { transform: scale(1.2); }
.theme-btn.active {
border-color: #ffffff;
box-shadow: 0 0 8px currentColor;
transform: scale(1.15);
}
#settings-panel input[type="number"] {
background: var(--bg);
border: 1px solid var(--border);
@@ -620,3 +651,36 @@ button:disabled { opacity: 0.3; cursor: not-allowed; }
}
body { overflow: hidden; }
/* ── Shield ───────────────────────────────── */
.shield-bar-bg {
width: 100%;
height: 4px;
background: rgba(0,212,255,0.15);
border-radius: 2px;
margin-top: 4px;
overflow: hidden;
}
.shield-bar {
height: 100%;
background: #00d4ff;
border-radius: 2px;
transition: width 0.1s linear;
box-shadow: 0 0 6px #00d4ff;
}
.shield-ready { color: #00d4ff !important; }
.shield-active { color: #00ffff !important; text-shadow: 0 0 8px #00ffff; }
.shield-cooldown { color: var(--dim) !important; }
kbd {
display: inline-block;
padding: 0 3px;
border: 1px solid var(--border);
border-radius: 2px;
font-size: 0.6rem;
font-family: inherit;
color: var(--dim);
}
@@ -51,6 +51,12 @@
<div class="score-value" id="score-display">0</div>
</div>
<div class="score-block">
<div class="score-label">Shield <kbd>E</kbd></div>
<div class="score-value shield-ready" id="shield-status-display">PRÊT</div>
<div class="shield-bar-bg"><div class="shield-bar" id="shield-bar"></div></div>
</div>
<div class="btn-group">
<button id="btn-start">Start</button>
<button id="btn-pause" disabled>Pause</button>
@@ -61,6 +67,15 @@
<!-- Panneau de configuration -->
<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>
</div>
</div>
<div class="settings-row">
<label for="input-ttd">Vitesse initiale (ms)</label>
<input type="number" id="input-ttd" min="100" max="3000" step="50" value="1000">
@@ -97,6 +112,7 @@
<div><span>W</span> Rot. droite</div>
<div><span>Espace</span> Drop</div>
<div><span>C</span> Hold</div>
<div><span>E</span> Shield</div>
</div>
</div>
@@ -111,6 +127,7 @@
<div class="score-label">Score</div>
<div class="score-value" id="opponent-score"></div>
</div>
<div id="opponent-shield-indicator" style="display:none;color:#00d4ff;font-size:0.75rem;text-align:center;letter-spacing:1px;margin-top:4px;">&#x1F6E1; SHIELD ACTIF</div>
</div>
<div id="opponent-wrapper">
+57 -3
View File
@@ -3,11 +3,12 @@
// ───────────────────────────────────────────
class Tetris {
constructor(onRender, onGameOver, onBlockPlaced = null, onLinesCleared = null) {
constructor(onRender, onGameOver, onBlockPlaced = null, onLinesCleared = null, onShieldChanged = null) {
this.onRender = onRender;
this.onGameOver = onGameOver;
this.onBlockPlaced = onBlockPlaced;
this.onLinesCleared = onLinesCleared;
this.onShieldChanged = onShieldChanged;
this.grid = this._createGrid(10, 20);
this.bufferGrid = this._createGrid(10, 5);
@@ -28,6 +29,12 @@ class Tetris {
this.isPaused = false;
this.canStore = true;
// Shield
this.shieldActive = false;
this.shieldActiveMs = 0;
this.shieldCooldownMs = 0;
this.shieldReady = true; // prêt dès le début
this.animationFrameId = null;
this.lastTime = 0;
this.accumulator = 0;
@@ -55,6 +62,10 @@ class Tetris {
this.timeToDown = this.initialTimeToDown;
this.storedPiece = null;
this.canStore = true;
this.shieldActive = false;
this.shieldActiveMs = 0;
this.shieldCooldownMs = 0;
this.shieldReady = true;
this._spawnNewPiece();
document.addEventListener('keydown', this._keyHandler);
this._startGameLoop();
@@ -108,6 +119,8 @@ class Tetris {
this.lastTime = currentTime;
this.accumulator += deltaTime;
this._updateShield(deltaTime);
while (this.isRunning && this.accumulator >= this.timeToDown) {
this._tick();
this.accumulator -= this.timeToDown;
@@ -174,11 +187,42 @@ class Tetris {
e.preventDefault();
if (!this.isPaused) this._storePiece();
break;
case 'e': case 'E':
e.preventDefault();
if (!this.isPaused) this._activateShield();
break;
}
this.onRender();
}
_activateShield() {
if (!this.shieldReady || this.shieldActive) return;
this.shieldActive = true;
this.shieldActiveMs = 3000;
this.shieldReady = false;
if (this.onShieldChanged) this.onShieldChanged('activated');
}
_updateShield(deltaTime) {
if (this.shieldActive) {
this.shieldActiveMs -= deltaTime;
if (this.shieldActiveMs <= 0) {
this.shieldActive = false;
this.shieldActiveMs = 0;
this.shieldCooldownMs = 60000;
if (this.onShieldChanged) this.onShieldChanged('deactivated');
}
} else if (!this.shieldReady) {
this.shieldCooldownMs -= deltaTime;
if (this.shieldCooldownMs <= 0) {
this.shieldCooldownMs = 0;
this.shieldReady = true;
if (this.onShieldChanged) this.onShieldChanged('ready');
}
}
}
_hardDrop() {
if (!this.currentPiece) return;
let dist = 0;
@@ -275,8 +319,17 @@ class Tetris {
const points = [0, 100, 300, 500, 800];
this.score += points[cleared];
this.count += points[cleared];
if (this.onLinesCleared && cleared > 0)
this.onLinesCleared(cleared, this.lastLandingCol);
if (cleared > 0) {
// Chaque ligne remplie réduit le cooldown du shield de 10s
if (!this.shieldActive && !this.shieldReady) {
this.shieldCooldownMs = Math.max(0, this.shieldCooldownMs - cleared * 10000);
if (this.shieldCooldownMs === 0) {
this.shieldReady = true;
if (this.onShieldChanged) this.onShieldChanged('ready');
}
}
if (this.onLinesCleared) this.onLinesCleared(cleared, this.lastLandingCol);
}
}
_makeHarder() {
@@ -361,6 +414,7 @@ class Tetris {
}
addGarbageLines(lines) {
if (this.shieldActive) return; // shield bloque les lignes garbage
if (!this.isRunning || !lines.length) return;
this.grid.splice(0, lines.length);
for (const line of lines) this.grid.push([...line]); // ...line pour faire une copie independante
+6
View File
@@ -113,6 +113,7 @@ 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;
@@ -170,6 +171,7 @@ socket.on('tetris:matched', (data) => {
// 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;
@@ -211,6 +213,10 @@ const game = new Tetris(
// onLinesCleared — relay duel
(count, holeCol) => {
if (duel) duel.onLocalLinesCleared(count, holeCol);
},
// onShieldChanged — relay duel
(event) => {
if (duel) duel.onLocalShieldChanged(event);
}
);
-6
View File
@@ -1,6 +0,0 @@
login 42
hide buttons if not logged
add CAT website
fix front ?