diff --git a/frontend/src/components/ProblemList.tsx b/frontend/src/components/ProblemList.tsx
index 7ba93d1..474ab34 100644
--- a/frontend/src/components/ProblemList.tsx
+++ b/frontend/src/components/ProblemList.tsx
@@ -2,25 +2,57 @@ import { useMemo } from 'react';
import { Balloon, Contest, Problem } from '../types';
import ProblemBox from './ProblemBox';
-const ProblemBlock = ({ problem, solves }: { problem: Problem, solves: number }) => {
- return useMemo(() => (
-
- ), [problem, solves]);
+const ProblemBlock = ({ problem, solves, required, solved = false }: {
+ problem: Problem,
+ solves: number,
+ required: number,
+ solved?: boolean
+}) => {
+ const getUrgencyLevel = (count: number): string => {
+ if (count > 100) return '5';
+ if (count > 65) return '4';
+ if (count > 30) return '3';
+ if (count > 15) return '2';
+ if (count > 0) return '1';
+ return '';
+ };
+ return useMemo(() => (
+
+
+
+ {solves}
+
+ {required > 0 && (
+
+ {required}
+
+ )}
+
+ ), [problem, solves, required, solved]);
};
const ProblemList = ({ contest, balloons }: { contest: Contest, balloons: Balloon[] }) => {
- return useMemo(() => (
-
- {contest.problems.map(problem => (
-
b.problemId === problem.id).length} />
- ))}
-
- ), [contest, balloons]);
+ return useMemo(() => (
+
+ {contest.problems.map(problem => {
+ const solvedBalloons = balloons.filter(b => b.problemId === problem.id);
+ const requiredCount = solvedBalloons.filter(
+ balloon => balloon.takenBy === null && !balloon.delivered
+ ).length;
+
+ return (
+
0}
+ />
+ );
+ })}
+
+
+ ), [contest, balloons]);
};
export default ProblemList;
diff --git a/frontend/src/index.css b/frontend/src/index.css
index 85bdcb9..76bb12e 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -1,334 +1,461 @@
:root {
- font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
- line-height: 1.5;
- font-weight: 400;
+ font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
+ line-height: 1.5;
+ font-weight: 400;
- color: #213547;
- background-color: #ffffff;
+ color: #213547;
+ background-color: #ffffff;
- font-synthesis: none;
- text-rendering: optimizeLegibility;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
}
body {
- margin: 0;
- min-width: 320px;
+ margin: 0;
+ min-width: 320px;
}
#root {
- /*margin: 0 auto;*/
- padding: 1em;
- /*max-width: 800px;*/
- min-height: 100vh;
- display: flex;
- flex-direction: column;
- box-sizing: border-box;
+ /*margin: 0 auto;*/
+ padding: 1em;
+ /*max-width: 800px;*/
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+ box-sizing: border-box;
}
nav>*,
footer>* {
- display: inline-block;
- margin-right: 1em;
+ display: inline-block;
+ margin-right: 1em;
}
nav>a.active {
- font-weight: bold;
- color: #000;
+ font-weight: bold;
+ color: #000;
}
a {
- user-select: none;
- color: #646cff;
- text-decoration: inherit;
- cursor: pointer;
+ user-select: none;
+ color: #646cff;
+ text-decoration: inherit;
+ cursor: pointer;
}
a:hover {
- color: #747bff;
+ color: #747bff;
}
main {
- margin: 1em 0;
+ margin: 1em 0;
}
input[type=text],
input[type=password] {
- display: block;
- font: inherit;
- margin-bottom: 0.7em;
+ display: block;
+ font: inherit;
+ margin-bottom: 0.7em;
}
button {
- border-radius: 8px;
- border: 1px solid transparent;
- padding: 0.4em 1.2em;
- font: inherit;
- font-weight: 500;
- background-color: #646cff;
- color: #fff;
- cursor: pointer;
- transition: border-color 0.25s;
+ border-radius: 8px;
+ border: 1px solid transparent;
+ padding: 0.4em 1.2em;
+ font: inherit;
+ font-weight: 500;
+ background-color: #646cff;
+ color: #fff;
+ cursor: pointer;
+ transition: border-color 0.25s;
}
button:hover {
- background-color: #747bff;
+ background-color: #747bff;
}
button:focus,
button:focus-visible {
- outline: 4px auto -webkit-focus-ring-color;
+ outline: 4px auto -webkit-focus-ring-color;
}
.form-error {
- color: red;
- margin-bottom: 0.7em;
+ color: red;
+ margin-bottom: 0.7em;
}
footer {
- margin-top: auto;
+ margin-top: auto;
}
table {
- border-spacing: 0;
- border-collapse: collapse;
+ border-spacing: 0;
+ border-collapse: collapse;
}
th, td {
- padding: 0.3em;
- padding-right: 3em;
+ padding: 0.3em;
+ padding-right: 3em;
}
th {
- text-align: left;
- border-bottom: 1px solid #000;
+ text-align: left;
+ border-bottom: 1px solid #000;
}
.sr-only {
- position: absolute;
- left: -10000px;
- top: auto;
- width: 1px;
- height: 1px;
- overflow: hidden;
+ position: absolute;
+ left: -10000px;
+ top: auto;
+ width: 1px;
+ height: 1px;
+ overflow: hidden;
}
.access-link.disabled {
- color: #777;
- cursor: not-allowed;
+ color: #777;
+ cursor: not-allowed;
}
.crossed {
- position: relative;
- overflow: hidden;
+ position: relative;
+ overflow: hidden;
}
.crossed:before,
.crossed:after {
- position: absolute;
- content: '';
- background: rgba(0, 0, 0, 0.2);
- display: block;
- width: 100%;
- height: 0.2em;
- -webkit-transform: rotate(-45deg);
- transform: rotate(-45deg);
- left: 0;
- right: 0;
- top: 0;
- bottom: 0;
- margin: auto;
+ position: absolute;
+ content: '';
+ background: rgba(0, 0, 0, 0.2);
+ display: block;
+ width: 100%;
+ height: 0.2em;
+ -webkit-transform: rotate(-45deg);
+ transform: rotate(-45deg);
+ left: 0;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ margin: auto;
}
.crossed:after {
- -webkit-transform: rotate(45deg);
- transform: rotate(45deg);
-}
-
-.problem-list {
- display: flex;
- gap: 0.5em;
+ -webkit-transform: rotate(45deg);
+ transform: rotate(45deg);
}
.problem {
- font-size: 1.6em;
- width: 1.2em;
- height: 1.2em;
- line-height: 1.2em;
- text-align: center;
- vertical-align: middle;
- border: 1px solid black;
-}
-
-.problem-solves {
- font-size: 0.9em;
- color: #777;
- text-align: center;
+ font-size: 1.6em;
+ font-weight: 600;
+ padding: 2px;
+ width: 1.2em;
+ height: 1.2em;
+ line-height: 1.2em;
+ text-align: center;
+ vertical-align: middle;
+ border: 1px solid #e2e8f0;
+ border-radius: 10px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+ box-shadow: 0 1px 6px rgba(0, 0, 0, 0.04);
+ user-select: none;
+ cursor: default;
}
.balloons-main {
- width: max-content;
- max-width: none;
+ width: max-content;
+ max-width: none;
}
.contest-name {
- margin-bottom: 0.7em;
+ margin-bottom: 0.7em;
}
.balloon-list {
- display: table;
+ display: table;
}
.balloon-row {
- display: table-row;
+ display: table-row;
}
.balloon-row > * {
- display: table-cell;
- vertical-align: middle;
+ display: table-cell;
+ vertical-align: middle;
}
.balloon-row > *:not(:first-child) {
- padding: 0 0.3em;
+ padding: 0 0.3em;
}
.balloon-row .fts,
.balloon-row .team-place,
.balloon-row .actions {
- font-size: 1.6em;
+ font-size: 1.6em;
}
.balloon-row .actions a {
- margin-right: 0.3em;
+ margin-right: 0.3em;
}
.balloon-row .team-name {
- font-size: 0.75em;
- min-width: 250px;
- max-width: 30vw;
+ font-size: 0.75em;
+ min-width: 250px;
+ max-width: 30vw;
}
.standings th,
.standings td {
- padding: 0.1em;
+ padding: 0.1em;
}
.standings .team-name,
.standings .team-place {
- padding: 0.1em 0.8em;
+ padding: 0.1em 0.8em;
}
.standings th:not(.team-name) {
- text-align: center;
+ text-align: center;
}
.standings td {
- padding: 0.1em;
+ padding: 0.1em;
}
.standings td.team-place {
- font-size: 1.6em;
+ font-size: 1.6em;
}
.standings td.team-name {
- font-size: 0.75em;
- min-width: 250px;
- max-width: 30vw;
+ font-size: 0.75em;
+ min-width: 250px;
+ max-width: 30vw;
}
.standings th.team-problem,
.standings td.team-problem {
- min-width: 2.1em;
+ min-width: 2.1em;
}
.hall-dropdown {
- display: inline-block;
- position: relative;
+ display: inline-block;
+ position: relative;
}
.hall-dropdown-toggle {
- color: #646cff;
- cursor: pointer;
+ color: #646cff;
+ cursor: pointer;
}
.hall-dropdown-toggle:hover {
- color: #747bff;
+ color: #747bff;
}
.dropdown-arrow:before {
- content: '▼';
+ content: '▼';
}
/* .hall-dropdown-toggle ~ .hall-dropdown-menu + .dropdown-arrow,*/
.hall-dropdown-toggle:has(~ .hall-dropdown-menu) .dropdown-arrow:before {
- content: '▲';
+ content: '▲';
}
.dropdown-arrow {
- margin-left: 0.3em;
- font-size: 0.8em;
+ margin-left: 0.3em;
+ font-size: 0.8em;
}
.hall-dropdown-menu {
- position: absolute;
- top: 100%;
- left: 0;
- margin-top: 0.25rem;
- padding-left: 0;
- min-width: 150px;
- background: white;
- border: 1px solid #ccc;
- border-radius: 4px;
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
- z-index: 1000;
+ position: absolute;
+ top: 100%;
+ left: 0;
+ margin-top: 0.25rem;
+ padding-left: 0;
+ min-width: 150px;
+ background: white;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+ z-index: 1000;
}
.hall-dropdown-menu li {
- box-sizing: border-box;
- display: block;
- width: 100%;
- padding: 0.5rem 0.75rem;
- text-align: left;
- border: none;
- background: none;
- cursor: pointer;
- color: inherit;
+ box-sizing: border-box;
+ display: block;
+ width: 100%;
+ padding: 0.5rem 0.75rem;
+ text-align: left;
+ border: none;
+ background: none;
+ cursor: pointer;
+ color: inherit;
}
.hall-dropdown-menu li:hover {
- background: #f5f5f5;
+ background: #f5f5f5;
}
.hall-dropdown-menu li.active {
- font-weight: bold;
+ font-weight: bold;
}
.hall-dropdown-menu li:first-child {
- border-top-left-radius: 4px;
- border-top-right-radius: 4px;
+ border-top-left-radius: 4px;
+ border-top-right-radius: 4px;
}
.hall-dropdown-menu li:last-child {
- border-bottom-left-radius: 4px;
- border-bottom-right-radius: 4px;
+ border-bottom-left-radius: 4px;
+ border-bottom-right-radius: 4px;
}
.connection-status {
- position: fixed;
- top: 4em;
- right: 1em;
- color: white;
- text-align: center;
- padding: 0.5em;
- z-index: 1000;
+ position: fixed;
+ top: 4em;
+ right: 1em;
+ color: white;
+ text-align: center;
+ padding: 0.5em;
+ z-index: 1000;
}
.connection-status.in-progress {
- background-color: #6b9fff;
+ background-color: #6b9fff;
}
.connection-status.lost {
- background-color: #ff6b6b;
+ background-color: #ff6b6b;
+}
+
+.problem-list {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ padding: 12px;
+ justify-content: start;
+ align-items: flex-start;
+ max-width: 100%;
+ overflow-x: auto;
+ min-height: 120px;
+}
+
+.problem-list > div {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ min-width: 40px;
+ padding: 4px 4px;
+ border-radius: 10px;
+ background: #f8f9fa;
+ border: 1px solid #e9ecef;
+ transition: all 0.2s ease;
+ position: relative;
+}
+
+.problem-list > div:hover {
+ background: #e9ecef;
+ transform: translateY(-2px);
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+}
+
+.problem-solves {
+ font-size: 18px;
+ font-weight: 700;
+ color: #2d3436;
+ margin-bottom: 2px;
+}
+
+.balloons-require {
+ font-size: 14px;
+ font-weight: 600;
+ padding: 2px 6px;
+ border-radius: 12px;
+ min-width: 24px;
+ text-align: center;
+ transition: all 0.2s ease;
+}
+
+.balloons-require:not(:empty) {
+ background: #ff6b6b;
+ color: white;
+ animation: pulse 2s infinite;
+}
+
+.balloons-require:empty {
+ display: none;
+}
+
+.balloons-require[urgency-level="1"] {
+ background: #ffa8a8;
+}
+
+.balloons-require[urgency-level="2"] {
+ background: #ff6b6b;
+}
+
+.balloons-require[urgency-level="3"] {
+ background: #fa5252;
+}
+
+.balloons-require[urgency-level="4"] {
+ background: #e03131;
+}
+
+.balloons-require[urgency-level="5"] {
+ background: #c92a2a;
+}
+
+@keyframes pulse {
+ 0% {
+ box-shadow: 0 0 0 0 rgba(255, 107, 107, 0.4);
+ }
+ 70% {
+ box-shadow: 0 0 0 6px rgba(255, 107, 107, 0);
+ }
+ 100% {
+ box-shadow: 0 0 0 0 rgba(255, 107, 107, 0);
+ }
+}
+
+@media (max-width: 480px) {
+ .problem-list {
+ gap: 6px;
+ padding: 8px;
+ }
+
+ .problem-list > div {
+ min-width: 44px;
+ padding: 6px 3px;
+ }
+
+ .problem-solves {
+ font-size: 16px;
+ }
+
+ .balloons-require {
+ font-size: 12px;
+ padding: 1px 5px;
+ }
+}
+
+.problem-list > div.solved-problem::after {
+ content: "✓";
+ position: absolute;
+ top: -6px;
+ right: -6px;
+ background: #51cf66;
+ color: white;
+ width: 16px;
+ height: 16px;
+ border-radius: 50%;
+ font-size: 10px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-weight: bold;
}
\ No newline at end of file