diff --git a/README.md b/README.md index fba6b02b..4a7c6a6d 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,10 @@ duplicates. Private history follows the seat through recovery or handover. Messages do not execute game actions, and AI opponents do not answer them yet. See [table discussion](docs/TABLE_DISCUSSION.md). +Use **Bribes** beneath the table to pay a non-allied faction. The recipient's +share becomes spendable at the next Mentat Pause; the panel shows your own +incoming bribes and the current payment limit. See [bribe controls](docs/BRIBE_CONTROLS.md). + The table header offers optional **Sound effects**, with mute, volume and a test sample. Short cues mark phase changes and automatic action notices. Sound starts off; preferences stay in your browser. See [table sounds](docs/TABLE_SOUNDS.md) diff --git a/app/globals.css b/app/globals.css index 474ec036..dda7e4db 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1942,3 +1942,10 @@ footer { .table-talk-messages p { white-space: pre-wrap; unicode-bidi: plaintext; margin: 0.5rem 0 0; font-size: 1rem; } .table-talk-author { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 0.25rem 1rem; } .table-talk-author time { font-size: 0.875rem; } +.bribe-panel { font-size: 1rem; } +.bribe-panel > summary { cursor: pointer; min-height: 44px; padding-block: .65rem; font-weight: 600; } +.bribe-panel p, .bribe-status { display: block; margin-block: .7rem; } +.bribe-fields { display: flex; flex-wrap: wrap; align-items: end; gap: .8rem; } +.bribe-fields label { display: grid; gap: .35rem; flex: 1 1 12rem; min-width: 0; } +.bribe-fields select, .bribe-fields input { min-height: 44px; width: 100%; min-width: 0; font-size: 1rem; } +.bribe-fields button { font-size: 1rem; min-height: 44px; max-width: 100%; height: auto; white-space: normal; overflow-wrap: anywhere; } diff --git a/components/bribes.tsx b/components/bribes.tsx new file mode 100644 index 00000000..1b692972 --- /dev/null +++ b/components/bribes.tsx @@ -0,0 +1,53 @@ +'use client'; +import { useId, useState } from 'react'; +import Link from 'next/link'; +import type { Action, GameView } from '@/game/engine'; +import { bribeAction } from '@/game/bribe-options'; +import { faction } from '@/game/catalog'; +import { Button } from '@/components/ui/button'; + +export function Bribes({ game: g, act, busy }: { + game: GameView; act: (action: Action) => void; busy: boolean; +}) { + const [target, setTarget] = useState(''); + const [amount, setAmount] = useState('1'); + const description = useId(); + if (g.status !== 'playing') return null; + const options = g.bribeOptions; + const recipient = options.targets.find(candidate => candidate.id === target); + const player = recipient && g.players.find(candidate => candidate.id === recipient.id); + const action = bribeAction(options, target, Number(amount)); + return ( +
+ Bribes{options.incoming > 0 ? ` · ${options.incoming} spice awaiting Mentat` : ''} +

Pay another non-allied faction now. The amount is public; the recipient can spend their share only from the next Mentat Pause. A message or promise alone does not pay spice.

+

Your incoming bribes: {options.incoming} spice, separate from your spendable supply.

+ {options.blocked ? {options.blocked} : null} + {options.targets.length === 0 ?

No non-allied recipient is available.

: ( +
+ + + +
+ )} +

{recipient ? `You may pay up to ${recipient.maximum} spice to this recipient.` : `Uncommitted spice: ${options.available}. Choose a recipient to see the payment limit.`}

+ {recipient?.limitation ?

{recipient.limitation}

: null} + {recipient && !options.blocked && !action ? {recipient.maximum < 1 ? 'No spice is available for this payment.' : `Enter a whole number from 1 to ${recipient.maximum}.`} : null} +

Eligible Bureaucrats may divert two spice to the Bank. AI opponents can receive spice, but do not negotiate deals yet. Bribe rules

+
+ ); +} diff --git a/components/game-table.tsx b/components/game-table.tsx index c650d5f0..f5de4015 100644 --- a/components/game-table.tsx +++ b/components/game-table.tsx @@ -58,6 +58,7 @@ import { smugglerBattleModeSupported, smugglerBattlePlanBlock } from '@/game/smu import { RihaniChoice, RihaniHistory } from './rihani-decipherer'; import { MentatQuestion, MentatHistory } from './mentat-question'; import { BureaucratPayment } from './bureaucrat-payment'; +import { Bribes } from './bribes'; import { SukGraduatePanel } from './suk-graduate'; import { NullentropyBox, NullentropySearch } from './nullentropy-box'; import { OrnithopterMovement } from './ornithopter-movement'; @@ -4823,6 +4824,7 @@ export function GameTable({ : ' · Flips at the next Mentat Pause'}

)} +