/* ============================================================================
   The roadmap, drawn as a calendar

   A roadmap is a picture of time. Horizontal is the calendar, vertical is which
   block is working, and a bar says this thing, roughly this long, roughly then.
   It answers one question, asked by people who do not read tickets: when will
   it be there. Every rule below serves that and nothing else.

   This file used to draw a dependency graph. The bars were placed by how many
   things had to finish before them, the columns were headed "Wave 1" and up,
   and there was no date anywhere on the screen. That is a real picture of a
   different subject, and calling it a roadmap did not make it one.

   Four regions.

   The RAIL, down the left, is one card per block. It sits outside the pane that
   scrolls sideways, so a reader nine months out still knows which block they
   are looking at. Its cards carry counts and never a percentage: a block does
   not end, and "0% delivered" is a sentence about a feature dressed up as a
   sentence about roadmap.

   The RULER along the top is months, weeks or quarters depending on how much
   calendar is on screen. The server picks the grain and writes the labels; this
   file only knows that a mark has a position and that some marks are heavier.

   The PLANE is the picture. A bar's left edge is its start date and its width
   is its length, both in days times a pixels-per-day the script owns. The TODAY
   line runs down the whole of it, because "are we late" is the second question
   and it is answered by one vertical rule.

   The TRAY under it names every feature that has no dates at all. A roadmap
   that silently dropped them would look finished while the actual planning work
   sat invisible, and moving things out of that tray is what a planning session
   is for.
   ========================================================================== */

/* The one page in this application whose subject is width. Everything else is a
   column of prose or a grid of cards and --content-max is right for those; a
   year of calendar inside a 1280px cap spends its width on a scrollbar. Written
   as a :has() rule here rather than a class on <main>, because _Layout renders
   that element and a page cannot reach it. */
.app-main:has(.roadmap-map) { max-width: none; }

.roadmap-map {
    display: grid;
    grid-template-columns: 260px minmax(0, 1fr);
    gap: var(--space-6);
    align-items: start;
}

/* Every write and every redraw goes through one render, and a screen that looks
   identical while one is in flight is a screen people press twice. */
.roadmap-busy { opacity: 0.72; }

/* ---- the rail ----------------------------------------------------------- */

.roadmap-rail {
    display: flex;
    flex-direction: column;
    /* No gap. Each card's height is set by the script to its band's height, and
       a gap here would put the fourth card half a band out of line with the
       fourth band. The separation is a border instead. */
    gap: 0;
}

/* The rail's own head, level with the ruler, so card one lines up with band
   one. Its height is set by the script from the same constant the ruler uses. */
.roadmap-rail-head {
    display: flex;
    align-items: flex-end;
    height: var(--rail-head);
    padding-bottom: var(--space-3);
    font-size: var(--text-2xs);
    font-weight: var(--weight-semibold);
    letter-spacing: var(--tracking-wider);
    text-transform: uppercase;
    color: var(--text-subtle);
}

.roadmap-rail-lane {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: var(--space-3);
    padding: var(--space-6);
    border-bottom: var(--border-width) solid var(--border);
    overflow: hidden;
}

.roadmap-rail-lane:first-of-type { border-top: var(--border-width) solid var(--border); }

.roadmap-rail-title {
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    color: var(--text);
    line-height: var(--leading-snug);
}

.roadmap-rail-title:hover { color: var(--text-link); }

.roadmap-rail-tallies {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
}

.roadmap-tally {
    font-size: var(--text-2xs);
    padding: var(--space-1) var(--space-4);
    border-radius: var(--radius-pill);
    background: var(--neutral-soft);
    color: var(--neutral-on-soft);
    white-space: nowrap;
}

/* A tally the style book already has a tone for wears it. The count of late
   features is the one number on the rail somebody has to act on. */
.roadmap-tally.tone-warning { background: var(--warning-soft); color: var(--warning-on-soft); }
.roadmap-tally.tone-success { background: var(--success-soft); color: var(--success-on-soft); }
.roadmap-tally.tone-info { background: var(--info-soft); color: var(--info-on-soft); }

.roadmap-rail-empty {
    font-size: var(--text-2xs);
    color: var(--text-subtle);
    line-height: var(--leading-snug);
}

/* ---- the plane ---------------------------------------------------------- */

/* Sideways only. The page owns the vertical scroll, which is what keeps the
   rail beside the bands without a sticky element inside a transformed box. */
.roadmap-viewport {
    position: relative;
    overflow-x: auto;
    overflow-y: hidden;
    background: var(--surface-sunken);
    border: var(--border-width) solid var(--border);
    border-radius: var(--radius-lg);
    user-select: none;
}

.roadmap-plane {
    position: relative;
    width: var(--plane-width);
    height: var(--plane-height);
}

/* ---- the ruler ---------------------------------------------------------- */

.roadmap-ruler {
    position: absolute;
    inset: 0 0 auto 0;
    height: var(--ruler-height);
    border-bottom: var(--border-width) solid var(--border);
}

.roadmap-mark {
    position: absolute;
    top: 0;
    left: var(--mark-x);
    height: 100%;
    padding: var(--space-4) var(--space-3);
    border-left: var(--border-width) solid var(--border);
    font-size: var(--text-2xs);
    color: var(--text-subtle);
    white-space: nowrap;
}

/* A major mark is a quarter boundary, a January, or the first Monday of a
   month, depending on the grain. It is what a reader navigates by, so it is the
   only label on the axis that carries a year. */
.roadmap-mark-major {
    border-left-color: var(--border-strong);
    color: var(--text);
    font-weight: var(--weight-semibold);
}

/* The lines carried down through the bands. Separate elements from the labels,
   because the labels live in a strip of fixed height and these run the whole
   way down. */
.roadmap-grid {
    position: absolute;
    top: var(--ruler-height);
    bottom: 0;
    left: var(--mark-x);
    width: 0;
    border-left: var(--border-width) solid var(--border);
    pointer-events: none;
}

.roadmap-grid-major { border-left-color: var(--border-strong); }

/* ---- today -------------------------------------------------------------- */

/* One vertical rule, and the most-read mark on the picture. Everything to its
   left has happened. It is drawn over the bars rather than under them, because
   a bar crossing today is exactly the bar somebody is looking at. */
.roadmap-today {
    position: absolute;
    top: 0;
    bottom: 0;
    left: var(--today-x);
    width: 0;
    border-left: var(--border-width-thick) solid var(--danger);
    pointer-events: none;
    z-index: 3;
}

.roadmap-today-flag {
    position: absolute;
    top: 0;
    left: 0;
    padding: var(--space-1) var(--space-3);
    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
    background: var(--danger);
    color: var(--text-inverse);
    font-size: var(--text-2xs);
    font-weight: var(--weight-semibold);
    white-space: nowrap;
}

/* ---- the bands ---------------------------------------------------------- */

/* Alternating, faintly, because a roadmap with nine blocks is nine rows a
   reader has to keep their place in and a hairline is not enough. */
.roadmap-band {
    position: absolute;
    left: 0;
    top: var(--band-y);
    width: 100%;
    height: var(--band-height);
    border-bottom: var(--border-width) solid var(--border);
}

.roadmap-band-alt { background: var(--surface-alt); }

.roadmap-band-empty {
    position: absolute;
    top: var(--band-y);
    left: 0;
    height: var(--band-height);
    display: flex;
    align-items: center;
    padding: 0 var(--space-8);
    font-size: var(--text-xs);
    color: var(--text-subtle);
}

/* The stretch a block's features happen to cover. A hairline behind them and
   never a bar: a block has no dates of its own, because it does not end, and
   anything drawn as solid as a feature would read as a promise somebody made. */
.roadmap-cover {
    position: absolute;
    left: var(--cover-x);
    top: var(--cover-y);
    width: var(--cover-width);
    height: 0;
    border-top: var(--border-width) dashed var(--border-strong);
    opacity: 0.5;
    pointer-events: none;
}

/* ---- a feature ---------------------------------------------------------- */

.roadmap-bar {
    position: absolute;
    left: var(--bar-x);
    top: var(--bar-y);
    width: var(--bar-width);
    height: var(--bar-height);
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: 0 var(--space-4);
    background: var(--surface);
    border: var(--border-width) solid var(--border-strong);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-sm);
    text-decoration: none;
    overflow: hidden;
    z-index: 2;
    transition: box-shadow var(--transition-fast) var(--ease-out);
}

.roadmap-bar:hover {
    box-shadow: var(--shadow-md);
    border-color: var(--brand);
}

/* How much of the feature's work is done, as a fill inside the bar rather than
   as a second bar under it. Length is time and fill is progress, and keeping
   them on one shape is what stops a reader reading either as the other. */
.roadmap-bar-fill {
    position: absolute;
    inset: 0 auto 0 0;
    width: var(--fill-width);
    opacity: 0.28;
    pointer-events: none;
}

.roadmap-bar-title {
    position: relative;
    font-size: var(--text-xs);
    font-weight: var(--weight-medium);
    color: var(--text);
    line-height: var(--leading-snug);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* A bar too narrow to hold its own title puts the title beside it instead. A
   three-day feature is four pixels wide at a year's zoom, and a picture where
   short work is anonymous is a picture that hides the short work. */
.roadmap-bar-outside {
    position: absolute;
    left: var(--label-x);
    top: var(--bar-y);
    height: var(--bar-height);
    display: flex;
    align-items: center;
    font-size: var(--text-xs);
    color: var(--text);
    white-space: nowrap;
    text-decoration: none;
    z-index: 2;
}

.roadmap-bar-outside:hover { color: var(--text-link); }

/* A deadline: an end date with nobody yet committed to when work starts. Drawn
   as a diamond on the day, with nothing to its left, because there is nothing
   to its left. */
.roadmap-diamond {
    position: absolute;
    left: var(--bar-x);
    top: var(--bar-y);
    width: var(--bar-height);
    height: var(--bar-height);
    transform: translateX(-50%) rotate(45deg);
    background: var(--surface);
    border: var(--border-width-thick) solid var(--border-strong);
    border-radius: var(--radius-xs);
    z-index: 2;
}

/* Work that began with no promised end runs to the right edge and is cut off
   there rather than ending. The fade is the only place on this picture where a
   drawn length is not a length somebody typed. */
.roadmap-bar-open {
    border-right: none;
    border-top-right-radius: 0;
    border-bottom-right-radius: 0;
    /* The mask reads alpha only, so the colour in it is never seen. currentColor rather than a
       literal, because the colour audit is right to refuse a hex value even where it cannot show. */
    mask-image: linear-gradient(to right, currentColor 70%, transparent 100%);
}

/* Past its end date and not done. The one thing on this screen somebody has to
   act on today, so it is the one thing wearing danger. */
.roadmap-bar-late {
    border-color: var(--danger);
    background: var(--danger-soft);
}

.roadmap-bar-late .roadmap-bar-title { color: var(--danger-on-soft); }

/* Hovering one bar answers one question: what does this wait on, and what waits
   on it. Everything unrelated goes quiet rather than away, because a bar that
   vanishes takes the shape of the roadmap with it. */
.roadmap-bar-dim { opacity: 0.3; }

.roadmap-bar-lit {
    border-color: var(--brand);
    box-shadow: var(--shadow-md);
}

/* ---- moving a bar ------------------------------------------------------- */

/* Sideways only. Which band a bar sits in is which block its feature hangs off,
   and which row inside the band is packing recomputed on every render, so
   neither is a position a mouse is allowed to change. There is no vertical drag
   here and there is not going to be one. */
.roadmap-bar,
.roadmap-diamond { cursor: grab; }

/* Held on the whole picture rather than on the bar, because the pointer leaves
   the bar the moment the bar moves under it and the cursor would flicker back
   to an arrow mid-gesture. */
.roadmap-dragging .roadmap-viewport,
.roadmap-dragging .roadmap-bar,
.roadmap-dragging .roadmap-diamond { cursor: grabbing; }

/* The two ends of a window bar, where one date is taken at a time. Only a window
   has them. A deadline has no start to move, and an open bar's right edge is
   where the picture stops rather than a day somebody typed, so a handle there
   would offer to invent an end date. Six pixels is a target a mouse can hit
   while leaving a body to grab between them; the script leaves both off a bar
   too narrow to hold that. */
.roadmap-grip {
    position: absolute;
    top: 0;
    bottom: 0;
    width: var(--space-3);
    z-index: 1;
    cursor: ew-resize;
}

.roadmap-grip-starts { left: 0; }

.roadmap-grip-ends { right: 0; }

/* Invisible until the pointer is on the bar. A bar wearing two permanent tabs
   reads as a control, and there are twenty of them on this screen. */
.roadmap-bar:hover .roadmap-grip { background: var(--brand-soft); }

/* The dates the drag is landing on, over the bar while it is happening. A bar is
   a length, the person moving one is choosing a day, and the day is the one
   thing a length cannot show. Above the today line, because it is the thing
   being read at that moment. */
.roadmap-tag {
    position: absolute;
    left: var(--tag-x);
    top: var(--tag-y);
    transform: translateY(-100%);
    padding: var(--space-1) var(--space-3);
    border-radius: var(--radius-sm);
    background: var(--tooltip-bg);
    color: var(--tooltip-text);
    font-size: var(--text-2xs);
    white-space: nowrap;
    pointer-events: none;
    z-index: 4;
}

/* ---- a feature being landed on the calendar ------------------------------ */

/* The bar a chip in the tray is being dragged in as. Dashed and hollow because
   nothing has been written yet: it is the shape the feature WOULD take, at the
   length the server said a fresh one takes, and the two dates over it are the
   two that get committed when the button comes up.

   It rides in the band of the block the feature already hangs off, however far
   the pointer wanders up or down. Which band a bar is in is which block claims
   it, and no mouse gesture on this screen is allowed to answer that. */
.roadmap-ghost {
    position: absolute;
    left: var(--bar-x);
    top: var(--bar-y);
    width: var(--bar-width);
    height: var(--bar-height);
    border: var(--border-width-thick) dashed var(--brand);
    border-radius: var(--radius-sm);
    background: var(--brand-soft);
    opacity: 0.7;
    pointer-events: none;
    z-index: 3;
}

.roadmap-chip { cursor: grab; }

.roadmap-dragging .roadmap-chip { cursor: grabbing; }

/* ---- the wires ---------------------------------------------------------- */

/* Dependencies are drawn faintly and mean little here: this picture is about
   time and a wire is about order. The one that earns its ink is the impossible
   one, where a feature is scheduled to start before the thing it waits on is
   scheduled to finish. That is a plan that cannot happen, it is invisible in
   either picture alone, and it is drawn in the colour that means act. */
.roadmap-wires {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
    z-index: 1;
}

.roadmap-wire {
    fill: none;
    stroke: var(--border-strong);
    stroke-width: var(--border-width);
    opacity: 0.35;
}

.roadmap-wire-impossible {
    stroke: var(--danger);
    stroke-width: var(--border-width-thick);
    opacity: 1;
}

.roadmap-wire-lit { stroke: var(--brand); opacity: 1; }

.roadmap-wire-dim { opacity: 0.1; }

.roadmap-arrow { fill: var(--border-strong); opacity: 0.35; }

.roadmap-arrow-impossible { fill: var(--danger); opacity: 1; }

.roadmap-arrow-lit { fill: var(--brand); opacity: 1; }

/* ---- drawing a wire ----------------------------------------------------- */

/* Wiring is a mode, and this is the whole of what the mode looks like: the
   dependencies come forward, the bars stop offering to move, and every wire
   grows a target wide enough to click.

   It is a mode because every pixel of a bar already means something to the
   mouse. The body moves it, and each end takes a date. A third handle between
   them would be one somebody hits by accident during the gesture that writes a
   date, and a modifier key would be a gesture nobody discovers. */
.roadmap-linking .roadmap-bar,
.roadmap-linking .roadmap-diamond { cursor: crosshair; }

.roadmap-linking .roadmap-grip { display: none; }

.roadmap-linking .roadmap-wire { opacity: 0.8; }

.roadmap-linking .roadmap-arrow { opacity: 0.8; }

/* A hairline is a two-pixel target. This is the same curve drawn wide and
   invisible over the top of it, and it accepts a click only while the mode is
   on: a wire that could be deleted by a stray click during a drag is a wire
   somebody deletes during a drag. */
.roadmap-wire-hit {
    fill: none;
    stroke: transparent;
    stroke-width: var(--space-6);
    pointer-events: none;
}

.roadmap-linking .roadmap-wire-hit {
    pointer-events: stroke;
    cursor: pointer;
}

/* Under the pointer, the wire that a click would take away is drawn as a wide
   soft band behind itself. The hairline is too thin to say "this one" with. */
.roadmap-linking .roadmap-wire-hit:hover { stroke: var(--danger-soft); }

/* The wire being drawn right now, following the pointer out of the bar it
   started on. Dashed for the reason the ghost bar is dashed: nothing has been
   written, and letting go somewhere that is not a bar writes nothing. */
.roadmap-rubber {
    fill: none;
    stroke: var(--brand);
    stroke-width: var(--border-width-thick);
    stroke-dasharray: 4 3;
    pointer-events: none;
}

/* What the mode is for, in words, under the picture. Only while it is on. */
.roadmap-hint {
    margin-top: var(--space-5);
    padding: var(--space-4) var(--space-6);
    border: var(--border-width) solid var(--brand);
    border-radius: var(--radius-md);
    background: var(--brand-soft);
    color: var(--brand-on-soft);
    font-size: var(--text-xs);
    line-height: var(--leading-snug);
}

/* ---- a refused drag ----------------------------------------------------- */

/* Why the bar went back. Under the picture rather than in a browser dialog: the
   bar is already where it started, the sentence is about that bar, and a dialog
   would hold the whole page still to say something the plan file already knows. */
.roadmap-notice {
    margin-top: var(--space-5);
    padding: var(--space-4) var(--space-6);
    border: var(--border-width) solid var(--danger);
    border-radius: var(--radius-md);
    background: var(--danger-soft);
    color: var(--danger-on-soft);
    font-size: var(--text-xs);
    line-height: var(--leading-snug);
}

/* ---- the tray ----------------------------------------------------------- */

.roadmap-tray {
    margin-top: var(--space-6);
    padding: var(--space-6);
    border: var(--border-width) dashed var(--border);
    border-radius: var(--radius-lg);
    background: var(--surface-alt);
}

.roadmap-tray-heading {
    font-size: var(--text-xs);
    font-weight: var(--weight-semibold);
    color: var(--text);
    margin-bottom: var(--space-2);
}

.roadmap-tray-note {
    font-size: var(--text-2xs);
    color: var(--text-subtle);
    margin-bottom: var(--space-5);
}

.roadmap-tray-group {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-3);
    padding: var(--space-3) 0;
    border-top: var(--border-width) solid var(--border);
}

.roadmap-tray-epic {
    flex: 0 0 200px;
    font-size: var(--text-2xs);
    font-weight: var(--weight-semibold);
    color: var(--text-subtle);
}

.roadmap-chip {
    font-size: var(--text-2xs);
    padding: var(--space-1) var(--space-4);
    border-radius: var(--radius-pill);
    background: var(--surface);
    border: var(--border-width) solid var(--border);
    color: var(--text);
    text-decoration: none;
    white-space: nowrap;
}

.roadmap-chip:hover { border-color: var(--brand); color: var(--text-link); }

/* A chip is a link and a drag source at once, so the browser's own drag of the
   href is turned off in the script. Nothing here can do that. */

/* ---- what the picture cannot draw --------------------------------------- */

.roadmap-legend {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-6);
    margin-top: var(--space-6);
    font-size: var(--text-2xs);
    color: var(--text-subtle);
}

.roadmap-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-4);
    padding: var(--space-18);
    font-size: var(--text-sm);
    color: var(--text-subtle);
    text-align: center;
}

/* A screen narrow enough that a 260px rail beside a calendar leaves no
   calendar. The rail goes above, which loses the block name while scrolling
   sideways and is the better of the two bad answers at this width. */
@media (max-width: 900px) {
    .roadmap-map { grid-template-columns: minmax(0, 1fr); }
    .roadmap-rail { flex-direction: row; flex-wrap: wrap; }
    .roadmap-rail-head { display: none; }
    .roadmap-rail-lane { height: auto !important; flex: 1 1 200px; }
}
