diff --git a/README.md b/README.md index bdf384f..1b2bc7e 100644 --- a/README.md +++ b/README.md @@ -151,24 +151,6 @@ bash dotfiles/install/install-macos-user.sh --- -## 🌱 Seasonal palettes (4seasons branch) - -The `4seasons` branch adds seasonal palettes with an optional static or dynamic mode. - -Season ranges (Europe/Oslo): - -- Spring: March–May -- Summer: June–August -- Autumn: September–mid November -- Winter: mid November–February - -When installing PS1 you can choose: - -- Dynamic (auto by date) -- Static (pick a single season) - ---- - ## 🧠 Features - **Clean, modern powerline-style prompt** diff --git a/dotfiles/install/install-linux-global.sh b/dotfiles/install/install-linux-global.sh index bb3ede8..4270088 100644 --- a/dotfiles/install/install-linux-global.sh +++ b/dotfiles/install/install-linux-global.sh @@ -23,6 +23,11 @@ MARKER_END="# <<< dotfiles (managed) <<<" # Select install mode (PS1, banner, or both) install_ps1=1 install_banner=1 +season_mode="dynamic" +season_choice="" +style_choice="aurora" +style_pack="standard" +contest_layout="day-time-user" if [[ -t 0 ]]; then echo "Install options:" echo " 1) PS1 only" @@ -35,6 +40,88 @@ if [[ -t 0 ]]; then ""|3) install_ps1=1; install_banner=1 ;; *) echo "Invalid choice, using both."; install_ps1=1; install_banner=1 ;; esac + + if [[ "$install_ps1" -eq 1 ]]; then + echo + echo "PS1 mode:" + echo " 1) Single season (static)" + echo " 2) Dynamic season (changing)" + echo " 3) Contest pack (static)" + read -r -p "Choose [3]: " season_mode_choice + case "${season_mode_choice}" in + 1) + season_mode="static" + echo "Pick a season:" + echo " 1) Winter" + echo " 2) Spring" + echo " 3) Summer" + echo " 4) Autumn" + read -r -p "Choose [1]: " season_pick + case "${season_pick}" in + 2) season_choice="spring" ;; + 3) season_choice="summer" ;; + 4) season_choice="autumn" ;; + ""|1) season_choice="winter" ;; + *) echo "Invalid choice, using winter."; season_choice="winter" ;; + esac + + style_pack="standard" + ;; + 2) + season_mode="dynamic" + style_pack="standard" + ;; + ""|3) + season_mode="static" + style_pack="contest" + contest_layout="day-time-user" + echo "Contest layout order:" + echo " 1) day-time-user" + echo " 2) user-time" + echo " 3) time-user" + echo " 4) day-time" + read -r -p "Choose [1]: " layout_pick + case "${layout_pick}" in + 2) contest_layout="user-time" ;; + 3) contest_layout="time-user" ;; + 4) contest_layout="day-time" ;; + ""|1) contest_layout="day-time-user" ;; + *) echo "Invalid choice, using day-time-user."; contest_layout="day-time-user" ;; + esac + ;; + *) + echo "Invalid choice, using dynamic." + season_mode="dynamic" + style_pack="standard" + ;; + esac + + if [[ "$style_pack" == "contest" ]]; then + echo + echo "Style presets:" + echo " 1) Aurora - Powerline blocks, seasonal palette, two-line" + echo " Preview: [date time user] > [@host /path]" + echo " 2) Neon - Magenta/cyan cyber blocks, high contrast" + echo " Preview: [user time] > [@host /path]" + echo " 3) Forge - Warm copper blocks, heavy frame" + echo " Preview: [date time user] > [@host /path]" + echo " 4) Circuit - Green scanline, minimal rails" + echo " Preview: [user time] - @host /path" + echo " 5) Mono - Clean monochrome, fast read" + echo " Preview: [date time user] @host /path" + read -r -p "Choose style [1]: " style_pick + case "${style_pick}" in + 2) style_choice="neon" ;; + 3) style_choice="forge" ;; + 4) style_choice="circuit" ;; + 5) style_choice="mono" ;; + ""|1) style_choice="aurora" ;; + *) echo "Invalid choice, using aurora."; style_choice="aurora" ;; + esac + else + style_choice="aurora" + fi + fi fi if [[ "$install_banner" -eq 1 ]]; then @@ -94,8 +181,27 @@ fi if [[ "$install_ps1" -eq 1 ]]; then cp -f "$SRC_PS1" "$DST_PS1" chmod 0644 "$DST_PS1" + + { + printf 'PS1_SEASON_MODE=%s\n' "$season_mode" + if [[ "$season_mode" == "static" && -n "$season_choice" ]]; then + printf 'PS1_SEASON=%s\n' "$season_choice" + fi + } > /etc/ps1-season + chmod 0644 /etc/ps1-season + + { + printf 'PS1_STYLE=%s\n' "$style_choice" + printf 'PS1_STYLE_PACK=%s\n' "$style_pack" + if [[ "$style_pack" == "contest" ]]; then + printf 'PS1_CONTEST_LAYOUT=%s\n' "$contest_layout" + fi + } > /etc/ps1-style + chmod 0644 /etc/ps1-style else rm -f "$DST_PS1" + rm -f /etc/ps1-season + rm -f /etc/ps1-style fi if [[ "$install_banner" -eq 1 ]]; then diff --git a/dotfiles/install/install-macos-user.sh b/dotfiles/install/install-macos-user.sh index e029999..8cc14d0 100644 --- a/dotfiles/install/install-macos-user.sh +++ b/dotfiles/install/install-macos-user.sh @@ -7,14 +7,119 @@ REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" SRC_PS1="$REPO_ROOT/ps1/ps1.sh" DST_DIR="$HOME/.config/ps1" DST_PS1="$DST_DIR/ps1.sh" +SEASON_FILE="$DST_DIR/season" +STYLE_FILE="$DST_DIR/style" MARKER_START="# >>> user ps1 (managed) >>>" MARKER_END="# <<< user ps1 (managed) <<<" +season_mode="dynamic" +season_choice="" +style_choice="aurora" +contest_layout="day-time-user" +if [[ -t 0 ]]; then + style_pack="standard" + echo "PS1 mode:" + echo " 1) Single season (static)" + echo " 2) Dynamic season (changing)" + echo " 3) Contest pack (static)" + read -r -p "Choose [3]: " season_mode_choice + case "${season_mode_choice}" in + 1) + season_mode="static" + echo "Pick a season:" + echo " 1) Winter" + echo " 2) Spring" + echo " 3) Summer" + echo " 4) Autumn" + read -r -p "Choose [1]: " season_pick + case "${season_pick}" in + 2) season_choice="spring" ;; + 3) season_choice="summer" ;; + 4) season_choice="autumn" ;; + ""|1) season_choice="winter" ;; + *) echo "Invalid choice, using winter."; season_choice="winter" ;; + esac + + style_pack="standard" + ;; + 2) + season_mode="dynamic" + style_pack="standard" + ;; + ""|3) + season_mode="static" + style_pack="contest" + contest_layout="day-time-user" + echo "Contest layout order:" + echo " 1) day-time-user" + echo " 2) user-time" + echo " 3) time-user" + echo " 4) day-time" + read -r -p "Choose [1]: " layout_pick + case "${layout_pick}" in + 2) contest_layout="user-time" ;; + 3) contest_layout="time-user" ;; + 4) contest_layout="day-time" ;; + ""|1) contest_layout="day-time-user" ;; + *) echo "Invalid choice, using day-time-user."; contest_layout="day-time-user" ;; + esac + ;; + *) + echo "Invalid choice, using dynamic." + season_mode="dynamic" + style_pack="standard" + ;; + esac + + if [[ "$style_pack" == "contest" ]]; then + echo + echo "Style presets:" + echo " 1) Aurora - Powerline blocks, seasonal palette, two-line" + echo " Preview: [date time user] > [@host /path]" + echo " 2) Neon - Magenta/cyan cyber blocks, high contrast" + echo " Preview: [user time] > [@host /path]" + echo " 3) Forge - Warm copper blocks, heavy frame" + echo " Preview: [date time user] > [@host /path]" + echo " 4) Circuit - Green scanline, minimal rails" + echo " Preview: [user time] - @host /path" + echo " 5) Mono - Clean monochrome, fast read" + echo " Preview: [date time user] @host /path" + read -r -p "Choose style [1]: " style_pick + case "${style_pick}" in + 2) style_choice="neon" ;; + 3) style_choice="forge" ;; + 4) style_choice="circuit" ;; + 5) style_choice="mono" ;; + ""|1) style_choice="aurora" ;; + *) echo "Invalid choice, using aurora."; style_choice="aurora" ;; + esac + else + style_choice="aurora" + fi +fi + mkdir -p "$DST_DIR" cp -f "$SRC_PS1" "$DST_PS1" chmod 0644 "$DST_PS1" +{ + printf 'PS1_SEASON_MODE=%s\n' "$season_mode" + if [[ "$season_mode" == "static" && -n "$season_choice" ]]; then + printf 'PS1_SEASON=%s\n' "$season_choice" + fi +} > "$SEASON_FILE" +chmod 0644 "$SEASON_FILE" + +{ + printf 'PS1_STYLE=%s\n' "$style_choice" + printf 'PS1_STYLE_PACK=%s\n' "$style_pack" + if [[ "$style_pack" == "contest" ]]; then + printf 'PS1_CONTEST_LAYOUT=%s\n' "$contest_layout" + fi +} > "$STYLE_FILE" +chmod 0644 "$STYLE_FILE" + add_source_block() { local file="$1" [[ -f "$file" ]] || touch "$file" diff --git a/dotfiles/ps1/ps1.sh b/dotfiles/ps1/ps1.sh index f5d194e..2f83d06 100644 --- a/dotfiles/ps1/ps1.sh +++ b/dotfiles/ps1/ps1.sh @@ -37,19 +37,180 @@ _ps1_has_nf() { return 1 } -# Time-based emoji (Europe/Oslo) +# Season selection (dynamic or static) +# PS1_SEASON_MODE=dynamic|static +# PS1_SEASON= winter|spring|summer|autumn +# Also reads config from /etc/ps1-season or ~/.config/ps1/season +_ps1_season_config() { + local cfg="" + if [[ -r /etc/ps1-season ]]; then + cfg="/etc/ps1-season" + elif [[ -r "$HOME/.config/ps1/season" ]]; then + cfg="$HOME/.config/ps1/season" + fi + + if [[ -n "$cfg" ]]; then + while IFS='=' read -r k v; do + k="${k//[[:space:]]/}" + v="${v//[[:space:]]/}" + case "$k" in + PS1_SEASON_MODE) export PS1_SEASON_MODE="$v" ;; + PS1_SEASON) export PS1_SEASON="$v" ;; + esac + done < "$cfg" + fi +} + +_ps1_season_dynamic() { + local mm dd m d + mm=$(TZ=Europe/Oslo date +%m); dd=$(TZ=Europe/Oslo date +%d) + m=$((10#$mm)); d=$((10#$dd)) + + if (( m >= 3 && m <= 5 )); then + echo "spring" + elif (( m >= 6 && m <= 8 )); then + echo "summer" + elif (( m == 9 || m == 10 || (m == 11 && d < 15) )); then + echo "autumn" + else + echo "winter" + fi +} + +_ps1_season() { + _ps1_season_config + local mode="${PS1_SEASON_MODE:-dynamic}" + local season="${PS1_SEASON:-}" + + if [[ "$mode" == "static" && -n "$season" ]]; then + echo "$season" + else + _ps1_season_dynamic + fi +} + +# Style selection (aurora/neon/forge/circuit/mono) +# PS1_STYLE=aurora|neon|forge|circuit|mono +# PS1_STYLE_PACK=standard|contest +# PS1_CONTEST_LAYOUT=day-time-user|user-time|time-user|day-time +# Also reads config from /etc/ps1-style or ~/.config/ps1/style +_ps1_style_config() { + local cfg="" + if [[ -r /etc/ps1-style ]]; then + cfg="/etc/ps1-style" + elif [[ -r "$HOME/.config/ps1/style" ]]; then + cfg="$HOME/.config/ps1/style" + fi + + if [[ -n "$cfg" ]]; then + while IFS='=' read -r k v; do + k="${k//[[:space:]]/}" + v="${v//[[:space:]]/}" + case "$k" in + PS1_STYLE) export PS1_STYLE="$v" ;; + PS1_STYLE_PACK) export PS1_STYLE_PACK="$v" ;; + PS1_CONTEST_LAYOUT) export PS1_CONTEST_LAYOUT="$v" ;; + esac + done < "$cfg" + fi +} + +_ps1_style() { + _ps1_style_config + local style="${PS1_STYLE:-aurora}" + echo "$style" +} + +_ps1_style_pack() { + _ps1_style_config + local pack="${PS1_STYLE_PACK:-standard}" + echo "$pack" +} + +_ps1_contest_layout() { + _ps1_style_config + local layout="${PS1_CONTEST_LAYOUT:-day-time-user}" + echo "$layout" +} + +# Time-based emoji with seasonal accents (Europe/Oslo) +_ps1_pick_icon() { + local list_name="$1" + local season="$2" + local day + local -a list + local idx + local offset=0 + + day=$(TZ=Europe/Oslo date +%j) + case "$season" in + spring) offset=11 ;; + summer) offset=23 ;; + autumn) offset=37 ;; + *) offset=0 ;; + esac + + eval "list=(\"\${${list_name}[@]}\")" + if ((${#list[@]} == 0)); then + echo "" + return + fi + + idx=$(( (10#$day + offset) % ${#list[@]} )) + echo "${list[$idx]}" +} + _ps1_symbol() { local hh mm h m hh=$(TZ=Europe/Oslo date +%H); mm=$(TZ=Europe/Oslo date +%M) h=$((10#$hh)); m=$((10#$mm)) - if (( h >= 5 && h <= 8 )); then echo "πŸŒ…" - elif (( h >= 9 && h <= 10 )); then echo "β˜•" + local season="${__PS1_SEASON:-winter}" + local pack="${__PS1_STYLE_PACK:-standard}" + local rot_season="${season}" + local sunrise coffee work evening night + local -a sunrise_list work_list + + if [[ "$pack" == "contest" ]]; then + rot_season="winter" + sunrise_list=( "⚑️" "πŸ›°οΈ" "🧬" "🧠" ) + work_list=( "πŸ› οΈ" "πŸ’Ύ" "πŸ§ͺ" "🧩" ) + coffee="β˜•"; evening="πŸŽ›οΈ"; night="πŸ•ΆοΈ" + else + case "$season" in + spring) + sunrise_list=( "🌷" "🌱" "🐣" "🌀" ) + work_list=( "🌿" "πŸͺ΄" "🐝" "🌼" ) + coffee="β˜•"; evening="🌀"; night="πŸŒ™" + ;; + summer) + sunrise_list=( "🌞" "πŸ–οΈ" "πŸŒ…" "🌀" ) + work_list=( "πŸ„" "πŸ–οΈ" "🚀" "🌴" ) + coffee="πŸ§ƒ"; evening="πŸŒ‡"; night="πŸŒ™" + ;; + autumn) + sunrise_list=( "🍁" "πŸ‚" "🌫️" "🌦️" ) + work_list=( "πŸ‚" "πŸ„" "πŸŽƒ" "πŸͺ΅" ) + coffee="β˜•"; evening="πŸŒ†"; night="πŸŒ™" + ;; + *) + sunrise_list=( "πŸ”οΈ" "🌨️" "❄️" "🌌" ) + work_list=( "🎿" "⛷️" "πŸ‚" "🧊" ) + coffee="β˜•"; evening="πŸŒ†"; night="πŸŒ™" + ;; + esac + fi + + sunrise="$(_ps1_pick_icon sunrise_list "$rot_season")" + work="$(_ps1_pick_icon work_list "$rot_season")" + + if (( h >= 5 && h <= 8 )); then echo "$sunrise" + elif (( h >= 9 && h <= 10 )); then echo "$coffee" elif (( h == 11 && m < 30 )); then echo "πŸ₯ͺ" - elif (( (h == 11 && m >= 30) || (h >= 12 && h <= 15) )); then echo "πŸ’»" + elif (( (h == 11 && m >= 30) || (h >= 12 && h <= 15) )); then echo "$work" elif (( h == 16 )); then echo "🍲" - elif (( h >= 17 && h <= 22 )); then echo "πŸŒ†" - else echo "πŸŒ™" + elif (( h >= 17 && h <= 22 )); then echo "$evening" + else echo "$night" fi } @@ -73,22 +234,60 @@ __PS1_SYM="" __PS1_PATH="" __PS1_STATUS=0 __PS1_USE_NF=0 +__PS1_SEASON="winter" +__PS1_STYLE="aurora" +__PS1_STYLE_PACK="standard" +__PS1_CONTEST_LAYOUT="day-time-user" _ps1_set_prompt() { local RST="\[\e[0m\]" - # Two-zone palette - # Zone 1: gray-blue pastel (date/time/user) - local Z1_BG="\[\e[48;5;61m\]" - local Z1_FG="\[\e[38;5;255m\]" - - # Zone 2: turquoise / cool green (host/path) - local Z2_BG="\[\e[48;5;37m\]" - local Z2_FG="\[\e[38;5;255m\]" - local PATH_FG="\[\e[38;5;194m\]" - - # Frame - local FRAME="\[\e[38;5;60m\]" + # Seasonal palettes (Z1 is darker than Z2) + local Z1_BG Z1_FG Z2_BG Z2_FG PATH_FG FRAME + if [[ "$__PS1_STYLE_PACK" == "contest" ]]; then + Z1_BG="\[\e[48;5;24m\]" + Z1_FG="\[\e[38;5;255m\]" + Z2_BG="\[\e[48;5;31m\]" + Z2_FG="\[\e[38;5;255m\]" + PATH_FG="\[\e[38;5;51m\]" + FRAME="\[\e[38;5;23m\]" + else + case "$__PS1_SEASON" in + spring) + Z1_BG="\[\e[48;5;71m\]" + Z1_FG="\[\e[38;5;255m\]" + Z2_BG="\[\e[48;5;120m\]" + Z2_FG="\[\e[38;5;22m\]" + PATH_FG="\[\e[38;5;22m\]" + FRAME="\[\e[38;5;65m\]" + ;; + summer) + Z1_BG="\[\e[48;5;142m\]" + Z1_FG="\[\e[38;5;255m\]" + Z2_BG="\[\e[48;5;214m\]" + Z2_FG="\[\e[38;5;0m\]" + PATH_FG="\[\e[38;5;232m\]" + FRAME="\[\e[38;5;130m\]" + ;; + autumn) + Z1_BG="\[\e[48;5;95m\]" + Z1_FG="\[\e[38;5;255m\]" + Z2_BG="\[\e[48;5;173m\]" + Z2_FG="\[\e[38;5;255m\]" + PATH_FG="\[\e[38;5;223m\]" + FRAME="\[\e[38;5;95m\]" + ;; + *) + # winter (default) + Z1_BG="\[\e[48;5;61m\]" + Z1_FG="\[\e[38;5;255m\]" + Z2_BG="\[\e[48;5;37m\]" + Z2_FG="\[\e[38;5;255m\]" + PATH_FG="\[\e[38;5;194m\]" + FRAME="\[\e[38;5;60m\]" + ;; + esac + fi # Status colors local OK="\[\e[38;5;76m\]" @@ -110,16 +309,87 @@ _ps1_set_prompt() { prompt_sym="${BAD}${BOLD}➜${NOBOLD}${RST}" fi - # Keep order: date time user | host path, newline, then prompt - PS1="\ -${FRAME}╭─${RST}\ -${Z1_BG}${Z1_FG}${left} \\d \\A \\u ${RST}${Z1_BG}${Z2_BG}${Z2_FG}${sep}${RST}\ -${Z2_BG}${Z2_FG} @\\h ${PATH_FG}${__PS1_PATH} ${RST}${Z2_BG}${Z2_FG}${right}${RST}\ + local style="${__PS1_STYLE:-aurora}" + local L1="\\d \\A \\u" + local L_HOST="@\\h" + local PREFIX="" + local HOST_PART="${L_HOST}" + local PATH_PART="${__PS1_PATH}" + local PATH_SEP=" " + if [[ "$__PS1_STYLE_PACK" == "contest" ]]; then + local layout="${__PS1_CONTEST_LAYOUT:-day-time-user}" + case "$layout" in + user-time) L1="\\u \\A" ;; + time-user) L1="\\A \\u" ;; + day-time) L1="\\d \\A" ;; + *) L1="\\d \\A \\u" ;; + esac + HOST_PART="@\\h - ${__PS1_PATH}" + PATH_PART="" + PATH_SEP="" + PREFIX="\n" + fi + + case "$style" in + neon) + local N1_BG="\[\e[48;5;201m\]" + local N1_FG="\[\e[38;5;255m\]" + local N2_BG="\[\e[48;5;45m\]" + local N2_FG="\[\e[38;5;16m\]" + local N_PATH_FG="\[\e[38;5;16m\]" + local N_FRAME="\[\e[38;5;199m\]" + PS1="\ +${PREFIX}${N_FRAME}╭─${RST}\ +${N1_BG}${N1_FG}${left} ${BOLD}\\u${NOBOLD} \\d \\A ${RST}${N1_BG}${N2_BG}${N2_FG}${sep}${RST}\ +${N2_BG}${N2_FG} ${HOST_PART}${PATH_SEP}${N_PATH_FG}${PATH_PART} ${RST}${N2_BG}${N2_FG}${right}${RST}\ +\n${N_FRAME}╰── ${RST}${prompt_sym} ${__PS1_SYM} " + ;; + forge) + local F1_BG="\[\e[48;5;94m\]" + local F1_FG="\[\e[38;5;230m\]" + local F2_BG="\[\e[48;5;166m\]" + local F2_FG="\[\e[38;5;231m\]" + local F_PATH_FG="\[\e[38;5;224m\]" + local F_FRAME="\[\e[38;5;130m\]" + PS1="\ +${PREFIX}${F_FRAME}╭─${RST}\ +${F1_BG}${F1_FG}${left} ${BOLD}${L1}${NOBOLD} ${RST}${F1_BG}${F2_BG}${F2_FG}${sep}${RST}\ +${F2_BG}${F2_FG} ${HOST_PART}${PATH_SEP}${F_PATH_FG}${PATH_PART} ${RST}${F2_BG}${F2_FG}${right}${RST}\ +\n${F_FRAME}╰── ${RST}${prompt_sym} ${__PS1_SYM} " + ;; + circuit) + local C_FG="\[\e[38;5;46m\]" + local C_DIM="\[\e[38;5;22m\]" + local C_PATH="\[\e[38;5;120m\]" + PS1="\ +${PREFIX}${C_DIM}┏━${RST}${C_FG}[${BOLD}\\u${NOBOLD} ${RST}${C_FG}\\A${RST}${C_DIM}]${RST}\ +${C_DIM}━${RST}${C_FG}${HOST_PART}${RST}${PATH_SEP}${C_PATH}${PATH_PART}${RST}\ +\n${C_DIM}┗━${RST}${prompt_sym} ${__PS1_SYM} " + ;; + mono) + local M_FG="\[\e[38;5;250m\]" + local M_DIM="\[\e[38;5;240m\]" + PS1="\ +${PREFIX}${M_FG}⟦${L1}⟧ ${M_DIM}${HOST_PART}${PATH_SEP}${M_FG}${PATH_PART}${RST}\ +\n${M_DIM}└─${RST}${prompt_sym} ${__PS1_SYM} " + ;; + *) + # aurora (default) + PS1="\ +${PREFIX}${FRAME}╭─${RST}\ +${Z1_BG}${Z1_FG}${left} ${L1} ${RST}${Z1_BG}${Z2_BG}${Z2_FG}${sep}${RST}\ +${Z2_BG}${Z2_FG} ${HOST_PART}${PATH_SEP}${PATH_FG}${PATH_PART} ${RST}${Z2_BG}${Z2_FG}${right}${RST}\ \n${FRAME}╰── ${RST}${prompt_sym} ${__PS1_SYM} " + ;; + esac } _ps1_update() { __PS1_STATUS=$? + __PS1_SEASON="$(_ps1_season)" + __PS1_STYLE_PACK="$(_ps1_style_pack)" + __PS1_CONTEST_LAYOUT="$(_ps1_contest_layout)" + __PS1_STYLE="$(_ps1_style)" __PS1_SYM="$(_ps1_symbol)" __PS1_PATH="$(_ps1_path)" if _ps1_has_nf; then __PS1_USE_NF=1; else __PS1_USE_NF=0; fi