1
0

input.css 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  1. @tailwind base;
  2. @tailwind components;
  3. @tailwind utilities;
  4. @layer base {
  5. /* Hide native spinner buttons on every number input app-wide. The
  6. team prefers keyboard typing over the tiny, inconsistent UA
  7. controls. */
  8. input[type="number"]::-webkit-outer-spin-button,
  9. input[type="number"]::-webkit-inner-spin-button {
  10. -webkit-appearance: none;
  11. margin: 0;
  12. }
  13. input[type="number"] {
  14. -moz-appearance: textfield;
  15. appearance: textfield;
  16. }
  17. /* Alpine x-cloak: keep elements hidden until Alpine boots and applies
  18. the matching x-show directive. Strict CSP forbids inline `style=`
  19. attributes, so we can't rely on the conventional pre-boot inline
  20. `display:none` — this rule fills the same gap. */
  21. [x-cloak] {
  22. display: none !important;
  23. }
  24. }
  25. @layer utilities {
  26. /* Phase 13: the Focus filter temporarily hides entire sw columns when the
  27. focused worker has no assignment on any visible row. Separate from the
  28. .hidden set driven by the Columns dropdown so clearing Focus restores
  29. the user's manual column picks. */
  30. .focus-auto-hidden {
  31. display: none;
  32. }
  33. }
  34. @layer components {
  35. /* htmx default indicator styles, shipped from our own stylesheet
  36. instead of htmx's built-in inline <style> injection (disabled via
  37. <meta name="htmx-config" content='{"includeIndicatorStyles":
  38. false}'> in views/layout.twig). The strict CSP blocks the inline
  39. injection and would otherwise log a `csp_violation` per page load. */
  40. .htmx-indicator { opacity: 0; visibility: hidden; }
  41. .htmx-request .htmx-indicator,
  42. .htmx-request.htmx-indicator { opacity: 1; visibility: visible;
  43. transition: opacity 200ms ease-in; }
  44. /* Phase 15: big-screen / beamer presentation scope. Applied via
  45. views/sprints/present.php on the root <main>. Tightens typography
  46. and cell padding so the task table fits the viewport without
  47. horizontal scroll at 1920×1080; hides drag handles and per-row
  48. delete buttons (not meaningful during a group discussion). The
  49. hamburger trigger stays visible — it carries the read-only task
  50. info pane (title / description / URL / refs) that presenters
  51. want during a discussion. */
  52. .beamer-root table {
  53. table-layout: fixed;
  54. font-size: clamp(0.75rem, 0.95vw, 1.05rem);
  55. }
  56. .beamer-root td,
  57. .beamer-root th {
  58. padding: 0.25rem 0.35rem;
  59. }
  60. .beamer-root .handle,
  61. .beamer-root [data-delete-task] {
  62. display: none;
  63. }
  64. /* JS-driven toggle (sprint-planner.js): when the rendered task table
  65. is wider than the viewport, rotate worker-column headers to save
  66. horizontal space. Leaves non-worker headers (Task / Owner / Prio
  67. / Tot) untouched. The rotation lives on an inline-block wrapper
  68. (.beamer-th-content) rather than the <th> itself, so the cell's
  69. `text-align: center` centers the vertical text horizontally
  70. inside the cell. */
  71. .beamer-vertical-headers thead th[data-sort-col^="sw-"] {
  72. padding: 0.5rem 0.25rem;
  73. }
  74. .beamer-vertical-headers thead th[data-sort-col^="sw-"] > .beamer-th-content {
  75. display: inline-block;
  76. writing-mode: vertical-rl;
  77. transform: rotate(180deg);
  78. }
  79. /* Phase 18: per-cell task-assignment status. The colour class lives
  80. on the <td> (so JS / filters can read data-status off the cell),
  81. but the visible tint is applied to the day input / read-only span
  82. inside — colouring just the <td> made the cell padding stripe a
  83. different colour from the field, which read as visual noise. The
  84. four .assign-status-* class names are interpolated server-side
  85. ("assign-status-<?= $st ?>"), so they're held in
  86. tailwind.config.js's safelist; otherwise the JIT prunes them. */
  87. .assign-status-zugewiesen { /* default: no tint */ }
  88. .assign-status-gestartet input[data-assign],
  89. .assign-status-gestartet > span.font-mono { background-color: theme('colors.yellow.200'); }
  90. .assign-status-abgeschlossen input[data-assign],
  91. .assign-status-abgeschlossen > span.font-mono { background-color: theme('colors.green.200'); }
  92. .assign-status-abgebrochen input[data-assign],
  93. .assign-status-abgebrochen > span.font-mono { background-color: theme('colors.red.200'); }
  94. .dark .assign-status-gestartet input[data-assign],
  95. .dark .assign-status-gestartet > span.font-mono { background-color: theme('colors.yellow.700'); }
  96. .dark .assign-status-abgeschlossen input[data-assign],
  97. .dark .assign-status-abgeschlossen > span.font-mono { background-color: theme('colors.green.700'); }
  98. .dark .assign-status-abgebrochen input[data-assign],
  99. .dark .assign-status-abgebrochen > span.font-mono { background-color: theme('colors.red.700'); }
  100. /* Cell popover — replaces the per-cell chevron status select.
  101. Body-attached, position: absolute (set in JS), shown to the
  102. right of the bound input/cell. Carries a 0..N slider (max
  103. configured via /settings → assignment_slider_max) and a
  104. column of status pills with coloured bullets. */
  105. .cell-popover {
  106. position: absolute;
  107. z-index: 50;
  108. min-width: 14rem;
  109. padding: 0.75rem;
  110. border-radius: 0.5rem;
  111. border: 1px solid theme('colors.slate.200');
  112. background-color: theme('colors.white');
  113. box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
  114. }
  115. .dark .cell-popover {
  116. border-color: theme('colors.slate.700');
  117. background-color: theme('colors.slate.800');
  118. }
  119. .cell-popover-grid {
  120. display: grid;
  121. grid-template-columns: auto 1fr;
  122. gap: 1rem;
  123. align-items: center;
  124. }
  125. .cell-popover-slider {
  126. display: flex;
  127. flex-direction: column;
  128. align-items: center;
  129. gap: 0.4rem;
  130. min-width: 2.5rem;
  131. }
  132. /* Vertical slider — `writing-mode + direction: rtl` covers modern
  133. browsers (low at the bottom, high at the top); the WebKit
  134. `slider-vertical` appearance and the Firefox `orient="vertical"`
  135. attribute (set in JS) are belt-and-braces for older engines. */
  136. .cell-popover-slider input[type="range"] {
  137. writing-mode: vertical-lr;
  138. direction: rtl;
  139. -webkit-appearance: slider-vertical;
  140. appearance: slider-vertical;
  141. width: 1.5rem;
  142. height: 8rem;
  143. accent-color: theme('colors.slate.600');
  144. cursor: pointer;
  145. }
  146. .dark .cell-popover-slider input[type="range"] {
  147. accent-color: theme('colors.slate.400');
  148. }
  149. .cell-popover-value {
  150. text-align: center;
  151. font-family: theme('fontFamily.mono');
  152. font-size: 0.875rem;
  153. color: theme('colors.slate.700');
  154. }
  155. .dark .cell-popover-value {
  156. color: theme('colors.slate.200');
  157. }
  158. .cell-popover-status {
  159. display: flex;
  160. flex-direction: column;
  161. gap: 0.25rem;
  162. min-width: 9rem;
  163. }
  164. .cell-popover-status-btn {
  165. display: flex;
  166. align-items: center;
  167. gap: 0.5rem;
  168. padding: 0.25rem 0.5rem;
  169. border-radius: 4px;
  170. text-align: left;
  171. font-size: 0.875rem;
  172. color: theme('colors.slate.700');
  173. background-color: transparent;
  174. border: 1px solid transparent;
  175. cursor: pointer;
  176. }
  177. .dark .cell-popover-status-btn {
  178. color: theme('colors.slate.200');
  179. }
  180. .cell-popover-status-btn:hover {
  181. background-color: theme('colors.slate.100');
  182. }
  183. .dark .cell-popover-status-btn:hover {
  184. background-color: theme('colors.slate.700');
  185. }
  186. .cell-popover-status-btn.cell-popover-active {
  187. border-color: theme('colors.slate.300');
  188. background-color: theme('colors.slate.50');
  189. }
  190. .dark .cell-popover-status-btn.cell-popover-active {
  191. border-color: theme('colors.slate.600');
  192. background-color: theme('colors.slate.700');
  193. }
  194. .cell-popover-bullet {
  195. display: inline-block;
  196. width: 0.75rem;
  197. height: 0.75rem;
  198. border-radius: 9999px;
  199. flex-shrink: 0;
  200. }
  201. .cell-popover-bullet.bullet-zugewiesen {
  202. border: 1px solid theme('colors.slate.300');
  203. }
  204. .dark .cell-popover-bullet.bullet-zugewiesen {
  205. border-color: theme('colors.slate.600');
  206. }
  207. .cell-popover-bullet.bullet-gestartet { background-color: theme('colors.yellow.300'); }
  208. .cell-popover-bullet.bullet-abgeschlossen { background-color: theme('colors.green.300'); }
  209. .cell-popover-bullet.bullet-abgebrochen { background-color: theme('colors.red.300'); }
  210. .dark .cell-popover-bullet.bullet-gestartet { background-color: theme('colors.yellow.500'); }
  211. .dark .cell-popover-bullet.bullet-abgeschlossen { background-color: theme('colors.green.500'); }
  212. .dark .cell-popover-bullet.bullet-abgebrochen { background-color: theme('colors.red.500'); }
  213. /* Phase 22: per-task hamburger menu, details modal, click-to-pickup
  214. reorder indicator, description popover. All single body-attached
  215. nodes; the JS positions them with absolute coordinates. */
  216. /* Task hamburger popup — three flex panes:
  217. [ menu | info | flyout (hidden by default) ]
  218. Body-attached, positioned by JS to the right of the trigger and
  219. vertically centred on it (cellPopover positioning model). */
  220. .task-menu {
  221. position: absolute;
  222. z-index: 60;
  223. border-radius: 0.5rem;
  224. border: 1px solid theme('colors.slate.200');
  225. background-color: theme('colors.white');
  226. box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.12), 0 4px 6px -4px rgb(0 0 0 / 0.1);
  227. }
  228. .dark .task-menu {
  229. border-color: theme('colors.slate.700');
  230. background-color: theme('colors.slate.800');
  231. }
  232. .task-menu-inner {
  233. display: flex;
  234. align-items: stretch;
  235. }
  236. .task-menu-list {
  237. display: flex;
  238. flex-direction: column;
  239. gap: 0.1rem;
  240. padding: 0.4rem;
  241. min-width: 12rem;
  242. border-right: 1px solid theme('colors.slate.100');
  243. }
  244. .dark .task-menu-list {
  245. border-right-color: theme('colors.slate.700');
  246. }
  247. .task-menu-item,
  248. .task-menu-sub-item {
  249. display: flex;
  250. width: 100%;
  251. align-items: center;
  252. justify-content: space-between;
  253. gap: 0.5rem;
  254. padding: 0.4rem 0.6rem;
  255. border-radius: 4px;
  256. text-align: left;
  257. font-size: 0.875rem;
  258. color: theme('colors.slate.700');
  259. background-color: transparent;
  260. cursor: pointer;
  261. white-space: nowrap;
  262. }
  263. .dark .task-menu-item,
  264. .dark .task-menu-sub-item {
  265. color: theme('colors.slate.200');
  266. }
  267. .task-menu-item:hover,
  268. .task-menu-sub-item:hover {
  269. background-color: theme('colors.slate.100');
  270. }
  271. .dark .task-menu-item:hover,
  272. .dark .task-menu-sub-item:hover {
  273. background-color: theme('colors.slate.700');
  274. }
  275. .task-menu-item-active {
  276. background-color: theme('colors.slate.100');
  277. }
  278. .dark .task-menu-item-active {
  279. background-color: theme('colors.slate.700');
  280. }
  281. .task-menu-item-danger {
  282. color: theme('colors.red.600');
  283. }
  284. .dark .task-menu-item-danger {
  285. color: theme('colors.red.400');
  286. }
  287. .task-menu-divider {
  288. height: 1px;
  289. margin: 0.25rem 0.25rem;
  290. background-color: theme('colors.slate.100');
  291. }
  292. .dark .task-menu-divider {
  293. background-color: theme('colors.slate.700');
  294. }
  295. /* Right pane: read-only task info. */
  296. .task-menu-info {
  297. display: flex;
  298. flex-direction: column;
  299. gap: 0.4rem;
  300. padding: 0.6rem 0.75rem;
  301. width: 18rem;
  302. max-width: 22rem;
  303. font-size: 0.825rem;
  304. color: theme('colors.slate.700');
  305. }
  306. .dark .task-menu-info {
  307. color: theme('colors.slate.100');
  308. }
  309. .task-menu-info-title {
  310. font-weight: 600;
  311. font-size: 0.9rem;
  312. color: theme('colors.slate.800');
  313. word-break: break-word;
  314. }
  315. .dark .task-menu-info-title {
  316. color: theme('colors.slate.100');
  317. }
  318. .task-menu-info-desc {
  319. white-space: pre-wrap;
  320. word-break: break-word;
  321. max-height: 12rem;
  322. overflow-y: auto;
  323. }
  324. .task-menu-info-url {
  325. font-size: 0.8rem;
  326. word-break: break-all;
  327. }
  328. .task-menu-info-url-link {
  329. color: theme('colors.blue.700');
  330. text-decoration: underline;
  331. }
  332. .dark .task-menu-info-url-link {
  333. color: theme('colors.blue.300');
  334. }
  335. .task-menu-info-refs-head {
  336. margin-top: 0.2rem;
  337. font-size: 0.7rem;
  338. text-transform: uppercase;
  339. letter-spacing: 0.05em;
  340. color: theme('colors.slate.500');
  341. margin-bottom: 0.2rem;
  342. }
  343. .dark .task-menu-info-refs-head {
  344. color: theme('colors.slate.400');
  345. }
  346. .task-menu-info-refs-list {
  347. display: flex;
  348. flex-wrap: wrap;
  349. gap: 0.25rem;
  350. }
  351. .task-menu-info-ref-chip {
  352. display: inline-flex;
  353. align-items: center;
  354. gap: 0.25rem;
  355. padding: 0.1rem 0.4rem;
  356. border-radius: 9999px;
  357. border: 1px solid theme('colors.slate.200');
  358. background-color: theme('colors.slate.50');
  359. color: theme('colors.slate.600');
  360. font-size: 0.75rem;
  361. text-decoration: none;
  362. }
  363. .task-menu-info-ref-chip:hover {
  364. background-color: theme('colors.slate.100');
  365. }
  366. .dark .task-menu-info-ref-chip {
  367. border-color: theme('colors.slate.600');
  368. background-color: theme('colors.slate.700');
  369. color: theme('colors.slate.300');
  370. }
  371. .dark .task-menu-info-ref-chip:hover {
  372. background-color: theme('colors.slate.600');
  373. }
  374. /* Flyout — third column for the sprint chooser when Move/Copy is
  375. picked. Slides in to the right of the info pane. */
  376. .task-menu-flyout {
  377. display: flex;
  378. flex-direction: column;
  379. padding: 0.4rem;
  380. width: 12rem;
  381. border-left: 1px solid theme('colors.slate.100');
  382. background-color: theme('colors.slate.50');
  383. }
  384. .dark .task-menu-flyout {
  385. border-left-color: theme('colors.slate.700');
  386. background-color: theme('colors.slate.700');
  387. }
  388. .task-menu-flyout-head {
  389. font-size: 0.7rem;
  390. text-transform: uppercase;
  391. letter-spacing: 0.05em;
  392. color: theme('colors.slate.500');
  393. padding: 0.2rem 0.5rem 0.4rem;
  394. }
  395. .dark .task-menu-flyout-head {
  396. color: theme('colors.slate.300');
  397. }
  398. .task-menu-flyout-list {
  399. display: flex;
  400. flex-direction: column;
  401. gap: 0.1rem;
  402. max-height: 16rem;
  403. overflow-y: auto;
  404. }
  405. .task-menu-sub-empty {
  406. padding: 0.4rem 0.6rem;
  407. font-size: 0.8rem;
  408. font-style: italic;
  409. color: theme('colors.slate.500');
  410. }
  411. .dark .task-menu-sub-empty {
  412. color: theme('colors.slate.400');
  413. }
  414. .task-modal-overlay {
  415. position: fixed;
  416. inset: 0;
  417. z-index: 70;
  418. display: flex;
  419. align-items: center;
  420. justify-content: center;
  421. padding: 1rem;
  422. background-color: rgb(15 23 42 / 0.45);
  423. }
  424. .task-modal-panel {
  425. width: 100%;
  426. max-width: 32rem;
  427. border-radius: 0.5rem;
  428. background-color: theme('colors.white');
  429. border: 1px solid theme('colors.slate.200');
  430. box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.3);
  431. display: flex;
  432. flex-direction: column;
  433. overflow: hidden;
  434. }
  435. .dark .task-modal-panel {
  436. background-color: theme('colors.slate.800');
  437. border-color: theme('colors.slate.700');
  438. }
  439. .task-modal-header {
  440. padding: 0.75rem 1rem;
  441. border-bottom: 1px solid theme('colors.slate.200');
  442. background-color: theme('colors.slate.50');
  443. color: theme('colors.slate.800');
  444. }
  445. .dark .task-modal-header {
  446. background-color: theme('colors.slate.700');
  447. border-color: theme('colors.slate.700');
  448. color: theme('colors.slate.100');
  449. }
  450. .task-modal-body {
  451. padding: 1rem;
  452. font-size: 0.875rem;
  453. }
  454. .task-modal-footer {
  455. padding: 0.5rem 1rem 0.75rem;
  456. display: flex;
  457. justify-content: flex-end;
  458. gap: 0.5rem;
  459. border-top: 1px solid theme('colors.slate.100');
  460. background-color: theme('colors.slate.50');
  461. }
  462. .dark .task-modal-footer {
  463. background-color: theme('colors.slate.700');
  464. border-color: theme('colors.slate.700');
  465. }
  466. .task-pickup-active {
  467. background-color: theme('colors.amber.100') !important;
  468. outline: 2px dashed theme('colors.amber.400');
  469. outline-offset: -2px;
  470. }
  471. .dark .task-pickup-active {
  472. background-color: theme('colors.amber.900') !important;
  473. outline-color: theme('colors.amber.500');
  474. }
  475. .task-pickup-indicator {
  476. position: absolute;
  477. height: 2px;
  478. background-color: theme('colors.amber.500');
  479. z-index: 65;
  480. pointer-events: none;
  481. box-shadow: 0 0 0 1px rgb(255 255 255 / 0.5);
  482. }
  483. }