/* =============================================================================
   Base element styling
   -----------------------------------------------------------------------------
   A small reset plus sensible defaults for bare HTML elements, expressed purely
   in terms of the tokens in tokens.css. No component styling lives here, and no
   literal values - if something needs a value that has no token, add the token.

   There is no CSS framework behind this on purpose: native elements plus tokens
   keep the payload small (the app is loaded over site connections) and keep the
   later switch to a real design system a matter of changing token values.

   The layout primitives and the responsive table patterns at the bottom are the
   exception to "no component styling": they are the shared vocabulary every
   screen is built from, and having them in one place is what stops each new
   screen inventing its own idea of how to behave on a phone.
   ============================================================================= */

*,
*::before,
*::after {
    box-sizing: border-box;
}

html {
    /* Respect the user's browser font size instead of pinning it. */
    -webkit-text-size-adjust: 100%;

    /* Stops a scroll that reaches the end of the page turning into the browser's own
       pull-to-refresh or rubber-band. In an installed PWA that gesture is the one most
       likely to be made by accident - a thumb flicking up a list of days - and having it
       reload the application mid-entry is the worst outcome available. `contain` keeps
       ordinary overscroll inside the page without disabling scrolling itself. */
    overscroll-behavior-y: contain;
}

body {
    margin: 0;
    background-color: var(--color-surface-page);
    color: var(--color-text);
    font-family: var(--font-family-base);
    font-size: var(--font-size-base);
    font-weight: var(--font-weight-regular);
    line-height: var(--line-height-base);
    letter-spacing: var(--letter-spacing-base);

    /* =========================================================================
       NO WORD MAY MAKE THE PAGE WIDER THAN THE SCREEN
       -------------------------------------------------------------------------
       This application is full of strings with nothing to break on: an address
       like kristian.tangstad@eyvi.onmicrosoft.com, a place path reading
       "Sone 1 / Smolt A / Fiskekar 1 / Transportsystem", a reference designation
       off a drawing, and Norwegian compounds - "Fisketransportkum" - that are one
       word however long they get. Left alone, any of them paints straight out of
       its box, and because overflowing inline text still counts toward the
       document's scroll width, the whole page could be dragged sideways and
       pinched out on a phone. It was reported as happening on many pages, which
       is exactly the shape of a defect that belongs to the text rather than to
       any one layout.

       ANYWHERE AND NOT break-word, and the difference is the whole fix rather
       than a nicety. Both break a long word that would otherwise overflow; only
       "anywhere" also lowers the text's contribution to intrinsic width. Measured
       at 360px against this stylesheet: with break-word a paragraph of URL stops
       overflowing and a table cell holding an address still drags the page 62px,
       because that cell is a grid track whose automatic minimum is the longest
       thing it cannot break - which break-word does not change. With "anywhere"
       both come back clean.

       It only ever acts where a word would otherwise overflow, so text with room
       to breathe is set exactly as it was. Components that want the stronger
       promise locally already say so themselves; this is the floor under them.
       ========================================================================= */
    overflow-wrap: anywhere;
}

h1, h2, h3, h4, h5, h6 {
    margin: 0 0 var(--space-3);
    font-family: var(--font-family-heading);
    font-weight: var(--font-weight-semibold);
    line-height: var(--line-height-tight);
    letter-spacing: var(--letter-spacing-tight);
}

/* Stops a heading breaking with one orphaned word, which happens constantly at
   phone widths and is the cheapest typographic win available. */
h1, h2, h3 {
    text-wrap: balance;
}

h1 { font-size: var(--font-size-2xl); }
h2 { font-size: var(--font-size-xl); }
h3 { font-size: var(--font-size-lg); }
h4, h5, h6 { font-size: var(--font-size-base); }

p {
    margin: 0 0 var(--space-4);
}

a {
    color: var(--color-link);
    text-decoration: none;
}

a:hover,
a:focus-visible {
    color: var(--color-link-hover);
    text-decoration: underline;
}

small {
    font-size: var(--font-size-sm);
}

code, pre, kbd, samp {
    font-family: var(--font-family-mono);
    font-size: var(--font-size-sm);
}

hr {
    border: 0;
    border-top: var(--border-width) solid var(--color-border);
    margin: var(--space-5) 0;
}

/* Media never widens its container. One rule, and no screen has to remember it. */
img, svg, video, canvas {
    max-width: 100%;
    height: auto;
}

/* -----------------------------------------------------------------------------
   Forms and buttons
   Minimum touch target everywhere: the app is used on phones and tablets, often
   with gloves on.
   ----------------------------------------------------------------------------- */

button,
input,
select,
textarea {
    font: inherit;
    color: inherit;
}

/* -----------------------------------------------------------------------------
   Touch behaviour
   -----------------------------------------------------------------------------
   Four small things that separate an installed PWA from a web page opened on a
   phone. None of them changes a layout; together they are most of why a screen
   feels native or does not.
   ----------------------------------------------------------------------------- */

a,
button,
label,
summary,
[role="button"] {
    /* Removes the ~300ms wait some engines keep in case a tap becomes a
       double-tap-to-zoom. The viewport is already width=device-width, so that
       gesture does nothing here and the delay buys nothing - it just makes every
       button feel a beat slow. */
    touch-action: manipulation;

    /* The grey or blue flash browsers paint over a tapped element. This
       application has its own pressed state (the translucent layer above), and
       two overlapping feedback effects read as a glitch rather than a press. */
    -webkit-tap-highlight-color: transparent;
}

button,
label,
.nav-menu__link {
    /* A long press on a control should not start selecting its text. On a phone
       that happens constantly - a thumb resting a fraction too long - and it
       leaves a highlighted word and a context menu over the thing being pressed.
       Inputs and textareas are deliberately untouched: selecting text in those is
       the point. */
    -webkit-user-select: none;
    user-select: none;
}

button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    min-height: var(--layout-touch-target-min);
    padding: var(--space-2) var(--space-4);
    border: var(--border-width) solid var(--color-border-strong);
    border-radius: var(--radius-md);
    background-color: var(--color-surface-container);
    color: var(--color-text);
    cursor: pointer;
    transition:
        background-color var(--motion-duration-fast) var(--motion-easing-standard),
        border-color var(--motion-duration-fast) var(--motion-easing-standard);
}

/* Hover and press are a translucent layer laid OVER whatever background the
   variant already has, using background-image so background-color survives
   underneath. One pair of rules therefore covers default, primary, secondary,
   danger and quiet buttons - which is why none of them define their own hover.
   This is EDC's interaction-state model; it is also the only approach that cannot
   drift out of step between variants. */
button:hover:not(:disabled) {
    background-image: linear-gradient(var(--color-state-hover), var(--color-state-hover));
    border-color: var(--color-border-outline);
}

button:active:not(:disabled) {
    background-image: linear-gradient(var(--color-state-pressed), var(--color-state-pressed));
}

button:disabled {
    opacity: 0.55;
    cursor: not-allowed;
}

/* The pale marine fill with dark text on it - 11.88:1. Note the text colour: white
   on this fill would be 1.28:1, which is why --color-on-primary exists and is
   dark. Do not "fix" it to white. */
button.primary {
    background-color: var(--color-primary);
    border-color: var(--color-primary);
    color: var(--color-on-primary);
    font-weight: var(--font-weight-medium);
}

button.secondary {
    background-color: var(--color-secondary);
    border-color: var(--color-secondary);
    color: var(--color-on-secondary);
    font-weight: var(--font-weight-medium);
}

/* For destructive actions, so "delete" never looks like "save". Quiet by default:
   an outline that states the intent without shouting on every list row. */
button.danger {
    background-color: var(--color-danger-subtle);
    border-color: var(--color-danger);
    color: var(--color-danger-text);
    font-weight: var(--font-weight-medium);
}

/* The filled form, for a confirmation dialog's final button. Uses the darkened
   tone because white on --color-danger is 3.73:1 and dark text on it is 4.08:1 -
   neither clears AA, so the pale red simply cannot be a filled button. */
button.danger-filled {
    background-color: var(--color-danger-strong);
    border-color: var(--color-danger-strong);
    color: var(--color-white);
    font-weight: var(--font-weight-medium);
}

/* An icon-only or inline control that should not read as a box. */
button.quiet {
    background-color: transparent;
    border-color: transparent;
    color: var(--color-text-secondary);
}

button.quiet:hover:not(:disabled) {
    border-color: transparent;
    color: var(--color-text);
}

input,
select,
textarea {
    min-height: var(--layout-touch-target-min);
    padding: var(--space-2) var(--space-3);
    border: var(--border-width) solid var(--color-border-strong);
    border-radius: var(--radius-md);
    /* Its own surface token: in dark mode an input reads as recessed rather than
       as another card, which is what tells you it can be typed into. */
    background-color: var(--color-surface-input);
    color: var(--color-text);
    /* Full width by default: on a phone a field narrower than its label is worse
       than useless, and every screen would otherwise re-specify this. */
    max-width: 100%;
}

textarea {
    /* Horizontal resizing breaks the layout; vertical is genuinely useful. */
    resize: vertical;
    min-height: calc(var(--layout-touch-target-min) * 2);
}

input:hover:not(:disabled),
select:hover:not(:disabled),
textarea:hover:not(:disabled) {
    border-color: var(--color-text-muted);
}

input:disabled,
select:disabled,
textarea:disabled {
    background-color: var(--color-surface-inset);
    opacity: 0.7;
    cursor: not-allowed;
}

::placeholder {
    color: var(--color-text-muted);
    opacity: 1; /* Firefox dims placeholders further; it is already muted enough. */
}

/* Brand-colours native checkboxes, radios and range thumbs in both themes,
   without replacing the controls and losing their platform behaviour.

   --color-accent, not --color-primary: browsers draw the tick in white regardless
   of the accent, so the pale marine fill would give a white tick on near-white and
   a checked box would be indistinguishable from an unchecked one. */
input[type="checkbox"],
input[type="radio"],
input[type="range"] {
    accent-color: var(--color-accent);
    min-height: 0;
}

input[type="checkbox"],
input[type="radio"] {
    /* Comfortably hittable with a glove, and larger than the browser default. */
    width: 1.15rem;
    height: 1.15rem;
    padding: 0;
}

label {
    display: inline-block;
    margin-bottom: var(--space-1);
    font-weight: var(--font-weight-medium);
}

fieldset {
    margin: 0 0 var(--space-5);
    padding: 0;
    border: 0;
}

legend {
    padding: 0;
    font-weight: var(--font-weight-semibold);
}

/* One consistent focus treatment, keyboard-only so pointer users are not shouted at.

   The :not() is load-bearing. Three things in this application are given focus without being
   operated - an inline panel when it opens, and the navigation drawer - so that a keyboard or
   screen-reader user lands on the thing that just appeared instead of several tab stops away from
   it. They carry tabindex="-1", which is what makes them focusable at all, and they are containers
   rather than controls.

   Painting a ring on those is wrong twice over. It says "you are here and can operate this" about a
   <section> that does nothing, and browsers disagree about when it happens: Chrome carries the
   previous element's focus-visible state across a programmatic focus() call, so whether a panel
   opened with a ring around it depended on how the reader got there. The result was a border around
   a panel's header on arriving at a page, cleared only by clicking elsewhere.

   Excluded here rather than overridden below, because this rule also sets border-radius - an
   override would have to undo that too, and would square the corners of anything it missed. */
:focus-visible:not([tabindex="-1"]) {
    outline: none;
    box-shadow: var(--shadow-focus);
    border-radius: var(--radius-sm);
}

/* The other half: suppress the browser's own default outline on those same containers. Without it
   the ring comes back in whatever each browser draws by default, which is the thing this is for. */
[tabindex="-1"]:focus {
    outline: none;
}

/* Blazor's built-in validation classes, mapped onto the token palette. */
.valid.modified:not([type=checkbox]) {
    border-color: var(--color-success);
}

.invalid {
    border-color: var(--color-danger);
}

.validation-message {
    display: block;
    margin-top: var(--space-1);
    /* The AA-safe tone: --color-danger is a fill, and is 3.73:1 as text on white. */
    color: var(--color-danger-text);
    font-size: var(--font-size-sm);
}

/* -----------------------------------------------------------------------------
   Tables
   -----------------------------------------------------------------------------
   Two patterns, because one does not fit both cases. Pick per screen:

   1. `.table-scroll` wrapper - a table that stays a table and scrolls sideways
      inside its own box. Right for a few columns of short values, and for
      anything where comparing rows matters more than reading one.

   2. `.data-table` - below --bp-md (48rem) each row becomes a card and each cell
      is labelled from its `data-label` attribute. Right for wide record lists,
      which are unreadable at phone width however much they scroll.

      Every <td> needs `data-label` matching its column heading, except cells
      holding only actions, which are left unlabelled and span the full width.

      TRADE-OFF, worth knowing: changing a table's `display` drops its table
      semantics in most browsers, so a screen reader stops announcing rows and
      columns. Markup using this pattern should therefore carry explicit roles -
      role="table" / role="row" / role="cell" - which restores them. Do not use
      this pattern without them.
   ----------------------------------------------------------------------------- */

table {
    width: 100%;
    border-collapse: collapse;
}

th, td {
    padding: var(--space-3);
    border-bottom: var(--border-width) solid var(--color-border);
    text-align: left;

    /* Top, not the browser default of middle. A row is several columns side by side, and when one cell
       wraps to three lines a centred neighbour floats to the middle of them - so the first line of one
       column no longer lines up with the first line of the next, and the eye has to hunt for where each
       cell starts. This is the same rule the multi-column panels follow: things beside each other start
       at the same height. */
    vertical-align: top;
}

thead th {
    background-color: var(--color-surface-table-header);
}

th {
    font-weight: var(--font-weight-semibold);
    /* text-secondary, not muted: a column heading is read, not merely glanced at. */
    color: var(--color-text-secondary);
    font-size: var(--font-size-sm);
    letter-spacing: var(--letter-spacing-wide);
    text-transform: uppercase;
}

.table-scroll {
    overflow-x: auto;
    /* Momentum scrolling on iOS. */
    -webkit-overflow-scrolling: touch;
}

@media (max-width: 48rem) {
    .data-table thead {
        display: none;
    }

    .data-table,
    .data-table tbody,
    .data-table tr,
    .data-table td {
        display: block;
        width: 100%;
    }

    .data-table tr {
        margin-bottom: var(--space-4);
        padding: var(--space-2) var(--space-4);
        border: var(--border-width) solid var(--color-border);
        border-radius: var(--radius-lg);
        background-color: var(--color-surface-container);
    }

    .data-table td {
        display: grid;
        grid-template-columns: minmax(6rem, 40%) 1fr;
        gap: var(--space-3);
        align-items: baseline;
        padding: var(--space-2) 0;
        border-bottom: var(--border-width) solid var(--color-border);
    }

    .data-table td:last-child {
        border-bottom: 0;
    }

    .data-table td::before {
        content: attr(data-label);
        font-size: var(--font-size-xs);
        font-weight: var(--font-weight-semibold);
        color: var(--color-text-muted);
        letter-spacing: var(--letter-spacing-wide);
        text-transform: uppercase;
    }

    /* An unlabelled cell - a row's actions - takes the full width instead of
       leaving an empty label column. */
    .data-table td:not([data-label]) {
        grid-template-columns: 1fr;
    }

    .data-table td:not([data-label])::before {
        content: none;
    }
}

/* -----------------------------------------------------------------------------
   Layout primitives
   -----------------------------------------------------------------------------
   Three utilities that between them cover most of what a screen needs, so that
   spacing comes from a layout rule rather than from margins on individual
   elements - which collapse, double up, and drift apart between screens.

   THINGS SIDE BY SIDE START AT THE SAME HEIGHT. Wherever a screen puts boxes in
   columns, the top of one is the top of the one beside it. A taller box may run
   past a shorter one and end anywhere - what must not happen is a box floating
   in the middle of its neighbour, because then no two columns start on the same
   line and the eye has to find each one separately.

   In practice that means the grid default, `align-items: stretch`, or an
   explicit `start`; `baseline` is the same promise made about text. It does NOT
   mean centring is wrong everywhere: a row of controls that are each one line
   tall - a label, a badge and a figure - is a single line, not columns of boxes,
   and there `center` is what lines a pill up against the text beside it. The
   rule is about boxes with height, and the two places it was broken were table
   cells (browsers default `td` to `vertical-align: middle`) and the approvals
   row, both corrected where they are defined.
   ----------------------------------------------------------------------------- */

/* Vertical rhythm. `--stack-gap` overrides the spacing per instance. */
.stack {
    display: flex;
    flex-direction: column;
    gap: var(--stack-gap, var(--space-5));
}

/* A horizontal group that wraps instead of overflowing - toolbars, filter rows,
   a page header's actions. Wrapping is the whole point: this is what keeps a row
   of buttons usable at 320px. */
.cluster {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--cluster-gap, var(--space-3));
}

/* Cards that reflow from several columns to one without a media query.
   `--grid-min` sets the narrowest a column may get before the count drops. */
.grid-auto {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(var(--grid-min, 18rem), 100%), 1fr));
    gap: var(--grid-gap, var(--space-5));
}

/* -----------------------------------------------------------------------------
   Accessibility helpers
   ----------------------------------------------------------------------------- */

/* =============================================================================
   OFF SCREEN, BUT STILL IN THE PAGE FOR ANYONE READING IT ALOUD
   -----------------------------------------------------------------------------
   THE table-layout LINE IS NOT DECORATION, and it is the reason the reports page
   and the manage dashboard could be dragged sideways while no other page could.
   Those two are the only ones that draw charts, and every chart carries its
   figures a second time as a <table class="visually-hidden"> so that a screen
   reader gets numbers rather than a picture.

   width: 1px does not constrain a table. Under the default table-layout: auto a
   specified width is only a MINIMUM - the used width is whatever the content
   needs - so a month of buckets laid itself out at its natural size and ignored
   the 1px entirely. Measured here: 1519px of table, and a document 1518px wide
   inside a 393px phone. Neither of the other two guards helps, because they are
   about paint rather than layout: overflow: hidden clips what is INSIDE the box,
   and clip removes it from the picture while leaving the box exactly where it
   was - and an absolutely positioned box still counts toward what its container
   can be scrolled to.

   table-layout: fixed makes the used width the specified one, so the table stops
   sizing itself to a month of columns. It is inert on everything that is not a
   table, and it changes nothing a screen reader can observe: the rows, the
   headers and their scopes are all still exactly where they were.
   ============================================================================= */
.visually-hidden {
    position: absolute;

    /* AND PINNED TO THE LEFT, which is the difference between hidden and merely
       invisible. Without a left the box takes its static position - wherever it
       would have sat in flow - and that can be anywhere, including well past the
       right edge of the screen. The tab strip on the manage pages is the case
       that proved it: five tabs in a container that scrolls, and inside the
       fourth a hidden ", 61 days waiting for a decision". Its box is a pixel
       wide, so nothing looked wrong, and it sat at x=393 on a 393px phone.

       The strip clips its overflow and did not clip this, because an absolutely
       positioned box is clipped by its CONTAINING BLOCK's chain rather than by
       its parents - and with no positioned ancestor between here and the page,
       this one's containing block was the page itself. So it hung outside the
       scroller and dragged the document 8px wider, invisible to every measure
       that asks an element how wide it is: the box really was one pixel, and it
       really was inside a container that clips.

       left: 0 takes the question away rather than answering it per container.
       Every hidden box now sits at its containing block's left edge, where it
       can push nothing rightward, whatever it is nested in. top is deliberately
       left alone so the text keeps its place in the vertical order. */
    left: 0;

    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
    table-layout: fixed;
}

/* AND THE CAPTION IS ALLOWED TO WRAP, which is the other half of the same bug.
   A caption sits outside the column grid, so table-layout: fixed does not govern
   it: the table can never be narrower than its caption wants to be. Held on one
   line by the nowrap above, "Hours by 8 rows and 11 periods. The busiest cell is
   12,5 hours." wanted 426px of it, and that was the 65px the page was still too
   wide after the columns had been dealt with.

   Letting it wrap costs nothing - it is inside a box clipped to a pixel and has
   not been visible since the day it was written - and with the overflow-wrap
   floor on body its width falls to its longest word. Measured on that caption
   and eleven columns of figures: 402px before, 12px after, which also settles
   that the cells themselves contribute nothing once the layout is fixed. */
.visually-hidden caption {
    white-space: normal;
}

@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }
}
