/*
 * Ledig Parkering — frontend styles
 * Mobile-first; scales up via media queries.
 */

.lpark {
    --lpark-text: #ffffff;
    --lpark-text-soft: rgba(255, 255, 255, 0.72);
    --lpark-text-muted: rgba(255, 255, 255, 0.55);
    /* Card surfaces are tokens because the light themes need a dark scrim while
       the dark themes need a white overlay - see the data-tod blocks below. All
       values are sampled from mockups/night.png and mockups/day.png. */
    --lpark-card-bg: rgba(255, 255, 255, 0.10);
    --lpark-card-bg-hover: rgba(255, 255, 255, 0.20);
    --lpark-card-bg-open: rgba(255, 255, 255, 0.16);
    --lpark-card-bg-active: rgba(255, 255, 255, 0.26);
    --lpark-card-border: rgba(255, 255, 255, 0.18);
    --lpark-card-border-hover: rgba(255, 255, 255, 0.40);
    --lpark-bar-bg: rgba(255, 255, 255, 0.20);
    --lpark-bar-fill: #ddc0f4;
    --lpark-pill-bg: rgba(255, 255, 255, 0.12);
    --lpark-radius: 24px;
    --lpark-radius-sm: 10px;
    --lpark-anim: 320ms cubic-bezier(0.4, 0, 0.2, 1);
    /* Expanding decelerates and takes longer than collapsing, which accelerates
       away. The asymmetry is most of what makes the panel feel unmechanical. */
    --lpark-expand: 380ms cubic-bezier(0.22, 1, 0.36, 1);
    --lpark-collapse: 260ms cubic-bezier(0.4, 0, 1, 1);

    position: relative;
    color: var(--lpark-text);
    /* Chrome and Safari paint a translucent blue box over the whole tap target,
       which since the entire card became clickable meant the entire card flashed.
       Suppressed because :active below provides designed press feedback instead.
       The property is inherited, so this covers the buttons inside too - which is
       why they each have an :active state. */
    -webkit-tap-highlight-color: transparent;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    padding: 32px 20px 40px;
    min-height: 100vh;
    box-sizing: border-box;
    overflow: hidden;
    isolation: isolate;
}

.lpark__bg {
    position: absolute;
    inset: 0;
    z-index: -1;
    /* Every theme uses one recipe, taken from the mockups: a soft bloom in the
       upper right, over a near-vertical ramp that lightens toward the bottom left.
       Only the stops change. Defaults to night. */
    background:
        radial-gradient(ellipse 36% 42% at 78% 16%, rgba(150, 110, 190, 0.22) 0%, transparent 72%),
        linear-gradient(190deg, #170628 0%, #2c114a 45%, #674384 100%);
    transition: background var(--lpark-anim);
}

.lpark__header {
    margin-bottom: 24px;
}

.lpark__greeting {
    margin: 0;
    font-size: 34px;
    font-weight: 500;
    letter-spacing: -0.02em;
    display: flex;
    align-items: center;
    gap: 12px;
}

.lpark__greeting-icon {
    font-size: 22px;
    opacity: 0.85;
}

/* Gradients go on .lpark__bg; token overrides go on .lpark itself. Declaring
   tokens on .lpark__bg - as this file used to - means they only inherit into that
   element's (empty) subtree, so the cards and text never saw them. */

/* Light-bottomed themes darken the card instead of lightening it. A white overlay
   over the pale lower half would wash the card out and leave white text on white.
   The indigo values are fitted from mockups/day.png. */
.lpark[data-tod="day"],
.lpark[data-tod="morning"] {
    --lpark-card-bg: rgba(70, 66, 150, 0.15);
    --lpark-card-bg-open: rgba(70, 66, 150, 0.20);
    --lpark-card-bg-hover: rgba(70, 66, 150, 0.23);
    --lpark-card-bg-active: rgba(70, 66, 150, 0.30);
    --lpark-card-border: rgba(70, 66, 150, 0.18);
    --lpark-card-border-hover: rgba(70, 66, 150, 0.38);
    --lpark-bar-fill: #ffffff;
}

.lpark[data-tod="morning"] .lpark__bg {
    background:
        radial-gradient(ellipse 36% 42% at 78% 16%, rgba(255, 255, 255, 0.18) 0%, transparent 72%),
        linear-gradient(186deg, #ed7b8e 0%, #feb48c 100%);
}
.lpark[data-tod="day"] .lpark__bg {
    background:
        radial-gradient(ellipse 36% 42% at 78% 16%, rgba(255, 255, 255, 0.06) 0%, transparent 72%),
        linear-gradient(184deg, #464a8e 0%, #618abf 45%, #7ccdf2 100%);
}
.lpark[data-tod="evening"] .lpark__bg {
    background:
        radial-gradient(ellipse 36% 42% at 78% 16%, rgba(255, 150, 110, 0.18) 0%, transparent 72%),
        linear-gradient(188deg, #170b2e 0%, #3a1b54 45%, #7e4a82 100%);
}
.lpark[data-tod="night"] .lpark__bg {
    background:
        radial-gradient(ellipse 36% 42% at 78% 16%, rgba(150, 110, 190, 0.22) 0%, transparent 72%),
        linear-gradient(190deg, #170628 0%, #2c114a 45%, #674384 100%);
}

/* Announced, never shown. */
.lpark-sr {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

.lpark__stale {
    margin: 8px 0 0;
    font-size: 13px;
    color: var(--lpark-text-muted);
}

.lpark__cards {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

@media (min-width: 720px) {
    .lpark { padding: 96px 32px 72px; }

    /* Centred on desktop only - mobile stays left-aligned, where a centred
       heading over left-aligned card content reads as a mistake. */
    .lpark__header {
        text-align: center;
        margin-bottom: 72px;
    }
    .lpark__greeting { justify-content: center; }

    /* Measured from mockups/night.png at 1920px: three 440px cards with 38px
       gaps, 1396px total, centred - about 73% of the viewport. Left full-width
       the cards grow to ~600px each and lose the mockup's proportions. */
    .lpark__cards {
        flex-direction: row;
        flex-wrap: wrap;
        align-items: flex-start;
        max-width: 1396px;
        margin-inline: auto;
        gap: 38px;
    }
    .lpark-card {
        flex: 1 1 300px;
        align-self: flex-start;
        padding: 26px 30px;
    }
}

.lpark-card {
    background: var(--lpark-card-bg);
    border: 1px solid var(--lpark-card-border);
    border-radius: var(--lpark-radius);
    padding: 18px 20px;
    backdrop-filter: blur(20px) saturate(140%);
    -webkit-backdrop-filter: blur(20px) saturate(140%);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
    transition: background var(--lpark-anim), border-color var(--lpark-anim),
                box-shadow var(--lpark-anim), transform var(--lpark-anim);
}

.lpark-card[data-expanded="true"] {
    background: var(--lpark-card-bg-open);
}

/* The heading is the handle. Pointer only there, so the affordance matches where
   the hover state is triggered from. */
.lpark-card__head { cursor: pointer; }

/* Hover only where there is a real pointer. On touch, :hover sticks after a tap
   and would leave the card lit until you tapped somewhere else. Scoping it here
   also keeps the lift transform off iOS, where transform plus backdrop-filter is
   a known flicker source. */
@media (hover: hover) and (pointer: fine) {
    /* Triggered by the heading only, applied to the whole card. Hovering the card
       itself used to do this, which on an expanded card lit and unlit in bands as
       the pointer crossed the forecast panel and the padding around it.
       Four cues at once, each individually restrained: brighter glass, a defined
       edge, a longer shadow and a 2px lift. One alone is too quiet to notice
       against a moving gradient. */
    .lpark-card:has(.lpark-card__title:hover) {
        background: var(--lpark-card-bg-hover);
        border-color: var(--lpark-card-border-hover);
        box-shadow: 0 14px 34px rgba(0, 0, 0, 0.18);
        transform: translateY(-2px);
    }
}

/* After the hover block, so pressing beats hovering. Kept outside the media query
   because press feedback matters most on touch. */
.lpark-card:has(.lpark-card__title:active) {
    background: var(--lpark-card-bg-active);
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
    transform: translateY(0);
}

/* The garage name is an h2 so screen-reader users can jump between garages;
   the wrapper carries no visual weight of its own. */
.lpark-card__title {
    margin: 0;
    font: inherit;
    font-weight: inherit;
}

.lpark-card__head {
    display: flex;
    width: 100%;
    align-items: center;
    justify-content: space-between;
    background: transparent;
    border: 0;
    color: inherit;
    padding: 0 0 14px;
    font-size: 19px;
    font-weight: 500;
    cursor: pointer;
    text-align: left;
}

/* The header is the primary control on the page; the browser default outline is
   close to invisible on the glass surface. --lpark-text follows the time-of-day
   theme, so the ring stays legible on both the dark and the light grounds. */
.lpark-card__head:focus-visible {
    outline: 2px solid var(--lpark-text);
    outline-offset: 4px;
    border-radius: 8px;
}

.lpark-card__chevron {
    font-size: 14px;
    opacity: 0.7;
    transition: transform var(--lpark-collapse);
}
.lpark-card[data-expanded="true"] .lpark-card__chevron {
    transform: rotate(90deg);
    transition: transform var(--lpark-expand);
}

.lpark-card__metrics {
    display: flex;
    flex-direction: column;
    gap: 14px;
}

.lpark-metric__row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    margin-bottom: 6px;
}

.lpark-metric__label {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-size: 14px;
    color: var(--lpark-text-soft);
}

.lpark-metric__icon {
    width: 16px;
    text-align: center;
    color: var(--lpark-text-soft);
    font-size: 14px;
}

.lpark-metric__value {
    font-size: 28px;
    font-weight: 500;
    font-variant-numeric: tabular-nums;
    transition: color var(--lpark-anim);
}
.lpark-metric__value.is-pulsing {
    animation: lpark-pulse 520ms ease-out;
}

/* Scale only. The previous version added a hard-coded white glow, which was both
   the loudest thing on the page and wrong in the "day" theme, where the numerals
   are dark navy on a light ground. */
@keyframes lpark-pulse {
    0%   { transform: scale(1); }
    40%  { transform: scale(1.2); }
    100% { transform: scale(1); }
}

/* Tooltips. Hover-only, so everything they say is also available to screen
   readers another way: the metric denominators sit in a .lpark-sr span beside the
   value, and the chart already carries a generated aria-label. */
[data-lpark-tip] { position: relative; }
[data-lpark-tip]::after {
    content: attr(data-lpark-tip);
    position: absolute;
    z-index: 3;
    left: 50%;
    bottom: calc(100% + 8px);
    transform: translateX(-50%) translateY(3px);
    padding: 6px 10px;
    border-radius: 8px;
    background: rgba(18, 9, 32, 0.94);
    color: #fff;
    font-size: 12px;
    line-height: 1.35;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    opacity: 0;
    pointer-events: none;
    transition: opacity 140ms ease, transform 140ms ease;
}
/* The forecast panel clips its overflow, so the chart's tooltip sits over the bars
   instead of above them, and wraps rather than running off the side. */
.lpark-forecast__chart[data-lpark-tip]::after {
    bottom: auto;
    top: 50%;
    transform: translate(-50%, -50%) scale(0.98);
    white-space: normal;
    width: max-content;
    max-width: 88%;
    text-align: center;
}
@media (hover: hover) and (pointer: fine) {
    [data-lpark-tip]:hover::after {
        opacity: 1;
        transform: translateX(-50%) translateY(0);
    }
    .lpark-forecast__chart[data-lpark-tip]:hover::after {
        opacity: 1;
        transform: translate(-50%, -50%) scale(1);
    }
}
@media (prefers-reduced-motion: reduce) {
    [data-lpark-tip]::after { transition: none; }
}

.lpark-bar {
    height: 4px;
    border-radius: 2px;
    background: var(--lpark-bar-bg);
    overflow: hidden;
}
.lpark-bar__fill {
    height: 100%;
    background: var(--lpark-bar-fill);
    border-radius: 2px;
    /* Keyed off the same --lpark-chart-delay as the forecast sweep, a lead ahead of
       it, so the card fills itself in top to bottom: spaces, chargers, then the
       chart. The width is an inline style, so the keyframe gives only a `from` and
       lets the implicit `to` pick up whatever that value is.
       Width rather than scaleX because the fill is pill-shaped - scaling would
       squash the rounded cap. Six short bars, so the layout cost is irrelevant. */
    animation: lpark-fill-in 450ms cubic-bezier(0.22, 1, 0.36, 1) backwards;
    animation-delay: calc(var(--lpark-chart-delay, 250ms) - 150ms);
    transition: width var(--lpark-anim);
}
/* Chargers follow spaces rather than moving with them. */
.lpark-metric:nth-child(2) .lpark-bar__fill {
    animation-delay: calc(var(--lpark-chart-delay, 250ms) - 70ms);
}

@keyframes lpark-fill-in {
    from { width: 0; }
}

/* Height animates via grid-template-rows 0fr -> 1fr rather than max-height or a
   JS-measured pixel height: the forecast chart is fetched *after* the card opens,
   and `1fr` tracks whatever the content becomes, so the late arrival reflows
   smoothly instead of jumping to a stale target. `display` is not animatable,
   which is why the old none/flex swap could only ever snap. */
.lpark-card__detail {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows var(--lpark-collapse);
}
.lpark-card[data-expanded="true"] .lpark-card__detail {
    grid-template-rows: 1fr;
    transition: grid-template-rows var(--lpark-expand);
}

.lpark-card__detail-inner {
    display: flex;
    flex-direction: column;
    gap: 16px;
    min-height: 0;
    overflow: hidden;
    opacity: 0;
    /* Keeps the day-nav buttons out of the tab order while collapsed - the grid
       row is 0fr but the content is still in the DOM. Flips only once the panel
       has finished closing. */
    visibility: hidden;
    transition: opacity 140ms ease, visibility 0s linear 260ms;
}
/* The divider, as a flex child, so the panel has no box of its own when closed.
   Deliberately not var(--lpark-card-border): translucent black over a 1px white
   highlight reads as an incised groove on any backdrop, because it darkens and
   lightens whatever is behind rather than committing to a colour. The card
   overlay compresses the range it has to survive, so both lines stay visible
   from night's darkest card through morning's lightest. The box-shadow sits in
   the 16px gap below and does not affect layout. */
.lpark-card__detail-inner::before {
    content: "";
    flex: 0 0 auto;
    height: 1px;
    margin-top: 16px;
    background: rgba(0, 0, 0, 0.18);
    box-shadow: 0 1px 0 rgba(255, 255, 255, 0.15);
}
.lpark-card[data-expanded="true"] .lpark-card__detail-inner {
    opacity: 1;
    visibility: visible;
    transition: opacity 240ms ease 120ms, visibility 0s linear 0s;
}

/* Set by parking.js only while it applies the desktop default state, so the cards
   render already open instead of three panels animating open at once on load.
   Removed on the same frame. */
.lpark.is-booting .lpark-card__detail,
.lpark.is-booting .lpark-card__detail-inner,
.lpark.is-booting .lpark-card__chevron {
    transition: none !important;
}

.lpark-card__price { display: flex; justify-content: center; }
.lpark-pill {
    display: inline-block;
    padding: 6px 14px;
    background: var(--lpark-pill-bg);
    border-radius: 999px;
    font-size: 13px;
}

.lpark-forecast {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.lpark-forecast__chart {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    height: 80px;
    gap: 3px;
}
.lpark-forecast__bar {
    flex: 0 1 6px;
    max-width: 6px;
    background: var(--lpark-bar-fill);
    border-radius: 999px;
    min-height: 6px;
    opacity: 0.55;
    transform-origin: bottom center;
    /* Bars are rebuilt on every render, so this replays when a card is expanded
       and when the day is changed - both deliberate actions - and never on the
       45-second live poll, which does not touch the chart. `backwards` holds them
       flat through the stagger delay instead of popping up first.
       --lpark-bar-i is set per bar in parking.js. */
    animation: lpark-bar-rise 400ms cubic-bezier(0.22, 1, 0.36, 1) backwards;
    /* The head start matters: renderForecast runs in the same tick as the expand,
       so without it the whole sweep plays behind the 380ms panel reveal and is
       already over by the time anything is visible. Keep it at 120ms or more.
       `backwards` holds the bars flat until then, so the panel opens empty and the
       chart draws itself. The long tail of the easing is deliberate - a bar is at
       96% height in half its duration, and the rest is dwell.
       --lpark-chart-delay overrides the 250ms while the cards are staggering in on
       page load, so each chart follows its own card rather than every chart firing
       at once - by which time the later cards have not appeared. parking.js sets it
       and clears it again when the card's entrance finishes. */
    animation-delay: calc(var(--lpark-chart-delay, 250ms) + var(--lpark-bar-i, 0) * 12ms);
    transition: height var(--lpark-anim), opacity var(--lpark-anim);
}

@keyframes lpark-bar-rise {
    from { transform: scaleY(0.04); }
    to   { transform: scaleY(1); }
}
/* Hours already gone, on today only. Quieter than the hours ahead but still
   readable as data - the morning is context for the afternoon. */
.lpark-forecast__bar--past {
    opacity: 0.3;
}

/* Width as well as brightness: brightness alone makes the current hour a
   colour-only distinction, which fails WCAG 1.4.1. */
.lpark-forecast__bar--current {
    flex-basis: 10px;
    max-width: 10px;
    opacity: 1;
    background: #ffffff;
}

/* 24 columns, one per bar, so a label sits under the hour it names. With
   space-between the four labels would spread to 0, 1/3, 2/3 and 1 while their
   bars sit at 0, 0.26, 0.52 and 0.78 - visibly adrift. */
.lpark-forecast__hours {
    display: grid;
    grid-template-columns: repeat(24, 1fr);
    font-size: 11px;
    color: var(--lpark-text-muted);
    font-variant-numeric: tabular-nums;
}
.lpark-forecast__hours span {
    grid-row: 1;
    justify-self: center;
    white-space: nowrap;
}
/* The 00:00 label is wider than its column, and the panel clips overflow, so it
   hangs off the left edge if centred. */
.lpark-forecast__hours span:first-child { justify-self: start; }

.lpark-forecast__day {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 24px;
}
.lpark-forecast__nav {
    background: transparent;
    border: 0;
    color: inherit;
    font-size: 13px;
    cursor: pointer;
    padding: 4px 10px;
    opacity: 0.7;
}
.lpark-forecast__nav:hover { opacity: 1; }
.lpark-forecast__nav:active {
    opacity: 1;
    background: var(--lpark-pill-bg);
    border-radius: 999px;
}
.lpark-forecast__nav:focus-visible {
    opacity: 1;
    outline: 2px solid var(--lpark-text);
    outline-offset: 2px;
    border-radius: 6px;
}
.lpark-forecast__day-label {
    font-size: 14px;
    min-width: 90px;
    text-align: center;
}

.lpark-forecast__status {
    display: flex;
    justify-content: center;
}
.lpark-forecast__status:empty { display: none; }
.lpark-forecast__status > span {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 8px 14px;
    background: var(--lpark-pill-bg);
    border-radius: 999px;
    font-size: 13px;
}

@media (prefers-reduced-motion: reduce) {
    .lpark, .lpark-card, .lpark-bar__fill, .lpark-card__chevron, .lpark__bg,
    .lpark-forecast__bar, .lpark-metric__value,
    .lpark-card__detail,
    .lpark-card__detail-inner,
    .lpark-card[data-expanded="true"] .lpark-card__chevron,
    .lpark-card[data-expanded="true"] .lpark-card__detail,
    .lpark-card[data-expanded="true"] .lpark-card__detail-inner {
        transition: none !important;
    }
    .lpark-metric__value.is-pulsing,
    .lpark-bar__fill,
    .lpark-metric:nth-child(2) .lpark-bar__fill,
    .lpark-forecast__bar { animation: none !important; }
    /* Colour and shadow cues stay; the positional nudge does not. */
    .lpark-card:has(.lpark-card__title:hover) { transform: none !important; }
}
