diff --git a/Cargo.toml b/Cargo.toml index 52537ac..5377337 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace.package] -version = "0.2.16" +version = "0.2.15" [workspace] resolver = "2" diff --git a/clients/web/assets/style.css b/clients/web/assets/style.css index 86e7cb8..24df8c0 100644 --- a/clients/web/assets/style.css +++ b/clients/web/assets/style.css @@ -1291,7 +1291,7 @@ a:hover { text-decoration: underline; } pointer-events: auto; display: flex; flex-direction: column; - align-items: center; + align-items: flex-end; gap: 3px; animation: scoring-panel-enter 0.3s ease-out; } @@ -1889,7 +1889,8 @@ a:hover { text-decoration: underline; } } .free-mode-error { - text-align: center; + display: flex; + align-items: center; gap: 0.75rem; background: rgba(180, 60, 30, 0.12); border: 1px solid rgba(180, 60, 30, 0.4); @@ -1899,6 +1900,7 @@ a:hover { text-decoration: underline; } box-sizing: border-box; } .free-mode-error-msg { + flex: 1; font-family: var(--font-ui); font-size: 0.85rem; color: #8b2000; @@ -2301,219 +2303,3 @@ a:hover { text-decoration: underline; } background: rgba(200,164,72,0.1); font-weight: 600; } - -/* Prevent horizontal scrollbar from the full-bleed strip */ -.game-overlay { overflow-x: hidden !important; } - -/* Board bar: hide die slots, keep the rail as a thin divider */ -.bar-die-slot { display: none !important; } -.board-bar { width: 5px; overflow: hidden; } - -/* ── Full-width in-flow player strip ─────────────────────────────────── */ -.players-strip { - width: 100vw; - margin-top: -1.5rem; /* undo game-overlay top padding */ - display: flex; - align-items: center; - background: var(--ui-parchment); - border-bottom: 2px solid var(--ui-gold-dark); - box-shadow: 0 2px 8px rgba(0,0,0,0.18); - padding: 0.35rem 1.5rem; - gap: 0.5rem; -} - -.strip-player { display: flex; align-items: center; flex: 1; min-width: 0; } -.strip-player-left { justify-content: flex-end; } -.strip-player-right { justify-content: flex-start; } - -.strip-active-zone { - display: flex; - align-items: center; - gap: 0.7rem; - border-radius: 8px; - padding: 0.28rem 0.5rem; - transition: background 0.15s; -} -.strip-active-zone.active { background: rgba(58,42,10,0.15); } - -/* Checker-style circles */ -.strip-avatar { - width: 38px; height: 38px; - border-radius: 50%; - flex-shrink: 0; -} -.strip-avatar-me { - background-image: - radial-gradient(ellipse 50% 35% at 36% 30%, rgba(255,255,255,0.65) 0%, transparent 100%), - radial-gradient(circle, transparent 68%, rgba(160,130,70,0.22) 68.5%, rgba(160,130,70,0.22) 71.5%, transparent 72%), - radial-gradient(circle, transparent 43%, rgba(160,130,70,0.17) 43.5%, rgba(160,130,70,0.17) 46.5%, transparent 47%), - radial-gradient(circle at 38% 32%, #ffffff 0%, var(--checker-white) 52%, #c0b288 100%); - border: 1.8px solid var(--checker-ring); - box-shadow: 0 2px 6px rgba(0,0,0,0.35), inset 0 -1px 3px rgba(0,0,0,0.15); -} -.strip-avatar-opp { - background-image: - radial-gradient(ellipse 40% 28% at 36% 30%, rgba(110,65,30,0.38) 0%, transparent 100%), - radial-gradient(circle, transparent 68%, rgba(200,164,72,0.18) 68.5%, rgba(200,164,72,0.18) 71.5%, transparent 72%), - radial-gradient(circle, transparent 43%, rgba(200,164,72,0.13) 43.5%, rgba(200,164,72,0.13) 46.5%, transparent 47%), - radial-gradient(circle at 38% 32%, #4a2e1a 0%, #1c1008 45%, var(--checker-black) 100%); - border: 1.8px solid var(--checker-ring); - box-shadow: 0 2px 6px rgba(0,0,0,0.5), inset 0 -1px 3px rgba(0,0,0,0.4); -} - -/* Strip peg overrides */ -.players-strip .peg-track { gap: 3px; } -.players-strip .peg-hole { width: 12px; height: 12px; } -.players-strip .peg-hole.filled { - background: #5aab38; border-color: #3a7828; - box-shadow: 0 0 5px rgba(90,171,56,0.55); -} -.players-strip .peg-hole.peg-opp.filled { - background: #c05030; border-color: #8a3018; - box-shadow: 0 0 5px rgba(192,80,48,0.55); -} - -/* Strip score-row-name: remove fixed width from v01 */ -.players-strip .score-row-name { width: auto; } - -/* No ghost bar below pts-counter in the strip */ -.players-strip .pts-counter-wrap { padding-bottom: 0; } - -/* Center "Trictrac" title */ -.players-strip-center { - flex-shrink: 0; - padding: 0 1rem; - border-left: 1px solid rgba(138,106,40,0.2); - border-right: 1px solid rgba(138,106,40,0.2); -} -.strip-title { - font-family: var(--font-display); - font-size: 2rem; - font-weight: 600; - color: var(--ui-ink); - letter-spacing: 0.03em; - white-space: nowrap; - margin-left: 1rem; -} - -/* ── Body: board + controls ──────────────────────────────────────────── */ -.main-body { - display: flex; - align-items: flex-start; - gap: 0.5rem; -} - -/* ── Controls column (sidebar on wide, row on narrow) ────────────────── */ -.controls { - display: flex; - flex-direction: column; - gap: 0.5rem; - align-self: stretch; -} -@media (min-width: 920px) { - .controls { - width: 200px; - } -} - -.ctrl-dice { - background: var(--board-rail); - border-radius: 5px; - border-top: 2px solid var(--ui-gold-dark); - box-shadow: 0 2px 6px rgba(0,0,0,0.2); - padding: 0.6rem 0.75rem 0.75rem; - display: flex; - flex-direction: column; - align-items: center; - gap: 0.5rem; - flex-shrink: 0; -} -.ctrl-dice-row { - display: flex; - gap: 0.55rem; - align-items: center; - justify-content: center; -} - -/* Free-mode toggle: light text on dark board-rail background */ -.ctrl-dice .free-mode-toggle { - color: var(--ui-parchment); - font-size: 0.7rem; - flex-wrap: wrap; - justify-content: center; - text-align: center; - gap: 0.3rem; -} -.ctrl-dice .free-mode-help { - border-color: rgba(242,232,208,0.35); - color: rgba(242,232,208,0.5); -} - -.ctrl-status { - background: var(--ui-parchment); - border-radius: 5px; - border-top: 2px solid var(--ui-gold-dark); - box-shadow: 0 2px 6px rgba(0,0,0,0.2); - padding: 0.65rem 0.75rem 0.75rem; - display: flex; - flex-direction: column; - justify-content: space-around; - align-items: center; - gap: 0.4rem; - flex: 1; - min-width: 0; -} -.ctrl-status .game-status { - color: var(--ui-ink); - text-shadow: none; - font-size: 1rem; - padding: 0; - width: auto; - text-align: center; - line-height: 1.3; -} -.ctrl-status .board-actions { - flex-wrap: wrap; - justify-content: center; - min-height: 0; -} -.ctrl-status .game-sub-prompt { - color: #887766; - padding: 0; - width: auto; - text-align: center; - font-size: 0.67rem; - line-height: 1.4; - margin: 0; -} - -.scoring-row .scoring-panels-container { - position: static; - top: auto; left: auto; right: auto; bottom: auto; - z-index: auto; - padding: 0; - pointer-events: auto; - display: flex; - flex-direction: column; - align-items: stretch; - gap: 4px; -} -.scoring-row .scoring-panel { - box-sizing: border-box; - margin: 0; -} - -/* ── Responsive: ≤919px → controls becomes a bottom bar ─────────────── */ -@media (max-width: 919px) { - .main-body { - flex-direction: column; - align-items: stretch; - } - .controls { - flex-direction: row; - width: 100%; - } - .ctrl-status { flex: 1; } - /* Hide pegs on small screens to save space in the strip */ - .players-strip .peg-track { display: none; } -} diff --git a/clients/web/src/game/components/board.rs b/clients/web/src/game/components/board.rs index c1a12c6..34266ca 100644 --- a/clients/web/src/game/components/board.rs +++ b/clients/web/src/game/components/board.rs @@ -39,7 +39,7 @@ fn field_zone_class(field_num: u8) -> &'static str { } /// Returns (d0_used, d1_used) for the bar dice display. -pub(crate) fn bar_matched_dice_used(staged: &[(u8, u8)], dice: (u8, u8)) -> (bool, bool) { +fn bar_matched_dice_used(staged: &[(u8, u8)], dice: (u8, u8)) -> (bool, bool) { let mut d0 = false; let mut d1 = false; for &(from, to) in staged { @@ -112,8 +112,7 @@ fn valid_origins_for(seqs: &[(CheckerMove, CheckerMove)], staged: &[(u8, u8)]) - } /// Pixel center of a board field in the SVG overlay coordinate space. -/// Geometry: field 60×180px, board padding 4px, row gap 4px, bar 5px, center-bar 12px. -/// Quarter width: 6×60 + 5×2(inter-field gap) = 370px. Board total: 761px. +/// Geometry: field 60×180px, board padding 4px, gap 4px, bar 20px, center-bar 12px. /// With triangular flèches, arrows target the WIDE BASE of each triangle — /// that is where the checker stack actually sits. fn field_center(f: usize, is_white: bool) -> Option<(f32, f32)> { @@ -138,9 +137,9 @@ fn field_center(f: usize, is_white: bool) -> Option<(f32, f32)> { } }; // Left-quarter field i center x: 4(pad) + i*62 + 30(half field) = 34 + 62i - // Right-quarter: 4 + 370(quarter) + 4(gap) + 5(bar) + 4(gap) + i*62 + 30 = 417 + 62i + // Right-quarter: 4 + 370(quarter) + 4(gap) + 68(bar) + 4(gap) + i*62 + 30 = 480 + 62i let x = if right { - 417.0 + qi as f32 * 62.0 + 480.0 + qi as f32 * 62.0 } else { 34.0 + qi as f32 * 62.0 }; @@ -252,11 +251,7 @@ fn free_mode_origins_for(board: [i8; 24], staged: &[(u8, u8)], is_white: bool) - (1u8..=24) .filter(|&f| { let v = displayed_value(board, staged, is_white, f); - if is_white { - v > 0 - } else { - v < 0 - } + if is_white { v > 0 } else { v < 0 } }) .collect() } @@ -283,11 +278,7 @@ fn free_mode_dests_for( let &(f0, t0) = &staged[0]; if t0 == 0 { // First move was an exit — can't reliably infer die, offer both - if dice.0 == dice.1 { - vec![dice.0] - } else { - vec![dice.0, dice.1] - } + if dice.0 == dice.1 { vec![dice.0] } else { vec![dice.0, dice.1] } } else { let dist: u8 = if is_white { t0.saturating_sub(f0) @@ -308,11 +299,7 @@ fn free_mode_dests_for( let opp_present = |f: u8| -> bool { let v = displayed_value(board, staged, is_white, f); - if is_white { - v < 0 - } else { - v > 0 - } + if is_white { v < 0 } else { v > 0 } }; let mut dests = vec![]; @@ -328,16 +315,7 @@ fn free_mode_dests_for( if dest >= 1 && dest <= 24 { let d = dest as u8; if !opp_present(d) { - if d == 13 && is_white && displayed_value(board, staged, is_white, 12) < 2 { - // prise de coin par puissance for white - dests.push(12) - } else if d == 12 && !is_white && displayed_value(board, staged, is_white, 13) > -2 - { - // prise de coin par puissance for black - dests.push(13) - } else { - dests.push(d); - } + dests.push(d); } } else if all_in_exit { dests.push(0); // exit @@ -698,11 +676,23 @@ pub fn Board( (&TOP_LEFT_B, &TOP_RIGHT_B, &BOT_LEFT_B, &BOT_RIGHT_B) }; + // Zone label pairs (top-left, top-right, bot-left, bot-right) per perspective. + let (label_tl, label_tr, label_bl, label_br) = if is_white { + ("", "jan de retour", "grand jan", "petit jan") + } else { + ("petit jan", "grand jan", "jan de retour", "") + }; + view! { // board-wrapper keeps zone labels outside .board so the SVG overlay // inside .board stays correctly positioned (position:absolute top:0 left:0 // is relative to .board, not the wrapper).
+
+
{label_tl}
+
+
{label_tr}
+
{fields_from(tl, true)}
@@ -717,7 +707,7 @@ pub fn Board(
// SVG overlay: arrows for hovered jan moves {move || { @@ -843,6 +833,11 @@ pub fn Board( }) }}
+
+
{label_bl}
+
+
{label_br}
+
} } diff --git a/clients/web/src/game/components/game_screen.rs b/clients/web/src/game/components/game_screen.rs index 46f9deb..7a73edf 100644 --- a/clients/web/src/game/components/game_screen.rs +++ b/clients/web/src/game/components/game_screen.rs @@ -4,17 +4,15 @@ use std::collections::VecDeque; use futures::channel::mpsc::UnboundedSender; use gloo_storage::Storage as _; use leptos::prelude::*; -use trictrac_store::{ - Board as StoreBoard, CheckerMove, Color, Dice as StoreDice, Jan, MoveError, MoveRules, -}; +use trictrac_store::{Board as StoreBoard, CheckerMove, Color, Dice as StoreDice, Jan, MoveError, MoveRules}; -use super::board::{bar_matched_dice_used, Board}; use super::die::Die; use crate::app::{GameUiState, NetCommand, PauseReason}; use crate::game::trictrac::types::{PlayerAction, PreGameRollState, SerStage, SerTurnStage}; use crate::i18n::*; use crate::portal::lobby::{qr_svg, room_url}; +use super::board::Board; use super::score_panel::MergedScorePanel; use super::scoring::ScoringPanel; @@ -49,9 +47,14 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView { let pending = use_context::>>().expect("pending not found in context"); let cmd_tx_effect = cmd_tx.clone(); + // Non-reactive counter so we can detect when staged_moves grows without + // returning a value from the Effect (which causes a Leptos reactive loop + // when the Effect also writes to the same signal it reads). let prev_staged_len = Cell::new(0usize); // ── Free-play mode ───────────────────────────────────────────────────────── + // When enabled the board shows all own-checker fields as valid origins and + // invalid moves produce an explanatory error rather than being suppressed. fn load_free_mode() -> bool { gloo_storage::LocalStorage::get::("trictrac_free_mode").unwrap_or(false) } @@ -59,11 +62,13 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView { gloo_storage::LocalStorage::set("trictrac_free_mode", val).ok(); } let free_mode: RwSignal = RwSignal::new(load_free_mode()); + // None = no error; Some(None) = generic invalid; Some(Some(e)) = specific rule error let move_error: RwSignal>> = RwSignal::new(None); Effect::new(move |_| { let moves = staged_moves.get(); let n = moves.len(); + // Play checker sound whenever a move is added (own moves, immediate feedback). if n > prev_staged_len.get() { crate::game::sound::play_checker_move(); } @@ -76,6 +81,7 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView { let m2 = to_cm(&moves[1]); if free_mode.get_untracked() { + // Mirror moves to White-perspective for validation (MoveRules always works as White) let (vm1, vm2) = if player_id == 0 { (m1, m2) } else { @@ -84,36 +90,39 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView { let mut store_board = StoreBoard::new(); store_board.set_positions(&Color::White, vs_board); let store_dice = StoreDice { values: vs_dice }; - let color = if player_id == 0 { - Color::White - } else { - Color::Black - }; + let color = if player_id == 0 { Color::White } else { Color::Black }; let rules = MoveRules::new(&color, &store_board, store_dice); if rules.moves_follow_rules(&(vm1, vm2)) { cmd_tx_effect .unbounded_send(NetCommand::Action(PlayerAction::Move(m1, m2))) .ok(); - staged_moves.set(vec![]); - selected_origin.set(None); - prev_staged_len.set(0); } else { + // moves_allowed gives the specific TricTrac rule that was broken (if any) let specific_err = rules.moves_allowed(&(vm1, vm2)).err(); move_error.set(Some(specific_err)); - // Keep staged_moves intact so pieces stay in place until Retry is clicked. } } else { cmd_tx_effect .unbounded_send(NetCommand::Action(PlayerAction::Move(m1, m2))) .ok(); - staged_moves.set(vec![]); - selected_origin.set(None); - prev_staged_len.set(0); } + + staged_moves.set(vec![]); + selected_origin.set(None); + // Reset the counter so the next turn starts clean. + prev_staged_len.set(0); } }); // ── Auto-roll effect ───────────────────────────────────────────────────── + // GameScreen is fully re-mounted on every ViewState update (state is a + // plain prop, not a signal), so this effect fires exactly once per + // RollDice phase entry and will not double-send. + // Guard: suppressed while waiting_for_confirm — the AfterOpponentMove + // buffered state shows the human's RollDice turn but the auto-roll must + // wait until the buffer is drained and the live screen state is shown. + // Guard: never auto-roll during the pre-game ceremony (the ceremony overlay + // has its own Roll button for PlayerAction::PreGameRoll). let show_roll = is_my_turn && vs.turn_stage == SerTurnStage::RollDice && vs.stage != SerStage::PreGameRoll; if show_roll && !waiting_for_confirm { @@ -132,11 +141,14 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView { let cmd_tx_go = cmd_tx.clone(); let cmd_tx_end_quit = cmd_tx.clone(); let cmd_tx_end_replay = cmd_tx.clone(); + // Only show the fallback Go button when there is no ScoringPanel showing it. let show_hold_go = is_my_turn && vs.turn_stage == SerTurnStage::HoldOrGoChoice && state.my_scored_event.is_none(); // ── Valid move sequences for this turn ───────────────────────────────────── + // Computed once per ViewState snapshot; used by Board (highlighting) and the + // empty-move button (visibility). let valid_sequences: Vec<(CheckerMove, CheckerMove)> = if is_move_stage && dice != (0, 0) { let mut store_board = StoreBoard::new(); store_board.set_positions(&Color::White, vs.board); @@ -158,13 +170,14 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView { } else { vec![] }; + // Clone for the empty-move button reactive closure (Board consumes the original). let valid_seqs_empty = valid_sequences.clone(); // ── Scores ───────────────────────────────────────────────────────────────── let my_score = vs.scores[player_id as usize].clone(); let opp_score = vs.scores[1 - player_id as usize].clone(); - // ── Ceremony state ────────────────────────────────────────────────────────── + // ── Ceremony state (extracted before vs is moved into Board) ──────────────── let is_ceremony = vs.stage == SerStage::PreGameRoll; let pre_game_roll_data: Option = vs.pre_game_roll.clone(); let my_name_ceremony = my_score.name.clone(); @@ -175,6 +188,8 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView { let my_scored_event = state.my_scored_event.clone(); let opp_scored_event = state.opp_scored_event.clone(); + // Values for MergedScorePanel — extracted before events are consumed. + // Don't animate points when a hole was gained (points wrap around 12). let my_pts_earned: u8 = my_scored_event.as_ref().map_or(0, |e| { if e.holes_gained == 0 { e.points_earned @@ -199,6 +214,7 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView { let last_moves = state.last_moves; + // fields where a battue (hit) was scored; ripple animation shown there. let hit_fields: Vec = { let is_hit_jan = |jan: &Jan| { matches!( @@ -230,7 +246,10 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView { fields }; - // ── Sound effects ────────────────────────────────────────────────────────── + // ── Sound effects (fire once on mount = once per state snapshot) ────────── + // Dice roll: dice are fresh for the currently active player (Move stage means + // someone just rolled). Skipped on turn-switch states where the old dice linger + // in RollDice/MarkPoints stage before the opponent has rolled. let active_is_move_stage = matches!( vs.turn_stage, SerTurnStage::Move | SerTurnStage::HoldOrGoChoice @@ -238,9 +257,12 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView { if show_dice && last_moves.is_none() && active_is_move_stage && !suppress_dice_anim { crate::game::sound::play_dice_roll(); } + // Checker move: moves were committed in the preceding action. if last_moves.is_some() { crate::game::sound::play_checker_move(); } + // Scoring: hole fanfare plays immediately; per-point ticks are driven by + // MergedScorePanel's counter animation so play_points_scored is not called here. if let Some(ref ev) = my_scored_event { if ev.holes_gained > 0 { crate::game::sound::play_hole_scored(); @@ -260,13 +282,6 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView { let room_id = state.room_id.clone(); let is_bot_game = state.is_bot_game; - // ── Active player indicator ──────────────────────────────────────────────── - let active_player_is_me: Option = if stage == SerStage::InGame { - Some(is_my_turn) - } else { - None - }; - // ── Game-over info ───────────────────────────────────────────────────────── let stage_is_ended = stage == SerStage::Ended; let winner_is_me = my_score.holes >= 12; @@ -288,8 +303,8 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView { }; view! { + // ── Game container ────────────────────────────────────────────────────
- // ── Share popover (while waiting for opponent) ─────────────────── {(!is_bot_game && stage == SerStage::PreGame).then(|| { let url_label = share_url.clone(); @@ -331,201 +346,20 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView { } })} - // ── Player strip (full-width, in-flow) ─────────────────────────── - - - // ── Board + controls (sidebar on wide, footer on narrow) ───────── -
- + - - // ── Controls: dice card + status/actions card ──────────────── -
- {show_dice.then(|| view! { -
-
- {move || { - let staged = staged_moves.get(); - let (u0, u1) = if suppress_dice_anim { - (true, true) - } else if is_move_stage { - bar_matched_dice_used(&staged, dice) - } else { - (false, false) - }; - view! { - - - } - }} -
- -
- })} - -
-
- {move || { - if let Some(ref reason) = pause_reason { - return String::from(match reason { - PauseReason::AfterOpponentRoll => t_string!(i18n, after_opponent_roll), - PauseReason::AfterOpponentGo => t_string!(i18n, after_opponent_go), - PauseReason::AfterOpponentMove => t_string!(i18n, after_opponent_move), - PauseReason::AfterOpponentPreGameRoll => t_string!(i18n, after_opponent_pre_game_roll), - }); - } - let n = staged_moves.get().len(); - if is_move_stage { - t_string!(i18n, select_move, n = n + 1) - } else { - String::from(match (&stage, is_my_turn, &turn_stage) { - (SerStage::Ended, _, _) => t_string!(i18n, game_over), - (SerStage::PreGame, _, _) | (SerStage::PreGameRoll, _, _) => t_string!(i18n, waiting_for_opponent), - (SerStage::InGame, true, SerTurnStage::RollDice) => t_string!(i18n, your_turn_roll), - (SerStage::InGame, true, SerTurnStage::HoldOrGoChoice) => t_string!(i18n, hold_or_go), - (SerStage::InGame, true, _) => t_string!(i18n, your_turn), - (SerStage::InGame, false, _) => t_string!(i18n, opponent_turn), - }) - } - }} -
- {move || { - let hint: String = if waiting_for_confirm { - t_string!(i18n, hint_continue).to_owned() - } else if is_move_stage { - t_string!(i18n, hint_move).to_owned() - } else if is_my_turn && turn_stage_for_sub == SerTurnStage::HoldOrGoChoice { - t_string!(i18n, hint_hold_or_go).to_owned() - } else { - String::new() - }; - (!hint.is_empty()).then(|| view! {

{hint}

}) - }} - // ── Free-mode error banner ───────────────────────────── - {move || { - move_error.get().map(|opt_err| { - let msg: String = match opt_err { - None => t_string!(i18n, err_invalid_move).to_owned(), - Some(MoveError::OpponentCorner) => t_string!(i18n, err_opponent_corner).to_owned(), - Some(MoveError::CornerNeedsTwoCheckers) => t_string!(i18n, err_corner_needs_two).to_owned(), - Some(MoveError::CornerByEffectPossible) => t_string!(i18n, err_corner_by_effect).to_owned(), - Some(MoveError::ExitNeedsAllCheckersOnLastQuarter) => t_string!(i18n, err_exit_needs_all_in_last_jan).to_owned(), - Some(MoveError::ExitByEffectPossible) => t_string!(i18n, err_exit_by_effect).to_owned(), - Some(MoveError::ExitNotFarthest) => t_string!(i18n, err_exit_not_farthest).to_owned(), - Some(MoveError::OpponentCanFillQuarter) => t_string!(i18n, err_opponent_can_fill_quarter).to_owned(), - Some(MoveError::MustFillQuarter) => t_string!(i18n, err_must_fill_quarter).to_owned(), - Some(MoveError::MustPlayAllDice) => t_string!(i18n, err_must_play_all_dice).to_owned(), - Some(MoveError::MustPlayStrongerDie) => t_string!(i18n, err_must_play_stronger_die).to_owned(), - }; - view! { -
- {msg} - -
- } - }) - }} -
- {waiting_for_confirm.then(|| view! { - - })} - {show_hold_go.then(|| view! { - - })} - {move || { - let staged = staged_moves.get(); - let show = is_move_stage && staged.len() < 2 && ( - valid_seqs_empty.is_empty() || match staged.len() { - 0 => valid_seqs_empty.iter().any(|(m1, _)| m1.get_from() == 0), - 1 => { - let (f0, t0) = staged[0]; - valid_seqs_empty.iter() - .filter(|(m1, _)| { - m1.get_from() as u8 == f0 - && m1.get_to() as u8 == t0 - }) - .any(|(_, m2)| m2.get_from() == 0) - } - _ => false, - } - ); - show.then(|| view! { - - }) - }} - {move || { - (is_move_stage && staged_moves.get().len() == 1).then(|| view! { - - }) - }} -
-
-
-
- - // ── Scoring notification panels ─────────────────────────────────── -
+ // Scoring detail panels — stacked at the right, overlapping if needed.
{my_scored_event.map(|event| view! { @@ -536,6 +370,157 @@ pub fn GameScreen(state: GameUiState) -> impl IntoView {
+ // ── Board ──────────────────────────────────────────────────────── + + + // ── Status, hints, and actions — cream strip below board ─ +
+
+ {move || { + if let Some(ref reason) = pause_reason { + return String::from(match reason { + PauseReason::AfterOpponentRoll => t_string!(i18n, after_opponent_roll), + PauseReason::AfterOpponentGo => t_string!(i18n, after_opponent_go), + PauseReason::AfterOpponentMove => t_string!(i18n, after_opponent_move), + PauseReason::AfterOpponentPreGameRoll => t_string!(i18n, after_opponent_pre_game_roll), + }); + } + let n = staged_moves.get().len(); + if is_move_stage { + t_string!(i18n, select_move, n = n + 1) + } else { + String::from(match (&stage, is_my_turn, &turn_stage) { + (SerStage::Ended, _, _) => t_string!(i18n, game_over), + (SerStage::PreGame, _, _) | (SerStage::PreGameRoll, _, _) => t_string!(i18n, waiting_for_opponent), + (SerStage::InGame, true, SerTurnStage::RollDice) => t_string!(i18n, your_turn_roll), + (SerStage::InGame, true, SerTurnStage::HoldOrGoChoice) => t_string!(i18n, hold_or_go), + (SerStage::InGame, true, _) => t_string!(i18n, your_turn), + (SerStage::InGame, false, _) => t_string!(i18n, opponent_turn), + }) + } + }} +
+ {move || { + let hint: String = if waiting_for_confirm { + t_string!(i18n, hint_continue).to_owned() + } else if is_move_stage { + t_string!(i18n, hint_move).to_owned() + } else if is_my_turn && turn_stage_for_sub == SerTurnStage::HoldOrGoChoice { + t_string!(i18n, hint_hold_or_go).to_owned() + } else { + String::new() + }; + (!hint.is_empty()).then(|| view! {

{hint}

}) + }} + // ── Free-mode error banner ───────────────────────────────────── + {move || { + move_error.get().map(|opt_err| { + let msg: String = match opt_err { + None => t_string!(i18n, err_invalid_move).to_owned(), + Some(MoveError::OpponentCorner) => t_string!(i18n, err_opponent_corner).to_owned(), + Some(MoveError::CornerNeedsTwoCheckers) => t_string!(i18n, err_corner_needs_two).to_owned(), + Some(MoveError::CornerByEffectPossible) => t_string!(i18n, err_corner_by_effect).to_owned(), + Some(MoveError::ExitNeedsAllCheckersOnLastQuarter) => t_string!(i18n, err_exit_needs_all_in_last_jan).to_owned(), + Some(MoveError::ExitByEffectPossible) => t_string!(i18n, err_exit_by_effect).to_owned(), + Some(MoveError::ExitNotFarthest) => t_string!(i18n, err_exit_not_farthest).to_owned(), + Some(MoveError::OpponentCanFillQuarter) => t_string!(i18n, err_opponent_can_fill_quarter).to_owned(), + Some(MoveError::MustFillQuarter) => t_string!(i18n, err_must_fill_quarter).to_owned(), + Some(MoveError::MustPlayAllDice) => t_string!(i18n, err_must_play_all_dice).to_owned(), + Some(MoveError::MustPlayStrongerDie) => t_string!(i18n, err_must_play_stronger_die).to_owned(), + }; + view! { +
+ {msg} + +
+ } + }) + }} +
+ {waiting_for_confirm.then(|| view! { + + })} + // Fallback Go button when no scoring panel (e.g. after reconnect) + {show_hold_go.then(|| view! { + + })} + {move || { + let staged = staged_moves.get(); + let show = is_move_stage && staged.len() < 2 && ( + valid_seqs_empty.is_empty() || match staged.len() { + 0 => valid_seqs_empty.iter().any(|(m1, _)| m1.get_from() == 0), + 1 => { + let (f0, t0) = staged[0]; + valid_seqs_empty.iter() + .filter(|(m1, _)| { + m1.get_from() as u8 == f0 + && m1.get_to() as u8 == t0 + }) + .any(|(_, m2)| m2.get_from() == 0) + } + _ => false, + } + ); + show.then(|| view! { + + }) + }} + {move || { + (is_move_stage && staged_moves.get().len() == 1).then(|| view! { + + }) + }} +
+ // ── Free-play mode toggle ───────────────────────────────────── + +
+ // ── Pre-game ceremony overlay ───────────────────────────────────── {is_ceremony.then(|| { let pgr = pre_game_roll_data.unwrap_or(PreGameRollState { diff --git a/clients/web/src/game/components/score_panel.rs b/clients/web/src/game/components/score_panel.rs index 94e5f8a..bd531f3 100644 --- a/clients/web/src/game/components/score_panel.rs +++ b/clients/web/src/game/components/score_panel.rs @@ -32,18 +32,19 @@ pub fn jan_label(jan: &Jan) -> String { } } -/// Full-width player strip at the top of the game screen. +/// Merged scoreboard showing both players above the board. /// -/// - Left side: me (right-aligned toward center): avatar → name → pegs → pts. -/// - Center: "Trictrac" italic title. -/// - Right side: opponent (left-aligned from center): pts → pegs → name → avatar. -/// - Active player zone gets a subtle rounded highlight. -/// - Points animate as a jackpot counter; new peg pops in with an animation. +/// - Two stacked rows for a clear race-to-12 visual comparison. +/// - Points shown as an animated jackpot counter (ticks up on each new point). +/// - Hole pegs are larger and use green (me) / red (opponent) instead of gold. +/// - When a hole is gained, the new peg pops in and a brief non-blocking label +/// appears instead of the old blocking toast popup. #[component] pub fn MergedScorePanel( my_score: PlayerScore, opp_score: PlayerScore, - /// Points just earned this turn; 0 = no animation. + /// Points just earned this turn; 0 = no animation. Set to 0 when a hole + /// was gained (points wrap around 12, counter stays at end value). #[prop(default = 0)] my_points_earned: u8, #[prop(default = 0)] opp_points_earned: u8, @@ -54,13 +55,14 @@ pub fn MergedScorePanel( /// True when my hole was scored under bredouille (shows ×2 in the flash). #[prop(default = false)] my_bredouille: bool, - /// `Some(true)` = my turn active, `Some(false)` = opponent active, `None` = no active turn. - #[prop(default = None)] - active_player_is_me: Option, ) -> impl IntoView { let i18n = use_i18n(); // ── Points counter signals ────────────────────────────────────────────── + // When no hole was gained: start from (current - earned) and tick up. + // When a hole was gained: points wrapped around 12, so skip the animation. + // On non-WASM there is no animation; start directly at the final value. + // Suppress the unused-variable warning for animation-only params. #[cfg(not(target_arch = "wasm32"))] let _ = (my_points_earned, opp_points_earned); #[cfg(not(target_arch = "wasm32"))] @@ -120,6 +122,10 @@ pub fn MergedScorePanel( } } + // ── Ghost bar widths (show the end value immediately — static reference) ─ + let my_bar_style = format!("width:{}%", (my_score.points as u32 * 100 / 12).min(100)); + let opp_bar_style = format!("width:{}%", (opp_score.points as u32 * 100 / 12).min(100)); + // ── Hole peg tracks ───────────────────────────────────────────────────── let my_holes = my_score.holes; let opp_holes = opp_score.holes; @@ -157,77 +163,73 @@ pub fn MergedScorePanel( let my_can_bredouille = my_score.can_bredouille; let opp_can_bredouille = opp_score.can_bredouille; - let my_active = active_player_is_me == Some(true); - let opp_active = active_player_is_me == Some(false); - view! { -
+
- // ── My player: left side, right-aligned toward center ─────────── -
-
-
-
- {my_name} - {t!(i18n, you_suffix)} -
- {my_can_bredouille.then(|| view! { - - "B" - - })} -
{my_pegs}
-
-
- {move || my_displayed_pts.get()} - "/12" -
-
- {(my_holes_gained > 0).then(|| { - let label = if my_bredouille { - format!("Trou {} · ×2 bredouille", my_holes) - } else { - format!("Trou {}", my_holes) - }; - view! { -
- {label} -
- } - })} + // ── My player row ─────────────────────────────────────────── +
+
+ {my_name} + {t!(i18n, you_suffix)}
-
- - // ── Center title ──────────────────────────────────────────────── -
- "Trictrac" -
- - // ── Opponent: right side, left-aligned from center ────────────── -
-
-
-
- {move || opp_displayed_pts.get()} - "/12" -
+
+
+
-
{opp_pegs}
- {opp_can_bredouille.then(|| view! { - - "B" - - })} -
- {opp_name} +
+ {move || my_displayed_pts.get()} + "/12"
-
+
{my_pegs}
+ {my_can_bredouille.then(|| view! { + + "B" + + })} + // Flash sits in the free space to the right of the pegs. + // margin-left:auto keeps it right-aligned inside the flex row + // without adding a new row, so the board never shifts down. + {(my_holes_gained > 0).then(|| { + let label = if my_bredouille { + format!("Trou {} · ×2 bredouille", my_holes) + } else { + format!("Trou {}", my_holes) + }; + view! { +
+ {label} +
+ } + })}
+
+ + // ── Opponent row ──────────────────────────────────────────── +
+
+ {opp_name} +
+
+
+
+
+
+ {move || opp_displayed_pts.get()} + "/12" +
+
+
{opp_pegs}
+ {opp_can_bredouille.then(|| view! { + + "B" + + })} +
} } diff --git a/devenv.lock b/devenv.lock index e6e8ef6..3f0905b 100644 --- a/devenv.lock +++ b/devenv.lock @@ -17,6 +17,62 @@ "type": "github" } }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1767039857, + "owner": "NixOS", + "repo": "flake-compat", + "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "flake-compat", + "type": "github" + } + }, + "git-hooks": { + "inputs": { + "flake-compat": "flake-compat", + "gitignore": "gitignore", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1778507602, + "owner": "cachix", + "repo": "git-hooks.nix", + "rev": "61ab0e80d9c7ab14c256b5b453d8b3fb0189ba0a", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "git-hooks.nix", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "git-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1762808025, + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "cb5e3fdca1de58ccbc3ef53de65bd372b48f567c", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, "nixpkgs": { "locked": { "lastModified": 1779102034, @@ -52,11 +108,15 @@ "root": { "inputs": { "devenv": "devenv", + "git-hooks": "git-hooks", "nixpkgs": "nixpkgs", - "nixpkgs-cmake3": "nixpkgs-cmake3" + "nixpkgs-cmake3": "nixpkgs-cmake3", + "pre-commit-hooks": [ + "git-hooks" + ] } } }, "root": "root", "version": 7 -} \ No newline at end of file +} diff --git a/doc/design/snapshots/2026-05-30-d4a2ea1c531827bfbc2410af20a00e349d606c87.html b/doc/design/snapshots/2026-05-30-d4a2ea1c531827bfbc2410af20a00e349d606c87.html deleted file mode 100644 index e2a19dc..0000000 --- a/doc/design/snapshots/2026-05-30-d4a2ea1c531827bfbc2410af20a00e349d606c87.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - Trictrac - - - - - -
Anonyme (vous)
6/12
Bot
2/12
+4 pts
Battage à vrai (petit jan)simple×1+4
jan de retour
13
14
15
16
17
18
19
20
21
22
23
24
11
12
11
10
9
8
7
6
5
4
3
2
1
10
grand jan
petit jan
Déplacez une dame (1 sur 2)

Cliquez une flêche soulignée pour déplacer

diff --git a/doc/design/snapshots/2026-05-30-d4a2ea1c531827bfbc2410af20a00e349d606c87_files/style-e86a95086579e325.css b/doc/design/snapshots/2026-05-30-d4a2ea1c531827bfbc2410af20a00e349d606c87_files/style-e86a95086579e325.css deleted file mode 100644 index 24df8c0..0000000 --- a/doc/design/snapshots/2026-05-30-d4a2ea1c531827bfbc2410af20a00e349d606c87_files/style-e86a95086579e325.css +++ /dev/null @@ -1,2305 +0,0 @@ -/* ── Google Fonts ───────────────────────────────────────────────── */ -@import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,600;1,400&family=Jost:wght@300;400;500&display=swap'); - -/* ── Design tokens ──────────────────────────────────────────────────── */ -:root { - --board-felt: #1d3d28; - --board-rail: #2a1508; - --field-ivory: #f0e6c8; - --field-burgundy: #7a1e2a; - --field-blue: #e5eadc; - --field-blue-light: #1a4f72; - --field-brown: #f2dfa0; - --field-brown-light: #6a2810; - --field-corner: #b8900a; - --checker-white: #f5edd8; - --checker-black: #1a0f06; - --checker-ring: #c8a448; - --ui-parchment: #f2e8d0; - --ui-parchment-dark: #e4d8b8; - --ui-ink: #2a1a08; - --ui-gold: #c8a448; - --ui-gold-dark: #8a6a28; - --ui-green-accent: #3a6b2a; - --ui-red-accent: #7a1e2a; - --font-display: 'Cormorant Garamond', Georgia, serif; - --font-ui: 'Jost', system-ui, sans-serif; -} - - -/* ── Reset & base ──────────────────────────────────────────────────── */ -*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } - -body { - font-family: var(--font-ui); - background: #8a7050; - background-image: - radial-gradient(ellipse at 20% 10%, rgba(80,48,16,0.35) 0%, transparent 60%), - radial-gradient(ellipse at 80% 90%, rgba(40,24,8,0.3) 0%, transparent 55%), - repeating-linear-gradient( - 45deg, transparent, transparent 3px, - rgba(0,0,0,0.03) 3px, rgba(0,0,0,0.03) 4px - ); - display: flex; - flex-direction: column; - min-height: 100vh; -} - -.hidden { display: none !important; } - -/* -- svg icons -- */ -.icon { - width: 1.2em; - height: 1.2em; - color: var(--ui-parchment); - vertical-align: -0.25em; - margin-right: 0.7em; -} - -/* ── Site navigation ─────────────────────────────────────────────── */ -.site-nav { - background: var(--board-rail); - border-bottom: 2px solid var(--ui-gold-dark); - padding: 0 1.5rem; - height: 52px; - display: flex; - align-items: center; - gap: 1.5rem; - position: sticky; - top: 0; - z-index: 50; - box-shadow: 0 2px 8px rgba(0,0,0,0.4); - flex-shrink: 0; -} - -.site-nav-brand { - font-family: var(--font-display); - font-size: 1.5rem; - font-weight: 600; - color: var(--ui-gold); - text-decoration: none; - letter-spacing: 0.1em; -} -.site-nav-brand:hover { color: #e0b840; } - -.site-nav-spacer { flex: 1; } - -.site-nav a { - font-family: var(--font-ui); - font-size: 0.9rem; - color: var(--ui-parchment); - text-decoration: none; - opacity: 0.8; - transition: opacity 0.15s, color 0.15s; -} -.site-nav a:hover { opacity: 1; } - -.site-nav-btn { - padding: 0.3rem 0.9rem; - font-family: var(--font-ui); - font-size: 0.85rem; - font-weight: 500; - letter-spacing: 0.03em; - border: 1px solid rgba(200,164,72,0.4); - border-radius: 4px; - background: transparent; - color: var(--ui-parchment); - cursor: pointer; - transition: background 0.15s, border-color 0.15s; -} -.site-nav-btn:hover { - background: rgba(200,164,72,0.12); - border-color: var(--ui-gold); -} - -/* ── Portal main content area ────────────────────────────────────── */ -.portal-main { - flex: 1; - max-width: 900px; - width: 100%; - margin: 2rem auto; - padding: 0 1.5rem; -} - -.portal-card { - background: var(--ui-parchment); - border-radius: 8px; - box-shadow: - 0 20px 60px rgba(0,0,0,0.55), - 0 0 3px 3px rgba(42,21,8,0.9) - ; - /* box-shadow: 0 4px 16px rgba(0,0,0,0.18); */ - /* border: 1px solid rgba(200,164,72,0.3); */ - /* border-top: 3px solid var(--ui-gold-dark); */ - padding: 1.75rem 2rem; - margin-bottom: 1.5rem; -} - -.portal-card h1 { - font-family: var(--font-display); - font-size: 2rem; - font-weight: 600; - color: var(--ui-ink); - letter-spacing: 0.04em; - margin-bottom: 0.25rem; -} -.portal-card h2 { - font-family: var(--font-display); - font-size: 1.35rem; - font-weight: 600; - color: var(--ui-ink); - margin-bottom: 0.75rem; -} - -.portal-meta { - font-size: 0.85rem; - color: #665544; - margin-bottom: 1.5rem; - font-family: var(--font-ui); -} - -/* ── Stats grid ──────────────────────────────────────────────────── */ -.stats-grid { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 1rem; - margin-bottom: 1.5rem; -} -.stat-box { - background: var(--ui-parchment); - border: 1px solid rgba(200,164,72,0.28); - border-radius: 6px; - padding: 1rem; - text-align: center; - box-shadow: 0 1px 4px rgba(0,0,0,0.1); -} -.stat-box .value { - font-family: var(--font-display); - font-size: 2.2rem; - font-weight: 600; - color: var(--ui-gold-dark); -} -.stat-box .label { - font-size: 0.78rem; - color: #665544; - margin-top: 0.2rem; - letter-spacing: 0.04em; - text-transform: uppercase; -} - -/* ── Tables ──────────────────────────────────────────────────────── */ -table { - width: 100%; - border-collapse: collapse; - font-size: 0.9rem; - font-family: var(--font-ui); -} -th { - text-align: left; - padding: 0.5rem 0.75rem; - border-bottom: 2px solid rgba(200,164,72,0.4); - color: #665544; - font-weight: 500; - font-size: 0.8rem; - letter-spacing: 0.05em; - text-transform: uppercase; -} -td { - padding: 0.5rem 0.75rem; - border-bottom: 1px solid rgba(200,164,72,0.12); - color: var(--ui-ink); -} -tr:last-child td { border-bottom: none; } -tr:hover td { background: rgba(200,164,72,0.05); } - -a { color: var(--ui-gold-dark); text-decoration: none; } -a:hover { text-decoration: underline; } - -/* ── Outcome classes ─────────────────────────────────────────────── */ -.outcome-win { color: var(--ui-green-accent); font-weight: 600; } -.outcome-loss { color: var(--ui-red-accent); font-weight: 600; } -.outcome-draw { color: #c07020; font-weight: 600; } - -/* ── Portal tabs ─────────────────────────────────────────────────── */ -.portal-tabs { - display: flex; - gap: 0; - margin-bottom: 1.5rem; - border-bottom: 1px solid rgba(200,164,72,0.3); -} -.portal-tab-btn { - padding: 0.55rem 1.5rem; - font-family: var(--font-ui); - font-size: 0.9rem; - background: transparent; - border: none; - cursor: pointer; - color: #665544; - border-bottom: 2px solid transparent; - margin-bottom: -1px; - transition: color 0.15s, border-color 0.15s; -} -.portal-tab-btn.active { - color: var(--ui-ink); - border-bottom-color: var(--ui-gold-dark); - font-weight: 500; -} - -/* ── Portal form ─────────────────────────────────────────────────── */ -.portal-label { - display: block; - font-size: 0.82rem; - color: #665544; - margin-bottom: 0.3rem; - letter-spacing: 0.03em; -} -.portal-input { - width: 100%; - padding: 0.55rem 0.85rem; - font-size: 0.95rem; - font-family: var(--font-ui); - border: 1px solid rgba(138,106,40,0.35); - border-radius: 5px; - background: rgba(255,252,240,0.8); - color: var(--ui-ink); - outline: none; - margin-bottom: 1rem; - transition: border-color 0.15s, box-shadow 0.15s; -} -.portal-input:focus { - border-color: var(--ui-gold); - box-shadow: 0 0 0 3px rgba(200,164,72,0.18); -} -.portal-submit-btn { - padding: 0.6rem 2rem; - font-family: var(--font-ui); - font-size: 0.9rem; - font-weight: 500; - background: linear-gradient(160deg, #4a7a38 0%, #2e5222 100%); - color: #e8f0e0; - border: none; - border-radius: 5px; - cursor: pointer; - box-shadow: 0 2px 5px rgba(0,0,0,0.25); - transition: opacity 0.15s; -} -.portal-submit-btn:disabled { opacity: 0.45; cursor: not-allowed; } -.portal-submit-btn:not(:disabled):hover { opacity: 0.9; } - -.portal-page-btn { - padding: 0.35rem 0.9rem; - font-family: var(--font-ui); - font-size: 0.85rem; - background: var(--board-rail); - color: var(--ui-parchment); - border: none; - border-radius: 4px; - cursor: pointer; - opacity: 0.85; - transition: opacity 0.15s; -} -.portal-page-btn:hover { opacity: 1; } - -.portal-loading { color: #665544; font-style: italic; padding: 1rem 0; } -.portal-empty { color: #aa9070; font-style: italic; padding: 1rem 0; } -.portal-error { color: var(--ui-red-accent); font-size: 0.875rem; margin-top: 0.5rem; } -.portal-success { color: var(--ui-green-accent); font-size: 0.875rem; margin-top: 0.5rem; } - -.flash-banner { - position: fixed; - top: 1.25rem; - left: 50%; - transform: translateX(-50%); - z-index: 500; - display: flex; - align-items: center; - gap: 1rem; - padding: 0.75rem 1.25rem; - background: var(--ui-green-accent); - color: #f5edd8; - border-radius: 6px; - box-shadow: 0 4px 16px rgba(0,0,0,0.35); - font-family: var(--font-ui); - font-size: 0.95rem; - max-width: 90vw; - animation: flash-in 0.2s ease; -} -@keyframes flash-in { - from { opacity: 0; transform: translateX(-50%) translateY(-0.5rem); } - to { opacity: 1; transform: translateX(-50%) translateY(0); } -} -.flash-dismiss { - background: none; - border: none; - color: inherit; - cursor: pointer; - font-size: 1rem; - opacity: 0.75; - padding: 0; - line-height: 1; -} -.flash-dismiss:hover { opacity: 1; } - -.portal-danger-zone { - border: 1px solid rgba(122, 30, 42, 0.4); - background: rgba(122, 30, 42, 0.04); -} -.portal-danger-zone h2 { - color: var(--ui-red-accent); -} -.portal-danger-btn { - padding: 0.5rem 1.25rem; - font-family: var(--font-ui); - font-size: 0.9rem; - background: var(--ui-red-accent); - color: #f5edd8; - border: none; - border-radius: 4px; - cursor: pointer; - transition: opacity 0.15s; -} -.portal-danger-btn:hover { opacity: 0.85; } -.portal-danger-btn:disabled { opacity: 0.45; cursor: not-allowed; } - -.portal-link { - color: var(--ui-gold); - text-decoration: none; - font-size: 0.875rem; -} -.portal-link:hover { text-decoration: underline; } - -.portal-verification-banner { - background: rgba(200,164,72,0.08); - border: 1px solid rgba(200,164,72,0.35); - border-radius: 6px; - padding: 1.25rem; - text-align: center; -} -.portal-verification-banner p { - margin-bottom: 0.75rem; - font-size: 0.9rem; -} - -/* ── Share URL row (lobby waiting card + game top bar) ──────────── */ -.share-url-row { - display: flex; - align-items: center; - gap: 0.5rem; - background: rgba(0,0,0,0.18); - border: 1px solid rgba(200,164,72,0.25); - border-radius: 5px; - padding: 0.4rem 0.6rem; -} -.share-url-text { - flex: 1; - font-family: var(--font-ui); - font-size: 0.72rem; - color: rgba(242,232,208,0.75); - word-break: break-all; - user-select: all; -} -.share-copy-btn { - flex-shrink: 0; - font-family: var(--font-ui); - font-size: 0.72rem; - padding: 0.2rem 0.6rem; - border: 1px solid rgba(200,164,72,0.4); - border-radius: 3px; - background: rgba(200,164,72,0.1); - color: var(--ui-parchment); - cursor: pointer; - transition: background 0.15s; - white-space: nowrap; -} -.share-copy-btn:hover { background: rgba(200,164,72,0.22); } - -/* ── QR code container ───────────────────────────────────────────── */ -.qr-container { - width: 160px; - height: 160px; - margin: 0 auto; - border-radius: 4px; - overflow: hidden; -} -.qr-container svg { width: 100%; height: 100%; display: block; } - -/* ── Share popover (in-game top bar) ─────────────────────────────── */ -.share-popover { - width: 100%; - background: rgba(0,0,0,0.3); - border: 1px solid rgba(200,164,72,0.2); - border-radius: 6px; - padding: 0.75rem 1rem; - display: flex; - flex-direction: column; - align-items: center; - gap: 0.4rem; - margin-bottom: 0.5rem; -} -.share-popover .qr-container { width: 120px; height: 120px; } -.share-popover-label { - font-size: 0.75rem; - color: rgba(242,232,208,0.6); - text-align: center; - margin: 0; -} - -/* ── Game overlay (full-screen, covers portal during play) ───────── */ -.game-overlay { - position: fixed; - inset: 0; - background: #8a7050; - background-image: - radial-gradient(ellipse at 20% 10%, rgba(80,48,16,0.35) 0%, transparent 60%), - radial-gradient(ellipse at 80% 90%, rgba(40,24,8,0.3) 0%, transparent 55%), - repeating-linear-gradient( - 45deg, transparent, transparent 3px, - rgba(0,0,0,0.03) 3px, rgba(0,0,0,0.03) 4px - ); - z-index: 200; - display: flex; - justify-content: center; - align-items: flex-start; - padding: 1.5rem; - overflow-y: auto; -} - -/* ── Login card (§11) ───────────────────────────────────────────────── */ -.login-card { - background: var(--ui-parchment); - border-radius: 8px; - box-shadow: - 0 20px 60px rgba(0,0,0,0.55), - 0 0 3px 3px rgba(42,21,8,0.9) - ; - /* box-shadow: - 0 20px 60px rgba(0,0,0,0.55), - 0 0 0 1px rgba(200,164,72,0.35), - 0 0 0 5px rgba(42,21,8,0.9), - 0 0 0 6px rgba(200,164,72,0.2); - */ - /* border-top: 3px solid var(--ui-gold-dark); */ - width: 340px; - margin-top: 5vh; - overflow: hidden; -} - -/* Decorative header — row of triangular flèches like the actual board */ -.login-card-header { - height: 52px; - background: var(--board-felt); - position: relative; - overflow: hidden; -} - -.login-board-stripe { - position: absolute; - inset: 0; - background: - repeating-linear-gradient( - 90deg, - var(--field-burgundy) 0, var(--field-burgundy) 50%, - var(--field-ivory) 50%, var(--field-ivory) 100% - ); - background-size: 34px 100%; - clip-path: polygon( - 0% 0%, 2.94% 100%, 5.88% 0%, 8.82% 100%, 11.76% 0%, - 14.7% 100%, 17.65% 0%, 20.59% 100%, 23.53% 0%, - 26.47% 100%, 29.41% 0%, 32.35% 100%, 35.29% 0%, - 38.24% 100%, 41.18% 0%, 44.12% 100%, 47.06% 0%, - 50% 100%, 52.94% 0%, 55.88% 100%, 58.82% 0%, - 61.76% 100%, 64.71% 0%, 67.65% 100%, 70.59% 0%, - 73.53% 100%, 76.47% 0%, 79.41% 100%, 82.35% 0%, - 85.29% 100%, 88.24% 0%, 91.18% 100%, 94.12% 0%, - 97.06% 100%, 100% 0% - ); - opacity: 0.9; -} - -.login-card-body { - display: flex; - flex-direction: column; - align-items: center; - gap: 0; - padding: 1.5rem 2rem 2rem; -} - -.login-lang-switcher { - align-self: flex-end; - margin-bottom: 0.75rem; -} - -/* Override lang-switcher colours for the parchment card */ -.login-card .lang-switcher button { - color: var(--ui-ink); - border-color: rgba(42,21,8,0.2); - opacity: 0.5; -} -.login-card .lang-switcher button.lang-active { - opacity: 1; - background: rgba(42,21,8,0.08); - border-color: rgba(42,21,8,0.35); -} - -.login-title { - font-family: var(--font-display); - font-size: 3.25rem; - font-weight: 600; - color: var(--ui-ink); - letter-spacing: 0.12em; - text-align: center; - line-height: 1; - margin-bottom: 0.3rem; -} - -.login-subtitle { - font-family: var(--font-display); - font-size: 0.85rem; - color: rgba(42,26,8,0.55); - text-align: center; - letter-spacing: 0.06em; - font-style: italic; - margin-bottom: 1.25rem; -} -.login-subtitle sup { - font-size: 0.65em; - vertical-align: super; -} - -.login-ornament { - color: var(--ui-gold); - font-size: 1rem; - opacity: 0.7; - margin-bottom: 1.25rem; - letter-spacing: 0.3em; - text-align: center; -} - -.error-msg { - color: #c03030; - font-size: 0.85rem; - text-align: center; - margin-bottom: 0.5rem; -} - -.login-input { - width: 100%; - padding: 0.55rem 0.85rem; - font-size: 0.95rem; - font-family: var(--font-ui); - border: 1px solid rgba(138,106,40,0.4); - border-radius: 5px; - background: rgba(255,252,240,0.8); - color: var(--ui-ink); - outline: none; - transition: border-color 0.15s, box-shadow 0.15s; - margin-bottom: 1rem; -} -.login-input:focus { - border-color: var(--ui-gold); - box-shadow: 0 0 0 3px rgba(200,164,72,0.2); -} - -.login-actions { - display: flex; - flex-direction: column; - gap: 0.55rem; - width: 100%; -} - -/* Login buttons styled as embossed wooden tiles */ -.login-btn { - width: 100%; - padding: 0.65rem 1rem; - font-family: var(--font-ui); - font-size: 0.9rem; - font-weight: 500; - letter-spacing: 0.04em; - border: none; - border-radius: 5px; - cursor: pointer; - transition: opacity 0.15s, transform 0.1s, box-shadow 0.15s; - position: relative; -} -.login-btn:disabled { opacity: 0.35; cursor: default; } -.login-btn:not(:disabled):hover { opacity: 0.92; transform: translateY(-1px); } -.login-btn:not(:disabled):active { transform: translateY(0); } - -.login-btn-primary { - background: linear-gradient(160deg, #4a7a38 0%, #2e5222 100%); - color: #e8f0e0; - box-shadow: 0 2px 6px rgba(0,0,0,0.3), inset 0 1px 0 rgba(255,255,255,0.12); -} -.login-btn-secondary { - background: linear-gradient(160deg, #3a2010 0%, #241408 100%); - color: #e4d4b4; - box-shadow: 0 2px 6px rgba(0,0,0,0.3), inset 0 1px 0 rgba(255,255,255,0.08); -} -.login-btn-bot { - background: linear-gradient(160deg, #2a4a6a 0%, #183050 100%); - color: #d0e0f0; - box-shadow: 0 2px 6px rgba(0,0,0,0.3), inset 0 1px 0 rgba(255,255,255,0.08); -} - -/* ── Connecting screen ──────────────────────────────────────────────── */ -.connecting { - font-family: var(--font-display); - font-size: 1.4rem; - font-style: italic; - margin-top: 4rem; - text-align: center; - color: var(--ui-parchment); - text-shadow: 0 1px 4px rgba(0,0,0,0.4); -} - -/* ── Game-action buttons ─────────────────────────────────────────────── */ -.btn { - padding: 0.5rem 1.25rem; - font-size: 0.95rem; - font-family: var(--font-ui); - font-weight: 500; - letter-spacing: 0.03em; - border: none; - border-radius: 4px; - cursor: pointer; - transition: opacity 0.15s, box-shadow 0.15s; -} -.btn:disabled { opacity: 0.4; cursor: default; } -.btn-primary { background: var(--ui-green-accent); color: #fff; } -.btn-secondary { background: var(--board-rail); color: #e8d8b8; } -.btn-bot { background: #2a5a7a; color: #fff; } -.btn:not(:disabled):hover { - opacity: 0.9; - box-shadow: 0 2px 6px rgba(0,0,0,0.25); -} - -/* ── Game container ─────────────────────────────────────────────────── */ -.game-container { - display: flex; - flex-direction: column; - align-items: center; - gap: 0.6rem; -} - -/* ── Language switcher (in-game) ────────────────────────────────────── */ -.lang-switcher { display: flex; gap: 0.25rem; } - -.lang-switcher button { - font-size: 0.7rem; - font-family: var(--font-ui); - letter-spacing: 0.05em; - padding: 0.15rem 0.4rem; - border: 1px solid rgba(200,164,72,0.3); - border-radius: 3px; - background: transparent; - cursor: pointer; - color: var(--ui-parchment); - opacity: 0.55; -} -.lang-switcher button.lang-active { - opacity: 1; - font-weight: 500; - background: rgba(200,164,72,0.15); - border-color: rgba(200,164,72,0.6); -} - -/* ── Top bar ─────────────────────────────────────────────────────────── */ -.top-bar { - display: flex; - justify-content: space-between; - align-items: center; - width: 100%; - color: var(--ui-parchment); - font-size: 0.85rem; - opacity: 0.8; -} - -.quit-link { - font-size: 0.8rem; - color: var(--ui-parchment); - text-decoration: underline; - text-underline-offset: 2px; - cursor: pointer; - opacity: 0.7; -} -.quit-link:hover { opacity: 1; } - -.playing-as { - font-size: 0.75rem; - color: rgba(242,232,208,0.7); - font-family: var(--font-ui); -} -.playing-as strong { color: rgba(242,232,208,0.9); } - -/* ── Game status bar (§10b) — above board ───────────────────────────── */ -.game-status { - font-family: var(--font-display); - font-size: 1.2rem; - font-style: italic; - color: var(--ui-parchment); - text-align: center; - letter-spacing: 0.04em; - padding: 0.2rem 1rem 0; - width: 100%; - text-shadow: 0 1px 4px rgba(0,0,0,0.4); -} - -/* ── Contextual sub-prompt (§8a) ────────────────────────────────────── */ -.game-sub-prompt { - font-family: var(--font-ui); - font-size: 0.72rem; - color: rgba(240,228,192,0.5); - text-align: center; - letter-spacing: 0.04em; - padding: 0.15rem 1rem 0; - width: 100%; -} - -/* ── Player score panel ─────────────────────────────────────────────── */ -.player-score-panel { - background: var(--ui-parchment); - border-radius: 5px; - padding: 0.45rem 1.25rem; - font-size: 0.88rem; - box-shadow: 0 2px 6px rgba(0,0,0,0.25); - width: 100%; - border-top: 2px solid var(--ui-gold-dark); - display: flex; - align-items: center; - gap: 1.5rem; -} - -.player-score-header { - flex-shrink: 0; - min-width: 90px; -} - -.player-name { - font-family: var(--font-display); - font-weight: 600; - font-size: 1.05rem; - color: var(--ui-ink); - letter-spacing: 0.02em; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - min-width: 0; -} - -.score-bars { display: flex; flex-direction: row; gap: 1.5rem; flex: 1; align-items: center; } - -.score-bar-row { - display: flex; - align-items: center; - gap: 0.5rem; - flex: 1; -} - -.score-bar-label { - font-size: 0.75rem; - color: #665544; - width: 3rem; - text-align: right; - flex-shrink: 0; -} - -/* ── Points bar ─────────────────────────────────────────────────────── */ -.score-bar { - flex: 1; - max-width: 220px; - height: 8px; - background: rgba(0,0,0,0.1); - border-radius: 4px; - overflow: hidden; - flex-shrink: 0; -} - -.score-bar-fill { - height: 100%; - border-radius: 4px; - transition: width 0.35s ease-out; -} - -.score-bar-points { background: linear-gradient(90deg, var(--ui-green-accent), #5a9b3a); } - -.score-bar-value { - font-size: 0.75rem; - color: #665544; - min-width: 2.5rem; - font-variant-numeric: tabular-nums; -} - -/* ── Hole peg tracker (§7a) ─────────────────────────────────────────── */ -.peg-track { - display: flex; - align-items: center; - gap: 3px; - flex-shrink: 0; -} - -.peg-hole { - width: 10px; - height: 10px; - border-radius: 50%; - border: 1.5px solid rgba(138,106,40,0.45); - background: rgba(0,0,0,0.06); - flex-shrink: 0; - transition: background 0.3s ease-out, border-color 0.3s, box-shadow 0.3s; -} - -.peg-hole.filled { - background: var(--ui-gold); - border-color: var(--ui-gold-dark); - box-shadow: 0 0 4px rgba(200,164,72,0.6); -} - -.bredouille-badge { - font-size: 0.62rem; - font-weight: 500; - color: #fff8e0; - background: linear-gradient(135deg, #c88800, #8a5800); - border: 1px solid rgba(200,164,72,0.5); - border-radius: 3px; - padding: 0.1em 0.4em; - letter-spacing: 0.06em; - cursor: default; - box-shadow: 0 1px 3px rgba(0,0,0,0.25); -} - -/* ── Merged scoreboard (both players, above board) ──────────────────── */ -.merged-score-panel { - background: var(--ui-parchment); - border-radius: 5px; - padding: 0.5rem 1.25rem 0.45rem; - font-size: 0.88rem; - box-shadow: 0 2px 6px rgba(0,0,0,0.25); - width: 100%; - border-top: 2px solid var(--ui-gold-dark); - display: flex; - flex-direction: column; - gap: 0.2rem; -} - -.score-row { - display: flex; - align-items: center; - gap: 1rem; -} - -.score-row-name { - width: 120px; - flex-shrink: 0; - display: flex; - align-items: baseline; - gap: 0.35rem; - overflow: hidden; -} - -.you-tag { - font-family: var(--font-ui); - font-size: 0.7rem; - color: #887766; - font-style: italic; - white-space: nowrap; - flex-shrink: 0; -} - -/* ── Jackpot points counter ─────────────────────────────────────────── */ -.pts-counter-wrap { - position: relative; - display: flex; - flex-direction: column; - align-items: center; - width: 72px; - flex-shrink: 0; - padding-bottom: 4px; -} - -.pts-ghost-bar-track { - position: absolute; - bottom: 0; - left: 0; - right: 0; - height: 3px; - background: rgba(0,0,0,0.07); - border-radius: 2px; - overflow: hidden; -} - -.pts-ghost-bar-fill { - height: 100%; - background: rgba(58,107,42,0.45); - border-radius: 2px; -} - -.pts-ghost-bar-opp { - background: rgba(122,30,42,0.4); -} - -.pts-counter-row { - display: flex; - align-items: baseline; - gap: 0.1rem; -} - -.pts-counter { - font-family: var(--font-display); - font-size: 1.9rem; - font-weight: 600; - color: var(--ui-ink); - line-height: 1; - font-variant-numeric: tabular-nums; - min-width: 1.4em; - text-align: right; -} - -.pts-max { - font-family: var(--font-ui); - font-size: 0.7rem; - color: #998877; - line-height: 1; - padding-bottom: 2px; -} - -/* ── Hole pegs — larger and coloured (me = green, opp = red) ─────────── */ -.merged-score-panel .peg-track { - gap: 4px; -} - -.merged-score-panel .peg-hole { - width: 14px; - height: 14px; - border-radius: 50%; - border: 1.5px solid rgba(138,106,40,0.3); - background: rgba(0,0,0,0.06); - flex-shrink: 0; - transition: background 0.3s ease-out, border-color 0.3s, box-shadow 0.3s; -} - -.merged-score-panel .peg-hole.filled { - background: #5aab38; - border-color: #3a7828; - box-shadow: 0 0 5px rgba(90,171,56,0.55); -} - -.merged-score-panel .peg-hole.peg-opp.filled { - background: #c05030; - border-color: #8a3018; - box-shadow: 0 0 5px rgba(192,80,48,0.55); -} - -/* Peg pop-in animation when a new hole is scored */ -@keyframes peg-pop { - 0% { transform: scale(0.15); opacity: 0; } - 45% { transform: scale(1.55); } - 70% { transform: scale(0.88); } - 100% { transform: scale(1.0); opacity: 1; } -} - -.merged-score-panel .peg-hole.peg-new { - animation: peg-pop 0.52s cubic-bezier(0.22, 0.61, 0.36, 1) forwards; -} - -/* Thin separator between the two player rows */ -.score-row-sep { - height: 1px; - background: rgba(0,0,0,0.07); - margin: 0.05rem 0; -} - -/* ── Non-blocking hole flash (replaces old toast) ───────────────────── */ -@keyframes hole-flash-in-out { - 0% { opacity: 0; transform: translateY(-3px); } - 14% { opacity: 1; transform: translateY(0); } - 65% { opacity: 1; } - 100% { opacity: 0; transform: translateY(2px); } -} - -.hole-flash { - margin-left: auto; - flex-shrink: 0; - white-space: nowrap; - font-family: var(--font-display); - font-size: 0.88rem; - font-weight: 600; - color: var(--ui-green-accent); - letter-spacing: 0.05em; - animation: hole-flash-in-out 2.5s ease-out forwards; - pointer-events: none; -} - -.hole-flash.hole-flash-bredouille { - color: var(--ui-gold-dark); -} - -/* ── Game bottom strip — status, hints, buttons on cream ────────────── */ -.game-bottom-strip { - background: var(--ui-parchment); - border-radius: 5px; - padding: 0.55rem 1.25rem 0.65rem; - width: 100%; - box-shadow: 0 2px 6px rgba(0,0,0,0.2); - border-top: 2px solid var(--ui-gold-dark); - display: flex; - flex-direction: column; - align-items: center; - gap: 0.35rem; - min-height: 3.2rem; -} - -/* Override text colours for the parchment background context */ -.game-bottom-strip .game-status { - color: var(--ui-ink); - text-shadow: none; - padding: 0; - font-size: 1.05rem; - width: auto; -} - -.game-bottom-strip .game-sub-prompt { - color: #887766; - padding: 0; - width: auto; -} - -/* ── Board + side panel ─────────────────────────────────────────────── */ -.board-and-panel { - position: relative; -} - -.side-panel { - position: absolute; - right: -8px; - top: 10px; - z-index: 20; - display: flex; - flex-direction: column; - gap: 0.5rem; - padding-top: 0.15rem; - pointer-events: none; -} - -.action-buttons { display: flex; flex-direction: column; gap: 0.5rem; } - -/* ── Dice bar ───────────────────────────────────────────────────────── */ -.dice-bar { - display: flex; - align-items: center; - gap: 0.6rem; - padding: 0.4rem 0.6rem; - background: rgba(42,21,8,0.15); - border-radius: 6px; - border: 1px solid rgba(200,164,72,0.2); - width: fit-content; -} - -/* ── Die face (SVG) ─────────────────────────────────────────────────── */ -@keyframes die-tumble { - 0% { transform: rotate(-45deg) scale(0.4) translateY(-8px); opacity: 0; } - 25% { transform: rotate(18deg) scale(1.22) translateY(0); opacity: 1; } - 45% { transform: rotate(-10deg) scale(0.91); } - 62% { transform: rotate(6deg) scale(1.06); } - 76% { transform: rotate(-3deg) scale(0.98); } - 88% { transform: rotate(1.5deg) scale(1.01); } - 100% { transform: rotate(0deg) scale(1); opacity: 1; } -} - -.die-face { - filter: drop-shadow(0 2px 3px rgba(0,0,0,0.3)); - animation: die-tumble 0.55s cubic-bezier(0.22, 0.61, 0.36, 1) both; -} - -.die-face rect { - fill: #fffef0; - stroke: #2a1a00; - stroke-width: 2; - transition: fill 0.18s, stroke 0.18s; -} -.die-face circle { - fill: #1a0a00; - transition: fill 0.18s; -} - -.bar-die-slot { - display: flex; - align-items: center; - justify-content: center; -} - -.die-face.die-double rect { stroke: var(--ui-gold); stroke-width: 2.5; } -.die-face.die-double { - filter: drop-shadow(0 0 6px rgba(200,164,72,0.7)) drop-shadow(0 2px 3px rgba(0,0,0,0.3)); -} - -.die-face.die-used { animation: none; opacity: 0.55; } -.die-face.die-used rect { fill: #d4d0c4; stroke: #9a8a70; } -.die-face.die-used circle { fill: #9a8a70; } - -.die-face .die-question { fill: #1a0a00; font-family: sans-serif; } -.die-face.die-used .die-question { fill: #9a8a70; } - -/* ── Jan panel ──────────────────────────────────────────────────────── */ -.jan-panel { - display: flex; - flex-direction: column; - gap: 2px; - background: var(--ui-parchment); - border-radius: 5px; - padding: 0.4rem 0.9rem; - font-size: 0.88rem; - box-shadow: 0 1px 4px rgba(0,0,0,0.15); - min-width: 260px; - border-top: 2px solid rgba(138,106,40,0.35); -} - -.jan-row { - display: flex; - align-items: center; - gap: 0.5rem; - padding: 2px 4px; - border-radius: 3px; -} -.jan-expandable { cursor: pointer; } -.jan-expandable:hover { background: rgba(0,0,0,0.05); } - -.jan-positive { color: #1a5c1a; } -.jan-negative { color: #8b1a1a; } - -.jan-label { flex: 1; } -.jan-tag { - font-size: 0.72rem; - padding: 0.1em 0.4em; - border-radius: 3px; - background: rgba(0,0,0,0.07); - color: #665544; - white-space: nowrap; -} -.jan-pts { font-weight: 600; text-align: right; min-width: 3rem; } - -.jan-moves { padding: 1px 4px 4px 1rem; display: flex; flex-direction: column; gap: 2px; } -.jan-moves.hidden { display: none; } -.jan-move-line { font-family: monospace; font-size: 0.78rem; color: #555; } - -/* ── Game-over overlay (§12) ────────────────────────────────────────── */ -.game-over-overlay { - position: fixed; - inset: 0; - background: rgba(0,0,0,0.65); - display: flex; - align-items: center; - justify-content: center; - z-index: 100; -} - -@keyframes game-over-appear { - from { transform: translateY(-24px) scale(0.94); opacity: 0; } - to { transform: translateY(0) scale(1); opacity: 1; } -} - -.game-over-box { - background: var(--ui-parchment); - border-radius: 8px; - padding: 2.5rem 3rem; - text-align: center; - box-shadow: 0 12px 40px rgba(0,0,0,0.5), 0 0 0 2px var(--ui-gold-dark); - display: flex; - flex-direction: column; - gap: 1.1rem; - min-width: 300px; - animation: game-over-appear 0.4s cubic-bezier(0.22, 0.61, 0.36, 1); -} - -.game-over-box h2 { - font-family: var(--font-display); - font-size: 2rem; - font-weight: 600; - color: var(--ui-ink); - letter-spacing: 0.06em; -} - -.game-over-winner { - font-family: var(--font-display); - font-size: 1.25rem; - color: var(--ui-green-accent); - font-style: italic; -} - -.game-over-score { - display: flex; - align-items: center; - justify-content: center; - gap: 0.75rem; - padding: 0.6rem 1rem; - background: rgba(0,0,0,0.05); - border-radius: 5px; - border: 1px solid rgba(138,106,40,0.2); -} - -.game-over-score-name { - font-family: var(--font-display); - font-size: 0.9rem; - color: #665544; - font-style: italic; - max-width: 80px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.game-over-score-nums { - font-family: var(--font-display); - font-size: 2.25rem; - font-weight: 600; - color: var(--ui-ink); - letter-spacing: 0.08em; - line-height: 1; -} - -.game-over-actions { display: flex; gap: 0.75rem; justify-content: center; } - -/* ── Score-area: position:relative wrapper for merged panel + scoring ── */ -.score-area { - position: relative; - width: 100%; -} - -/* ── Scoring panels container — right of the hole counter ───────────── */ -/* Stacked column, right-aligned, covering the free space in each row. */ -/* overflow:visible lets tall panels float over the board below. */ -.scoring-panels-container { - position: absolute; - top: 0; - bottom: 0; - right: 0; - display: flex; - flex-direction: column; - justify-content: space-around; - align-items: flex-end; - padding: 4px 8px; - z-index: 10; - pointer-events: none; - overflow: visible; -} - -/* ── Scoring notification panel (§6b) ───────────────────────────────── */ -@keyframes scoring-panel-enter { - from { opacity: 0; transform: translateX(10px); } - to { opacity: 1; transform: translateX(0); } -} - -.scoring-panel-wrapper { - pointer-events: auto; - display: flex; - flex-direction: column; - align-items: flex-end; - gap: 3px; - animation: scoring-panel-enter 0.3s ease-out; -} - -/* "+" expand button: hidden while the panel is expanded */ -.scoring-panel-wrapper:not(.scoring-minimized) .scoring-expand-btn { - display: none; -} - -/* Full panel card: hidden once minimised */ -.scoring-panel-wrapper.scoring-minimized .scoring-panel { - display: none; -} - -/* "+" expand button ─────────────────────────────────────────────────── */ -.scoring-expand-btn { - font-family: var(--font-display); - font-size: 0.9rem; - line-height: 1; - background: var(--ui-parchment); - border: 1.5px solid var(--ui-gold-dark); - border-radius: 3px; - padding: 2px 7px; - cursor: pointer; - color: var(--ui-ink); - opacity: 0.72; - box-shadow: 0 1px 3px rgba(0,0,0,0.18); - transition: opacity 0.15s; -} -.scoring-expand-btn:hover { opacity: 1; } - -/* ── Panel head: scoring total + "−" collapse link ──────────────────── */ -.scoring-panel-head { - display: flex; - align-items: baseline; - gap: 0.5rem; -} - -.scoring-collapse-btn { - font-size: 0.78rem; - line-height: 1; - background: none; - border: none; - cursor: pointer; - color: rgba(0,0,0,0.35); - padding: 0 1px; - margin-left: auto; - flex-shrink: 0; - transition: color 0.15s; -} -.scoring-collapse-btn:hover { color: rgba(0,0,0,0.65); } - -/* ── Inner scoring card ─────────────────────────────────────────────── */ -.scoring-panel { - background: var(--ui-parchment); - border-radius: 5px; - padding: 0.45rem 0.85rem; - font-size: 0.84rem; - box-shadow: 0 2px 8px rgba(0,0,0,0.22); - border-left: 3px solid var(--ui-green-accent); - display: flex; - flex-direction: column; - gap: 4px; - width: 320px; -} - -.scoring-total { - font-family: var(--font-display); - font-weight: 600; - font-size: 1rem; - color: #1a5c1a; - white-space: nowrap; -} - -.scoring-jan-row { - display: flex; - align-items: center; - gap: 0.4rem; - padding: 2px 3px; - border-radius: 3px; - cursor: default; - white-space: nowrap; -} -.scoring-jan-row:hover { background: rgba(0,0,0,0.05); } - -.scoring-panel-opp { border-left-color: var(--board-rail); } -.scoring-panel-opp .scoring-total { color: var(--ui-red-accent); } - -.scoring-hole { - display: flex; - align-items: center; - gap: 0.4rem; - font-weight: 600; - color: var(--ui-red-accent); - margin-top: 3px; - padding-top: 4px; - border-top: 1px solid rgba(0,0,0,0.1); -} - -.hold-go-buttons { display: flex; gap: 0.5rem; margin-top: 4px; } - -@media (min-width: 1492px) { - .side-panel { - right: auto; - left: calc(100% + 1rem); - } -} - -/* ── Board wrapper ──────────────────────────────────────────────────── */ -.board-wrapper { - display: flex; - flex-direction: column; - gap: 3px; -} - -/* ── Zone labels (§2a) ──────────────────────────────────────────────── */ -.zone-labels-row { - display: flex; - gap: 4px; - padding: 0 8px; -} - -.zone-label { - font-family: var(--font-display); - font-size: 0.57rem; - font-style: italic; - color: rgba(240,228,192,0.48); - letter-spacing: 0.1em; - text-align: center; - pointer-events: none; - line-height: 1; -} - -.zone-label-quarter { width: 370px; flex-shrink: 0; } -.zone-label-bar { width: 68px; flex-shrink: 0; } - -/* ── Board ──────────────────────────────────────────────────────────── */ -.board { - background: var(--board-felt); - border: 4px solid var(--board-rail); - border-radius: 6px; - padding: 4px; - display: flex; - flex-direction: column; - gap: 4px; - user-select: none; - box-shadow: - 0 6px 16px rgba(0,0,0,0.5), - inset 0 1px 0 rgba(255,255,255,0.04); - position: relative; -} - -.board-row { display: flex; gap: 4px; } -.board-quarter { display: flex; gap: 2px; } - -.board-bar { - width: 68px; - background: var(--board-rail); - border-radius: 4px; - box-shadow: inset 0 0 6px rgba(0,0,0,0.5); - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - overflow: visible; -} - -.board-center-bar { - height: 12px; - background: var(--board-rail); - border-radius: 2px; - box-shadow: inset 0 0 4px rgba(0,0,0,0.4); -} - -/* ── Fields (§1) ────────────────────────────────────────────────────── */ -.field { - --fc: var(--field-ivory); - width: 60px; - height: 180px; - background: transparent; - isolation: isolate; - border-radius: 3px; - display: flex; - flex-direction: column; - align-items: center; - justify-content: flex-end; - padding: 4px 2px; - position: relative; -} - -.field::before { - content: ''; - position: absolute; - inset: 0; - z-index: -1; - background: var(--fc); - clip-path: polygon(0% 100%, 50% 0%, 100% 100%); - transition: background 0.12s; -} - -.top-row .field::before { - clip-path: polygon(0% 0%, 100% 0%, 50% 100%); -} - -.top-row .field { justify-content: flex-start; } - -/* ── Zone alternating colours ────────────────────────────────── */ -.board-quarter .field.zone-petit:nth-child(odd), -.board-quarter .field.zone-grand:nth-child(odd) { --fc: var(--field-burgundy); } -.board-quarter .field.zone-petit:nth-child(even), -.board-quarter .field.zone-grand:nth-child(even) { --fc: var(--field-ivory); } - -.board-quarter .field.zone-opponent:nth-child(odd), -.board-quarter .field.zone-retour:nth-child(odd) { --fc: var(--field-burgundy); } -.board-quarter .field.zone-opponent:nth-child(even), -.board-quarter .field.zone-retour:nth-child(even) { --fc: var(--field-ivory); } - -/* ── Point indicator: first N fields reflect each player's score & bredouille */ -.board-quarter .field.zone-petit.point-bredouille:nth-child(odd), -.board-quarter .field.zone-grand.point-bredouille:nth-child(odd) { --fc: var(--field-blue-light); } -.board-quarter .field.zone-petit.point-bredouille:nth-child(even), -.board-quarter .field.zone-grand.point-bredouille:nth-child(even) { --fc: var(--field-blue); } - -.board-quarter .field.zone-petit.point-no-bredouille:nth-child(odd), -.board-quarter .field.zone-grand.point-no-bredouille:nth-child(odd) { --fc: var(--field-blue-light); } -.board-quarter .field.zone-petit.point-no-bredouille:nth-child(even), -.board-quarter .field.zone-grand.point-no-bredouille:nth-child(even) { --fc: var(--field-blue); } - -.board-quarter .field.zone-opponent.point-bredouille:nth-child(odd), -.board-quarter .field.zone-retour.point-bredouille:nth-child(odd) { --fc: var(--field-blue-light); } -.board-quarter .field.zone-opponent.point-bredouille:nth-child(even), -.board-quarter .field.zone-retour.point-bredouille:nth-child(even) { --fc: var(--field-blue); } - -.board-quarter .field.zone-opponent.point-no-bredouille:nth-child(odd), -.board-quarter .field.zone-retour.point-no-bredouille:nth-child(odd) { --fc: var(--field-blue-light); } -.board-quarter .field.zone-opponent.point-no-bredouille:nth-child(even), -.board-quarter .field.zone-retour.point-no-bredouille:nth-child(even) { --fc: var(--field-blue); } - -.field.corner::after { - content: '♛'; - position: absolute; - z-index: -1; - bottom: 22px; - font-size: 0.7rem; - color: rgba(255,248,210,0.38); - pointer-events: none; - line-height: 1; -} -.top-row .field.corner::after { bottom: auto; top: 22px; } - -@keyframes corner-pulse { - 0%, 100% { filter: drop-shadow(0 0 0px rgba(200,164,72,0)); } - 50% { filter: drop-shadow(0 0 7px rgba(200,164,72,0.55)); } -} -.field.corner.corner-available { - animation: corner-pulse 1.5s ease-in-out infinite; -} - -@keyframes exit-glow { - 0%, 100% { filter: drop-shadow(0 0 0px rgba(232,192,96,0)); } - 50% { filter: drop-shadow(0 0 5px rgba(232,192,96,0.5)); } -} -.field.exit-eligible { - animation: exit-glow 2s ease-in-out infinite; -} - -/* ── Exit sign (§8c) — circle+arrow outside the board ──────────────── */ -.exit-btn { - pointer-events: none; - opacity: 0.3; - transition: opacity 0.2s, transform 0.15s; -} -.exit-btn.exit-active { - pointer-events: auto; - cursor: pointer; - opacity: 1; - animation: exit-btn-pulse 1.4s ease-in-out infinite; -} -.exit-btn.exit-active:hover { - transform: scale(1.1); -} -@keyframes exit-btn-pulse { - 0%, 100% { filter: drop-shadow(0 0 3px rgba(200,160,20,0.3)); } - 50% { filter: drop-shadow(0 0 9px rgba(200,160,20,0.85)); } -} - -.field.jan-hovered { - --fc: rgba(190, 140, 35, 0.8) !important; -} - -@keyframes hit-ripple { - from { transform: translate(-50%, -50%) scale(0.4); opacity: 0.9; } - to { transform: translate(-50%, -50%) scale(2.2); opacity: 0; } -} -.hit-ripple { - position: absolute; - left: 50%; - width: 36px; - height: 36px; - border-radius: 50%; - border: 2px solid rgba(200, 164, 72, 0.9); - pointer-events: none; - animation: hit-ripple 0.5s ease-out forwards; -} -.hit-ripple-top { top: 26px; } -.hit-ripple-bot { bottom: 26px; } - -.field.clickable { - cursor: pointer; -} -.field.clickable:hover { - --fc: rgba(200,170,50,0.18) !important; -} -.field.selected { - /* natural triangle color; tab is the indicator */ -} - -/* ── Tab indicators: small markers at the field's wide base ──────── */ -/* Bot-row: tabs hang below; top-row: tabs hang above. */ -/* The tab sits at ≈ -6px which lands on the board's wooden rail. */ - -.field.clickable::after, -.field.selected::after { - content: ''; - position: absolute; - left: 50%; - transform: translateX(-50%); - width: 22px; - height: 8px; - pointer-events: none; - z-index: 2; -} - -.bot-row .field.clickable::after, -.bot-row .field.selected::after { - bottom: -6px; - top: auto; - border-radius: 0 0 10px 10px; -} -.top-row .field.clickable::after, -.top-row .field.selected::after { - top: -6px; - bottom: auto; - border-radius: 10px 10px 0 0; -} - -/* Possible origin: hollow gold outline */ -.field.clickable:not(.dest):not(.selected)::after { - background: rgba(210,170,30,0.15); - border: 1.5px solid rgba(210,170,30,0.75); - box-shadow: 0 0 4px rgba(210,170,30,0.3); -} - -/* Selected origin: filled amber, breathing glow */ -.field.selected::after { - background: linear-gradient(to bottom, #e8b020, #c07808); - border: 1px solid rgba(255,225,65,0.55); - animation: tab-pulse 1.2s ease-in-out infinite; -} - -@keyframes tab-pulse { - 0%, 100% { box-shadow: 0 0 5px rgba(220,155,15,0.55), 0 0 2px rgba(255,220,50,0.3); } - 50% { box-shadow: 0 0 13px rgba(240,178,22,0.88), 0 0 6px rgba(255,230,60,0.6); } -} - -/* Valid destination: soft ivory/pearl */ -.field.clickable.dest:not(.selected)::after { - background: rgba(240,230,205,0.88); - border: 1.5px solid rgba(190,165,105,0.65); - box-shadow: 0 0 3px rgba(190,165,105,0.2); -} -.field.clickable.dest:not(.selected):hover::after { - background: rgba(228,210,162,0.95); - border-color: rgba(210,175,40,0.72); - box-shadow: 0 0 7px rgba(210,175,40,0.42); -} - -.field-num { - font-size: 0.58rem; - color: rgba(0,0,0,0.28); - position: absolute; - bottom: 3px; - line-height: 1; - font-variant-numeric: tabular-nums; -} - -.board-quarter .field.zone-petit:nth-child(odd) .field-num, -.board-quarter .field.zone-grand:nth-child(odd) .field-num, -.board-quarter .field.zone-retour:nth-child(odd) .field-num, -.board-quarter .field.zone-opponent:nth-child(odd) .field-num { - color: rgba(240,215,190,0.38); -} - -.field.corner .field-num { color: rgba(255,248,200,0.4); } -.top-row .field-num { bottom: auto; top: 3px; } - -/* ── Checkers ───────────────────────────────────────────────────────── */ -.checker-stack { - display: flex; - flex-direction: column; - align-items: center; -} - -.checker { - width: 40px; - height: 40px; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - font-size: 0.78rem; - font-weight: 600; - flex-shrink: 0; -} - -.checker + .checker { margin-top: -4px; } - -.checker.white { - background-image: - radial-gradient(ellipse 50% 35% at 36% 30%, - rgba(255,255,255,0.65) 0%, transparent 100%), - radial-gradient(circle, - transparent 68%, rgba(160,130,70,0.22) 68.5%, - rgba(160,130,70,0.22) 71.5%, transparent 72%), - radial-gradient(circle, - transparent 43%, rgba(160,130,70,0.17) 43.5%, - rgba(160,130,70,0.17) 46.5%, transparent 47%), - radial-gradient(circle at 38% 32%, - #ffffff 0%, var(--checker-white) 52%, #c0b288 100%); - border: 1.8px solid var(--checker-ring); - box-shadow: - 0 2px 6px rgba(0,0,0,0.4), - inset 0 -1px 3px rgba(0,0,0,0.15); - color: #443322; -} - -.checker.black { - background-image: - radial-gradient(ellipse 40% 28% at 36% 30%, - rgba(110,65,30,0.38) 0%, transparent 100%), - radial-gradient(circle, - transparent 68%, rgba(200,164,72,0.18) 68.5%, - rgba(200,164,72,0.18) 71.5%, transparent 72%), - radial-gradient(circle, - transparent 43%, rgba(200,164,72,0.13) 43.5%, - rgba(200,164,72,0.13) 46.5%, transparent 47%), - radial-gradient(circle at 38% 32%, - #4a2e1a 0%, #1c1008 45%, var(--checker-black) 100%); - border: 1.8px solid var(--checker-ring); - box-shadow: - 0 2px 6px rgba(0,0,0,0.55), - inset 0 -1px 3px rgba(0,0,0,0.4); - color: #c8b898; -} - -/* ── Hole toast (§6a) ───────────────────────────────────────────────── */ -@keyframes toast-rise { - from { transform: translate(-50%, -40%); opacity: 0; } - to { transform: translate(-50%, -50%); opacity: 1; } -} -@keyframes toast-fade { - from { opacity: 1; } - to { opacity: 0; pointer-events: none; } -} - -.hole-toast { - position: fixed; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - background: rgba(22,10,2,0.93); - border: 2px solid var(--ui-gold); - border-radius: 8px; - padding: 1.5rem 3.5rem; - text-align: center; - z-index: 50; - pointer-events: none; - box-shadow: - 0 12px 40px rgba(0,0,0,0.65), - 0 0 0 1px rgba(200,164,72,0.25), - inset 0 1px 0 rgba(200,164,72,0.1); - animation: - toast-rise 0.25s cubic-bezier(0.22, 0.61, 0.36, 1), - toast-fade 0.5s ease-in 1.4s forwards; -} - -.hole-toast-title { - font-family: var(--font-display); - font-size: 3.25rem; - font-weight: 600; - color: var(--ui-gold); - letter-spacing: 0.1em; - line-height: 1; -} - -.hole-toast-count { - font-family: var(--font-display); - font-size: 1.1rem; - color: rgba(200,164,72,0.68); - margin-top: 0.35rem; - letter-spacing: 0.06em; -} - -.hole-toast-bredouille { - font-family: var(--font-ui); - font-size: 0.75rem; - letter-spacing: 0.08em; - color: rgba(200,164,72,0.55); - margin-top: 0.4rem; - text-transform: uppercase; -} - -@keyframes bredouille-shimmer { - 0%, 100% { box-shadow: 0 12px 40px rgba(0,0,0,0.65), 0 0 0 2px rgba(200,164,72,0.4), inset 0 0 0 rgba(200,164,72,0); } - 50% { box-shadow: 0 12px 40px rgba(0,0,0,0.65), 0 0 0 4px rgba(200,164,72,0.7), inset 0 0 24px rgba(200,164,72,0.08); } -} -.hole-toast.hole-toast-bredouille { - border-width: 2.5px; - border-color: var(--ui-gold); - padding: 2rem 4rem; - animation: - toast-rise 0.3s cubic-bezier(0.22, 0.61, 0.36, 1), - bredouille-shimmer 0.9s ease-in-out 0.3s 2, - toast-fade 0.5s ease-in 2.2s forwards; -} -.hole-toast.hole-toast-bredouille .hole-toast-title { font-size: 3.75rem; } -.hole-toast.hole-toast-bredouille .hole-toast-bredouille { - font-size: 0.85rem; - color: rgba(200,164,72,0.8); - letter-spacing: 0.14em; -} - -/* ── Checker slide animation (§4a) ─────────────────────────────────── */ -@keyframes checker-slide-in { - from { transform: translate(var(--slide-dx, 0px), var(--slide-dy, 0px)); } - to { transform: none; } -} -.checker.arriving { - animation: checker-slide-in 0.28s cubic-bezier(0.25, 0.46, 0.45, 0.94); -} -.field:has(.checker.arriving) { - isolation: auto; - z-index: 10; - position: relative; -} - -/* ── Checker lift on selected field (§4b) ───────────────────────────── */ -.field.selected .checker-stack { - transform: translateY(-5px); - filter: drop-shadow(0 8px 12px rgba(0,0,0,0.6)); - transition: transform 0.12s ease-out, filter 0.12s ease-out; -} - -/* ── Action buttons below board (§10c) ──────────────────────────────── */ -.board-actions { - display: flex; - gap: 0.55rem; - justify-content: center; - align-items: center; - flex-wrap: wrap; - min-height: 2rem; -} - -/* ── Free-play mode ─────────────────────────────────────────────────────── */ -.free-mode-toggle { - display: flex; - align-items: center; - gap: 0.4rem; - font-family: var(--font-ui); - font-size: 0.78rem; - color: #887766; - cursor: pointer; - user-select: none; - padding-top: 0.1rem; -} -.free-mode-toggle input[type="checkbox"] { - accent-color: var(--ui-gold); - cursor: pointer; - width: 0.85rem; - height: 0.85rem; -} -.free-mode-help { - display: inline-flex; - align-items: center; - justify-content: center; - width: 1rem; - height: 1rem; - border-radius: 50%; - border: 1px solid #a89880; - font-size: 0.65rem; - font-style: normal; - color: #a89880; - cursor: help; - flex-shrink: 0; -} - -.free-mode-error { - display: flex; - align-items: center; - gap: 0.75rem; - background: rgba(180, 60, 30, 0.12); - border: 1px solid rgba(180, 60, 30, 0.4); - border-radius: 4px; - padding: 0.4rem 0.75rem; - width: 100%; - box-sizing: border-box; -} -.free-mode-error-msg { - flex: 1; - font-family: var(--font-ui); - font-size: 0.85rem; - color: #8b2000; - font-style: italic; -} - -/* ── Pre-game ceremony overlay ──────────────────────────────────────────── */ -.ceremony-overlay { - position: fixed; - inset: 0; - background: rgba(0,0,0,0.65); - display: flex; - align-items: center; - justify-content: center; - z-index: 100; -} - -.ceremony-box { - background: var(--ui-parchment); - border-radius: 8px; - padding: 2.5rem 3rem; - text-align: center; - box-shadow: 0 12px 40px rgba(0,0,0,0.5), 0 0 0 2px var(--ui-gold-dark); - display: flex; - flex-direction: column; - align-items: center; - gap: 1.4rem; - min-width: 300px; - animation: game-over-appear 0.4s cubic-bezier(0.22, 0.61, 0.36, 1); -} - -.ceremony-box h2 { - font-family: var(--font-display); - font-size: 1.8rem; - font-weight: 600; - color: var(--ui-ink); - letter-spacing: 0.06em; -} - -.ceremony-dice { - display: flex; - gap: 3rem; - align-items: flex-end; -} - -.ceremony-die-slot { - display: flex; - flex-direction: column; - align-items: center; - gap: 0.5rem; -} - -.ceremony-die-label { - font-family: var(--font-ui); - font-size: 0.85rem; - color: var(--ui-ink); - font-weight: 500; -} - -.ceremony-tie { - font-family: var(--font-display); - font-size: 1rem; - color: var(--ui-red-accent); - font-style: italic; -} - -.ceremony-result { - font-family: var(--font-display); - font-size: 1.15rem; - font-weight: 600; - color: var(--ui-gold-dark); - letter-spacing: 0.04em; -} - -/* ── Nickname modal (anonymous player name chooser) ─────────────────── */ -.nickname-backdrop { - position: fixed; - inset: 0; - background: rgba(0,0,0,0.6); - display: flex; - align-items: center; - justify-content: center; - z-index: 300; -} - -.nickname-modal { - background: var(--ui-parchment); - border-radius: 8px; - padding: 2rem 2rem 1.75rem; - width: min(360px, 90vw); - display: flex; - flex-direction: column; - gap: 1rem; - box-shadow: - 0 20px 60px rgba(0,0,0,0.55), - 0 0 0 1px rgba(200,164,72,0.35), - 0 0 0 5px rgba(42,21,8,0.9), - 0 0 0 6px rgba(200,164,72,0.2); - animation: game-over-appear 0.25s cubic-bezier(0.22, 0.61, 0.36, 1); -} - -.nickname-modal-title { - font-family: var(--font-display); - font-size: 1.5rem; - font-weight: 600; - color: var(--ui-ink); - text-align: center; - letter-spacing: 0.04em; -} - -.nickname-modal-hint { - font-family: var(--font-ui); - font-size: 0.8rem; - color: rgba(42,26,8,0.6); - text-align: center; - margin-bottom: -0.25rem; -} - -.nickname-modal-alt { - text-align: center; - font-size: 0.8rem; - color: rgba(42,26,8,0.55); - padding-top: 0.5rem; - border-top: 1px solid rgba(138,106,40,0.2); -} - -.nickname-modal-alt a { - color: var(--ui-gold-dark); - text-decoration: none; - font-weight: 500; -} - -.nickname-modal-alt a:hover { text-decoration: underline; } - -/* ── Game hamburger button (☰ → ✕ animation) ────────────────────────── */ -.game-hamburger { - position: fixed; - top: 0.6rem; - left: 0.6rem; - z-index: 251; - width: 36px; - height: 36px; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 5px; - background: var(--board-rail); - border: 1px solid rgba(200,164,72,0.35); - border-radius: 5px; - cursor: pointer; - transition: background 0.15s, border-color 0.15s; -} -.game-hamburger:hover { - background: #3d1f0a; - border-color: rgba(200,164,72,0.65); -} - -.hb-bar { - display: block; - width: 16px; - height: 2px; - background: var(--ui-parchment); - border-radius: 1px; - transition: transform 0.25s cubic-bezier(0.22, 0.61, 0.36, 1), opacity 0.2s; - transform-origin: center; -} -/* Top bar rotates down to form \ */ -.game-hamburger-open .hb-top { transform: translateY(7px) rotate(45deg); } -/* Middle bar fades out */ -.game-hamburger-open .hb-mid { opacity: 0; transform: scaleX(0); } -/* Bottom bar rotates up to form / */ -.game-hamburger-open .hb-bot { transform: translateY(-7px) rotate(-45deg); } - -/* ── Game sidebar ────────────────────────────────────────────────────── */ -.game-sidebar { - position: fixed; - top: 0; - left: 0; - height: 100vh; - width: 280px; - z-index: 250; - background: var(--board-rail); - border-right: 1px solid rgba(200,164,72,0.25); - display: flex; - flex-direction: column; - transform: translateX(-100%); - transition: transform 0.25s cubic-bezier(0.22, 0.61, 0.36, 1); - overflow-y: auto; -} -.game-sidebar-open { - transform: translateX(0); -} - -.game-sidebar-header { - display: flex; - align-items: center; - justify-content: space-between; - padding: 1rem 1rem; - border-bottom: 1px solid rgba(200,164,72,0.2); - flex-shrink: 0; -} - -.game-sidebar-brand { - font-family: var(--font-display); - font-size: 1.3rem; - font-weight: 600; - color: var(--ui-gold); - letter-spacing: 0.06em; - margin-left: 45px; -} - -.game-sidebar-close { - width: 28px; - height: 28px; - display: flex; - align-items: center; - justify-content: center; - background: transparent; - border: 1px solid rgba(200,164,72,0.25); - border-radius: 4px; - color: var(--ui-parchment); - font-size: 0.85rem; - cursor: pointer; - opacity: 0.65; - transition: opacity 0.15s; -} -.game-sidebar-close:hover { opacity: 1; } - -.game-sidebar-section { - padding: 0.9rem 1rem; - border-bottom: 1px solid rgba(200,164,72,0.12); - display: flex; - flex-direction: row; - gap: 0.55rem; -} - -.game-sidebar-label { - font-size: 0.7rem; - font-family: var(--font-ui); - letter-spacing: 0.07em; - text-transform: uppercase; - color: rgba(242,232,208,0.45); -} - -.game-sidebar-link { - font-family: var(--font-ui); - font-size: 0.85rem; - color: var(--ui-parchment); - text-decoration: none; - opacity: 0.8; - transition: opacity 0.15s; - cursor: pointer; -} -.game-sidebar-link:hover { opacity: 1; text-decoration: underline; text-underline-offset: 2px; } - -.game-sidebar-btn { - font-family: var(--font-ui); - font-size: 0.82rem; - padding: 0.4rem 0.75rem; - border: 1px solid rgba(200,164,72,0.35); - border-radius: 4px; - background: rgba(200,164,72,0.1); - color: var(--ui-parchment); - cursor: pointer; - text-align: left; - transition: background 0.15s; -} -.game-sidebar-btn:hover { background: rgba(200,164,72,0.22); } - -.game-sidebar-btn-newgame { - background: rgba(58,107,42,0.25); - border-color: rgba(58,107,42,0.55); - font-weight: 500; -} -.game-sidebar-btn-newgame:hover { background: rgba(58,107,42,0.42); } - -.game-sidebar-qr { - width: 100%; - height: auto; - aspect-ratio: 1; - max-width: 200px; - margin: 0 auto; -} - -/* Push the version wrapper to the bottom of the sidebar flex column */ -.sidebar-footer { - margin-top: auto; - border-top: 1px solid rgba(200,164,72,0.12); -} - -.site-nav-infolinks { - margin: 2em 0 1em; - text-align: center; - font-size: 0.9rem; - color: rgba(200,164,72,0.4); - display: flex; - flex-direction: row; - align-items: center; -} - -.site-nav-infolinks > a { - width: 100%; -} - -.site-nav-version { - margin: 2em 0 1em; - display: block; - text-align: center; - font-family: var(--font-ui); - font-size: 0.7rem; - letter-spacing: 0.06em; - color: rgba(200,164,72,0.4); -} - -/* ── Content pages (markdown-rendered) ─────────────────────────────────────── */ - -.content-page h1 { - font-family: var(--font-display); - font-size: 2rem; - font-weight: 600; - color: var(--ui-ink); - letter-spacing: 0.04em; - margin-bottom: 0.5rem; -} -.content-page h2 { - font-family: var(--font-display); - font-size: 1.4rem; - font-weight: 600; - color: var(--ui-ink); - margin: 1.75rem 0 0.5rem; - border-bottom: 1px solid rgba(200,164,72,0.25); - padding-bottom: 0.25rem; -} -.content-page h3 { - font-family: var(--font-display); - font-size: 1.1rem; - font-weight: 600; - color: var(--ui-ink); - margin: 1.25rem 0 0.4rem; -} -.content-page p { - line-height: 1.7; - margin-bottom: 0.9rem; - color: var(--ui-ink); -} -.content-page ul, -.content-page ol { - margin: 0.5rem 0 1rem 1.5rem; - line-height: 1.7; - color: var(--ui-ink); -} -.content-page li { - margin-bottom: 0.25rem; -} -.content-page a { - color: var(--ui-gold-dark); - text-decoration: underline; -} -.content-page a:hover { - color: var(--ui-ink); -} -.content-page code { - font-family: monospace; - background: rgba(0,0,0,0.07); - border-radius: 3px; - padding: 0.1em 0.35em; - font-size: 0.88em; -} -.content-page pre { - background: rgba(0,0,0,0.07); - border-radius: 5px; - padding: 1rem 1.25rem; - overflow-x: auto; - margin-bottom: 1rem; -} -.content-page pre code { - background: none; - padding: 0; -} -.content-page blockquote { - border-left: 3px solid rgba(200,164,72,0.5); - margin: 0.75rem 0; - padding: 0.25rem 1rem; - color: #665544; - font-style: italic; -} -.content-page table { - border-collapse: collapse; - width: 100%; - margin-bottom: 1rem; -} -.content-page th, -.content-page td { - border: 1px solid rgba(200,164,72,0.3); - padding: 0.4rem 0.75rem; - text-align: left; -} -.content-page th { - background: rgba(200,164,72,0.1); - font-weight: 600; -} diff --git a/doc/design/snapshots/2026-06-06-7b036e3ee1a8d3d686a664d74717fd23df1d3169.html b/doc/design/snapshots/2026-06-06-7b036e3ee1a8d3d686a664d74717fd23df1d3169.html deleted file mode 100644 index 4ac9d36..0000000 --- a/doc/design/snapshots/2026-06-06-7b036e3ee1a8d3d686a664d74717fd23df1d3169.html +++ /dev/null @@ -1,153 +0,0 @@ - - - - - - Trictrac - - - - - - - -
Anonymous (you)
6/12
Trictrac
6/12
Bot
jan de retour
13
14
15
16
17
18
19
20
21
22
23
24
8
12
11
10
9
8
7
6
5
4
3
2
1
11
grand jan
petit jan
Move a checker (1 of 2)

Click a highlighted field to move a checker

Cannot play in a quarter the opponent can still fill
+2 pts
True hit (big jan)simple×1+2
diff --git a/doc/design/snapshots/2026-06-06-7b036e3ee1a8d3d686a664d74717fd23df1d3169_files/style-b42680e382d603c7.css b/doc/design/snapshots/2026-06-06-7b036e3ee1a8d3d686a664d74717fd23df1d3169_files/style-b42680e382d603c7.css deleted file mode 100644 index 58db762..0000000 --- a/doc/design/snapshots/2026-06-06-7b036e3ee1a8d3d686a664d74717fd23df1d3169_files/style-b42680e382d603c7.css +++ /dev/null @@ -1,2528 +0,0 @@ -/* ── Google Fonts ───────────────────────────────────────────────── */ -@import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,600;1,400&family=Jost:wght@300;400;500&display=swap'); - -/* ── Design tokens ──────────────────────────────────────────────────── */ -:root { - --board-felt: #1d3d28; - --board-rail: #2a1508; - --field-ivory: #f0e6c8; - --field-burgundy: #7a1e2a; - --field-blue: #e5eadc; - --field-blue-light: #1a4f72; - --field-brown: #f2dfa0; - --field-brown-light: #6a2810; - --field-corner: #b8900a; - --checker-white: #f5edd8; - --checker-black: #1a0f06; - --checker-ring: #c8a448; - --ui-parchment: #f2e8d0; - --ui-parchment-dark: #e4d8b8; - --ui-ink: #2a1a08; - --ui-gold: #c8a448; - --ui-gold-dark: #8a6a28; - --ui-green-accent: #3a6b2a; - --ui-red-accent: #7a1e2a; - --font-display: 'Cormorant Garamond', Georgia, serif; - --font-ui: 'Jost', system-ui, sans-serif; -} - - -/* ── Reset & base ──────────────────────────────────────────────────── */ -*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } - -body { - font-family: var(--font-ui); - background: #8a7050; - background-image: - radial-gradient(ellipse at 20% 10%, rgba(80,48,16,0.35) 0%, transparent 60%), - radial-gradient(ellipse at 80% 90%, rgba(40,24,8,0.3) 0%, transparent 55%), - repeating-linear-gradient( - 45deg, transparent, transparent 3px, - rgba(0,0,0,0.03) 3px, rgba(0,0,0,0.03) 4px - ); - display: flex; - flex-direction: column; - min-height: 100vh; -} - -.hidden { display: none !important; } - -/* -- svg icons -- */ -.icon { - width: 1.2em; - height: 1.2em; - color: var(--ui-parchment); - vertical-align: -0.25em; - margin-right: 0.7em; -} - -/* ── Site navigation ─────────────────────────────────────────────── */ -.site-nav { - background: var(--board-rail); - border-bottom: 2px solid var(--ui-gold-dark); - padding: 0 1.5rem; - height: 52px; - display: flex; - align-items: center; - gap: 1.5rem; - position: sticky; - top: 0; - z-index: 50; - box-shadow: 0 2px 8px rgba(0,0,0,0.4); - flex-shrink: 0; -} - -.site-nav-brand { - font-family: var(--font-display); - font-size: 1.5rem; - font-weight: 600; - color: var(--ui-gold); - text-decoration: none; - letter-spacing: 0.1em; -} -.site-nav-brand:hover { color: #e0b840; } - -.site-nav-spacer { flex: 1; } - -.site-nav a { - font-family: var(--font-ui); - font-size: 0.9rem; - color: var(--ui-parchment); - text-decoration: none; - opacity: 0.8; - transition: opacity 0.15s, color 0.15s; -} -.site-nav a:hover { opacity: 1; } - -.site-nav-btn { - padding: 0.3rem 0.9rem; - font-family: var(--font-ui); - font-size: 0.85rem; - font-weight: 500; - letter-spacing: 0.03em; - border: 1px solid rgba(200,164,72,0.4); - border-radius: 4px; - background: transparent; - color: var(--ui-parchment); - cursor: pointer; - transition: background 0.15s, border-color 0.15s; -} -.site-nav-btn:hover { - background: rgba(200,164,72,0.12); - border-color: var(--ui-gold); -} - -/* ── Portal main content area ────────────────────────────────────── */ -.portal-main { - flex: 1; - max-width: 900px; - width: 100%; - margin: 2rem auto; - padding: 0 1.5rem; -} - -.portal-card { - background: var(--ui-parchment); - border-radius: 8px; - box-shadow: - 0 20px 60px rgba(0,0,0,0.55), - 0 0 3px 3px rgba(42,21,8,0.9) - ; - /* box-shadow: 0 4px 16px rgba(0,0,0,0.18); */ - /* border: 1px solid rgba(200,164,72,0.3); */ - /* border-top: 3px solid var(--ui-gold-dark); */ - padding: 1.75rem 2rem; - margin-bottom: 1.5rem; -} - -.portal-card h1 { - font-family: var(--font-display); - font-size: 2rem; - font-weight: 600; - color: var(--ui-ink); - letter-spacing: 0.04em; - margin-bottom: 0.25rem; -} -.portal-card h2 { - font-family: var(--font-display); - font-size: 1.35rem; - font-weight: 600; - color: var(--ui-ink); - margin-bottom: 0.75rem; -} - -.portal-meta { - font-size: 0.85rem; - color: #665544; - margin-bottom: 1.5rem; - font-family: var(--font-ui); -} - -/* ── Stats grid ──────────────────────────────────────────────────── */ -.stats-grid { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 1rem; - margin-bottom: 1.5rem; -} -.stat-box { - background: var(--ui-parchment); - border: 1px solid rgba(200,164,72,0.28); - border-radius: 6px; - padding: 1rem; - text-align: center; - box-shadow: 0 1px 4px rgba(0,0,0,0.1); -} -.stat-box .value { - font-family: var(--font-display); - font-size: 2.2rem; - font-weight: 600; - color: var(--ui-gold-dark); -} -.stat-box .label { - font-size: 0.78rem; - color: #665544; - margin-top: 0.2rem; - letter-spacing: 0.04em; - text-transform: uppercase; -} - -/* ── Tables ──────────────────────────────────────────────────────── */ -table { - width: 100%; - border-collapse: collapse; - font-size: 0.9rem; - font-family: var(--font-ui); -} -th { - text-align: left; - padding: 0.5rem 0.75rem; - border-bottom: 2px solid rgba(200,164,72,0.4); - color: #665544; - font-weight: 500; - font-size: 0.8rem; - letter-spacing: 0.05em; - text-transform: uppercase; -} -td { - padding: 0.5rem 0.75rem; - border-bottom: 1px solid rgba(200,164,72,0.12); - color: var(--ui-ink); -} -tr:last-child td { border-bottom: none; } -tr:hover td { background: rgba(200,164,72,0.05); } - -a { color: var(--ui-gold-dark); text-decoration: none; } -a:hover { text-decoration: underline; } - -/* ── Outcome classes ─────────────────────────────────────────────── */ -.outcome-win { color: var(--ui-green-accent); font-weight: 600; } -.outcome-loss { color: var(--ui-red-accent); font-weight: 600; } -.outcome-draw { color: #c07020; font-weight: 600; } - -/* ── Portal tabs ─────────────────────────────────────────────────── */ -.portal-tabs { - display: flex; - gap: 0; - margin-bottom: 1.5rem; - border-bottom: 1px solid rgba(200,164,72,0.3); -} -.portal-tab-btn { - padding: 0.55rem 1.5rem; - font-family: var(--font-ui); - font-size: 0.9rem; - background: transparent; - border: none; - cursor: pointer; - color: #665544; - border-bottom: 2px solid transparent; - margin-bottom: -1px; - transition: color 0.15s, border-color 0.15s; -} -.portal-tab-btn.active { - color: var(--ui-ink); - border-bottom-color: var(--ui-gold-dark); - font-weight: 500; -} - -/* ── Portal form ─────────────────────────────────────────────────── */ -.portal-label { - display: block; - font-size: 0.82rem; - color: #665544; - margin-bottom: 0.3rem; - letter-spacing: 0.03em; -} -.portal-input { - width: 100%; - padding: 0.55rem 0.85rem; - font-size: 0.95rem; - font-family: var(--font-ui); - border: 1px solid rgba(138,106,40,0.35); - border-radius: 5px; - background: rgba(255,252,240,0.8); - color: var(--ui-ink); - outline: none; - margin-bottom: 1rem; - transition: border-color 0.15s, box-shadow 0.15s; -} -.portal-input:focus { - border-color: var(--ui-gold); - box-shadow: 0 0 0 3px rgba(200,164,72,0.18); -} -.portal-submit-btn { - padding: 0.6rem 2rem; - font-family: var(--font-ui); - font-size: 0.9rem; - font-weight: 500; - background: linear-gradient(160deg, #4a7a38 0%, #2e5222 100%); - color: #e8f0e0; - border: none; - border-radius: 5px; - cursor: pointer; - box-shadow: 0 2px 5px rgba(0,0,0,0.25); - transition: opacity 0.15s; -} -.portal-submit-btn:disabled { opacity: 0.45; cursor: not-allowed; } -.portal-submit-btn:not(:disabled):hover { opacity: 0.9; } - -.portal-page-btn { - padding: 0.35rem 0.9rem; - font-family: var(--font-ui); - font-size: 0.85rem; - background: var(--board-rail); - color: var(--ui-parchment); - border: none; - border-radius: 4px; - cursor: pointer; - opacity: 0.85; - transition: opacity 0.15s; -} -.portal-page-btn:hover { opacity: 1; } - -.portal-loading { color: #665544; font-style: italic; padding: 1rem 0; } -.portal-empty { color: #aa9070; font-style: italic; padding: 1rem 0; } -.portal-error { color: var(--ui-red-accent); font-size: 0.875rem; margin-top: 0.5rem; } -.portal-success { color: var(--ui-green-accent); font-size: 0.875rem; margin-top: 0.5rem; } - -.flash-banner { - position: fixed; - top: 1.25rem; - left: 50%; - transform: translateX(-50%); - z-index: 500; - display: flex; - align-items: center; - gap: 1rem; - padding: 0.75rem 1.25rem; - background: var(--ui-green-accent); - color: #f5edd8; - border-radius: 6px; - box-shadow: 0 4px 16px rgba(0,0,0,0.35); - font-family: var(--font-ui); - font-size: 0.95rem; - max-width: 90vw; - animation: flash-in 0.2s ease; -} -@keyframes flash-in { - from { opacity: 0; transform: translateX(-50%) translateY(-0.5rem); } - to { opacity: 1; transform: translateX(-50%) translateY(0); } -} -.flash-dismiss { - background: none; - border: none; - color: inherit; - cursor: pointer; - font-size: 1rem; - opacity: 0.75; - padding: 0; - line-height: 1; -} -.flash-dismiss:hover { opacity: 1; } - -.portal-danger-zone { - border: 1px solid rgba(122, 30, 42, 0.4); - background: rgba(122, 30, 42, 0.04); -} -.portal-danger-zone h2 { - color: var(--ui-red-accent); -} -.portal-danger-btn { - padding: 0.5rem 1.25rem; - font-family: var(--font-ui); - font-size: 0.9rem; - background: var(--ui-red-accent); - color: #f5edd8; - border: none; - border-radius: 4px; - cursor: pointer; - transition: opacity 0.15s; -} -.portal-danger-btn:hover { opacity: 0.85; } -.portal-danger-btn:disabled { opacity: 0.45; cursor: not-allowed; } - -.portal-link { - color: var(--ui-gold); - text-decoration: none; - font-size: 0.875rem; -} -.portal-link:hover { text-decoration: underline; } - -.portal-verification-banner { - background: rgba(200,164,72,0.08); - border: 1px solid rgba(200,164,72,0.35); - border-radius: 6px; - padding: 1.25rem; - text-align: center; -} -.portal-verification-banner p { - margin-bottom: 0.75rem; - font-size: 0.9rem; -} - -/* ── Share URL row (lobby waiting card + game top bar) ──────────── */ -.share-url-row { - display: flex; - align-items: center; - gap: 0.5rem; - background: rgba(0,0,0,0.18); - border: 1px solid rgba(200,164,72,0.25); - border-radius: 5px; - padding: 0.4rem 0.6rem; -} -.share-url-text { - flex: 1; - font-family: var(--font-ui); - font-size: 0.72rem; - color: rgba(242,232,208,0.75); - word-break: break-all; - user-select: all; -} -.share-copy-btn { - flex-shrink: 0; - font-family: var(--font-ui); - font-size: 0.72rem; - padding: 0.2rem 0.6rem; - border: 1px solid rgba(200,164,72,0.4); - border-radius: 3px; - background: rgba(200,164,72,0.1); - color: var(--ui-parchment); - cursor: pointer; - transition: background 0.15s; - white-space: nowrap; -} -.share-copy-btn:hover { background: rgba(200,164,72,0.22); } - -/* ── QR code container ───────────────────────────────────────────── */ -.qr-container { - width: 160px; - height: 160px; - margin: 0 auto; - border-radius: 4px; - overflow: hidden; -} -.qr-container svg { width: 100%; height: 100%; display: block; } - -/* ── Share popover (in-game top bar) ─────────────────────────────── */ -.share-popover { - width: 100%; - background: rgba(0,0,0,0.3); - border: 1px solid rgba(200,164,72,0.2); - border-radius: 6px; - padding: 0.75rem 1rem; - display: flex; - flex-direction: column; - align-items: center; - gap: 0.4rem; - margin-bottom: 0.5rem; -} -.share-popover .qr-container { width: 120px; height: 120px; } -.share-popover-label { - font-size: 0.75rem; - color: rgba(242,232,208,0.6); - text-align: center; - margin: 0; -} - -/* ── Game overlay (full-screen, covers portal during play) ───────── */ -.game-overlay { - position: fixed; - inset: 0; - background: #8a7050; - background-image: - radial-gradient(ellipse at 20% 10%, rgba(80,48,16,0.35) 0%, transparent 60%), - radial-gradient(ellipse at 80% 90%, rgba(40,24,8,0.3) 0%, transparent 55%), - repeating-linear-gradient( - 45deg, transparent, transparent 3px, - rgba(0,0,0,0.03) 3px, rgba(0,0,0,0.03) 4px - ); - z-index: 200; - display: flex; - justify-content: center; - align-items: flex-start; - padding: 1.5rem; - overflow-y: auto; -} - -/* ── Login card (§11) ───────────────────────────────────────────────── */ -.login-card { - background: var(--ui-parchment); - border-radius: 8px; - box-shadow: - 0 20px 60px rgba(0,0,0,0.55), - 0 0 3px 3px rgba(42,21,8,0.9) - ; - /* box-shadow: - 0 20px 60px rgba(0,0,0,0.55), - 0 0 0 1px rgba(200,164,72,0.35), - 0 0 0 5px rgba(42,21,8,0.9), - 0 0 0 6px rgba(200,164,72,0.2); - */ - /* border-top: 3px solid var(--ui-gold-dark); */ - width: 340px; - margin-top: 5vh; - overflow: hidden; -} - -/* Decorative header — row of triangular flèches like the actual board */ -.login-card-header { - height: 52px; - background: var(--board-felt); - position: relative; - overflow: hidden; -} - -.login-board-stripe { - position: absolute; - inset: 0; - background: - repeating-linear-gradient( - 90deg, - var(--field-burgundy) 0, var(--field-burgundy) 50%, - var(--field-ivory) 50%, var(--field-ivory) 100% - ); - background-size: 34px 100%; - clip-path: polygon( - 0% 0%, 2.94% 100%, 5.88% 0%, 8.82% 100%, 11.76% 0%, - 14.7% 100%, 17.65% 0%, 20.59% 100%, 23.53% 0%, - 26.47% 100%, 29.41% 0%, 32.35% 100%, 35.29% 0%, - 38.24% 100%, 41.18% 0%, 44.12% 100%, 47.06% 0%, - 50% 100%, 52.94% 0%, 55.88% 100%, 58.82% 0%, - 61.76% 100%, 64.71% 0%, 67.65% 100%, 70.59% 0%, - 73.53% 100%, 76.47% 0%, 79.41% 100%, 82.35% 0%, - 85.29% 100%, 88.24% 0%, 91.18% 100%, 94.12% 0%, - 97.06% 100%, 100% 0% - ); - opacity: 0.9; -} - -.login-card-body { - display: flex; - flex-direction: column; - align-items: center; - gap: 0; - padding: 1.5rem 2rem 2rem; -} - -.login-lang-switcher { - align-self: flex-end; - margin-bottom: 0.75rem; -} - -/* Override lang-switcher colours for the parchment card */ -.login-card .lang-switcher button { - color: var(--ui-ink); - border-color: rgba(42,21,8,0.2); - opacity: 0.5; -} -.login-card .lang-switcher button.lang-active { - opacity: 1; - background: rgba(42,21,8,0.08); - border-color: rgba(42,21,8,0.35); -} - -.login-title { - font-family: var(--font-display); - font-size: 3.25rem; - font-weight: 600; - color: var(--ui-ink); - letter-spacing: 0.12em; - text-align: center; - line-height: 1; - margin-bottom: 0.3rem; -} - -.login-subtitle { - font-family: var(--font-display); - font-size: 0.85rem; - color: rgba(42,26,8,0.55); - text-align: center; - letter-spacing: 0.06em; - font-style: italic; - margin-bottom: 1.25rem; -} -.login-subtitle sup { - font-size: 0.65em; - vertical-align: super; -} - -.login-ornament { - color: var(--ui-gold); - font-size: 1rem; - opacity: 0.7; - margin-bottom: 1.25rem; - letter-spacing: 0.3em; - text-align: center; -} - -.error-msg { - color: #c03030; - font-size: 0.85rem; - text-align: center; - margin-bottom: 0.5rem; -} - -.login-input { - width: 100%; - padding: 0.55rem 0.85rem; - font-size: 0.95rem; - font-family: var(--font-ui); - border: 1px solid rgba(138,106,40,0.4); - border-radius: 5px; - background: rgba(255,252,240,0.8); - color: var(--ui-ink); - outline: none; - transition: border-color 0.15s, box-shadow 0.15s; - margin-bottom: 1rem; -} -.login-input:focus { - border-color: var(--ui-gold); - box-shadow: 0 0 0 3px rgba(200,164,72,0.2); -} - -.login-actions { - display: flex; - flex-direction: column; - gap: 0.55rem; - width: 100%; -} - -/* Login buttons styled as embossed wooden tiles */ -.login-btn { - width: 100%; - padding: 0.65rem 1rem; - font-family: var(--font-ui); - font-size: 0.9rem; - font-weight: 500; - letter-spacing: 0.04em; - border: none; - border-radius: 5px; - cursor: pointer; - transition: opacity 0.15s, transform 0.1s, box-shadow 0.15s; - position: relative; -} -.login-btn:disabled { opacity: 0.35; cursor: default; } -.login-btn:not(:disabled):hover { opacity: 0.92; transform: translateY(-1px); } -.login-btn:not(:disabled):active { transform: translateY(0); } - -.login-btn-primary { - background: linear-gradient(160deg, #4a7a38 0%, #2e5222 100%); - color: #e8f0e0; - box-shadow: 0 2px 6px rgba(0,0,0,0.3), inset 0 1px 0 rgba(255,255,255,0.12); -} -.login-btn-secondary { - background: linear-gradient(160deg, #3a2010 0%, #241408 100%); - color: #e4d4b4; - box-shadow: 0 2px 6px rgba(0,0,0,0.3), inset 0 1px 0 rgba(255,255,255,0.08); -} -.login-btn-bot { - background: linear-gradient(160deg, #2a4a6a 0%, #183050 100%); - color: #d0e0f0; - box-shadow: 0 2px 6px rgba(0,0,0,0.3), inset 0 1px 0 rgba(255,255,255,0.08); -} - -/* ── Connecting screen ──────────────────────────────────────────────── */ -.connecting { - font-family: var(--font-display); - font-size: 1.4rem; - font-style: italic; - margin-top: 4rem; - text-align: center; - color: var(--ui-parchment); - text-shadow: 0 1px 4px rgba(0,0,0,0.4); -} - -/* ── Game-action buttons ─────────────────────────────────────────────── */ -.btn { - padding: 0.5rem 1.25rem; - font-size: 0.95rem; - font-family: var(--font-ui); - font-weight: 500; - letter-spacing: 0.03em; - border: none; - border-radius: 4px; - cursor: pointer; - transition: opacity 0.15s, box-shadow 0.15s; -} -.btn:disabled { opacity: 0.4; cursor: default; } -.btn-primary { background: var(--ui-green-accent); color: #fff; } -.btn-secondary { background: var(--board-rail); color: #e8d8b8; } -.btn-bot { background: #2a5a7a; color: #fff; } -.btn:not(:disabled):hover { - opacity: 0.9; - box-shadow: 0 2px 6px rgba(0,0,0,0.25); -} - -/* ── Game container ─────────────────────────────────────────────────── */ -.game-container { - display: flex; - flex-direction: column; - align-items: center; - gap: 0.6rem; -} - -/* ── Language switcher (in-game) ────────────────────────────────────── */ -.lang-switcher { display: flex; gap: 0.25rem; } - -.lang-switcher button { - font-size: 0.7rem; - font-family: var(--font-ui); - letter-spacing: 0.05em; - padding: 0.15rem 0.4rem; - border: 1px solid rgba(200,164,72,0.3); - border-radius: 3px; - background: transparent; - cursor: pointer; - color: var(--ui-parchment); - opacity: 0.55; -} -.lang-switcher button.lang-active { - opacity: 1; - font-weight: 500; - background: rgba(200,164,72,0.15); - border-color: rgba(200,164,72,0.6); -} - -/* ── Top bar ─────────────────────────────────────────────────────────── */ -.top-bar { - display: flex; - justify-content: space-between; - align-items: center; - width: 100%; - color: var(--ui-parchment); - font-size: 0.85rem; - opacity: 0.8; -} - -.quit-link { - font-size: 0.8rem; - color: var(--ui-parchment); - text-decoration: underline; - text-underline-offset: 2px; - cursor: pointer; - opacity: 0.7; -} -.quit-link:hover { opacity: 1; } - -.playing-as { - font-size: 0.75rem; - color: rgba(242,232,208,0.7); - font-family: var(--font-ui); -} -.playing-as strong { color: rgba(242,232,208,0.9); } - -/* ── Game status bar (§10b) — above board ───────────────────────────── */ -.game-status { - font-family: var(--font-display); - font-size: 1.2rem; - font-style: italic; - color: var(--ui-parchment); - text-align: center; - letter-spacing: 0.04em; - padding: 0.2rem 1rem 0; - width: 100%; - text-shadow: 0 1px 4px rgba(0,0,0,0.4); -} - -/* ── Contextual sub-prompt (§8a) ────────────────────────────────────── */ -.game-sub-prompt { - font-family: var(--font-ui); - font-size: 0.72rem; - color: rgba(240,228,192,0.5); - text-align: center; - letter-spacing: 0.04em; - padding: 0.15rem 1rem 0; - width: 100%; -} - -/* ── Player score panel ─────────────────────────────────────────────── */ -.player-score-panel { - background: var(--ui-parchment); - border-radius: 5px; - padding: 0.45rem 1.25rem; - font-size: 0.88rem; - box-shadow: 0 2px 6px rgba(0,0,0,0.25); - width: 100%; - border-top: 2px solid var(--ui-gold-dark); - display: flex; - align-items: center; - gap: 1.5rem; -} - -.player-score-header { - flex-shrink: 0; - min-width: 90px; -} - -.player-name { - font-family: var(--font-display); - font-weight: 600; - font-size: 1.05rem; - color: var(--ui-ink); - letter-spacing: 0.02em; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - min-width: 0; -} - -.score-bars { display: flex; flex-direction: row; gap: 1.5rem; flex: 1; align-items: center; } - -.score-bar-row { - display: flex; - align-items: center; - gap: 0.5rem; - flex: 1; -} - -.score-bar-label { - font-size: 0.75rem; - color: #665544; - width: 3rem; - text-align: right; - flex-shrink: 0; -} - -/* ── Points bar ─────────────────────────────────────────────────────── */ -.score-bar { - flex: 1; - max-width: 220px; - height: 8px; - background: rgba(0,0,0,0.1); - border-radius: 4px; - overflow: hidden; - flex-shrink: 0; -} - -.score-bar-fill { - height: 100%; - border-radius: 4px; - transition: width 0.35s ease-out; -} - -.score-bar-points { background: linear-gradient(90deg, var(--ui-green-accent), #5a9b3a); } - -.score-bar-value { - font-size: 0.75rem; - color: #665544; - min-width: 2.5rem; - font-variant-numeric: tabular-nums; -} - -/* ── Hole peg tracker (§7a) ─────────────────────────────────────────── */ -.peg-track { - display: flex; - align-items: center; - gap: 3px; - flex-shrink: 0; -} - -.peg-hole { - width: 10px; - height: 10px; - border-radius: 50%; - border: 1.5px solid rgba(138,106,40,0.45); - background: rgba(0,0,0,0.06); - flex-shrink: 0; - transition: background 0.3s ease-out, border-color 0.3s, box-shadow 0.3s; -} - -.peg-hole.filled { - background: var(--ui-gold); - border-color: var(--ui-gold-dark); - box-shadow: 0 0 4px rgba(200,164,72,0.6); -} - -.bredouille-badge { - font-size: 0.62rem; - font-weight: 500; - color: #fff8e0; - background: linear-gradient(135deg, #c88800, #8a5800); - border: 1px solid rgba(200,164,72,0.5); - border-radius: 3px; - padding: 0.1em 0.4em; - letter-spacing: 0.06em; - cursor: default; - box-shadow: 0 1px 3px rgba(0,0,0,0.25); -} - -/* ── Merged scoreboard (both players, above board) ──────────────────── */ -.merged-score-panel { - background: var(--ui-parchment); - border-radius: 5px; - padding: 0.5rem 1.25rem 0.45rem; - font-size: 0.88rem; - box-shadow: 0 2px 6px rgba(0,0,0,0.25); - width: 100%; - border-top: 2px solid var(--ui-gold-dark); - display: flex; - flex-direction: column; - gap: 0.2rem; -} - -.score-row { - display: flex; - align-items: center; - gap: 1rem; -} - -.score-row-name { - width: 120px; - flex-shrink: 0; - display: flex; - align-items: baseline; - gap: 0.35rem; - overflow: hidden; -} - -.you-tag { - font-family: var(--font-ui); - font-size: 0.7rem; - color: #887766; - font-style: italic; - white-space: nowrap; - flex-shrink: 0; -} - -/* ── Jackpot points counter ─────────────────────────────────────────── */ -.pts-counter-wrap { - position: relative; - display: flex; - flex-direction: column; - align-items: center; - width: 72px; - flex-shrink: 0; - padding-bottom: 4px; -} - -.pts-ghost-bar-track { - position: absolute; - bottom: 0; - left: 0; - right: 0; - height: 3px; - background: rgba(0,0,0,0.07); - border-radius: 2px; - overflow: hidden; -} - -.pts-ghost-bar-fill { - height: 100%; - background: rgba(58,107,42,0.45); - border-radius: 2px; -} - -.pts-ghost-bar-opp { - background: rgba(122,30,42,0.4); -} - -.pts-counter-row { - display: flex; - align-items: baseline; - gap: 0.1rem; -} - -.pts-counter { - font-family: var(--font-display); - font-size: 1.9rem; - font-weight: 600; - color: var(--ui-ink); - line-height: 1; - font-variant-numeric: tabular-nums; - min-width: 1.4em; - text-align: right; -} - -.pts-max { - font-family: var(--font-ui); - font-size: 0.7rem; - color: #998877; - line-height: 1; - padding-bottom: 2px; -} - -/* ── Hole pegs — larger and coloured (me = green, opp = red) ─────────── */ -.merged-score-panel .peg-track { - gap: 4px; -} - -.merged-score-panel .peg-hole { - width: 14px; - height: 14px; - border-radius: 50%; - border: 1.5px solid rgba(138,106,40,0.3); - background: rgba(0,0,0,0.06); - flex-shrink: 0; - transition: background 0.3s ease-out, border-color 0.3s, box-shadow 0.3s; -} - -.merged-score-panel .peg-hole.filled { - background: #5aab38; - border-color: #3a7828; - box-shadow: 0 0 5px rgba(90,171,56,0.55); -} - -.merged-score-panel .peg-hole.peg-opp.filled { - background: #c05030; - border-color: #8a3018; - box-shadow: 0 0 5px rgba(192,80,48,0.55); -} - -/* Peg pop-in animation when a new hole is scored */ -@keyframes peg-pop { - 0% { transform: scale(0.15); opacity: 0; } - 45% { transform: scale(1.55); } - 70% { transform: scale(0.88); } - 100% { transform: scale(1.0); opacity: 1; } -} - -.merged-score-panel .peg-hole.peg-new { - animation: peg-pop 0.52s cubic-bezier(0.22, 0.61, 0.36, 1) forwards; -} - -/* Thin separator between the two player rows */ -.score-row-sep { - height: 1px; - background: rgba(0,0,0,0.07); - margin: 0.05rem 0; -} - -/* ── Non-blocking hole flash (replaces old toast) ───────────────────── */ -@keyframes hole-flash-in-out { - 0% { opacity: 0; transform: translateY(-3px); } - 14% { opacity: 1; transform: translateY(0); } - 65% { opacity: 1; } - 100% { opacity: 0; transform: translateY(2px); } -} - -.hole-flash { - margin-left: auto; - flex-shrink: 0; - white-space: nowrap; - font-family: var(--font-display); - font-size: 0.88rem; - font-weight: 600; - color: var(--ui-green-accent); - letter-spacing: 0.05em; - animation: hole-flash-in-out 2.5s ease-out forwards; - pointer-events: none; -} - -.hole-flash.hole-flash-bredouille { - color: var(--ui-gold-dark); -} - -/* ── Game bottom strip — status, hints, buttons on cream ────────────── */ -.game-bottom-strip { - background: var(--ui-parchment); - border-radius: 5px; - padding: 0.55rem 1.25rem 0.65rem; - width: 100%; - box-shadow: 0 2px 6px rgba(0,0,0,0.2); - border-top: 2px solid var(--ui-gold-dark); - display: flex; - flex-direction: column; - align-items: center; - gap: 0.35rem; - min-height: 3.2rem; -} - -/* Override text colours for the parchment background context */ -.game-bottom-strip .game-status { - color: var(--ui-ink); - text-shadow: none; - padding: 0; - font-size: 1.05rem; - width: auto; -} - -.game-bottom-strip .game-sub-prompt { - color: #887766; - padding: 0; - width: auto; -} - -/* ── Board + side panel ─────────────────────────────────────────────── */ -.board-and-panel { - position: relative; -} - -.side-panel { - position: absolute; - right: -8px; - top: 10px; - z-index: 20; - display: flex; - flex-direction: column; - gap: 0.5rem; - padding-top: 0.15rem; - pointer-events: none; -} - -.action-buttons { display: flex; flex-direction: column; gap: 0.5rem; } - -/* ── Dice bar ───────────────────────────────────────────────────────── */ -.dice-bar { - display: flex; - align-items: center; - gap: 0.6rem; - padding: 0.4rem 0.6rem; - background: rgba(42,21,8,0.15); - border-radius: 6px; - border: 1px solid rgba(200,164,72,0.2); - width: fit-content; -} - -/* ── Die face (SVG) ─────────────────────────────────────────────────── */ -@keyframes die-tumble { - 0% { transform: rotate(-45deg) scale(0.4) translateY(-8px); opacity: 0; } - 25% { transform: rotate(18deg) scale(1.22) translateY(0); opacity: 1; } - 45% { transform: rotate(-10deg) scale(0.91); } - 62% { transform: rotate(6deg) scale(1.06); } - 76% { transform: rotate(-3deg) scale(0.98); } - 88% { transform: rotate(1.5deg) scale(1.01); } - 100% { transform: rotate(0deg) scale(1); opacity: 1; } -} - -.die-face { - filter: drop-shadow(0 2px 3px rgba(0,0,0,0.3)); - animation: die-tumble 0.55s cubic-bezier(0.22, 0.61, 0.36, 1) both; -} - -.die-face rect { - fill: #fffef0; - stroke: #2a1a00; - stroke-width: 2; - transition: fill 0.18s, stroke 0.18s; -} -.die-face circle { - fill: #1a0a00; - transition: fill 0.18s; -} - -.bar-die-slot { - display: flex; - align-items: center; - justify-content: center; -} - -.die-face.die-double rect { stroke: var(--ui-gold); stroke-width: 2.5; } -.die-face.die-double { - filter: drop-shadow(0 0 6px rgba(200,164,72,0.7)) drop-shadow(0 2px 3px rgba(0,0,0,0.3)); -} - -.die-face.die-used { animation: none; opacity: 0.55; } -.die-face.die-used rect { fill: #d4d0c4; stroke: #9a8a70; } -.die-face.die-used circle { fill: #9a8a70; } - -.die-face .die-question { fill: #1a0a00; font-family: sans-serif; } -.die-face.die-used .die-question { fill: #9a8a70; } - -/* ── Jan panel ──────────────────────────────────────────────────────── */ -.jan-panel { - display: flex; - flex-direction: column; - gap: 2px; - background: var(--ui-parchment); - border-radius: 5px; - padding: 0.4rem 0.9rem; - font-size: 0.88rem; - box-shadow: 0 1px 4px rgba(0,0,0,0.15); - min-width: 260px; - border-top: 2px solid rgba(138,106,40,0.35); -} - -.jan-row { - display: flex; - align-items: center; - gap: 0.5rem; - padding: 2px 4px; - border-radius: 3px; -} -.jan-expandable { cursor: pointer; } -.jan-expandable:hover { background: rgba(0,0,0,0.05); } - -.jan-positive { color: #1a5c1a; } -.jan-negative { color: #8b1a1a; } - -.jan-label { flex: 1; } -.jan-tag { - font-size: 0.72rem; - padding: 0.1em 0.4em; - border-radius: 3px; - background: rgba(0,0,0,0.07); - color: #665544; - white-space: nowrap; -} -.jan-pts { font-weight: 600; text-align: right; min-width: 3rem; } - -.jan-moves { padding: 1px 4px 4px 1rem; display: flex; flex-direction: column; gap: 2px; } -.jan-moves.hidden { display: none; } -.jan-move-line { font-family: monospace; font-size: 0.78rem; color: #555; } - -/* ── Game-over overlay (§12) ────────────────────────────────────────── */ -.game-over-overlay { - position: fixed; - inset: 0; - background: rgba(0,0,0,0.65); - display: flex; - align-items: center; - justify-content: center; - z-index: 100; -} - -@keyframes game-over-appear { - from { transform: translateY(-24px) scale(0.94); opacity: 0; } - to { transform: translateY(0) scale(1); opacity: 1; } -} - -.game-over-box { - background: var(--ui-parchment); - border-radius: 8px; - padding: 2.5rem 3rem; - text-align: center; - box-shadow: 0 12px 40px rgba(0,0,0,0.5), 0 0 0 2px var(--ui-gold-dark); - display: flex; - flex-direction: column; - gap: 1.1rem; - min-width: 300px; - animation: game-over-appear 0.4s cubic-bezier(0.22, 0.61, 0.36, 1); -} - -.game-over-box h2 { - font-family: var(--font-display); - font-size: 2rem; - font-weight: 600; - color: var(--ui-ink); - letter-spacing: 0.06em; -} - -.game-over-winner { - font-family: var(--font-display); - font-size: 1.25rem; - color: var(--ui-green-accent); - font-style: italic; -} - -.game-over-score { - display: flex; - align-items: center; - justify-content: center; - gap: 0.75rem; - padding: 0.6rem 1rem; - background: rgba(0,0,0,0.05); - border-radius: 5px; - border: 1px solid rgba(138,106,40,0.2); -} - -.game-over-score-name { - font-family: var(--font-display); - font-size: 0.9rem; - color: #665544; - font-style: italic; - max-width: 80px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.game-over-score-nums { - font-family: var(--font-display); - font-size: 2.25rem; - font-weight: 600; - color: var(--ui-ink); - letter-spacing: 0.08em; - line-height: 1; -} - -.game-over-actions { display: flex; gap: 0.75rem; justify-content: center; } - -/* ── Score-area: position:relative wrapper for merged panel + scoring ── */ -.score-area { - position: relative; - width: 100%; -} - -/* ── Scoring panels container — right of the hole counter ───────────── */ -/* Stacked column, right-aligned, covering the free space in each row. */ -/* overflow:visible lets tall panels float over the board below. */ -.scoring-panels-container { - position: absolute; - top: 0; - bottom: 0; - right: 0; - display: flex; - flex-direction: column; - justify-content: space-around; - align-items: flex-end; - padding: 4px 8px; - z-index: 10; - pointer-events: none; - overflow: visible; -} - -/* ── Scoring notification panel (§6b) ───────────────────────────────── */ -@keyframes scoring-panel-enter { - from { opacity: 0; transform: translateX(10px); } - to { opacity: 1; transform: translateX(0); } -} - -.scoring-panel-wrapper { - pointer-events: auto; - display: flex; - flex-direction: column; - align-items: flex-end; - gap: 3px; - animation: scoring-panel-enter 0.3s ease-out; -} - -/* "+" expand button: hidden while the panel is expanded */ -.scoring-panel-wrapper:not(.scoring-minimized) .scoring-expand-btn { - display: none; -} - -/* Full panel card: hidden once minimised */ -.scoring-panel-wrapper.scoring-minimized .scoring-panel { - display: none; -} - -/* "+" expand button ─────────────────────────────────────────────────── */ -.scoring-expand-btn { - font-family: var(--font-display); - font-size: 0.9rem; - line-height: 1; - background: var(--ui-parchment); - border: 1.5px solid var(--ui-gold-dark); - border-radius: 3px; - padding: 2px 7px; - cursor: pointer; - color: var(--ui-ink); - opacity: 0.72; - box-shadow: 0 1px 3px rgba(0,0,0,0.18); - transition: opacity 0.15s; -} -.scoring-expand-btn:hover { opacity: 1; } - -/* ── Panel head: scoring total + "−" collapse link ──────────────────── */ -.scoring-panel-head { - display: flex; - align-items: baseline; - gap: 0.5rem; -} - -.scoring-collapse-btn { - font-size: 0.78rem; - line-height: 1; - background: none; - border: none; - cursor: pointer; - color: rgba(0,0,0,0.35); - padding: 0 1px; - margin-left: auto; - flex-shrink: 0; - transition: color 0.15s; -} -.scoring-collapse-btn:hover { color: rgba(0,0,0,0.65); } - -/* ── Inner scoring card ─────────────────────────────────────────────── */ -.scoring-panel { - background: var(--ui-parchment); - border-radius: 5px; - padding: 0.45rem 0.85rem; - font-size: 0.84rem; - box-shadow: 0 2px 8px rgba(0,0,0,0.22); - border-left: 3px solid var(--ui-green-accent); - display: flex; - flex-direction: column; - gap: 4px; - width: 320px; -} - -.scoring-total { - font-family: var(--font-display); - font-weight: 600; - font-size: 1rem; - color: #1a5c1a; - white-space: nowrap; -} - -.scoring-jan-row { - display: flex; - align-items: center; - gap: 0.4rem; - padding: 2px 3px; - border-radius: 3px; - cursor: default; - white-space: nowrap; -} -.scoring-jan-row:hover { background: rgba(0,0,0,0.05); } - -.scoring-panel-opp { border-left-color: var(--board-rail); } -.scoring-panel-opp .scoring-total { color: var(--ui-red-accent); } - -.scoring-hole { - display: flex; - align-items: center; - gap: 0.4rem; - font-weight: 600; - color: var(--ui-red-accent); - margin-top: 3px; - padding-top: 4px; - border-top: 1px solid rgba(0,0,0,0.1); -} - -.hold-go-buttons { display: flex; gap: 0.5rem; margin-top: 4px; } - -@media (min-width: 1492px) { - .side-panel { - right: auto; - left: calc(100% + 1rem); - } -} - -/* ── Board wrapper ──────────────────────────────────────────────────── */ -.board-wrapper { - display: flex; - flex-direction: column; - gap: 3px; -} - -/* ── Zone labels (§2a) ──────────────────────────────────────────────── */ -.zone-labels-row { - display: flex; - gap: 4px; - padding: 0 8px; -} - -.zone-label { - font-family: var(--font-display); - font-size: 0.57rem; - font-style: italic; - color: rgba(240,228,192,0.48); - letter-spacing: 0.1em; - text-align: center; - pointer-events: none; - line-height: 1; -} - -.zone-label-quarter { width: 370px; flex-shrink: 0; } -.zone-label-bar { width: 68px; flex-shrink: 0; } - -/* ── Board ──────────────────────────────────────────────────────────── */ -.board { - background: var(--board-felt); - border: 4px solid var(--board-rail); - border-radius: 6px; - padding: 4px; - display: flex; - flex-direction: column; - gap: 4px; - user-select: none; - box-shadow: - 0 6px 16px rgba(0,0,0,0.5), - inset 0 1px 0 rgba(255,255,255,0.04); - position: relative; -} - -.board-row { display: flex; gap: 4px; } -.board-quarter { display: flex; gap: 2px; } - -.board-bar { - width: 68px; - background: var(--board-rail); - border-radius: 4px; - box-shadow: inset 0 0 6px rgba(0,0,0,0.5); - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - overflow: visible; -} - -.board-center-bar { - height: 12px; - background: var(--board-rail); - border-radius: 2px; - box-shadow: inset 0 0 4px rgba(0,0,0,0.4); -} - -/* ── Fields (§1) ────────────────────────────────────────────────────── */ -.field { - --fc: var(--field-ivory); - width: 60px; - height: 180px; - background: transparent; - isolation: isolate; - border-radius: 3px; - display: flex; - flex-direction: column; - align-items: center; - justify-content: flex-end; - padding: 4px 2px; - position: relative; -} - -.field::before { - content: ''; - position: absolute; - inset: 0; - z-index: -1; - background: var(--fc); - clip-path: polygon(0% 100%, 50% 0%, 100% 100%); - transition: background 0.12s; -} - -.top-row .field::before { - clip-path: polygon(0% 0%, 100% 0%, 50% 100%); -} - -.top-row .field { justify-content: flex-start; } - -/* ── Zone alternating colours ────────────────────────────────── */ -.board-quarter .field.zone-petit:nth-child(odd), -.board-quarter .field.zone-grand:nth-child(odd) { --fc: var(--field-burgundy); } -.board-quarter .field.zone-petit:nth-child(even), -.board-quarter .field.zone-grand:nth-child(even) { --fc: var(--field-ivory); } - -.board-quarter .field.zone-opponent:nth-child(odd), -.board-quarter .field.zone-retour:nth-child(odd) { --fc: var(--field-burgundy); } -.board-quarter .field.zone-opponent:nth-child(even), -.board-quarter .field.zone-retour:nth-child(even) { --fc: var(--field-ivory); } - -/* ── Point indicator: first N fields reflect each player's score & bredouille */ -.board-quarter .field.zone-petit.point-bredouille:nth-child(odd), -.board-quarter .field.zone-grand.point-bredouille:nth-child(odd) { --fc: var(--field-blue-light); } -.board-quarter .field.zone-petit.point-bredouille:nth-child(even), -.board-quarter .field.zone-grand.point-bredouille:nth-child(even) { --fc: var(--field-blue); } - -.board-quarter .field.zone-petit.point-no-bredouille:nth-child(odd), -.board-quarter .field.zone-grand.point-no-bredouille:nth-child(odd) { --fc: var(--field-blue-light); } -.board-quarter .field.zone-petit.point-no-bredouille:nth-child(even), -.board-quarter .field.zone-grand.point-no-bredouille:nth-child(even) { --fc: var(--field-blue); } - -.board-quarter .field.zone-opponent.point-bredouille:nth-child(odd), -.board-quarter .field.zone-retour.point-bredouille:nth-child(odd) { --fc: var(--field-blue-light); } -.board-quarter .field.zone-opponent.point-bredouille:nth-child(even), -.board-quarter .field.zone-retour.point-bredouille:nth-child(even) { --fc: var(--field-blue); } - -.board-quarter .field.zone-opponent.point-no-bredouille:nth-child(odd), -.board-quarter .field.zone-retour.point-no-bredouille:nth-child(odd) { --fc: var(--field-blue-light); } -.board-quarter .field.zone-opponent.point-no-bredouille:nth-child(even), -.board-quarter .field.zone-retour.point-no-bredouille:nth-child(even) { --fc: var(--field-blue); } - -.field.corner::after { - content: '♛'; - position: absolute; - z-index: -1; - bottom: 22px; - font-size: 0.7rem; - color: rgba(255,248,210,0.38); - pointer-events: none; - line-height: 1; -} -.top-row .field.corner::after { bottom: auto; top: 22px; } - -@keyframes corner-pulse { - 0%, 100% { filter: drop-shadow(0 0 0px rgba(200,164,72,0)); } - 50% { filter: drop-shadow(0 0 7px rgba(200,164,72,0.55)); } -} -.field.corner.corner-available { - animation: corner-pulse 1.5s ease-in-out infinite; -} - -@keyframes exit-glow { - 0%, 100% { filter: drop-shadow(0 0 0px rgba(232,192,96,0)); } - 50% { filter: drop-shadow(0 0 5px rgba(232,192,96,0.5)); } -} -.field.exit-eligible { - animation: exit-glow 2s ease-in-out infinite; -} - -/* ── Exit sign (§8c) — circle+arrow outside the board ──────────────── */ -.exit-btn { - pointer-events: none; - opacity: 0.3; - transition: opacity 0.2s, transform 0.15s; -} -.exit-btn.exit-active { - pointer-events: auto; - cursor: pointer; - opacity: 1; - animation: exit-btn-pulse 1.4s ease-in-out infinite; -} -.exit-btn.exit-active:hover { - transform: scale(1.1); -} -@keyframes exit-btn-pulse { - 0%, 100% { filter: drop-shadow(0 0 3px rgba(200,160,20,0.3)); } - 50% { filter: drop-shadow(0 0 9px rgba(200,160,20,0.85)); } -} - -.field.jan-hovered { - --fc: rgba(190, 140, 35, 0.8) !important; -} - -@keyframes hit-ripple { - from { transform: translate(-50%, -50%) scale(0.4); opacity: 0.9; } - to { transform: translate(-50%, -50%) scale(2.2); opacity: 0; } -} -.hit-ripple { - position: absolute; - left: 50%; - width: 36px; - height: 36px; - border-radius: 50%; - border: 2px solid rgba(200, 164, 72, 0.9); - pointer-events: none; - animation: hit-ripple 0.5s ease-out forwards; -} -.hit-ripple-top { top: 26px; } -.hit-ripple-bot { bottom: 26px; } - -.field.clickable { - cursor: pointer; -} -.field.clickable:hover { - --fc: rgba(200,170,50,0.18) !important; -} -.field.selected { - /* natural triangle color; tab is the indicator */ -} - -/* ── Tab indicators: small markers at the field's wide base ──────── */ -/* Bot-row: tabs hang below; top-row: tabs hang above. */ -/* The tab sits at ≈ -6px which lands on the board's wooden rail. */ - -.field.clickable::after, -.field.selected::after { - content: ''; - position: absolute; - left: 50%; - transform: translateX(-50%); - width: 22px; - height: 8px; - pointer-events: none; - z-index: 2; -} - -.bot-row .field.clickable::after, -.bot-row .field.selected::after { - bottom: -6px; - top: auto; - border-radius: 0 0 10px 10px; -} -.top-row .field.clickable::after, -.top-row .field.selected::after { - top: -6px; - bottom: auto; - border-radius: 10px 10px 0 0; -} - -/* Possible origin: hollow gold outline */ -.field.clickable:not(.dest):not(.selected)::after { - background: rgba(210,170,30,0.15); - border: 1.5px solid rgba(210,170,30,0.75); - box-shadow: 0 0 4px rgba(210,170,30,0.3); -} - -/* Selected origin: filled amber, breathing glow */ -.field.selected::after { - background: linear-gradient(to bottom, #e8b020, #c07808); - border: 1px solid rgba(255,225,65,0.55); - animation: tab-pulse 1.2s ease-in-out infinite; -} - -@keyframes tab-pulse { - 0%, 100% { box-shadow: 0 0 5px rgba(220,155,15,0.55), 0 0 2px rgba(255,220,50,0.3); } - 50% { box-shadow: 0 0 13px rgba(240,178,22,0.88), 0 0 6px rgba(255,230,60,0.6); } -} - -/* Valid destination: soft ivory/pearl */ -.field.clickable.dest:not(.selected)::after { - background: rgba(240,230,205,0.88); - border: 1.5px solid rgba(190,165,105,0.65); - box-shadow: 0 0 3px rgba(190,165,105,0.2); -} -.field.clickable.dest:not(.selected):hover::after { - background: rgba(228,210,162,0.95); - border-color: rgba(210,175,40,0.72); - box-shadow: 0 0 7px rgba(210,175,40,0.42); -} - -.field-num { - font-size: 0.58rem; - color: rgba(0,0,0,0.28); - position: absolute; - bottom: 3px; - line-height: 1; - font-variant-numeric: tabular-nums; -} - -.board-quarter .field.zone-petit:nth-child(odd) .field-num, -.board-quarter .field.zone-grand:nth-child(odd) .field-num, -.board-quarter .field.zone-retour:nth-child(odd) .field-num, -.board-quarter .field.zone-opponent:nth-child(odd) .field-num { - color: rgba(240,215,190,0.38); -} - -.field.corner .field-num { color: rgba(255,248,200,0.4); } -.top-row .field-num { bottom: auto; top: 3px; } - -/* ── Checkers ───────────────────────────────────────────────────────── */ -.checker-stack { - display: flex; - flex-direction: column; - align-items: center; -} - -.checker { - width: 40px; - height: 40px; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - font-size: 0.78rem; - font-weight: 600; - flex-shrink: 0; -} - -.checker + .checker { margin-top: -4px; } - -.checker.white { - background-image: - radial-gradient(ellipse 50% 35% at 36% 30%, - rgba(255,255,255,0.65) 0%, transparent 100%), - radial-gradient(circle, - transparent 68%, rgba(160,130,70,0.22) 68.5%, - rgba(160,130,70,0.22) 71.5%, transparent 72%), - radial-gradient(circle, - transparent 43%, rgba(160,130,70,0.17) 43.5%, - rgba(160,130,70,0.17) 46.5%, transparent 47%), - radial-gradient(circle at 38% 32%, - #ffffff 0%, var(--checker-white) 52%, #c0b288 100%); - border: 1.8px solid var(--checker-ring); - box-shadow: - 0 2px 6px rgba(0,0,0,0.4), - inset 0 -1px 3px rgba(0,0,0,0.15); - color: #443322; -} - -.checker.black { - background-image: - radial-gradient(ellipse 40% 28% at 36% 30%, - rgba(110,65,30,0.38) 0%, transparent 100%), - radial-gradient(circle, - transparent 68%, rgba(200,164,72,0.18) 68.5%, - rgba(200,164,72,0.18) 71.5%, transparent 72%), - radial-gradient(circle, - transparent 43%, rgba(200,164,72,0.13) 43.5%, - rgba(200,164,72,0.13) 46.5%, transparent 47%), - radial-gradient(circle at 38% 32%, - #4a2e1a 0%, #1c1008 45%, var(--checker-black) 100%); - border: 1.8px solid var(--checker-ring); - box-shadow: - 0 2px 6px rgba(0,0,0,0.55), - inset 0 -1px 3px rgba(0,0,0,0.4); - color: #c8b898; -} - -/* ── Hole toast (§6a) ───────────────────────────────────────────────── */ -@keyframes toast-rise { - from { transform: translate(-50%, -40%); opacity: 0; } - to { transform: translate(-50%, -50%); opacity: 1; } -} -@keyframes toast-fade { - from { opacity: 1; } - to { opacity: 0; pointer-events: none; } -} - -.hole-toast { - position: fixed; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - background: rgba(22,10,2,0.93); - border: 2px solid var(--ui-gold); - border-radius: 8px; - padding: 1.5rem 3.5rem; - text-align: center; - z-index: 50; - pointer-events: none; - box-shadow: - 0 12px 40px rgba(0,0,0,0.65), - 0 0 0 1px rgba(200,164,72,0.25), - inset 0 1px 0 rgba(200,164,72,0.1); - animation: - toast-rise 0.25s cubic-bezier(0.22, 0.61, 0.36, 1), - toast-fade 0.5s ease-in 1.4s forwards; -} - -.hole-toast-title { - font-family: var(--font-display); - font-size: 3.25rem; - font-weight: 600; - color: var(--ui-gold); - letter-spacing: 0.1em; - line-height: 1; -} - -.hole-toast-count { - font-family: var(--font-display); - font-size: 1.1rem; - color: rgba(200,164,72,0.68); - margin-top: 0.35rem; - letter-spacing: 0.06em; -} - -.hole-toast-bredouille { - font-family: var(--font-ui); - font-size: 0.75rem; - letter-spacing: 0.08em; - color: rgba(200,164,72,0.55); - margin-top: 0.4rem; - text-transform: uppercase; -} - -@keyframes bredouille-shimmer { - 0%, 100% { box-shadow: 0 12px 40px rgba(0,0,0,0.65), 0 0 0 2px rgba(200,164,72,0.4), inset 0 0 0 rgba(200,164,72,0); } - 50% { box-shadow: 0 12px 40px rgba(0,0,0,0.65), 0 0 0 4px rgba(200,164,72,0.7), inset 0 0 24px rgba(200,164,72,0.08); } -} -.hole-toast.hole-toast-bredouille { - border-width: 2.5px; - border-color: var(--ui-gold); - padding: 2rem 4rem; - animation: - toast-rise 0.3s cubic-bezier(0.22, 0.61, 0.36, 1), - bredouille-shimmer 0.9s ease-in-out 0.3s 2, - toast-fade 0.5s ease-in 2.2s forwards; -} -.hole-toast.hole-toast-bredouille .hole-toast-title { font-size: 3.75rem; } -.hole-toast.hole-toast-bredouille .hole-toast-bredouille { - font-size: 0.85rem; - color: rgba(200,164,72,0.8); - letter-spacing: 0.14em; -} - -/* ── Checker slide animation (§4a) ─────────────────────────────────── */ -@keyframes checker-slide-in { - from { transform: translate(var(--slide-dx, 0px), var(--slide-dy, 0px)); } - to { transform: none; } -} -.checker.arriving { - animation: checker-slide-in 0.28s cubic-bezier(0.25, 0.46, 0.45, 0.94); -} -.field:has(.checker.arriving) { - isolation: auto; - z-index: 10; - position: relative; -} - -/* ── Checker lift on selected field (§4b) ───────────────────────────── */ -.field.selected .checker-stack { - transform: translateY(-5px); - filter: drop-shadow(0 8px 12px rgba(0,0,0,0.6)); - transition: transform 0.12s ease-out, filter 0.12s ease-out; -} - -/* ── Action buttons below board (§10c) ──────────────────────────────── */ -.board-actions { - display: flex; - gap: 0.55rem; - justify-content: center; - align-items: center; - flex-wrap: wrap; - min-height: 2rem; -} - -/* ── Free-play mode ─────────────────────────────────────────────────────── */ -.free-mode-toggle { - display: flex; - align-items: center; - gap: 0.4rem; - font-family: var(--font-ui); - font-size: 0.78rem; - color: #887766; - cursor: pointer; - user-select: none; - padding-top: 0.1rem; -} -.free-mode-toggle input[type="checkbox"] { - accent-color: var(--ui-gold); - cursor: pointer; - width: 0.85rem; - height: 0.85rem; -} -.free-mode-help { - display: inline-flex; - align-items: center; - justify-content: center; - width: 1rem; - height: 1rem; - border-radius: 50%; - border: 1px solid #a89880; - font-size: 0.65rem; - font-style: normal; - color: #a89880; - cursor: help; - flex-shrink: 0; -} - -.free-mode-error { - display: flex; - align-items: center; - gap: 0.75rem; - background: rgba(180, 60, 30, 0.12); - border: 1px solid rgba(180, 60, 30, 0.4); - border-radius: 4px; - padding: 0.4rem 0.75rem; - width: 100%; - box-sizing: border-box; -} -.free-mode-error-msg { - flex: 1; - font-family: var(--font-ui); - font-size: 0.85rem; - color: #8b2000; - font-style: italic; -} - -/* ── Pre-game ceremony overlay ──────────────────────────────────────────── */ -.ceremony-overlay { - position: fixed; - inset: 0; - background: rgba(0,0,0,0.65); - display: flex; - align-items: center; - justify-content: center; - z-index: 100; -} - -.ceremony-box { - background: var(--ui-parchment); - border-radius: 8px; - padding: 2.5rem 3rem; - text-align: center; - box-shadow: 0 12px 40px rgba(0,0,0,0.5), 0 0 0 2px var(--ui-gold-dark); - display: flex; - flex-direction: column; - align-items: center; - gap: 1.4rem; - min-width: 300px; - animation: game-over-appear 0.4s cubic-bezier(0.22, 0.61, 0.36, 1); -} - -.ceremony-box h2 { - font-family: var(--font-display); - font-size: 1.8rem; - font-weight: 600; - color: var(--ui-ink); - letter-spacing: 0.06em; -} - -.ceremony-dice { - display: flex; - gap: 3rem; - align-items: flex-end; -} - -.ceremony-die-slot { - display: flex; - flex-direction: column; - align-items: center; - gap: 0.5rem; -} - -.ceremony-die-label { - font-family: var(--font-ui); - font-size: 0.85rem; - color: var(--ui-ink); - font-weight: 500; -} - -.ceremony-tie { - font-family: var(--font-display); - font-size: 1rem; - color: var(--ui-red-accent); - font-style: italic; -} - -.ceremony-result { - font-family: var(--font-display); - font-size: 1.15rem; - font-weight: 600; - color: var(--ui-gold-dark); - letter-spacing: 0.04em; -} - -/* ── Nickname modal (anonymous player name chooser) ─────────────────── */ -.nickname-backdrop { - position: fixed; - inset: 0; - background: rgba(0,0,0,0.6); - display: flex; - align-items: center; - justify-content: center; - z-index: 300; -} - -.nickname-modal { - background: var(--ui-parchment); - border-radius: 8px; - padding: 2rem 2rem 1.75rem; - width: min(360px, 90vw); - display: flex; - flex-direction: column; - gap: 1rem; - box-shadow: - 0 20px 60px rgba(0,0,0,0.55), - 0 0 0 1px rgba(200,164,72,0.35), - 0 0 0 5px rgba(42,21,8,0.9), - 0 0 0 6px rgba(200,164,72,0.2); - animation: game-over-appear 0.25s cubic-bezier(0.22, 0.61, 0.36, 1); -} - -.nickname-modal-title { - font-family: var(--font-display); - font-size: 1.5rem; - font-weight: 600; - color: var(--ui-ink); - text-align: center; - letter-spacing: 0.04em; -} - -.nickname-modal-hint { - font-family: var(--font-ui); - font-size: 0.8rem; - color: rgba(42,26,8,0.6); - text-align: center; - margin-bottom: -0.25rem; -} - -.nickname-modal-alt { - text-align: center; - font-size: 0.8rem; - color: rgba(42,26,8,0.55); - padding-top: 0.5rem; - border-top: 1px solid rgba(138,106,40,0.2); -} - -.nickname-modal-alt a { - color: var(--ui-gold-dark); - text-decoration: none; - font-weight: 500; -} - -.nickname-modal-alt a:hover { text-decoration: underline; } - -/* ── Game hamburger button (☰ → ✕ animation) ────────────────────────── */ -.game-hamburger { - position: fixed; - top: 0.6rem; - left: 0.6rem; - z-index: 251; - width: 36px; - height: 36px; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 5px; - background: var(--board-rail); - border: 1px solid rgba(200,164,72,0.35); - border-radius: 5px; - cursor: pointer; - transition: background 0.15s, border-color 0.15s; -} -.game-hamburger:hover { - background: #3d1f0a; - border-color: rgba(200,164,72,0.65); -} - -.hb-bar { - display: block; - width: 16px; - height: 2px; - background: var(--ui-parchment); - border-radius: 1px; - transition: transform 0.25s cubic-bezier(0.22, 0.61, 0.36, 1), opacity 0.2s; - transform-origin: center; -} -/* Top bar rotates down to form \ */ -.game-hamburger-open .hb-top { transform: translateY(7px) rotate(45deg); } -/* Middle bar fades out */ -.game-hamburger-open .hb-mid { opacity: 0; transform: scaleX(0); } -/* Bottom bar rotates up to form / */ -.game-hamburger-open .hb-bot { transform: translateY(-7px) rotate(-45deg); } - -/* ── Game sidebar ────────────────────────────────────────────────────── */ -.game-sidebar { - position: fixed; - top: 0; - left: 0; - height: 100vh; - width: 280px; - z-index: 250; - background: var(--board-rail); - border-right: 1px solid rgba(200,164,72,0.25); - display: flex; - flex-direction: column; - transform: translateX(-100%); - transition: transform 0.25s cubic-bezier(0.22, 0.61, 0.36, 1); - overflow-y: auto; -} -.game-sidebar-open { - transform: translateX(0); -} - -.game-sidebar-header { - display: flex; - align-items: center; - justify-content: space-between; - padding: 1rem 1rem; - border-bottom: 1px solid rgba(200,164,72,0.2); - flex-shrink: 0; -} - -.game-sidebar-brand { - font-family: var(--font-display); - font-size: 1.3rem; - font-weight: 600; - color: var(--ui-gold); - letter-spacing: 0.06em; - margin-left: 45px; -} - -.game-sidebar-close { - width: 28px; - height: 28px; - display: flex; - align-items: center; - justify-content: center; - background: transparent; - border: 1px solid rgba(200,164,72,0.25); - border-radius: 4px; - color: var(--ui-parchment); - font-size: 0.85rem; - cursor: pointer; - opacity: 0.65; - transition: opacity 0.15s; -} -.game-sidebar-close:hover { opacity: 1; } - -.game-sidebar-section { - padding: 0.9rem 1rem; - border-bottom: 1px solid rgba(200,164,72,0.12); - display: flex; - flex-direction: row; - gap: 0.55rem; -} - -.game-sidebar-label { - font-size: 0.7rem; - font-family: var(--font-ui); - letter-spacing: 0.07em; - text-transform: uppercase; - color: rgba(242,232,208,0.45); -} - -.game-sidebar-link { - font-family: var(--font-ui); - font-size: 0.85rem; - color: var(--ui-parchment); - text-decoration: none; - opacity: 0.8; - transition: opacity 0.15s; - cursor: pointer; -} -.game-sidebar-link:hover { opacity: 1; text-decoration: underline; text-underline-offset: 2px; } - -.game-sidebar-btn { - font-family: var(--font-ui); - font-size: 0.82rem; - padding: 0.4rem 0.75rem; - border: 1px solid rgba(200,164,72,0.35); - border-radius: 4px; - background: rgba(200,164,72,0.1); - color: var(--ui-parchment); - cursor: pointer; - text-align: left; - transition: background 0.15s; -} -.game-sidebar-btn:hover { background: rgba(200,164,72,0.22); } - -.game-sidebar-btn-newgame { - background: rgba(58,107,42,0.25); - border-color: rgba(58,107,42,0.55); - font-weight: 500; -} -.game-sidebar-btn-newgame:hover { background: rgba(58,107,42,0.42); } - -.game-sidebar-qr { - width: 100%; - height: auto; - aspect-ratio: 1; - max-width: 200px; - margin: 0 auto; -} - -/* Push the version wrapper to the bottom of the sidebar flex column */ -.sidebar-footer { - margin-top: auto; - border-top: 1px solid rgba(200,164,72,0.12); -} - -.site-nav-infolinks { - margin: 2em 0 1em; - text-align: center; - font-size: 0.9rem; - color: rgba(200,164,72,0.4); - display: flex; - flex-direction: row; - align-items: center; -} - -.site-nav-infolinks > a { - width: 100%; -} - -.site-nav-version { - margin: 2em 0 1em; - display: block; - text-align: center; - font-family: var(--font-ui); - font-size: 0.7rem; - letter-spacing: 0.06em; - color: rgba(200,164,72,0.4); -} - -/* ── Content pages (markdown-rendered) ─────────────────────────────────────── */ - -.content-page h1 { - font-family: var(--font-display); - font-size: 2rem; - font-weight: 600; - color: var(--ui-ink); - letter-spacing: 0.04em; - margin-bottom: 0.5rem; -} -.content-page h2 { - font-family: var(--font-display); - font-size: 1.4rem; - font-weight: 600; - color: var(--ui-ink); - margin: 1.75rem 0 0.5rem; - border-bottom: 1px solid rgba(200,164,72,0.25); - padding-bottom: 0.25rem; -} -.content-page h3 { - font-family: var(--font-display); - font-size: 1.1rem; - font-weight: 600; - color: var(--ui-ink); - margin: 1.25rem 0 0.4rem; -} -.content-page p { - line-height: 1.7; - margin-bottom: 0.9rem; - color: var(--ui-ink); -} -.content-page ul, -.content-page ol { - margin: 0.5rem 0 1rem 1.5rem; - line-height: 1.7; - color: var(--ui-ink); -} -.content-page li { - margin-bottom: 0.25rem; -} -.content-page a { - color: var(--ui-gold-dark); - text-decoration: underline; -} -.content-page a:hover { - color: var(--ui-ink); -} -.content-page code { - font-family: monospace; - background: rgba(0,0,0,0.07); - border-radius: 3px; - padding: 0.1em 0.35em; - font-size: 0.88em; -} -.content-page pre { - background: rgba(0,0,0,0.07); - border-radius: 5px; - padding: 1rem 1.25rem; - overflow-x: auto; - margin-bottom: 1rem; -} -.content-page pre code { - background: none; - padding: 0; -} -.content-page blockquote { - border-left: 3px solid rgba(200,164,72,0.5); - margin: 0.75rem 0; - padding: 0.25rem 1rem; - color: #665544; - font-style: italic; -} -.content-page table { - border-collapse: collapse; - width: 100%; - margin-bottom: 1rem; -} -.content-page th, -.content-page td { - border: 1px solid rgba(200,164,72,0.3); - padding: 0.4rem 0.75rem; - text-align: left; -} -.content-page th { - background: rgba(200,164,72,0.1); - font-weight: 600; -} - -/* ══════════════════════════════════════════════════════════════════════ - Layout variation 07 — scrolling strip + sidebar controls - ══════════════════════════════════════════════════════════════════════ */ - -/* Prevent horizontal scrollbar from the full-bleed strip */ -.game-overlay { overflow-x: hidden !important; } - -/* Board bar: hide die slots, keep the rail as a thin divider */ -.bar-die-slot { display: none !important; } -.board-bar { width: 5px; overflow: hidden; } - -/* ── Full-width in-flow player strip ─────────────────────────────────── */ -.v07-strip { - width: 100vw; - margin-top: -1.5rem; /* undo game-overlay top padding */ - margin-left: calc(50% - 50vw); /* align to viewport left */ - display: flex; - align-items: center; - background: var(--ui-parchment); - border-bottom: 2px solid var(--ui-gold-dark); - box-shadow: 0 2px 8px rgba(0,0,0,0.18); - padding: 0.35rem 1.5rem; - gap: 0.5rem; -} - -.v07-player { display: flex; align-items: center; flex: 1; min-width: 0; } -.v07-player-left { justify-content: flex-end; } -.v07-player-right { justify-content: flex-start; } - -.v07-active-zone { - display: flex; - align-items: center; - gap: 0.7rem; - border-radius: 8px; - padding: 0.28rem 0.5rem; - transition: background 0.15s; -} -.v07-active-zone.active { background: rgba(58,42,10,0.08); } - -/* Checker-style circles */ -.v07-avatar { - width: 38px; height: 38px; - border-radius: 50%; - flex-shrink: 0; -} -.v07-avatar-me { - background-image: - radial-gradient(ellipse 50% 35% at 36% 30%, rgba(255,255,255,0.65) 0%, transparent 100%), - radial-gradient(circle, transparent 68%, rgba(160,130,70,0.22) 68.5%, rgba(160,130,70,0.22) 71.5%, transparent 72%), - radial-gradient(circle, transparent 43%, rgba(160,130,70,0.17) 43.5%, rgba(160,130,70,0.17) 46.5%, transparent 47%), - radial-gradient(circle at 38% 32%, #ffffff 0%, var(--checker-white) 52%, #c0b288 100%); - border: 1.8px solid var(--checker-ring); - box-shadow: 0 2px 6px rgba(0,0,0,0.35), inset 0 -1px 3px rgba(0,0,0,0.15); -} -.v07-avatar-opp { - background-image: - radial-gradient(ellipse 40% 28% at 36% 30%, rgba(110,65,30,0.38) 0%, transparent 100%), - radial-gradient(circle, transparent 68%, rgba(200,164,72,0.18) 68.5%, rgba(200,164,72,0.18) 71.5%, transparent 72%), - radial-gradient(circle, transparent 43%, rgba(200,164,72,0.13) 43.5%, rgba(200,164,72,0.13) 46.5%, transparent 47%), - radial-gradient(circle at 38% 32%, #4a2e1a 0%, #1c1008 45%, var(--checker-black) 100%); - border: 1.8px solid var(--checker-ring); - box-shadow: 0 2px 6px rgba(0,0,0,0.5), inset 0 -1px 3px rgba(0,0,0,0.4); -} - -/* Strip peg overrides */ -.v07-strip .peg-track { gap: 3px; } -.v07-strip .peg-hole { width: 12px; height: 12px; } -.v07-strip .peg-hole.filled { - background: #5aab38; border-color: #3a7828; - box-shadow: 0 0 5px rgba(90,171,56,0.55); -} -.v07-strip .peg-hole.peg-opp.filled { - background: #c05030; border-color: #8a3018; - box-shadow: 0 0 5px rgba(192,80,48,0.55); -} - -/* Strip score-row-name: remove fixed width from v01 */ -.v07-strip .score-row-name { width: auto; } - -/* No ghost bar below pts-counter in the strip */ -.v07-strip .pts-counter-wrap { padding-bottom: 0; } - -/* Center "Trictrac" title */ -.v07-strip-center { - flex-shrink: 0; - padding: 0 1rem; - border-left: 1px solid rgba(138,106,40,0.2); - border-right: 1px solid rgba(138,106,40,0.2); -} -.v07-title { - font-family: var(--font-display); - font-size: 1.4rem; - font-weight: 600; - font-style: italic; - color: var(--ui-ink); - letter-spacing: 0.03em; - white-space: nowrap; -} - -/* ── Body: board + controls ──────────────────────────────────────────── */ -.v07-body { - display: flex; - align-items: flex-start; - gap: 0.5rem; - width: 100%; -} - -/* ── Controls column (sidebar on wide, row on narrow) ────────────────── */ -.v07-controls { - display: flex; - flex-direction: column; - gap: 0.5rem; - width: 152px; - flex-shrink: 0; - align-self: stretch; -} - -.v07-ctrl-dice { - background: var(--board-rail); - border-radius: 5px; - border-top: 2px solid var(--ui-gold-dark); - box-shadow: 0 2px 6px rgba(0,0,0,0.2); - padding: 0.6rem 0.75rem 0.75rem; - display: flex; - flex-direction: column; - align-items: center; - gap: 0.5rem; - flex-shrink: 0; -} -.v07-ctrl-dice-row { - display: flex; - gap: 0.55rem; - align-items: center; - justify-content: center; -} - -/* Free-mode toggle: light text on dark board-rail background */ -.v07-ctrl-dice .free-mode-toggle { - color: var(--ui-parchment); - font-size: 0.7rem; - flex-wrap: wrap; - justify-content: center; - text-align: center; - gap: 0.3rem; -} -.v07-ctrl-dice .free-mode-help { - border-color: rgba(242,232,208,0.35); - color: rgba(242,232,208,0.5); -} - -.v07-ctrl-status { - background: var(--ui-parchment); - border-radius: 5px; - border-top: 2px solid var(--ui-gold-dark); - box-shadow: 0 2px 6px rgba(0,0,0,0.2); - padding: 0.65rem 0.75rem 0.75rem; - display: flex; - flex-direction: column; - align-items: center; - gap: 0.4rem; - flex: 1; - min-width: 0; -} -.v07-ctrl-status .game-status { - color: var(--ui-ink); - text-shadow: none; - font-size: 1rem; - padding: 0; - width: auto; - text-align: center; - line-height: 1.3; -} -.v07-ctrl-status .board-actions { - flex-wrap: wrap; - justify-content: center; - min-height: 0; -} -.v07-ctrl-status .game-sub-prompt { - color: #887766; - padding: 0; - width: auto; - text-align: center; - font-size: 0.67rem; - line-height: 1.4; - margin: 0; -} - -/* ── Scoring panels row (below board+controls, in-flow) ──────────────── */ -.v07-scoring-row { width: 100%; } - -/* Reset absolute positioning from the old score-area context */ -.v07-scoring-row .scoring-panels-container { - position: static; - top: auto; left: auto; right: auto; bottom: auto; - z-index: auto; - padding: 0; - pointer-events: auto; - display: flex; - flex-direction: column; - align-items: stretch; - gap: 4px; -} -.v07-scoring-row .scoring-panel { - width: 100%; - box-sizing: border-box; - margin: 0; -} - -/* ── Responsive: ≤919px → controls becomes a bottom bar ─────────────── */ -@media (max-width: 919px) { - .v07-body { - flex-direction: column; - align-items: stretch; - } - .v07-controls { - flex-direction: row; - width: 100%; - } - .v07-ctrl-status { flex: 1; } - /* Hide pegs on small screens to save space in the strip */ - .v07-strip .peg-track { display: none; } -} diff --git a/doc/design/variations/01-dice-sidebar.html b/doc/design/variations/01-dice-sidebar.html deleted file mode 100644 index 7d0222d..0000000 --- a/doc/design/variations/01-dice-sidebar.html +++ /dev/null @@ -1,418 +0,0 @@ - - - - - - Variation 01 — Dés en sidebar droite - - - - - - - - -
-
- Trictrac -
- - -
-
- -
- - Se connecter -
- -
- - -
-
- -
-
- - -
-
- - -
-
-
-
- Anonyme - (vous) -
-
-
-
-
-
- 6 - /12 -
-
-
-
-
-
-
-
-
-
-
-
-
-
- Bot -
-
-
-
-
-
- 2 - /12 -
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
+4 pts
- -
-
- Battage à vrai (petit jan) - simple - ×1 - +4 -
-
-
-
-
- - -
- - -
- -
- - -
- -
-
- 13 -
-
- 14 -
-
- 15 -
-
-
-
-
-
-
- 16 -
-
- 17 -
-
- 18 -
-
- - -
- -
-
- 19 -
-
-
-
-
-
20
-
21
-
22
-
23
-
- 24 -
-
-
-
-
11
-
-
-
- -
- -
- - -
- -
-
- 12 -
-
11
-
10
-
- 9 -
-
-
- 8 -
-
-
-
-
-
- 7 -
-
-
-
-
-
- - -
- -
-
6
-
5
-
4
-
3
-
2
-
- 1 -
-
10
-
-
-
-
-
-
- -
- - - -
- -
- - -
- - - - - - - -
- -
- -
-
- - - diff --git a/doc/design/variations/02-horizontal-header.html b/doc/design/variations/02-horizontal-header.html deleted file mode 100644 index 09502a5..0000000 --- a/doc/design/variations/02-horizontal-header.html +++ /dev/null @@ -1,628 +0,0 @@ - - - - - - Variation 02 — En-tête horizontal · Scoring latéral - - - - - - - - -
-
- Trictrac -
- - -
-
- -
- - Se connecter -
- -
- - -
-
- -
-
- - -
-
- - -
- - -
-
- Anonyme - (vous) -
-
-
-
-
-
-
-
-
-
- -
- 6 - /12 -
-
- - -
- VS - jeu en 12 trous - à vous de jouer -
- - -
-
- Bot -
-
-
-
-
-
-
-
-
-
-
- 2 - /12 -
-
- -
- - -
- - -
-
- -
-
-
+4 pts
- -
-
- Battage à vrai (petit jan) - simple - ×1 - +4 -
-
-
- -
Aucun événement de marque
-
- - -
-
- - -
-
-
13
-
14
-
- 15 -
-
-
-
-
16
-
17
-
18
-
-
-
-
- 19 -
-
-
-
20
-
21
-
22
-
23
-
- 24 -
-
-
11
-
-
-
-
- -
- - -
-
-
- 12 -
-
11
-
10
-
- 9 -
-
-
- 8 -
-
-
- 7 -
-
-
-
-
-
6
-
5
-
4
-
3
-
2
-
- 1 -
-
10
-
-
-
-
-
- - - -
-
- - -
- - - - - -
- -
- - -
-
- - - - - - - - - - - - - - - - -
-
-
Déplacez une dame (1 sur 2)
-
-
-
- -
-
- - - diff --git a/doc/design/variations/03-dark-modern.html b/doc/design/variations/03-dark-modern.html deleted file mode 100644 index ce480c0..0000000 --- a/doc/design/variations/03-dark-modern.html +++ /dev/null @@ -1,655 +0,0 @@ - - - - - - Variation 03 — Dark Modern · Dock de contrôle - - - - - - - - -
-
- Trictrac -
- - -
-
- -
- - Se connecter -
- -
- - -
-
- -
-
- - -
-
- - -
- - -
-
A
-
- Anonyme - (vous) -
-
-
-
-
-
-
-
-
-
- -
- 6 - /12 -
-
- - -
- VS - jeu en 12 trous - votre tour -
- - -
-
B
-
- Bot -
-
-
-
-
-
-
-
-
-
-
- 2 - /12 -
-
- -
- - -
- - -
-
- -
-
-
+4 pts
- -
-
- Battage à vrai (petit jan) - simple - ×1 - +4 -
-
-
-
- - -
-
- - -
-
-
13
-
14
-
- 15 -
-
-
-
-
16
-
17
-
18
-
-
-
-
- 19 -
-
-
-
20
-
21
-
22
-
23
-
- 24 -
-
-
11
-
-
-
-
- -
- - -
-
-
- 12 -
-
11
-
10
-
- 9 -
-
-
- 8 -
-
-
- 7 -
-
-
-
-
-
6
-
5
-
4
-
3
-
2
-
- 1 -
-
10
-
-
-
-
-
- - - -
-
- -
- - -
- - -
- - - - - - - - - - - - - - - - - - -
- - -
-
Déplacez une dame (1 sur 2)
-
- -
-
- - -
- -
- -
- -
-
- - - diff --git a/doc/design/variations/04-warm-bottom-scoring.html b/doc/design/variations/04-warm-bottom-scoring.html deleted file mode 100644 index d92a2d3..0000000 --- a/doc/design/variations/04-warm-bottom-scoring.html +++ /dev/null @@ -1,620 +0,0 @@ - - - - - - Variation 04 — Warm Classic · Scoring en bas - - - - - - - - -
-
- Trictrac -
- - -
-
- -
- - Se connecter -
- -
- - -
-
- -
-
- - -
-
- - -
- - -
-
A
-
- Anonyme - (vous) -
-
-
-
-
-
-
-
-
-
- -
- 6 - /12 -
-
- - -
- VS - jeu en 12 trous - votre tour -
- - -
-
B
-
- Bot -
-
-
-
-
-
-
-
-
-
-
- 2 - /12 -
-
- -
- - -
-
- - -
-
-
13
-
14
-
- 15 -
-
-
-
-
16
-
17
-
18
-
-
-
-
- 19 -
-
-
-
20
-
21
-
22
-
23
-
- 24 -
-
-
11
-
-
-
-
- -
- - -
-
-
- 12 -
-
11
-
10
-
- 9 -
-
-
- 8 -
-
-
- 7 -
-
-
-
-
-
6
-
5
-
4
-
3
-
2
-
- 1 -
-
10
-
-
-
-
-
- - - -
-
- - -
- - -
-
- -
-
-
+4 pts
- -
-
- Battage à vrai (petit jan) - simple - ×1 - +4 -
-
-
-
- - - - -
- - -
- - -
- - - - - - - - - - - - - - - - - - -
- - -
-
Déplacez une dame (1 sur 2)
-
- -
-
- - -
- -
- -
- -
-
- - - diff --git a/doc/design/variations/05-warm-sticky-header.html b/doc/design/variations/05-warm-sticky-header.html deleted file mode 100644 index 6d661a7..0000000 --- a/doc/design/variations/05-warm-sticky-header.html +++ /dev/null @@ -1,582 +0,0 @@ - - - - - - Variation 05 — Warm · Sticky header · Scoring below dock - - - - - - - - -
-
- Trictrac -
- - -
-
- -
- - Se connecter -
- -
- - -
-
- -
-
- - -
-
- - -
- - -
-
- 6 - /12 -
- -
-
-
-
-
-
-
-
-
-
- Anonyme - (vous) -
-
A
-
- - -
- VS - jeu en 12 trous - votre tour -
- - -
-
B
-
- Bot -
-
-
-
-
-
-
-
-
-
-
- 2 - /12 -
-
- -
- - -
-
- - -
-
-
13
-
14
-
- 15 -
-
-
-
-
16
-
17
-
18
-
-
-
-
- 19 -
-
-
-
20
-
21
-
22
-
23
-
- 24 -
-
-
11
-
-
-
-
- -
- - -
-
-
- 12 -
-
11
-
10
-
- 9 -
-
-
- 8 -
-
-
- 7 -
-
-
-
-
-
6
-
5
-
4
-
3
-
2
-
- 1 -
-
10
-
-
-
-
-
- - - -
-
- - -
- - -
-
- - - - - - - - - - - - - - - - - - -
- -
- - -
-
Déplacez une dame (1 sur 2)
-
- -
-
- -
- - -
-
-
- -
-
-
+4 pts
- -
-
- Battage à vrai (petit jan) - simple - ×1 - +4 -
-
-
-
-
- -
-
- - - diff --git a/doc/design/variations/06-wide-header.html b/doc/design/variations/06-wide-header.html deleted file mode 100644 index 9a9ed8f..0000000 --- a/doc/design/variations/06-wide-header.html +++ /dev/null @@ -1,571 +0,0 @@ - - - - - - Variation 06 — Wide fixed header · Scoring below dock - - - - - - - - -
-
- Trictrac -
- - -
-
- -
- - Se connecter -
- -
- - -
-
- -
-
- - -
-
- - -
- - -
- - -
A
- - -
- Anonyme - (vous) -
- - -
-
-
-
-
-
-
-
-
-
-
-
-
-
- - -
-
-
-
-
- 6 - /12 -
-
- -
- - -
- VS - jeu en 12 trous - votre tour -
- - -
- - -
-
-
-
-
- 2 - /12 -
-
- - -
-
-
-
-
-
-
-
-
-
-
-
-
-
- - -
- Bot -
- - -
B
- -
- -
- - -
-
- - -
-
-
13
-
14
-
- 15 -
-
-
-
-
16
-
17
-
18
-
-
-
-
- 19 -
-
-
-
20
-
21
-
22
-
23
-
- 24 -
-
-
11
-
-
-
-
- -
- - -
-
-
- 12 -
-
11
-
10
-
- 9 -
-
-
- 8 -
-
-
- 7 -
-
-
-
-
-
6
-
5
-
4
-
3
-
2
-
- 1 -
-
10
-
-
-
-
-
- - - -
-
- - -
- - -
-
- - - - - - - - - - - - - - - - -
- -
- - -
-
Déplacez une dame (1 sur 2)
-
- -
-
- -
- - -
-
-
- -
-
-
+4 pts
- -
-
- Battage à vrai (petit jan) - simple - ×1 - +4 -
-
-
-
-
- -
-
- - - diff --git a/doc/design/variations/07-scrolling-header.html b/doc/design/variations/07-scrolling-header.html deleted file mode 100644 index 122e772..0000000 --- a/doc/design/variations/07-scrolling-header.html +++ /dev/null @@ -1,711 +0,0 @@ - - - - - - Variation 07 — Scrolling header · Responsive sidebar/footer - - - - - - - - -
-
- Trictrac -
- - -
-
- -
- - Se connecter -
- -
- - -
-
- -
-
- - -
-
- - -
- - -
-
- -
A
- -
- Anonyme - (vous) -
- -
-
-
-
-
-
-
-
-
-
-
- -
-
- 6 - /12 -
-
-
-
- - -
- Trictrac -
- - -
-
- -
-
- 2 - /12 -
-
- -
-
-
-
-
-
-
-
-
- -
- Bot -
- -
B
-
-
- -
- - -
- - -
-
- - -
-
-
13
-
14
-
- 15 -
-
-
-
-
16
-
17
-
18
-
-
-
-
- 19 -
-
-
-
20
-
21
-
22
-
23
-
- 24 -
-
-
11
-
-
-
-
- -
- - -
-
-
- 12 -
-
11
-
10
-
- 9 -
-
-
- 8 -
-
-
- 7 -
-
-
-
-
-
6
-
5
-
4
-
3
-
2
-
- 1 -
-
10
-
-
-
-
-
- - - -
-
- - -
- -
-
- - - - - - - - - - - - - - - - -
- -
- -
-
Déplacez une dame (1 sur 2)
-
- -
-
- -
- -
- - -
- -
-
- - - - - - - - - - - - - - - - -
- -
- -
-
Déplacez une dame (1 sur 2)
-
- -
-
- -
- - -
-
-
- -
-
-
+4 pts
- -
-
- Battage à vrai (petit jan) - simple - ×1 - +4 -
-
-
-
-
- -
-
- - - diff --git a/doc/ui-mockup/inGame-moving.html b/doc/ui-mockup/inGame-moving.html new file mode 100644 index 0000000..5b47414 --- /dev/null +++ b/doc/ui-mockup/inGame-moving.html @@ -0,0 +1,11 @@ + + + + Trictrac + + + + + + +
Trictrac
Debug
Anonyme (vous)
6/12
B
Bot
0/12
jan de retour
13
14
15
16
17
18
19
20
21
22
23
24
8
12
11
10
9
8
7
6
5
4
3
2
1
8
grand jan
petit jan
Déplacez une dame (2 sur 2)

Cliquez un champ surligné pour déplacer

diff --git a/doc/ui-mockup/inGame-pointsForOpponent.html b/doc/ui-mockup/inGame-pointsForOpponent.html new file mode 100644 index 0000000..da85a24 --- /dev/null +++ b/doc/ui-mockup/inGame-pointsForOpponent.html @@ -0,0 +1,11 @@ + + + + Trictrac + + + + + + +
Trictrac
Debug
Anonyme (vous)
0/12
B
Bot
0/12
B
Adversaire +8 pts
Battage à vrai (grand jan)simple×4+8
Trou adverse ! 1/12
jan de retour
13
14
15
16
17
18
19
20
21
22
23
24
10
12
11
10
9
8
7
6
5
4
3
2
1
10
grand jan
petit jan
L'adversaire a lancé les dés

Cliquez Continuer quand vous êtes prêt

diff --git a/doc/ui-mockup/inGame-pointsForUser.html b/doc/ui-mockup/inGame-pointsForUser.html new file mode 100644 index 0000000..4b21963 --- /dev/null +++ b/doc/ui-mockup/inGame-pointsForUser.html @@ -0,0 +1,9 @@ + + + + Trictrac + + + + +
Trictrac
Debug
Anonyme (vous)
6/12
B
Bot
0/12
+6 pts
Battage à vrai (petit jan)simple×1+4
Battage à vrai (grand jan)simple×1+2
jan de retour
13
14
15
16
17
18
19
20
21
22
23
24
9
12
11
10
9
8
7
6
5
4
3
2
1
10
grand jan
petit jan
Déplacez une dame (1 sur 2)

Cliquez un champ surligné pour déplacer

diff --git a/doc/design/snapshots/2026-05-30-d4a2ea1c531827bfbc2410af20a00e349d606c87_files/style.css b/doc/ui-mockup/style.css similarity index 91% rename from doc/design/snapshots/2026-05-30-d4a2ea1c531827bfbc2410af20a00e349d606c87_files/style.css rename to doc/ui-mockup/style.css index 24df8c0..e81e0de 100644 --- a/doc/design/snapshots/2026-05-30-d4a2ea1c531827bfbc2410af20a00e349d606c87_files/style.css +++ b/doc/ui-mockup/style.css @@ -305,62 +305,6 @@ a:hover { text-decoration: underline; } .portal-error { color: var(--ui-red-accent); font-size: 0.875rem; margin-top: 0.5rem; } .portal-success { color: var(--ui-green-accent); font-size: 0.875rem; margin-top: 0.5rem; } -.flash-banner { - position: fixed; - top: 1.25rem; - left: 50%; - transform: translateX(-50%); - z-index: 500; - display: flex; - align-items: center; - gap: 1rem; - padding: 0.75rem 1.25rem; - background: var(--ui-green-accent); - color: #f5edd8; - border-radius: 6px; - box-shadow: 0 4px 16px rgba(0,0,0,0.35); - font-family: var(--font-ui); - font-size: 0.95rem; - max-width: 90vw; - animation: flash-in 0.2s ease; -} -@keyframes flash-in { - from { opacity: 0; transform: translateX(-50%) translateY(-0.5rem); } - to { opacity: 1; transform: translateX(-50%) translateY(0); } -} -.flash-dismiss { - background: none; - border: none; - color: inherit; - cursor: pointer; - font-size: 1rem; - opacity: 0.75; - padding: 0; - line-height: 1; -} -.flash-dismiss:hover { opacity: 1; } - -.portal-danger-zone { - border: 1px solid rgba(122, 30, 42, 0.4); - background: rgba(122, 30, 42, 0.04); -} -.portal-danger-zone h2 { - color: var(--ui-red-accent); -} -.portal-danger-btn { - padding: 0.5rem 1.25rem; - font-family: var(--font-ui); - font-size: 0.9rem; - background: var(--ui-red-accent); - color: #f5edd8; - border: none; - border-radius: 4px; - cursor: pointer; - transition: opacity 0.15s; -} -.portal-danger-btn:hover { opacity: 0.85; } -.portal-danger-btn:disabled { opacity: 0.45; cursor: not-allowed; } - .portal-link { color: var(--ui-gold); text-decoration: none; @@ -1855,58 +1799,6 @@ a:hover { text-decoration: underline; } min-height: 2rem; } -/* ── Free-play mode ─────────────────────────────────────────────────────── */ -.free-mode-toggle { - display: flex; - align-items: center; - gap: 0.4rem; - font-family: var(--font-ui); - font-size: 0.78rem; - color: #887766; - cursor: pointer; - user-select: none; - padding-top: 0.1rem; -} -.free-mode-toggle input[type="checkbox"] { - accent-color: var(--ui-gold); - cursor: pointer; - width: 0.85rem; - height: 0.85rem; -} -.free-mode-help { - display: inline-flex; - align-items: center; - justify-content: center; - width: 1rem; - height: 1rem; - border-radius: 50%; - border: 1px solid #a89880; - font-size: 0.65rem; - font-style: normal; - color: #a89880; - cursor: help; - flex-shrink: 0; -} - -.free-mode-error { - display: flex; - align-items: center; - gap: 0.75rem; - background: rgba(180, 60, 30, 0.12); - border: 1px solid rgba(180, 60, 30, 0.4); - border-radius: 4px; - padding: 0.4rem 0.75rem; - width: 100%; - box-sizing: border-box; -} -.free-mode-error-msg { - flex: 1; - font-family: var(--font-ui); - font-size: 0.85rem; - color: #8b2000; - font-style: italic; -} - /* ── Pre-game ceremony overlay ──────────────────────────────────────────── */ .ceremony-overlay { position: fixed; @@ -2153,7 +2045,6 @@ a:hover { text-decoration: underline; } text-decoration: none; opacity: 0.8; transition: opacity 0.15s; - cursor: pointer; } .game-sidebar-link:hover { opacity: 1; text-decoration: underline; text-underline-offset: 2px; } @@ -2185,121 +2076,3 @@ a:hover { text-decoration: underline; } max-width: 200px; margin: 0 auto; } - -/* Push the version wrapper to the bottom of the sidebar flex column */ -.sidebar-footer { - margin-top: auto; - border-top: 1px solid rgba(200,164,72,0.12); -} - -.site-nav-infolinks { - margin: 2em 0 1em; - text-align: center; - font-size: 0.9rem; - color: rgba(200,164,72,0.4); - display: flex; - flex-direction: row; - align-items: center; -} - -.site-nav-infolinks > a { - width: 100%; -} - -.site-nav-version { - margin: 2em 0 1em; - display: block; - text-align: center; - font-family: var(--font-ui); - font-size: 0.7rem; - letter-spacing: 0.06em; - color: rgba(200,164,72,0.4); -} - -/* ── Content pages (markdown-rendered) ─────────────────────────────────────── */ - -.content-page h1 { - font-family: var(--font-display); - font-size: 2rem; - font-weight: 600; - color: var(--ui-ink); - letter-spacing: 0.04em; - margin-bottom: 0.5rem; -} -.content-page h2 { - font-family: var(--font-display); - font-size: 1.4rem; - font-weight: 600; - color: var(--ui-ink); - margin: 1.75rem 0 0.5rem; - border-bottom: 1px solid rgba(200,164,72,0.25); - padding-bottom: 0.25rem; -} -.content-page h3 { - font-family: var(--font-display); - font-size: 1.1rem; - font-weight: 600; - color: var(--ui-ink); - margin: 1.25rem 0 0.4rem; -} -.content-page p { - line-height: 1.7; - margin-bottom: 0.9rem; - color: var(--ui-ink); -} -.content-page ul, -.content-page ol { - margin: 0.5rem 0 1rem 1.5rem; - line-height: 1.7; - color: var(--ui-ink); -} -.content-page li { - margin-bottom: 0.25rem; -} -.content-page a { - color: var(--ui-gold-dark); - text-decoration: underline; -} -.content-page a:hover { - color: var(--ui-ink); -} -.content-page code { - font-family: monospace; - background: rgba(0,0,0,0.07); - border-radius: 3px; - padding: 0.1em 0.35em; - font-size: 0.88em; -} -.content-page pre { - background: rgba(0,0,0,0.07); - border-radius: 5px; - padding: 1rem 1.25rem; - overflow-x: auto; - margin-bottom: 1rem; -} -.content-page pre code { - background: none; - padding: 0; -} -.content-page blockquote { - border-left: 3px solid rgba(200,164,72,0.5); - margin: 0.75rem 0; - padding: 0.25rem 1rem; - color: #665544; - font-style: italic; -} -.content-page table { - border-collapse: collapse; - width: 100%; - margin-bottom: 1rem; -} -.content-page th, -.content-page td { - border: 1px solid rgba(200,164,72,0.3); - padding: 0.4rem 0.75rem; - text-align: left; -} -.content-page th { - background: rgba(200,164,72,0.1); - font-weight: 600; -} diff --git a/flake.nix b/flake.nix index a256857..3da667e 100644 --- a/flake.nix +++ b/flake.nix @@ -103,7 +103,7 @@ trictrac = with final; rustPlatform.buildRustPackage { pname = "trictrac"; - version = "0.2.16"; # trictrac-version + version = "0.2.15"; # trictrac-version src = ./.; nativeBuildInputs = [ pkg-config ];