/* Telenor Software Engineering Workshop */

:root {
    --bg: #0b1020;
    --panel: #121a30;
    --panel-alt: #0e1527;
    --line: #24304d;
    --text: #e8eefc;
    --muted: #93a2c4;
    --accent: #4fc3f7;
    --accent-strong: #00a1e4;
    --good: #3ddc97;
    --bad: #ff6b6b;
    --warn: #ffc857;

    /* The editor's line-number column. The textarea's left padding is derived
       from this, so the two layers cannot drift apart by changing one. */
    --gutter: 44px;
    --line-number: #55648c;

    --topic-device-events: #ffb454;
    --topic-alarms: #ff6b6b;
    --topic-tickets: #4fc3f7;
    --topic-notifications: #3ddc97;
    /* Not a topic. Deliberately dull: it is a place things sit, not travel. */
    --store: #8b94a7;
    /* Questions asked of another service. A colour of its own, because the
       whole point of the node is that it is not one of the Kafka hops. */
    --call: #7aa2f7;

    --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}

* { box-sizing: border-box; }

/* The browser's own `[hidden] { display: none }` is a plain attribute selector,
   so any class rule setting `display` silently beats it and the element stays on
   screen. That is how round 0's "no code needed" panel ended up showing on every
   round, next to the editor. Settle it once, here, for everything. */
[hidden] { display: none !important; }

html, body {
    height: 100%;
    margin: 0;
}

body {
    background: var(--bg);
    color: var(--text);
    font: 15px/1.55 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    display: flex;
    flex-direction: column;
}

.workshop {
    height: auto;
    min-height: 100vh;
}

.workshop :is(button, summary, a, [tabindex]):focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
}

/* ---- top bar ---- */

.topbar {
    display: flex;
    align-items: center;
    gap: 28px;
    padding: 0 20px;
    height: 56px;
    background: var(--panel-alt);
    border-bottom: 1px solid var(--line);
    flex: none;
}

.brand {
    display: flex;
    align-items: center;
    gap: 10px;
    font-weight: 600;
    letter-spacing: .2px;
    white-space: nowrap;
}

.mark {
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: var(--accent-strong);
    box-shadow: 0 0 0 4px rgba(0, 161, 228, .18);
}

.rounds {
    display: flex;
    gap: 6px;
    /* Takes the space left over, and gives it up first when the bar is tight.
       overflow stays visible: a round scrolled off the edge is a round nobody
       knows is there. */
    flex: 0 1 auto;
    min-width: 0;
}

/* Every tab carries its title, but only the selected one shows it. */
.round-tab .tab-title { display: none; }
.round-tab.selected .tab-title { display: inline; }

.round-tab {
    display: flex;
    align-items: center;
    gap: 8px;
    flex: none;
    background: transparent;
    border: 1px solid transparent;
    color: var(--muted);
    padding: 6px 12px;
    border-radius: 8px;
    font: inherit;
    font-size: 13.5px;
    cursor: pointer;
    white-space: nowrap;
}

.round-tab:hover { color: var(--text); }

.round-tab.selected {
    background: var(--panel);
    border-color: var(--line);
    color: var(--text);
}

.tick {
    display: grid;
    place-items: center;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: #22304f;
    font-size: 12px;
    font-weight: 700;
}

.live-link {
    margin-left: auto;
    flex: none;
    color: var(--muted);
    text-decoration: none;
    font-size: 12.5px;
    font-family: var(--mono);
    border: 1px solid var(--line);
    padding: 5px 12px;
    border-radius: 20px;
}

.live-link:hover { color: var(--accent); border-color: var(--accent); }

.round-tab.selected .tick { background: var(--accent-strong); color: #04121f; }

.runtime-note {
    flex: none;
    margin: 0;
    padding: 10px 20px;
    background: var(--panel-alt);
    border-bottom: 1px solid var(--line);
    color: var(--muted);
    font-size: 14px;
}

.runtime-note strong { color: var(--text); }

/* ---- layout ---- */

.board-strip {
    position: relative;
    flex: none;
    margin: 12px 20px 0;
}

.board-toolbar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 12px;
    padding: 8px 14px;
    border-bottom: 1px solid var(--line);
}

.board-toolbar h2 {
    margin: 0;
    font-size: 15px;
    font-weight: 600;
}

.board-toolbar .timing { margin-left: auto; }

.board-toolbar button {
    font-size: 13px;
    padding: 5px 10px;
}

/* ---------------------------------------------------------------------------
 * The answer, next to the button that asked for it.
 *
 * Results sit below the editor, so the page used to wait for the diagram to
 * finish and then scroll itself down to them - four seconds in front of an
 * answer that already existed, followed by being moved somewhere else. The
 * verdict now appears here the moment it arrives, and going to the detail is
 * a choice rather than something the page does to you.
 * ------------------------------------------------------------------------ */
.run-status {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 10px;
    margin: 0;
    padding: 9px 18px;
    border-bottom: 1px solid var(--line);
    font-size: 14px;
    font-weight: 600;
}

.run-status.busy { color: var(--muted); font-weight: 500; }
.run-status.good { color: var(--good); background: rgba(61, 220, 151, .08); }
.run-status.bad { color: var(--bad); background: rgba(255, 107, 107, .07); }

.run-status .to-results {
    margin-left: auto;
    border: 1px solid var(--line);
    border-radius: 7px;
    background: transparent;
    color: var(--muted);
    font: 500 13px/1 inherit;
    font-family: inherit;
    padding: 6px 10px;
    cursor: pointer;
}

.run-status .to-results:hover { color: var(--text); border-color: var(--accent); }

.runtime-info { position: relative; }

.runtime-info summary {
    color: var(--muted);
    font-size: 13px;
    cursor: pointer;
}

.runtime-info .runtime-note {
    position: absolute;
    top: calc(100% + 10px);
    left: 0;
    z-index: 5;
    width: min(420px, calc(100vw - 48px));
    padding: 16px;
    border: 1px solid var(--line);
    border-radius: 10px;
    box-shadow: 0 12px 30px rgba(0, 0, 0, .3);
}

.layout {
    flex: none;
    display: grid;
    grid-template-columns: clamp(380px, 32vw, 460px) minmax(0, 1fr);
    align-items: start;
    gap: 20px;
    padding: 20px;
    width: 100%;
    max-width: 1800px;
    margin: 0 auto;
}

.workspace {
    display: flex;
    flex-direction: column;
    gap: 16px;
    min-width: 0;
}

.panel {
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: 12px;
    display: flex;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
    overflow: hidden;
}

.panel.board-strip { overflow: visible; }

.brief {
    padding: 26px;
    font-size: 18px;
    line-height: 1.65;
}

.panel-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 12px 16px;
    border-bottom: 1px solid var(--line);
    flex: none;
}

.panel-head h2 {
    margin: 0;
    font-size: 13px;
    text-transform: uppercase;
    letter-spacing: .09em;
    color: var(--muted);
}

/* ---- brief ---- */

.round-heading {
    display: flex;
    gap: 14px;
    align-items: flex-start;
    margin-bottom: 14px;
}

.round-number {
    display: grid;
    place-items: center;
    width: 38px;
    height: 38px;
    flex: none;
    border-radius: 10px;
    background: var(--accent-strong);
    color: #04121f;
    font-weight: 700;
    font-size: 18px;
}

.brief h1 { margin: 0; font-size: 26px; line-height: 1.25; }

.goal { margin: 10px 0 0; color: var(--accent); font-size: 16px; }

/* ---------------------------------------------------------------------------
 * The incident, in one line.
 *
 * The task-first layout collapsed all the scenario prose behind a toggle, and
 * with it went the reason any of this is happening: thirty people were writing
 * a Kafka listener with no fault, no site and nobody waiting at the other end.
 * This is the smallest thing that puts them back - their own device, their own
 * customers - without restoring the wall of text nobody read.
 * ------------------------------------------------------------------------ */
.hook {
    margin: 12px 0 0;
    padding: 12px 14px;
    border-left: 3px solid var(--warn);
    border-radius: 0 8px 8px 0;
    background: rgba(255, 200, 87, .07);
    color: var(--text);
    font-size: 17px;
    line-height: 1.5;
}

.hook code {
    font-family: var(--mono);
    font-size: 15px;
    color: var(--warn);
    overflow-wrap: anywhere;
}

/* ---------------------------------------------------------------------------
 * Predict, then run, then explain.
 *
 * A guess costs nothing and changes what the run is. Somebody who has decided
 * out loud that saving a ticket is enough reads the empty topic differently
 * from somebody watching a progress bar. Nothing is submitted, marked or
 * timed: there is no answer box, because the answer is on the next screen.
 * ------------------------------------------------------------------------ */
.prompt {
    margin: 16px 0 0;
    padding: 12px 14px;
    border: 1px dashed var(--line);
    border-radius: 9px;
    background: var(--panel-alt);
}

.prompt h3 {
    margin: 0 0 6px;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: .09em;
    color: var(--muted);
}

.prompt p { margin: 0; font-size: 16px; color: #cfd9f0; line-height: 1.5; }

.prompt code {
    font-family: var(--mono);
    font-size: 14px;
    color: var(--accent);
    overflow-wrap: anywhere;
}

.prompt.predict { border-left: 3px solid var(--accent); }
.prompt.explain { border-left: 3px solid var(--good); margin: 18px 0 0; }
.results .prompt.explain h3 { margin: 0 0 6px; }

.prose p { margin: 0 0 11px; color: #cfd9f0; }

.prose code, .goal code, #hint-list code {
    font-family: var(--mono);
    font-size: 16px;
    overflow-wrap: anywhere;
    background: rgba(79, 195, 247, .12);
    color: var(--accent);
    padding: 1px 5px;
    border-radius: 4px;
}

.task-heading {
    margin: 14px 0 16px;
    font-size: 16px;
    font-weight: 600;
}

.task-steps {
    margin: 0;
    padding-left: 26px;
}

.task-steps li {
    padding-left: 8px;
    margin-bottom: 18px;
}

.task-steps li::marker { color: var(--accent); font-weight: 600; }

/* ---- editor file navigation ---- */

.editable-files {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    padding: 12px 16px;
    border-bottom: 1px solid var(--line);
}

.files-label {
    align-self: center;
    margin-right: 4px;
    color: var(--muted);
    font-size: 13px;
}

.editable-files button {
    border: 1px solid var(--line);
    border-radius: 7px;
    background: var(--panel-alt);
    color: var(--text);
    font: 14px/1.4 var(--mono);
    padding: 7px 10px;
    cursor: pointer;
    overflow-wrap: anywhere;
}

.editable-files button[aria-pressed="true"] {
    border-color: var(--accent);
    background: rgba(79, 195, 247, .12);
    color: var(--accent);
}

.project {
    padding: 10px 16px;
    border-bottom: 1px solid var(--line);
}

.project-head {
    font-size: 14px;
    color: var(--text);
    cursor: pointer;
}

.project-head span {
    margin-left: 8px;
    font-size: 13px;
    color: var(--muted);
}

.tree {
    list-style: none;
    margin: 12px 0 0;
    padding: 0 4px 4px;
    max-height: 260px;
    overflow-y: auto;
    font-family: var(--mono);
    font-size: 14px;
    line-height: 1.5;
}

.tree-module {
    display: flex;
    align-items: baseline;
    gap: 8px;
    margin-top: 8px;
    color: #cfd9f0;
    font-weight: 600;
}

.tree-module:first-child { margin-top: 0; }

.mod-base {
    color: var(--muted);
    font-weight: 400;
    font-size: 12px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.tree-folder {
    padding-left: 12px;
    color: var(--muted);
}

.tree-file { padding-left: 24px; }
.tree-file.shallow { padding-left: 12px; }

.tree-file button {
    display: flex;
    align-items: baseline;
    gap: 6px;
    width: 100%;
    text-align: left;
    background: transparent;
    border: 0;
    border-radius: 5px;
    padding: 5px 6px;
    font: inherit;
    color: #9fb3d1;
    cursor: pointer;
}

.tree-file button:hover { background: rgba(79, 195, 247, .08); color: var(--text); }

.tree-file.editable button { color: var(--text); }

.tree-file.open button {
    background: rgba(79, 195, 247, .15);
    color: var(--accent);
}

.tree-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Three states, not two: yours, ours-but-real, and here to be read. */
.tree-badge {
    flex: none;
    margin-left: auto;
    font-size: 11px;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--muted);
    border: 1px solid var(--line);
    border-radius: 4px;
    padding: 0 4px;
}

.hints { margin-top: 16px; }

.hint-toggle {
    background: transparent;
    border: 1px dashed var(--line);
    color: var(--muted);
    padding: 7px 12px;
    border-radius: 8px;
    font: inherit;
    font-size: 15px;
    cursor: pointer;
    width: 100%;
}

.hint-toggle:hover { color: var(--text); border-color: var(--accent); }

#hint-list {
    margin: 10px 0 0;
    padding-left: 20px;
    color: #cfd9f0;
    font-size: 17px;
}

#hint-list li { margin-bottom: 7px; }

.background, .telenor {
    margin-top: 20px;
    border-top: 1px solid var(--line);
    padding-top: 14px;
}

.background summary, .telenor summary {
    cursor: pointer;
    color: var(--accent);
    font-size: 16px;
    font-weight: 600;
}

.background .prose, .telenor .prose { margin-top: 14px; font-size: 16px; }

/* ---- editor ---- */

.editor-panel { min-height: 540px; }

.editor-panel.watch-only { min-height: 240px; }

.editor-head {
    flex-wrap: wrap;
    gap: 12px;
}

/* `alarm-service  AlarmService.kt` - the module in the same muted uppercase the
 * other panel heads use, the file name in the font it is written in. */
#editor-title {
    display: flex;
    flex-direction: column;
    gap: 3px;
    min-width: 0;
}

.head-file {
    font-family: var(--mono);
    font-size: 14px;
    text-transform: none;
    letter-spacing: 0;
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* One line about why the file exists, for the file you are looking at. */
.file-note {
    margin: 0;
    padding: 10px 16px;
    border-bottom: 1px solid var(--line);
    font-size: 14px;
    line-height: 1.45;
    color: var(--muted);
    flex: none;
}

.file-note code {
    font-family: var(--mono);
    font-size: 13px;
    color: var(--accent);
}

/* A file you can read but not change. The editor stays exactly where it was -
 * moving it would make opening a reference file feel like leaving the round. */
.editor-panel.is-readonly .editor textarea {
    cursor: default;
    caret-color: transparent;
}

.editor-panel.is-readonly .editor { opacity: .82; }

.actions { display: flex; gap: 8px; }

.editor-head .actions { margin-left: auto; }

button.primary, button.ghost {
    font: inherit;
    font-size: 13.5px;
    font-weight: 600;
    padding: 7px 16px;
    border-radius: 8px;
    cursor: pointer;
    border: 1px solid transparent;
}

button.primary {
    background: var(--accent-strong);
    color: #04121f;
}

button.primary:hover { background: var(--accent); }
button.primary:disabled { opacity: .55; cursor: default; }

button.ghost {
    background: transparent;
    border-color: var(--line);
    color: var(--muted);
}

button.ghost:hover { color: var(--text); }

.editor {
    position: relative;
    flex: 1;
    min-height: 340px;
    overflow: hidden;
}

.editor .highlight,
.editor textarea {
    position: absolute;
    inset: 0;
    margin: 0;
    padding: 16px;
    font-family: var(--mono);
    font-size: 15px;
    line-height: 1.6;
    tab-size: 4;
    /* Both layers must wrap identically to keep the highlighting under the text. */
    white-space: pre-wrap;
    overflow-wrap: break-word;
    word-break: normal;
    overflow-x: hidden;
    overflow-y: auto;
    border: 0;
}

.editor .highlight {
    pointer-events: none;
    color: var(--text);
    background: var(--panel-alt);
}

/*
 * Line numbers.
 *
 * The number lives inside its own line rather than in a column beside the
 * editor, because a separate column would have to be told where each line
 * begins, and with wrapping on that is a measurement rather than a sum. Here
 * the line is a block, the number is absolutely positioned inside its left
 * padding, and the browser does the rest.
 *
 * That padding is also what makes a wrapped line indent under the code instead
 * of running back under the numbers - and the textarea's own padding-left is
 * this plus the editor's, so both layers still break lines in the same places.
 */
/*
 * <code> is an inline element, and the lines inside it are blocks. A block
 * inside an inline box makes the browser split the inline in two and invent
 * anonymous boxes around the pieces, which is legal but is not the same box
 * model in every engine - and this layer has to match a textarea to the pixel.
 * Saying "block" out loud costs nothing and removes the question.
 */
.editor .highlight code {
    display: block;
}

.editor .code-line {
    display: block;
    position: relative;
    padding-left: var(--gutter);
    min-height: 1.6em;
}

.editor .ln {
    position: absolute;
    left: 0;
    width: calc(var(--gutter) - 12px);
    text-align: right;
    color: var(--line-number);
    user-select: none;
}

.editor .code-line.has-error .ln {
    color: var(--bad);
    font-weight: 600;
}

.editor .code-line.has-error {
    background: rgba(255, 107, 107, .10);
}

.editor textarea {
    /* Must equal the shared padding plus the gutter, or the text drifts off
       the colours behind it. checkEditorFits() asserts exactly that. */
    padding-left: calc(16px + var(--gutter));
    background: transparent;
    color: transparent;
    caret-color: var(--accent);
    resize: none;
    outline: none;
}

.editor textarea::selection { background: rgba(79, 195, 247, .28); }

.editor-empty {
    flex: 1;
    display: grid;
    place-content: center;
    text-align: center;
    color: var(--muted);
    padding: 30px;
}

/* Kotlin tokens */
.tok-keyword { color: #ff8fb1; }
.tok-string { color: #b7e39b; }
.tok-comment { color: #6b7ba3; font-style: italic; }
.tok-annotation { color: var(--warn); }
.tok-type { color: #7fd4ff; }
.tok-constant { color: #c8a2ff; }
.tok-number { color: #f7b267; }

/* ---- board ---- */

.results-panel { overflow: hidden; }

#board { padding: 8px 10px 10px; flex: none; }

.workshop #board {
    padding: 2px 10px 4px;
    overflow-x: auto;
}

.workshop .board-svg { min-width: 1100px; }

/* Centred and capped: on a 27" monitor an uncapped board would be half the
   screen tall and the dots would take an age to cross it. */
.board-svg {
    width: 100%;
    max-width: 1500px;
    height: auto;
    display: block;
    margin: 0 auto;
}

.node rect {
    fill: #16203a;
    stroke: var(--line);
    stroke-width: 1.5;
    transition: fill .25s, stroke .25s;
}

.node.active rect {
    fill: #1b3350;
    stroke: var(--accent);
}

.node.dead-end rect {
    stroke: var(--bad);
    stroke-dasharray: 5 4;
}

.node-label {
    fill: var(--text);
    font-family: var(--mono);
    font-size: 13px;
    text-anchor: middle;
}

.node-role {
    fill: var(--muted);
    font-size: 11px;
    text-anchor: middle;
}

.pipe {
    stroke: var(--line);
    stroke-width: 2;
    marker-end: url(#arrow);
    color: var(--line);
}

.pipe-live {
    stroke: var(--accent);
    stroke-width: 2;
    opacity: 0;
    transition: opacity .3s;
}

.edge.active .pipe-live { opacity: .55; }

.topic {
    fill: var(--muted);
    font-family: var(--mono);
    font-size: 11px;
    text-anchor: middle;
}

.edge.active .topic { fill: var(--accent); }

.topic-count {
    fill: var(--accent);
    font-size: 10.5px;
    text-anchor: middle;
}

.dot {
    fill: var(--accent);
    filter: drop-shadow(0 0 6px currentColor);
}

.dot.topic-device-events { fill: var(--topic-device-events); }
.dot.topic-alarms { fill: var(--topic-alarms); }
.dot.topic-tickets { fill: var(--topic-tickets); }
.dot.topic-notifications { fill: var(--topic-notifications); }
.dot.topic-ticket-db { fill: var(--store); }
.dot.topic-inventory-service { fill: var(--call); }

.dot.landed { transition: r .35s, opacity .35s; r: 14; opacity: 0; }

/* ---- results ---- */

.results {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: 10px 16px 18px;
}

.workshop .results {
    flex: none;
    overflow-y: visible;
    padding: 14px 20px 20px;
}

.placeholder { color: var(--muted); font-size: 14px; }

.timing { color: var(--muted); font-size: 12px; font-family: var(--mono); }

.verdict {
    font-weight: 700;
    font-size: 15px;
    padding: 10px 12px;
    border-radius: 9px;
    margin: 10px 0 12px;
}

.verdict.good { background: rgba(61, 220, 151, .13); color: var(--good); }
.verdict.bad { background: rgba(255, 107, 107, .12); color: var(--bad); }
.verdict.neutral { background: rgba(255, 200, 87, .12); color: var(--warn); }

.note { color: #cfd9f0; font-size: 14px; }

/* ---------------------------------------------------------------------------
 * What the run got right, said before what it did not.
 *
 * Round 2 sends everybody to a failing run on purpose: save the ticket, look
 * at the empty topic, understand why publishing exists. Leading that with a
 * red "Not quite yet" and burying the reason under a list of crosses is how
 * following the instructions correctly ends up feeling like getting it wrong.
 * ------------------------------------------------------------------------ */
.run-note {
    margin: 0 0 14px;
    padding: 12px 14px;
    border-radius: 9px;
    border-left: 3px solid var(--line);
    background: var(--panel-alt);
}

.run-note strong { display: block; font-size: 15px; margin-bottom: 4px; }
.run-note p { margin: 0; font-size: 14px; color: #cfd9f0; line-height: 1.5; }

.run-note code {
    font-family: var(--mono);
    font-size: 13px;
    color: var(--accent);
    overflow-wrap: anywhere;
}

.run-note.win { border-left-color: var(--good); background: rgba(61, 220, 151, .08); }
.run-note.win strong { color: var(--good); }
.run-note.info { border-left-color: var(--accent); background: rgba(79, 195, 247, .07); }
.run-note.info strong { color: var(--accent); }

.checks { list-style: none; margin: 0 0 14px; padding: 0; }

.checks li {
    display: flex;
    gap: 10px;
    align-items: flex-start;
    padding: 6px 0;
    font-size: 14px;
}

.checks .icon { font-weight: 700; width: 16px; flex: none; }
.checks .pass .icon { color: var(--good); }
.checks .fail .icon { color: var(--bad); }
.checks .fail { color: #cfd9f0; }

.checks em {
    display: block;
    color: var(--muted);
    font-size: 12.5px;
    font-style: normal;
    font-family: var(--mono);
    margin-top: 2px;
}

/* What a tick actually saw. A green line with no evidence behind it is a
   grade; this is the run telling you what it found. */
.checks em.win { color: var(--good); }

.checks-count {
    font-size: 12px;
    color: var(--muted);
    margin: 0 0 6px;
    font-family: var(--mono);
}

.results h3 {
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: .08em;
    color: var(--muted);
    margin: 16px 0 8px;
}

.events { list-style: none; margin: 0; padding: 0; }

.events li {
    display: block;
    padding: 5px 0;
    border-bottom: 1px solid rgba(36, 48, 77, .55);
}

.payload-head, .payload summary {
    display: flex;
    gap: 9px;
    align-items: baseline;
}

.events code {
    font-family: var(--mono);
    font-size: 14px;
    color: #cfd9f0;
    word-break: break-word;
}

.topic-tag {
    font-family: var(--mono);
    font-size: 12px;
    padding: 2px 7px;
    border-radius: 20px;
    flex: none;
    background: #22304f;
    color: var(--muted);
}

.topic-tag.topic-device-events { background: rgba(255, 180, 84, .16); color: var(--topic-device-events); }
.topic-tag.topic-alarms { background: rgba(255, 107, 107, .16); color: var(--topic-alarms); }
.topic-tag.topic-tickets { background: rgba(79, 195, 247, .16); color: var(--topic-tickets); }
.topic-tag.topic-notifications { background: rgba(61, 220, 151, .16); color: var(--topic-notifications); }
.topic-tag.topic-ticket-db { background: rgba(139, 148, 167, .16); color: var(--store); }
.topic-tag.topic-call { background: rgba(122, 162, 247, .16); color: var(--call); }

/* ---------------------------------------------------------------------------
 * Reading the payload.
 *
 * This was one line of `key=value key=value` at 12px, wrapping wherever it ran
 * out of room, and the field somebody was looking for was somewhere in the
 * middle of it. Round 1 asks whether the customer list on the alarm is right;
 * you cannot answer that from a line you cannot read. Folded by default, so
 * six messages are still six lines - but the one you open is a table.
 * ------------------------------------------------------------------------ */

.payload { margin: 0; }
.payload summary { cursor: pointer; list-style: none; }
.payload summary::-webkit-details-marker { display: none; }
.payload summary::before {
    content: "\25B8";
    color: var(--muted);
    font-size: 11px;
    flex: none;
}
.payload[open] summary::before { content: "\25BE"; }
.payload summary:hover code { color: var(--text); }
.payload summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.fields {
    display: grid;
    grid-template-columns: minmax(9ch, max-content) 1fr;
    gap: 4px 14px;
    margin: 8px 0 10px 22px;
    padding: 10px 12px;
    background: var(--panel-alt);
    border-radius: 8px;
    border: 1px solid var(--line);
}

.fields dt {
    font-family: var(--mono);
    font-size: 16px;
    color: var(--muted);
    margin: 0;
}

.fields dd {
    font-family: var(--mono);
    font-size: 16px;
    color: #cfd9f0;
    margin: 0;
    word-break: break-word;
}

/* The fields this round is actually about, picked out of the ones that are
   merely present. Everything is still shown. */
.fields dt.key { color: var(--accent); }
.fields dd.key { color: var(--text); }

.calls .status {
    font-family: var(--mono);
    font-size: 12px;
    flex: none;
    margin-left: auto;
}

.calls .status.ok { color: var(--good); }
.calls .status.bad { color: var(--bad); }

.response {
    margin: 8px 0 10px 22px;
    padding: 10px 12px;
    background: var(--panel-alt);
    border: 1px solid var(--line);
    border-radius: 8px;
    font-family: var(--mono);
    font-size: 16px;
    color: #cfd9f0;
    white-space: pre-wrap;
    word-break: break-word;
    overflow-x: auto;
}

.diagnostics { list-style: none; margin: 0; padding: 0; }

.diagnostics li {
    font-family: var(--mono);
    font-size: 12.5px;
    color: #ffd7d7;
    padding: 7px 10px;
    background: rgba(255, 107, 107, .08);
    border-left: 2px solid var(--bad);
    border-radius: 0 6px 6px 0;
    margin-bottom: 7px;
}

/* "line 12" is a button now: it puts the caret on line 12. A number you cannot
   act on is only half a message, which is the whole reason the gutter exists. */
.diagnostics .where {
    display: inline-block;
    color: var(--bad);
    margin-right: 9px;
    font: inherit;
    background: none;
    border: 0;
    padding: 0;
    text-decoration: underline dotted;
    text-underline-offset: 3px;
}

.diagnostics li.jumpable { cursor: pointer; }
.diagnostics li.jumpable:hover { background: rgba(255, 107, 107, .16); }

/* The compiler's own wording, and then ours. Deliberately not monospace and not
   red: it should read as somebody helping, not as more error. */
.diagnostics .hint {
    display: block;
    margin-top: 8px;
    padding-top: 8px;
    border-top: 1px solid rgba(255, 107, 107, .22);
    font-family: inherit;
    font-size: 13.5px;
    line-height: 1.5;
    color: var(--text);
}

.diagnostics .hint::before {
    content: "Try this — ";
    color: var(--accent);
    font-weight: 600;
}

.diagnostics .hint code {
    font-family: var(--mono);
    font-size: 12.5px;
    background: rgba(79, 195, 247, .12);
    color: var(--accent);
    padding: 1px 5px;
    border-radius: 4px;
}

.error { color: var(--bad); font-size: 14px; }

.logs {
    font-family: var(--mono);
    font-size: 12px;
    background: var(--panel-alt);
    border: 1px solid var(--line);
    border-radius: 8px;
    padding: 10px 12px;
    color: #cfd9f0;
    overflow-x: auto;
    margin: 0;
}

/* ---- narrow screens ---- */

@media (max-width: 1200px) {
    .workshop .topbar {
        height: auto;
        min-height: 56px;
        padding: 12px 16px;
        flex-wrap: wrap;
        gap: 12px;
    }

    .workshop .rounds {
        order: 1;
        flex-basis: 100%;
        flex-wrap: wrap;
    }

}

@media (max-width: 960px) {
    .layout { grid-template-columns: 1fr; }
}

@media (max-width: 640px) {
    .layout { padding: 12px; gap: 12px; }
    .board-strip { margin: 12px 12px 0; }
    .brief { padding: 20px; }
    .runtime-info { position: static; }
    .runtime-info .runtime-note {
        top: 48px;
        left: 12px;
        right: 12px;
        width: auto;
    }
    .board-toolbar .timing { display: none; }
    .board-toolbar button { margin-left: auto; }
    .project-head span { display: block; margin: 4px 0 0; }
    .workshop .brand { flex-basis: 100%; }
    .workshop .my-case { margin-left: 0; }
    .workshop .round-tab { padding: 6px 8px; }
    .workshop .round-tab .tab-title { display: none; }
}

/* A service's own database -------------------------------------------------
 *
 * Drawn to look like a different kind of thing from a Kafka pipe on purpose.
 * Dashed, dimmer, no arrowhead, and nothing ever leaves it. The picture is the
 * argument: saving a ticket tells one service, publishing tells the company.
 */

.store-link .pipe {
    stroke-dasharray: 5 5;
    opacity: .5;
}

.store-link .pipe-live {
    stroke: var(--store);
    stroke-dasharray: 5 5;
}

.store-link .topic-count {
    fill: var(--store);
    font-size: 12px;
    /* Beside the dashed line, not centred on it - the attribute on the element
       loses to this rule, so it has to be said here. */
    text-anchor: start;
}

.store-node rect {
    fill: rgba(139, 148, 167, .07);
    stroke: rgba(139, 148, 167, .38);
    stroke-dasharray: 5 4;
}

.store-node .node-label {
    fill: var(--store);
    font-size: 13px;
}

.store-node .node-role {
    fill: var(--store);
    opacity: .75;
    font-size: 11px;
}

.store-node.active rect {
    fill: rgba(139, 148, 167, .18);
    stroke: var(--store);
}

/* A service you ask a question of, rather than announce to. Deliberately
   reads like the database - off the chain, hanging under its one caller - but
   solid rather than dashed, because something is running there, and with a
   dot that comes back. */
.call-link .pipe {
    stroke-dasharray: 2 6;
    opacity: .55;
}

.call-link .pipe-live {
    stroke: var(--call);
    stroke-dasharray: 2 6;
}

.call-link .topic-count {
    fill: var(--call);
    font-size: 12px;
    text-anchor: start;
}

.call-node rect {
    fill: rgba(122, 162, 247, .07);
    stroke: rgba(122, 162, 247, .42);
}

.call-node .node-label {
    fill: var(--call);
    font-size: 13px;
}

.call-node .node-role {
    fill: var(--call);
    opacity: .75;
    font-size: 11px;
}

.call-node.active rect {
    fill: rgba(122, 162, 247, .18);
    stroke: var(--call);
}

/* The answer coming back, hollow so the two halves of one call can be told
   apart as they cross. */
.dot.answer {
    fill: none;
    stroke: var(--call);
    stroke-width: 2.5;
}

.aside {
    margin: -6px 0 10px;
    color: var(--muted);
    font-size: 13px;
}

/* A box you can click ------------------------------------------------------
 *
 * The board is the thing people look at, so it is also the most obvious place
 * to say who writes what and how far you have got. Boxes with a round are
 * clickable and carry its number; the equipment and the customer are inert,
 * which is itself the point - those are not yours to write.
 */

.node.has-round { cursor: pointer; }

.node.has-round:hover rect {
    stroke: var(--accent);
    fill: #1b3350;
}

.round-badge circle {
    fill: #22304f;
    stroke: var(--line);
    stroke-width: 1.5;
}

.round-badge text {
    fill: var(--muted);
    font-family: var(--mono);
    font-size: 13px;
    font-weight: 700;
    text-anchor: middle;
}

.node.current-round rect {
    stroke: var(--accent);
    stroke-width: 2;
}

.node.current-round .round-badge circle {
    fill: var(--accent-strong);
    stroke: var(--accent-strong);
}

.node.current-round .round-badge text { fill: #04121f; }

.node.solved-round .round-badge circle {
    fill: var(--good, #4ade80);
    stroke: var(--good, #4ade80);
}

.node.solved-round .round-badge text { fill: #04121f; }

/* Where to go next ---------------------------------------------------------
 *
 * Ticks told you a check passed. They never told you the round was over, which
 * left people re-reading a brief they had already finished.
 */

.next-step {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    margin: 12px 0 4px;
    padding: 12px 14px;
    border: 1px solid var(--accent);
    border-radius: 10px;
    background: rgba(0, 180, 255, .07);
}

.next-step span { color: var(--muted); font-size: 14px; }

.next-step.done {
    display: block;
    font-size: 14px;
    line-height: 1.6;
    color: var(--text);
}

.next-step.done strong { color: var(--accent); }

.next-round {
    margin-left: auto;
    border: 0;
    border-radius: 8px;
    padding: 9px 16px;
    background: var(--accent-strong);
    color: #04121f;
    font: 600 14px/1 inherit;
    font-family: inherit;
    cursor: pointer;
}

.next-round:hover { filter: brightness(1.1); }


/* Which of the fifty incidents belongs to this browser.
   Sits in the top bar because it is context, not an instruction - and because
   the device id is what you scan the projector wall for. */
.my-case {
    display: flex;
    align-items: baseline;
    gap: 8px;
    margin-left: auto;
    padding: 4px 10px;
    border: 1px solid var(--line);
    border-radius: 999px;
    background: var(--panel);
    max-width: 34ch;
    min-width: 0;
    flex: 0 1 auto;
    overflow: hidden;
}

.my-case .case-label {
    font-size: 10.5px;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--muted);
    white-space: nowrap;
}

.my-case .case-id {
    font-family: var(--mono);
    font-size: 12px;
    color: var(--accent);
}

/* The one string that matches the projector. It stays at every width the
   workshop is run at: scanning the wall for your own device is the trick that
   makes thirty simultaneous alarms mean anything. */
.my-case .case-device {
    font-family: var(--mono);
    font-size: 12px;
    color: var(--text);
    white-space: nowrap;
    flex: none;
}

.my-case .case-headline {
    font-family: var(--mono);
    font-size: 12px;
    color: var(--muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* The device is what matches the projector; the headline is the nice-to-have,
   so it is the first thing to go when the bar runs out of room. */
@media (max-width: 1280px) {
    .my-case .case-headline { display: none; }
}


/* Handing the laptop to the next person, or clearing a demo out of the way.
   Reset only ever put the starter code back; progress had no way out at all
   short of devtools, which is not a thing to teach thirty people at 09:00. */
.start-over {
    font-size: 12px;
    padding: 5px 11px;
    white-space: nowrap;
}

/* ---------------------------------------------------------------------------
 * The frontend round's preview: the participant's own component, running.
 *
 * Drawn as a window rather than as more of the results panel, because it is
 * not our output - it is theirs. The frame is the difference between "here is
 * what we think of your code" and "here is your screen".
 * ------------------------------------------------------------------------ */

.screen {
    border: 1px solid var(--line);
    border-radius: 8px;
    overflow: hidden;
    background: #fff;
    margin-top: 8px;
}

.screen-body {
    padding: 12px 14px;
    min-height: 64px;
    font-size: 13px;
    color: #1b1b1b;
}

.screen-body p {
    margin: 0;
    color: #6b6b6b;
}

.screen-body ul,
.screen-body ol {
    list-style: none;
    margin: 0;
    padding: 0;
}

.screen-body li {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 10px;
    align-items: baseline;
    padding: 8px 10px;
    border-left: 3px solid transparent;
    border-bottom: 1px solid #f0f0f0;
}

.screen-body li:last-child { border-bottom: 0; }

.screen-body strong {
    font-family: var(--mono, ui-monospace, monospace);
    font-size: 12px;
}

/*
 * The two class names round 5's hint promises the stylesheet already knows.
 * Written against .screen-body rather than a list, so they work whatever
 * element the participant decided to hang them on.
 */
.screen-body .open {
    border-left-color: #d97706;
    background: #fffbf3;
}

.screen-body .closed {
    border-left-color: #16a34a;
    background: #fafafa;
    color: #7b7b7b;
}

.screen-body .status {
    margin-left: auto;
    font-size: 11px;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: #6b6b6b;
}


/* ---------------------------------------------------------------------------
 * The customer, at the end of it.
 *
 * Four rounds spent insisting the point is somebody at the other end of the
 * line, and the workshop finished on a column of green ticks. These are the
 * messages the participant's own notification-service wrote, to the customers
 * on their own fault, in their own words. Drawn as a phone rather than as more
 * results, for the same reason the queue preview is drawn as a screen: it is
 * not our output, and it is not a grade.
 * ------------------------------------------------------------------------ */

.customer-cards {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
}

.customer-card {
    flex: 1 1 260px;
    max-width: 420px;
    padding: 12px 14px;
    border: 1px solid var(--line);
    border-radius: 14px;
    background: rgba(61, 220, 151, .06);
    border-left: 3px solid var(--good);
}

.customer-card .card-top {
    display: flex;
    align-items: baseline;
    gap: 8px;
    margin-bottom: 7px;
}

.customer-card .who {
    font-family: var(--mono);
    font-size: 13px;
    color: var(--good);
}

.customer-card .channel {
    margin-left: auto;
    font-size: 10.5px;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--muted);
}

.customer-card .message {
    margin: 0;
    font-size: 16px;
    line-height: 1.45;
    color: var(--text);
    overflow-wrap: anywhere;
}

.report-retry {
    margin: 14px 0 0;
    font-size: 13px;
    color: var(--muted);
}

.report-retry button {
    border: 1px solid var(--line);
    border-radius: 7px;
    background: transparent;
    color: var(--accent);
    font: 500 13px/1 inherit;
    font-family: inherit;
    padding: 5px 9px;
    cursor: pointer;
}


/* ---------------------------------------------------------------------------
 * Asking for help without putting your hand up.
 *
 * In a room of thirty there is a reliable number of people who will sit in
 * front of the same red error for twenty minutes rather than be the one who
 * asks. The projector cannot solve that - showing who is behind would be worse
 * than saying nothing. This goes to exactly one screen, which is not the one
 * on the wall, and it carries an alias and a device rather than a name.
 * ------------------------------------------------------------------------ */

.help-button.waiting {
    color: var(--warn);
    border-color: var(--warn);
}

.help-button {
    font-size: 12px;
    padding: 5px 11px;
    white-space: nowrap;
}

.help-panel {
    position: absolute;
    right: 20px;
    top: 56px;
    z-index: 40;
    width: 320px;
    max-width: calc(100vw - 40px);
    padding: 16px;
    border: 1px solid var(--line);
    border-radius: 12px;
    background: var(--panel);
    box-shadow: 0 18px 44px rgba(0, 0, 0, .5);
}

.help-panel h2 { margin: 0 0 6px; font-size: 16px; }
.help-panel p { margin: 0 0 12px; font-size: 14px; color: #cfd9f0; line-height: 1.45; }
.help-panel .aside { font-size: 12.5px; color: var(--muted); }

.help-panel code {
    font-family: var(--mono);
    font-size: 13px;
    color: var(--accent);
}

.help-reasons {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.help-reasons button {
    text-align: left;
    padding: 10px 12px;
    border: 1px solid var(--line);
    border-radius: 9px;
    background: var(--panel-alt);
    color: var(--text);
    font: 500 14px/1.35 inherit;
    font-family: inherit;
    cursor: pointer;
}

.help-reasons button:hover { border-color: var(--accent); color: var(--accent); }

.help-error {
    margin: 10px 0 0;
    font-size: 13px;
    color: var(--bad);
}
